From: auh@moto-timo.dev
To: openembedded-devel@lists.openembedded.org
Subject: [AUH] python3-behave: upgrading to 6 FAILED
Date: Wed, 12 Jul 2023 05:30:10 +0000 (UTC) [thread overview]
Message-ID: <20230712053010.DA7C6C001DC@smtp.lore.kernel.org> (raw)
[-- 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
+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	oOmMl_1ASDTDHpw
+zKvR_jVNC!>#Dfe-o2>ib5jY5>8-I+<3k@IRoZ-NQaiF&3IS*DF`bej1V%Caf(vIq-
+z%uvMG`0j_|_c~`m!+mXLD<X?l)s0g1Mar8{_fpj((Q}2%$Y+My_LfYOiz*lA%gTWO
+z`UGeQ{+VYrMyP!B2}Nl3Nujl_ddVvC&dIyKT5a=QhE2xs%JqJUpx0*qy3SaYD<~Tr
+zg*BnCCWA=#Lp4v^CIsUEaAzg?VB(`y7zQxUMS?DwEV$F9?P%C(7(a}4lsK)*JZRaX
+z?|e1n$?%MZTuPuTh+8HSY`v5bHvK*f_~)fI+wf|02%z&bE-s(xv6s8%EyPe-%k+w`
+zmzLAuj&GplP$Uf*NHey`g7mGcVsmt>Cza}Rq!lI?yS~Cv!d%@B?0bW;Lq8mxMrp#I
+zr6nsT+C{hPWkyFYmfCCFHG<T`CdH3Yuc=kb;%T8l)?UZpO)zOY3Aa60E$HrPQ2@)3
+zmHfmbQz=4>F1vzw_ATQCuve5domR4Fm3PifgeCQ-`tlD4^wP?T-{15!s{GE-G&We_
+z{>0LY&YXe?R=KsEAETL~_<p9AB=zh-cF>p(0TjD7N%AI$g|bMdALn%5wOay%A@iG(
+zhz}b;o$C$QcGRc4Cf&GKy4j&|mZL34&l$$n@X+Sco|OkL{#Bkqa`#i0xrvgkSRM%H
+zbwabU-;EN0c!_ub5pG#>9yW73b)f36)`A&BD#>oz%E5TWr-v?iPiohOx5sGNurZ5)
+zaG1|gp4e5rQlppfnpu{%35CGM5*Gx#B@>{zAWeR+Qk@HaRFhbV|BJDAijpPV!Y#|T
+zZQFM3@-Ex9ZQHhO+pgMW+qUhld*4ovKI3$Z81a&CnfWW$n)6$^7O$7khBtP;Wc_PD
+zC;v#GfluflQBF!u;bj|3<|k%1yQqUZSa}}J1#j8J2GNx)>+D^)QWng4yS}Ii@N(J^
+zcIh>BZ+L(v^J)UOnzAK{j4%3q%*Hewl3~Me0|-OSO53N9`Sn$lgxEhCjuk|$@`|jp
+z+Dgh3vbz+W%oN=UhS=pg>NSgJ&ea59sx>aWP9}0zmSPDPUtAt)(#scZaTyet>vN;X
+zj{yxjih>vUy9hz7eNa(z;))FIQUqOAMhgWjl6dQfLp53(F-*&0(Ie!w!fux$mO;WV
+z!@@$z+560wiRM+TXI@i8Th?<dak7|9&$3BFvUt=N6)ObDvv`={DQztVLXk9BWk4ii
+zJi;t1s~)11kdN$IX}Wsc2CbbAB<x(c?yUnAhCc9Qf(fh)ezL+K-%6y|FGDnubxvIw
+zbOLs2%pRA(XYt#!+LuUt+fU|jwV8*_@qBZK{5&}I6ML!mqY&u;8_sth3z#)isHCQT
+zc9Hbj$o6B`Ri9liqqNGqPU3ZL4{_|H64w^+GaW1zU}W)ho)#YueYnVH@w9R3qLSKr
+zri4-7T$;%&4!phs#(Jmo-Mcq5mg>~(5b`^cFj;)!<v5J&)m4)sP4i}iWA~Fco=2UH
+zZ3A$<ctN^C$nzV>!qstoM)mALh=m?^{-E9%JZ$k32MON1uVjmH8=imiux>j6nsBq$
+zv<Ji!LUe_d6oBH}+Esu!Fs7U9c<vL!_azJv!Iwel$B!!U>LcE4+{xhWR3oR1Sv9||
+zb(4iQzWFh)jd?<aQK*3j!Gwb?EtA{R!}WL%Fw1VIfq>154g^<})_7_CVrNJDz2A?Q
+z{k!zKxK=-!wp|~-v7+*&o(@=V1p&YW*VOqb^Ye}p-Q=qge8@!NHOqR3|J*A9V{+B7
+z8(#tWTVJ}k7TTij8fw$qz6j6$RNi}?*dGd4E+TC-2{Nv)ub<!iz*_6{WCggnT9e~p
+z*<JXb-+I2>aJmuA#k7<uaN9+km4~o5!kmc&J{=}XjQ@dRnNo^xim4&i%8oRAFj0P>
+z132jGahKcXZiwwlkc9bESmHsBc=56CKr>5B&Zv(wKfKH0TCe^36>2JI$NvjDS~PsV
+zRR#a@hGc_qFwh8Cu*4)<se!mn(nchGu~qeIL^=eUHbzwKhd7KTsk@yA0lr?ulZf#C
+zel9b~D-hQv*~=g;q-JtZv^S_%zGaYCI!~9gg#Q_PhR?A=l|}TMn`RGRrV5-K@eGNV
+z99Y3Qt7MKvZKcTb&9aj**da?wRC?M>cGIm75(g`rXukRg3*~Vqt8GL>|1eNsn|{sq
+z?xcJ9BcdRFiQU6paP04#-@1ubWcslv==#*w&fYvXFI51U$I4LE-t@=6t+4|U?ndz{
+znpym{TM}jBg97ynr8Ihi1$5nfsjr(jHMX^+tD}8^J`p#zGN$}PqxXcpZ8UP8LwJbn
+z?UP3PHqr{#s*xn;o>uxENCO;Iy@C$4SxQPSfM!z-eWChoLqPF++1xZL*7l3=x`~!X
+z9nHWTjOr5!qaNxab?*r)rU1FB={mKj{cL;QbBG;ZMO1J|EfIu%gSWGxhO*}Ngtcz`
+z_0r-}7tzYk7qfGA7#;CEuM!K-&R#07ju3<BlJTHKZwgn(2MF0SC-kCGJ{)}^hL7+2
+z!K`^_$9K}#2U#5}DAoJ|yMfxNnA2f&x{`BX4hbU_3rI~MgF*T=KJ0nO6Z3|$&_Y3Q
+z$TI+?>;5c6xn>B37lSlkLDKjKe9=3mPb=RK3;;~0ZTTCcuJ|_$Ej#P`enT}D@ADlc
+zu|(7NjS`Dt9s(>Vy<%OI-HIg#Hmv<RqkF$PuIO?;Zbb}baQ`=ct#)CK7C8p4hJB4u
+zr^%8fdk7lORxk*%2KNdBOmci_6!as`NIl!^F(=<_Gm0K{@0$W<8wCb#b(Q<TZkFd(
+z8FBB<Q?hk<1m9WLLwt@}2I+YPKMYsH4OnikiDbcyFyMG@)SJ2o&->Fue0Zo$t~3LP
+zuHr6#%k63ZqLA3vHORv)bE$5}RRb3nw>xY|1>EgLpbHy_BttvF&LvNY+<6I&{=wkA
+zZZSR{V@+_%npO}armRbR+(==Z{-G)2;;@W_8$+-dY$q@l8z*E@a*#3WXc`Z80UAqK
+zz<N{~xY1A?DFOZO;!1&`vezru8rOIWn7?vutK=?O9-FZck_FiQ6~txYbOjoS=*5_G
+zKes*}-#anb=IMS_macSd^UNHcj3fOGD%>QuB!>l%0kQ%I#EG`;O?@faV7Xk@!@n@W
+z))nY+T{2^(I!ZOdTSj~nd&-_Bf`Cc2JXQJNSBvD~Kj*~Ce?VUnmHwc<JSgVdl%2&z
+zrM-#rmP3WKqX=_H4tL7~NeK#g-mlhM<IkvKcbqc<fsVIW9b<2g+@<x~`mKRH9`o_<
+zx9SYdYc*t*8na4r3gD#W!Lc%6OF@x}sMnqtN(kH)M>QT2Y17p0eBu~Pi`)wiF^cba
+z#rH2dGD*-)U|^`{8wnJ~F<-tlhlxO|nf;!g)kSofOh$ILNu=PX07#RAV&%kX6TcJ=
+zn5QUB2zh>dtL3>VCp+MKLESymk{i9884s#hmjdp1vHdDnN9D{Uy#*fL@Oq~pgv<mg
+zp5KC+C&Gkn#VB8$d8SD))f`e}?kvsz;RLA%{*pxO21oh?F&E1P1Y!6i{n_jw$;?3_
+znKae-Irml79ob5y*05-A)?Gu@FU=zY$jqf1x4fej@!6ZjJr5XWW+y(_y`X1#=0s(&
+zCZGSB`#05$wkj@cp9*`M`moy_DGDRnDp$I~<6e^j7SjL3cz>*@dI=2{qHKKLegz!2
+z5>&CPaG|8mJbyRBiNwf@Ne?b|WP;XXV*%J-G;DTSXv<%+w6t1cmuh+JSF$8$wqjat
+z78z?F`Gu;4BOe%4YM{oCJ%r1kv*{nc7yh`j@>e7)>npV2zG7vf!m9kNCQrf-a?-y7
+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'
next reply other threads:[~2023-07-12 5:30 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-07-12 5:30 auh [this message]
-- strict thread matches above, loose matches on Subject: below --
2023-07-31 5:08 [AUH] python3-behave: upgrading to 6 FAILED auh
2023-08-05 7:38 auh
2023-11-04 1:38 auh
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230712053010.DA7C6C001DC@smtp.lore.kernel.org \
--to=auh@moto-timo.dev \
--cc=openembedded-devel@lists.openembedded.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox