busser-behave 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.cane +0 -0
- data/.gitignore +17 -0
- data/.tailor +4 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/LICENSE +15 -0
- data/README.md +41 -0
- data/Rakefile +68 -0
- data/busser-behave.gemspec +30 -0
- data/features/plugin_install_command.feature +11 -0
- data/features/plugin_list_command.feature +8 -0
- data/features/support/env.rb +13 -0
- data/features/test_command.feature +31 -0
- data/lib/busser/behave/version.rb +26 -0
- data/lib/busser/runner_plugin/behave.rb +37 -0
- data/vendor/behave/CHANGES.rst +483 -0
- data/vendor/behave/LICENSE +23 -0
- data/vendor/behave/MANIFEST.in +37 -0
- data/vendor/behave/PROJECT_INFO.rst +21 -0
- data/vendor/behave/README.rst +112 -0
- data/vendor/behave/VERSION.txt +1 -0
- data/vendor/behave/behave.ini +22 -0
- data/vendor/behave/behave/__init__.py +30 -0
- data/vendor/behave/behave/__main__.py +187 -0
- data/vendor/behave/behave/_stepimport.py +185 -0
- data/vendor/behave/behave/_types.py +134 -0
- data/vendor/behave/behave/api/__init__.py +7 -0
- data/vendor/behave/behave/api/async_step.py +283 -0
- data/vendor/behave/behave/capture.py +227 -0
- data/vendor/behave/behave/compat/__init__.py +5 -0
- data/vendor/behave/behave/compat/collections.py +20 -0
- data/vendor/behave/behave/configuration.py +788 -0
- data/vendor/behave/behave/contrib/__init__.py +0 -0
- data/vendor/behave/behave/contrib/scenario_autoretry.py +73 -0
- data/vendor/behave/behave/formatter/__init__.py +12 -0
- data/vendor/behave/behave/formatter/_builtins.py +39 -0
- data/vendor/behave/behave/formatter/_registry.py +135 -0
- data/vendor/behave/behave/formatter/ansi_escapes.py +91 -0
- data/vendor/behave/behave/formatter/base.py +200 -0
- data/vendor/behave/behave/formatter/formatters.py +57 -0
- data/vendor/behave/behave/formatter/json.py +253 -0
- data/vendor/behave/behave/formatter/null.py +12 -0
- data/vendor/behave/behave/formatter/plain.py +158 -0
- data/vendor/behave/behave/formatter/pretty.py +351 -0
- data/vendor/behave/behave/formatter/progress.py +287 -0
- data/vendor/behave/behave/formatter/rerun.py +114 -0
- data/vendor/behave/behave/formatter/sphinx_steps.py +372 -0
- data/vendor/behave/behave/formatter/sphinx_util.py +118 -0
- data/vendor/behave/behave/formatter/steps.py +497 -0
- data/vendor/behave/behave/formatter/tags.py +178 -0
- data/vendor/behave/behave/i18n.py +614 -0
- data/vendor/behave/behave/importer.py +102 -0
- data/vendor/behave/behave/json_parser.py +264 -0
- data/vendor/behave/behave/log_capture.py +233 -0
- data/vendor/behave/behave/matchers.py +402 -0
- data/vendor/behave/behave/model.py +1737 -0
- data/vendor/behave/behave/model_core.py +416 -0
- data/vendor/behave/behave/model_describe.py +105 -0
- data/vendor/behave/behave/parser.py +615 -0
- data/vendor/behave/behave/reporter/__init__.py +0 -0
- data/vendor/behave/behave/reporter/base.py +45 -0
- data/vendor/behave/behave/reporter/junit.py +473 -0
- data/vendor/behave/behave/reporter/summary.py +94 -0
- data/vendor/behave/behave/runner.py +753 -0
- data/vendor/behave/behave/runner_util.py +417 -0
- data/vendor/behave/behave/step_registry.py +112 -0
- data/vendor/behave/behave/tag_expression.py +111 -0
- data/vendor/behave/behave/tag_matcher.py +465 -0
- data/vendor/behave/behave/textutil.py +137 -0
- data/vendor/behave/behave/userdata.py +130 -0
- data/vendor/behave/behave4cmd0/__all_steps__.py +12 -0
- data/vendor/behave/behave4cmd0/__init__.py +5 -0
- data/vendor/behave/behave4cmd0/__setup.py +11 -0
- data/vendor/behave/behave4cmd0/command_shell.py +216 -0
- data/vendor/behave/behave4cmd0/command_shell_proc.py +256 -0
- data/vendor/behave/behave4cmd0/command_steps.py +532 -0
- data/vendor/behave/behave4cmd0/command_util.py +147 -0
- data/vendor/behave/behave4cmd0/failing_steps.py +49 -0
- data/vendor/behave/behave4cmd0/log/__init__.py +1 -0
- data/vendor/behave/behave4cmd0/log/steps.py +395 -0
- data/vendor/behave/behave4cmd0/note_steps.py +29 -0
- data/vendor/behave/behave4cmd0/passing_steps.py +36 -0
- data/vendor/behave/behave4cmd0/pathutil.py +146 -0
- data/vendor/behave/behave4cmd0/setup_command_shell.py +24 -0
- data/vendor/behave/behave4cmd0/textutil.py +304 -0
- data/vendor/behave/bin/behave +44 -0
- data/vendor/behave/bin/behave.cmd +10 -0
- data/vendor/behave/bin/behave.junit_filter.py +85 -0
- data/vendor/behave/bin/behave.step_durations.py +163 -0
- data/vendor/behave/bin/behave2cucumber_json.py +63 -0
- data/vendor/behave/bin/behave_cmd.py +44 -0
- data/vendor/behave/bin/convert_i18n_yaml.py +77 -0
- data/vendor/behave/bin/explore_platform_encoding.py +24 -0
- data/vendor/behave/bin/i18n.yml +621 -0
- data/vendor/behave/bin/invoke +8 -0
- data/vendor/behave/bin/invoke.cmd +9 -0
- data/vendor/behave/bin/json.format.py +167 -0
- data/vendor/behave/bin/jsonschema_validate.py +122 -0
- data/vendor/behave/bin/make_localpi.py +279 -0
- data/vendor/behave/bin/project_bootstrap.sh +30 -0
- data/vendor/behave/bin/toxcmd.py +270 -0
- data/vendor/behave/bin/toxcmd3.py +270 -0
- data/vendor/behave/conftest.py +27 -0
- data/vendor/behave/docs/Makefile +154 -0
- data/vendor/behave/docs/_static/agogo.css +501 -0
- data/vendor/behave/docs/_static/behave_logo.png +0 -0
- data/vendor/behave/docs/_static/behave_logo1.png +0 -0
- data/vendor/behave/docs/_static/behave_logo2.png +0 -0
- data/vendor/behave/docs/_static/behave_logo3.png +0 -0
- data/vendor/behave/docs/_themes/LICENSE +45 -0
- data/vendor/behave/docs/_themes/kr/layout.html +17 -0
- data/vendor/behave/docs/_themes/kr/relations.html +19 -0
- data/vendor/behave/docs/_themes/kr/static/flasky.css_t +480 -0
- data/vendor/behave/docs/_themes/kr/static/small_flask.css +90 -0
- data/vendor/behave/docs/_themes/kr/theme.conf +7 -0
- data/vendor/behave/docs/_themes/kr_small/layout.html +22 -0
- data/vendor/behave/docs/_themes/kr_small/static/flasky.css_t +287 -0
- data/vendor/behave/docs/_themes/kr_small/theme.conf +10 -0
- data/vendor/behave/docs/api.rst +408 -0
- data/vendor/behave/docs/appendix.rst +19 -0
- data/vendor/behave/docs/behave.rst +640 -0
- data/vendor/behave/docs/behave.rst-template +86 -0
- data/vendor/behave/docs/behave_ecosystem.rst +81 -0
- data/vendor/behave/docs/comparison.rst +85 -0
- data/vendor/behave/docs/conf.py +293 -0
- data/vendor/behave/docs/context_attributes.rst +66 -0
- data/vendor/behave/docs/django.rst +192 -0
- data/vendor/behave/docs/formatters.rst +61 -0
- data/vendor/behave/docs/gherkin.rst +673 -0
- data/vendor/behave/docs/index.rst +57 -0
- data/vendor/behave/docs/install.rst +60 -0
- data/vendor/behave/docs/more_info.rst +184 -0
- data/vendor/behave/docs/new_and_noteworthy.rst +18 -0
- data/vendor/behave/docs/new_and_noteworthy_v1.2.4.rst +11 -0
- data/vendor/behave/docs/new_and_noteworthy_v1.2.5.rst +814 -0
- data/vendor/behave/docs/new_and_noteworthy_v1.2.6.rst +255 -0
- data/vendor/behave/docs/parse_builtin_types.rst +59 -0
- data/vendor/behave/docs/philosophy.rst +235 -0
- data/vendor/behave/docs/regular_expressions.rst +71 -0
- data/vendor/behave/docs/related.rst +14 -0
- data/vendor/behave/docs/test_domains.rst +62 -0
- data/vendor/behave/docs/tutorial.rst +636 -0
- data/vendor/behave/docs/update_behave_rst.py +100 -0
- data/vendor/behave/etc/json/behave.json-schema +172 -0
- data/vendor/behave/etc/junit.xml/behave_junit.xsd +103 -0
- data/vendor/behave/etc/junit.xml/junit-4.xsd +92 -0
- data/vendor/behave/examples/async_step/README.txt +8 -0
- data/vendor/behave/examples/async_step/behave.ini +14 -0
- data/vendor/behave/examples/async_step/features/async_dispatch.feature +8 -0
- data/vendor/behave/examples/async_step/features/async_run.feature +6 -0
- data/vendor/behave/examples/async_step/features/environment.py +28 -0
- data/vendor/behave/examples/async_step/features/steps/async_dispatch_steps.py +26 -0
- data/vendor/behave/examples/async_step/features/steps/async_steps34.py +10 -0
- data/vendor/behave/examples/async_step/features/steps/async_steps35.py +10 -0
- data/vendor/behave/examples/async_step/testrun_example.async_dispatch.txt +11 -0
- data/vendor/behave/examples/async_step/testrun_example.async_run.txt +9 -0
- data/vendor/behave/examples/env_vars/README.rst +26 -0
- data/vendor/behave/examples/env_vars/behave.ini +15 -0
- data/vendor/behave/examples/env_vars/behave_run.output_example.txt +12 -0
- data/vendor/behave/examples/env_vars/features/env_var.feature +6 -0
- data/vendor/behave/examples/env_vars/features/steps/env_var_steps.py +38 -0
- data/vendor/behave/features/README.txt +12 -0
- data/vendor/behave/features/background.feature +392 -0
- data/vendor/behave/features/capture_stderr.feature +172 -0
- data/vendor/behave/features/capture_stdout.feature +125 -0
- data/vendor/behave/features/cmdline.lang_list.feature +33 -0
- data/vendor/behave/features/configuration.default_paths.feature +116 -0
- data/vendor/behave/features/context.global_params.feature +35 -0
- data/vendor/behave/features/context.local_params.feature +17 -0
- data/vendor/behave/features/directory_layout.advanced.feature +147 -0
- data/vendor/behave/features/directory_layout.basic.feature +75 -0
- data/vendor/behave/features/directory_layout.basic2.feature +87 -0
- data/vendor/behave/features/environment.py +53 -0
- data/vendor/behave/features/exploratory_testing.with_table.feature +141 -0
- data/vendor/behave/features/feature.description.feature +0 -0
- data/vendor/behave/features/feature.exclude_from_run.feature +96 -0
- data/vendor/behave/features/formatter.help.feature +30 -0
- data/vendor/behave/features/formatter.json.feature +420 -0
- data/vendor/behave/features/formatter.progress3.feature +235 -0
- data/vendor/behave/features/formatter.rerun.feature +296 -0
- data/vendor/behave/features/formatter.steps.feature +181 -0
- data/vendor/behave/features/formatter.steps_catalog.feature +100 -0
- data/vendor/behave/features/formatter.steps_doc.feature +140 -0
- data/vendor/behave/features/formatter.steps_usage.feature +404 -0
- data/vendor/behave/features/formatter.tags.feature +134 -0
- data/vendor/behave/features/formatter.tags_location.feature +183 -0
- data/vendor/behave/features/formatter.user_defined.feature +196 -0
- data/vendor/behave/features/i18n.unicode_problems.feature +445 -0
- data/vendor/behave/features/logcapture.clear_handlers.feature +114 -0
- data/vendor/behave/features/logcapture.feature +188 -0
- data/vendor/behave/features/logcapture.filter.feature +130 -0
- data/vendor/behave/features/logging.no_capture.feature +99 -0
- data/vendor/behave/features/logging.setup_format.feature +157 -0
- data/vendor/behave/features/logging.setup_level.feature +168 -0
- data/vendor/behave/features/logging.setup_with_configfile.feature +137 -0
- data/vendor/behave/features/parser.background.sad_cases.feature +129 -0
- data/vendor/behave/features/parser.feature.sad_cases.feature +144 -0
- data/vendor/behave/features/runner.abort_by_user.feature +305 -0
- data/vendor/behave/features/runner.continue_after_failed_step.feature +136 -0
- data/vendor/behave/features/runner.default_format.feature +175 -0
- data/vendor/behave/features/runner.dry_run.feature +184 -0
- data/vendor/behave/features/runner.feature_listfile.feature +223 -0
- data/vendor/behave/features/runner.hook_errors.feature +382 -0
- data/vendor/behave/features/runner.multiple_formatters.feature +285 -0
- data/vendor/behave/features/runner.scenario_autoretry.feature +131 -0
- data/vendor/behave/features/runner.select_files_by_regexp.example.feature +71 -0
- data/vendor/behave/features/runner.select_files_by_regexp.feature +84 -0
- data/vendor/behave/features/runner.select_scenarios_by_file_location.feature +403 -0
- data/vendor/behave/features/runner.select_scenarios_by_name.feature +289 -0
- data/vendor/behave/features/runner.select_scenarios_by_tag.feature +225 -0
- data/vendor/behave/features/runner.stop_after_failure.feature +122 -0
- data/vendor/behave/features/runner.tag_logic.feature +67 -0
- data/vendor/behave/features/runner.unknown_formatter.feature +23 -0
- data/vendor/behave/features/runner.use_stage_implementations.feature +126 -0
- data/vendor/behave/features/scenario.description.feature +171 -0
- data/vendor/behave/features/scenario.exclude_from_run.feature +217 -0
- data/vendor/behave/features/scenario_outline.basics.feature +100 -0
- data/vendor/behave/features/scenario_outline.improved.feature +177 -0
- data/vendor/behave/features/scenario_outline.name_annotation.feature +157 -0
- data/vendor/behave/features/scenario_outline.parametrized.feature +401 -0
- data/vendor/behave/features/scenario_outline.tagged_examples.feature +118 -0
- data/vendor/behave/features/step.async_steps.feature +225 -0
- data/vendor/behave/features/step.duplicated_step.feature +106 -0
- data/vendor/behave/features/step.execute_steps.feature +59 -0
- data/vendor/behave/features/step.execute_steps.with_table.feature +65 -0
- data/vendor/behave/features/step.import_other_step_module.feature +103 -0
- data/vendor/behave/features/step.pending_steps.feature +128 -0
- data/vendor/behave/features/step.undefined_steps.feature +307 -0
- data/vendor/behave/features/step.use_step_library.feature +44 -0
- data/vendor/behave/features/step_dialect.generic_steps.feature +189 -0
- data/vendor/behave/features/step_dialect.given_when_then.feature +89 -0
- data/vendor/behave/features/step_param.builtin_types.with_float.feature +239 -0
- data/vendor/behave/features/step_param.builtin_types.with_integer.feature +305 -0
- data/vendor/behave/features/step_param.custom_types.feature +134 -0
- data/vendor/behave/features/steps/behave_active_tags_steps.py +86 -0
- data/vendor/behave/features/steps/behave_context_steps.py +67 -0
- data/vendor/behave/features/steps/behave_model_tag_logic_steps.py +105 -0
- data/vendor/behave/features/steps/behave_model_util.py +105 -0
- data/vendor/behave/features/steps/behave_select_files_steps.py +83 -0
- data/vendor/behave/features/steps/behave_tag_expression_steps.py +166 -0
- data/vendor/behave/features/steps/behave_undefined_steps.py +101 -0
- data/vendor/behave/features/steps/use_steplib_behave4cmd.py +12 -0
- data/vendor/behave/features/summary.undefined_steps.feature +114 -0
- data/vendor/behave/features/tags.active_tags.feature +385 -0
- data/vendor/behave/features/tags.default_tags.feature +104 -0
- data/vendor/behave/features/tags.tag_expression.feature +105 -0
- data/vendor/behave/features/userdata.feature +331 -0
- data/vendor/behave/invoke.yaml +21 -0
- data/vendor/behave/issue.features/README.txt +17 -0
- data/vendor/behave/issue.features/environment.py +97 -0
- data/vendor/behave/issue.features/issue0030.feature +21 -0
- data/vendor/behave/issue.features/issue0031.feature +16 -0
- data/vendor/behave/issue.features/issue0032.feature +28 -0
- data/vendor/behave/issue.features/issue0035.feature +74 -0
- data/vendor/behave/issue.features/issue0040.feature +154 -0
- data/vendor/behave/issue.features/issue0041.feature +135 -0
- data/vendor/behave/issue.features/issue0042.feature +230 -0
- data/vendor/behave/issue.features/issue0044.feature +51 -0
- data/vendor/behave/issue.features/issue0046.feature +77 -0
- data/vendor/behave/issue.features/issue0052.feature +66 -0
- data/vendor/behave/issue.features/issue0059.feature +29 -0
- data/vendor/behave/issue.features/issue0063.feature +102 -0
- data/vendor/behave/issue.features/issue0064.feature +97 -0
- data/vendor/behave/issue.features/issue0065.feature +18 -0
- data/vendor/behave/issue.features/issue0066.feature +80 -0
- data/vendor/behave/issue.features/issue0067.feature +90 -0
- data/vendor/behave/issue.features/issue0069.feature +64 -0
- data/vendor/behave/issue.features/issue0072.feature +32 -0
- data/vendor/behave/issue.features/issue0073.feature +228 -0
- data/vendor/behave/issue.features/issue0075.feature +18 -0
- data/vendor/behave/issue.features/issue0077.feature +89 -0
- data/vendor/behave/issue.features/issue0080.feature +49 -0
- data/vendor/behave/issue.features/issue0081.feature +138 -0
- data/vendor/behave/issue.features/issue0083.feature +69 -0
- data/vendor/behave/issue.features/issue0084.feature +69 -0
- data/vendor/behave/issue.features/issue0085.feature +119 -0
- data/vendor/behave/issue.features/issue0092.feature +66 -0
- data/vendor/behave/issue.features/issue0096.feature +173 -0
- data/vendor/behave/issue.features/issue0099.feature +130 -0
- data/vendor/behave/issue.features/issue0109.feature +60 -0
- data/vendor/behave/issue.features/issue0111.feature +53 -0
- data/vendor/behave/issue.features/issue0112.feature +64 -0
- data/vendor/behave/issue.features/issue0114.feature +118 -0
- data/vendor/behave/issue.features/issue0116.feature +71 -0
- data/vendor/behave/issue.features/issue0125.feature +49 -0
- data/vendor/behave/issue.features/issue0127.feature +64 -0
- data/vendor/behave/issue.features/issue0139.feature +67 -0
- data/vendor/behave/issue.features/issue0142.feature +37 -0
- data/vendor/behave/issue.features/issue0143.feature +54 -0
- data/vendor/behave/issue.features/issue0145.feature +63 -0
- data/vendor/behave/issue.features/issue0148.feature +105 -0
- data/vendor/behave/issue.features/issue0152.feature +52 -0
- data/vendor/behave/issue.features/issue0159.feature +74 -0
- data/vendor/behave/issue.features/issue0162.feature +86 -0
- data/vendor/behave/issue.features/issue0171.feature +16 -0
- data/vendor/behave/issue.features/issue0172.feature +51 -0
- data/vendor/behave/issue.features/issue0175.feature +91 -0
- data/vendor/behave/issue.features/issue0177.feature +40 -0
- data/vendor/behave/issue.features/issue0181.feature +36 -0
- data/vendor/behave/issue.features/issue0184.feature +144 -0
- data/vendor/behave/issue.features/issue0186.feature +12 -0
- data/vendor/behave/issue.features/issue0188.feature +60 -0
- data/vendor/behave/issue.features/issue0191.feature +178 -0
- data/vendor/behave/issue.features/issue0194.feature +215 -0
- data/vendor/behave/issue.features/issue0197.feature +11 -0
- data/vendor/behave/issue.features/issue0216.feature +129 -0
- data/vendor/behave/issue.features/issue0226.feature +51 -0
- data/vendor/behave/issue.features/issue0228.feature +41 -0
- data/vendor/behave/issue.features/issue0230.feature +46 -0
- data/vendor/behave/issue.features/issue0231.feature +77 -0
- data/vendor/behave/issue.features/issue0238.feature +52 -0
- data/vendor/behave/issue.features/issue0251.feature +15 -0
- data/vendor/behave/issue.features/issue0280.feature +118 -0
- data/vendor/behave/issue.features/issue0288.feature +95 -0
- data/vendor/behave/issue.features/issue0300.feature +49 -0
- data/vendor/behave/issue.features/issue0302.feature +91 -0
- data/vendor/behave/issue.features/issue0309.feature +52 -0
- data/vendor/behave/issue.features/issue0330.feature +124 -0
- data/vendor/behave/issue.features/issue0349.feature +9 -0
- data/vendor/behave/issue.features/issue0361.feature +79 -0
- data/vendor/behave/issue.features/issue0383.feature +76 -0
- data/vendor/behave/issue.features/issue0384.feature +103 -0
- data/vendor/behave/issue.features/issue0385.feature +109 -0
- data/vendor/behave/issue.features/issue0424.feature +66 -0
- data/vendor/behave/issue.features/issue0446.feature +116 -0
- data/vendor/behave/issue.features/issue0449.feature +42 -0
- data/vendor/behave/issue.features/issue0453.feature +42 -0
- data/vendor/behave/issue.features/issue0457.feature +65 -0
- data/vendor/behave/issue.features/issue0462.feature +38 -0
- data/vendor/behave/issue.features/issue0476.feature +39 -0
- data/vendor/behave/issue.features/issue0487.feature +92 -0
- data/vendor/behave/issue.features/issue0506.feature +77 -0
- data/vendor/behave/issue.features/issue0510.feature +51 -0
- data/vendor/behave/issue.features/requirements.txt +12 -0
- data/vendor/behave/issue.features/steps/ansi_steps.py +20 -0
- data/vendor/behave/issue.features/steps/behave_hooks_steps.py +10 -0
- data/vendor/behave/issue.features/steps/use_steplib_behave4cmd.py +13 -0
- data/vendor/behave/more.features/formatter.json.validate_output.feature +37 -0
- data/vendor/behave/more.features/steps/tutorial_steps.py +16 -0
- data/vendor/behave/more.features/steps/use_steplib_behave4cmd.py +7 -0
- data/vendor/behave/more.features/tutorial.feature +6 -0
- data/vendor/behave/py.requirements/README.txt +5 -0
- data/vendor/behave/py.requirements/all.txt +16 -0
- data/vendor/behave/py.requirements/basic.txt +21 -0
- data/vendor/behave/py.requirements/develop.txt +28 -0
- data/vendor/behave/py.requirements/docs.txt +6 -0
- data/vendor/behave/py.requirements/json.txt +7 -0
- data/vendor/behave/py.requirements/more_py26.txt +8 -0
- data/vendor/behave/py.requirements/testing.txt +10 -0
- data/vendor/behave/pytest.ini +24 -0
- data/vendor/behave/setup.cfg +29 -0
- data/vendor/behave/setup.py +118 -0
- data/vendor/behave/setuptools_behave.py +130 -0
- data/vendor/behave/tasks/__behave.py +45 -0
- data/vendor/behave/tasks/__init__.py +55 -0
- data/vendor/behave/tasks/__main__.py +70 -0
- data/vendor/behave/tasks/_setup.py +135 -0
- data/vendor/behave/tasks/_vendor/README.rst +35 -0
- data/vendor/behave/tasks/_vendor/invoke.zip +0 -0
- data/vendor/behave/tasks/_vendor/path.py +1725 -0
- data/vendor/behave/tasks/_vendor/pathlib.py +1280 -0
- data/vendor/behave/tasks/_vendor/six.py +868 -0
- data/vendor/behave/tasks/clean.py +246 -0
- data/vendor/behave/tasks/docs.py +97 -0
- data/vendor/behave/tasks/requirements.txt +17 -0
- data/vendor/behave/tasks/test.py +192 -0
- data/vendor/behave/test/__init__.py +0 -0
- data/vendor/behave/test/_importer_candidate.py +3 -0
- data/vendor/behave/test/reporters/__init__.py +0 -0
- data/vendor/behave/test/reporters/test_summary.py +240 -0
- data/vendor/behave/test/test_ansi_escapes.py +73 -0
- data/vendor/behave/test/test_configuration.py +172 -0
- data/vendor/behave/test/test_formatter.py +265 -0
- data/vendor/behave/test/test_formatter_progress.py +39 -0
- data/vendor/behave/test/test_formatter_rerun.py +97 -0
- data/vendor/behave/test/test_formatter_tags.py +57 -0
- data/vendor/behave/test/test_importer.py +151 -0
- data/vendor/behave/test/test_log_capture.py +29 -0
- data/vendor/behave/test/test_matchers.py +236 -0
- data/vendor/behave/test/test_model.py +871 -0
- data/vendor/behave/test/test_parser.py +1590 -0
- data/vendor/behave/test/test_runner.py +1074 -0
- data/vendor/behave/test/test_step_registry.py +96 -0
- data/vendor/behave/test/test_tag_expression.py +506 -0
- data/vendor/behave/test/test_tag_expression2.py +462 -0
- data/vendor/behave/test/test_tag_matcher.py +729 -0
- data/vendor/behave/test/test_userdata.py +184 -0
- data/vendor/behave/tests/README.txt +12 -0
- data/vendor/behave/tests/__init__.py +0 -0
- data/vendor/behave/tests/api/__ONLY_PY34_or_newer.txt +0 -0
- data/vendor/behave/tests/api/__init__.py +0 -0
- data/vendor/behave/tests/api/_test_async_step34.py +130 -0
- data/vendor/behave/tests/api/_test_async_step35.py +75 -0
- data/vendor/behave/tests/api/test_async_step.py +18 -0
- data/vendor/behave/tests/api/testing_support.py +94 -0
- data/vendor/behave/tests/api/testing_support_async.py +21 -0
- data/vendor/behave/tests/issues/test_issue0336.py +66 -0
- data/vendor/behave/tests/issues/test_issue0449.py +55 -0
- data/vendor/behave/tests/issues/test_issue0453.py +62 -0
- data/vendor/behave/tests/issues/test_issue0458.py +54 -0
- data/vendor/behave/tests/issues/test_issue0495.py +65 -0
- data/vendor/behave/tests/unit/__init__.py +0 -0
- data/vendor/behave/tests/unit/test_behave4cmd_command_shell_proc.py +135 -0
- data/vendor/behave/tests/unit/test_capture.py +280 -0
- data/vendor/behave/tests/unit/test_model_core.py +56 -0
- data/vendor/behave/tests/unit/test_textutil.py +267 -0
- data/vendor/behave/tools/test-features/background.feature +9 -0
- data/vendor/behave/tools/test-features/environment.py +8 -0
- data/vendor/behave/tools/test-features/french.feature +11 -0
- data/vendor/behave/tools/test-features/outline.feature +39 -0
- data/vendor/behave/tools/test-features/parse.feature +10 -0
- data/vendor/behave/tools/test-features/step-data.feature +60 -0
- data/vendor/behave/tools/test-features/steps/steps.py +120 -0
- data/vendor/behave/tools/test-features/tags.feature +18 -0
- data/vendor/behave/tox.ini +159 -0
- metadata +562 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
"""
|
3
|
+
Invoke build script (python based).
|
4
|
+
|
5
|
+
.. seealso:: https://github.com/pyinvoke/invoke
|
6
|
+
"""
|
7
|
+
|
8
|
+
from __future__ import print_function
|
9
|
+
from invoke import task, Collection
|
10
|
+
import sys
|
11
|
+
|
12
|
+
# USE_PTY = os.isatty(sys.stdout)
|
13
|
+
USE_PTY = sys.stdout.isatty()
|
14
|
+
|
15
|
+
# ---------------------------------------------------------------------------
|
16
|
+
# TASKS
|
17
|
+
# ---------------------------------------------------------------------------
|
18
|
+
@task(help={
|
19
|
+
"args": "Command line args for behave",
|
20
|
+
"format": "Formatter to use",
|
21
|
+
})
|
22
|
+
def behave_test(ctx, args="", format=""): # XXX , echo=False):
|
23
|
+
"""Run behave tests."""
|
24
|
+
format = format or ctx.behave_test.format
|
25
|
+
options = ctx.behave_test.options or ""
|
26
|
+
args = args or ctx.behave_test.args
|
27
|
+
behave = "{python} bin/behave".format(python=sys.executable)
|
28
|
+
ctx.run("{behave} -f {format} {options} {args}".format(
|
29
|
+
behave=behave, format=format, options=options, args=args),
|
30
|
+
pty=USE_PTY)
|
31
|
+
|
32
|
+
|
33
|
+
# ---------------------------------------------------------------------------
|
34
|
+
# TASK MANAGEMENT / CONFIGURATION
|
35
|
+
# ---------------------------------------------------------------------------
|
36
|
+
# namespace.add_task(behave_test, default=True)
|
37
|
+
namespace = Collection()
|
38
|
+
namespace.add_task(behave_test, default=True)
|
39
|
+
namespace.configure({
|
40
|
+
"behave_test": {
|
41
|
+
"args": "",
|
42
|
+
"format": "progress2",
|
43
|
+
"options": "", # -- NOTE: Overide in configfile "invoke.yaml"
|
44
|
+
},
|
45
|
+
})
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
"""
|
3
|
+
Invoke build script.
|
4
|
+
Show all tasks with::
|
5
|
+
|
6
|
+
invoke -l
|
7
|
+
|
8
|
+
.. seealso::
|
9
|
+
|
10
|
+
* http://pyinvoke.org
|
11
|
+
* https://github.com/pyinvoke/invoke
|
12
|
+
"""
|
13
|
+
|
14
|
+
from __future__ import absolute_import
|
15
|
+
|
16
|
+
# -----------------------------------------------------------------------------
|
17
|
+
# BOOTSTRAP PATH: Use provided vendor bundle if "invoke" is not installed
|
18
|
+
# -----------------------------------------------------------------------------
|
19
|
+
INVOKE_MINVERSION = "0.14.0"
|
20
|
+
from . import _setup
|
21
|
+
_setup.setup_path()
|
22
|
+
_setup.require_invoke_minversion(INVOKE_MINVERSION)
|
23
|
+
|
24
|
+
# -----------------------------------------------------------------------------
|
25
|
+
# IMPORTS:
|
26
|
+
# -----------------------------------------------------------------------------
|
27
|
+
from invoke import Collection
|
28
|
+
import sys
|
29
|
+
|
30
|
+
# -- TASK-LIBRARY:
|
31
|
+
from . import clean
|
32
|
+
from . import docs
|
33
|
+
from . import test
|
34
|
+
|
35
|
+
# -----------------------------------------------------------------------------
|
36
|
+
# TASKS:
|
37
|
+
# -----------------------------------------------------------------------------
|
38
|
+
# None
|
39
|
+
|
40
|
+
|
41
|
+
# -----------------------------------------------------------------------------
|
42
|
+
# TASK CONFIGURATION:
|
43
|
+
# -----------------------------------------------------------------------------
|
44
|
+
namespace = Collection()
|
45
|
+
namespace.add_task(clean.clean)
|
46
|
+
namespace.add_task(clean.clean_all)
|
47
|
+
namespace.add_collection(Collection.from_module(docs))
|
48
|
+
namespace.add_collection(Collection.from_module(test))
|
49
|
+
|
50
|
+
# -- INJECT: clean configuration into this namespace
|
51
|
+
namespace.configure(clean.namespace.configuration())
|
52
|
+
if sys.platform.startswith("win"):
|
53
|
+
# -- OVERRIDE SETTINGS: For platform=win32, ... (Windows)
|
54
|
+
run_settings = dict(echo=True, pty=False, shell="C:/Windows/System32/cmd.exe")
|
55
|
+
namespace.configure({"run": run_settings})
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
"""
|
3
|
+
Provides "invoke" script when invoke is not installed.
|
4
|
+
Note that this approach uses the "tasks/_vendor/invoke.zip" bundle package.
|
5
|
+
|
6
|
+
Usage::
|
7
|
+
|
8
|
+
# -- INSTEAD OF: invoke command
|
9
|
+
# Show invoke version
|
10
|
+
python -m tasks --version
|
11
|
+
|
12
|
+
# List all tasks
|
13
|
+
python -m tasks -l
|
14
|
+
|
15
|
+
.. seealso::
|
16
|
+
|
17
|
+
* http://pyinvoke.org
|
18
|
+
* https://github.com/pyinvoke/invoke
|
19
|
+
|
20
|
+
|
21
|
+
Examples for Invoke Scripts using the Bundle
|
22
|
+
-------------------------------------------------------------------------------
|
23
|
+
|
24
|
+
For UNIX like platforms:
|
25
|
+
|
26
|
+
.. code-block:: sh
|
27
|
+
|
28
|
+
#!/bin/sh
|
29
|
+
#!/bin/bash
|
30
|
+
# RUN INVOKE: From bundled ZIP file (with Bourne shell/bash script).
|
31
|
+
# FILE: invoke.sh (in directory that contains tasks/ directory)
|
32
|
+
|
33
|
+
HERE=$(dirname $0)
|
34
|
+
export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
|
35
|
+
|
36
|
+
python ${HERE}/tasks/_vendor/invoke.zip $*
|
37
|
+
|
38
|
+
|
39
|
+
For Windows platform:
|
40
|
+
|
41
|
+
.. code-block:: bat
|
42
|
+
|
43
|
+
@echo off
|
44
|
+
REM RUN INVOKE: From bundled ZIP file (with Windows Batchfile).
|
45
|
+
REM FILE: invoke.cmd (in directory that contains tasks/ directory)
|
46
|
+
|
47
|
+
setlocal
|
48
|
+
set HERE=%~dp0
|
49
|
+
set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes"
|
50
|
+
if not defined PYTHON set PYTHON=python
|
51
|
+
|
52
|
+
%PYTHON% %HERE%tasks/_vendor/invoke.zip "%*"
|
53
|
+
"""
|
54
|
+
|
55
|
+
from __future__ import absolute_import
|
56
|
+
import os
|
57
|
+
import sys
|
58
|
+
|
59
|
+
# -----------------------------------------------------------------------------
|
60
|
+
# BOOTSTRAP PATH: Use provided vendor bundle if "invoke" is not installed
|
61
|
+
# -----------------------------------------------------------------------------
|
62
|
+
# NOTE: tasks/__init__.py performs sys.path setup.
|
63
|
+
os.environ["INVOKE_TASKS_USE_VENDOR_BUNDLES"] = "yes"
|
64
|
+
|
65
|
+
# -----------------------------------------------------------------------------
|
66
|
+
# AUTO-MAIN:
|
67
|
+
# -----------------------------------------------------------------------------
|
68
|
+
if __name__ == "__main__":
|
69
|
+
from invoke.main import program
|
70
|
+
sys.exit(program.run())
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
Decides if vendor bundles are used or not.
|
4
|
+
Setup python path accordingly.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from __future__ import absolute_import, print_function
|
8
|
+
import os.path
|
9
|
+
import sys
|
10
|
+
|
11
|
+
# -----------------------------------------------------------------------------
|
12
|
+
# DEFINES:
|
13
|
+
# -----------------------------------------------------------------------------
|
14
|
+
HERE = os.path.dirname(__file__)
|
15
|
+
TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor")
|
16
|
+
INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip")
|
17
|
+
INVOKE_BUNDLE_VERSION = "0.13.0"
|
18
|
+
|
19
|
+
DEBUG_SYSPATH = False
|
20
|
+
|
21
|
+
|
22
|
+
# -----------------------------------------------------------------------------
|
23
|
+
# EXCEPTIONS:
|
24
|
+
# -----------------------------------------------------------------------------
|
25
|
+
class VersionRequirementError(SystemExit): pass
|
26
|
+
|
27
|
+
# -----------------------------------------------------------------------------
|
28
|
+
# FUNCTIONS:
|
29
|
+
# -----------------------------------------------------------------------------
|
30
|
+
def setup_path(invoke_minversion=None):
|
31
|
+
"""Setup python search and add ``TASKS_VENDOR_DIR`` (if available)."""
|
32
|
+
# print("INVOKE.tasks: setup_path")
|
33
|
+
if not os.path.isdir(TASKS_VENDOR_DIR):
|
34
|
+
print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR)
|
35
|
+
return
|
36
|
+
elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path:
|
37
|
+
# -- SETUP ALREADY DONE:
|
38
|
+
# return
|
39
|
+
pass
|
40
|
+
|
41
|
+
use_vendor_bundles = os.environ.get("INVOKE_TASKS_USE_VENDOR_BUNDLES", "no")
|
42
|
+
if need_vendor_bundles(invoke_minversion):
|
43
|
+
use_vendor_bundles = "yes"
|
44
|
+
|
45
|
+
if use_vendor_bundles == "yes":
|
46
|
+
syspath_insert(0, os.path.abspath(TASKS_VENDOR_DIR))
|
47
|
+
if setup_path_for_bundle(INVOKE_BUNDLE, pos=1):
|
48
|
+
import invoke
|
49
|
+
bundle_path = os.path.relpath(INVOKE_BUNDLE, os.getcwd())
|
50
|
+
print("USING: %s (version: %s)" % (bundle_path, invoke.__version__))
|
51
|
+
else:
|
52
|
+
# -- BEST-EFFORT: May rescue something
|
53
|
+
syspath_append(os.path.abspath(TASKS_VENDOR_DIR))
|
54
|
+
setup_path_for_bundle(INVOKE_BUNDLE, pos=len(sys.path))
|
55
|
+
|
56
|
+
if DEBUG_SYSPATH:
|
57
|
+
for index, p in enumerate(sys.path):
|
58
|
+
print(" %d. %s" % (index, p))
|
59
|
+
|
60
|
+
|
61
|
+
def require_invoke_minversion(min_version, verbose=False):
|
62
|
+
"""Ensures that :mod:`invoke` has at the least the :param:`min_version`.
|
63
|
+
Otherwise,
|
64
|
+
|
65
|
+
:param min_version: Minimal acceptable invoke version (as string).
|
66
|
+
:param verbose: Indicates if invoke.version should be shown.
|
67
|
+
:raises: VersionRequirementError=SystemExit if requirement fails.
|
68
|
+
"""
|
69
|
+
# -- REQUIRES: sys.path is setup and contains invoke
|
70
|
+
try:
|
71
|
+
import invoke
|
72
|
+
invoke_version = invoke.__version__
|
73
|
+
except ImportError:
|
74
|
+
invoke_version = "__NOT_INSTALLED"
|
75
|
+
|
76
|
+
if invoke_version < min_version:
|
77
|
+
message = "REQUIRE: invoke.version >= %s (but was: %s)" % \
|
78
|
+
(min_version, invoke_version)
|
79
|
+
message += "\nUSE: pip install invoke>=%s" % min_version
|
80
|
+
raise VersionRequirementError(message)
|
81
|
+
|
82
|
+
INVOKE_VERSION = os.environ.get("INVOKE_VERSION", None)
|
83
|
+
if verbose and not INVOKE_VERSION:
|
84
|
+
os.environ["INVOKE_VERSION"] = invoke_version
|
85
|
+
print("USING: invoke.version=%s" % invoke_version)
|
86
|
+
|
87
|
+
def need_vendor_bundles(invoke_minversion=None):
|
88
|
+
invoke_minversion = invoke_minversion or "0.0.0"
|
89
|
+
need_vendor_answers = []
|
90
|
+
need_vendor_answers.append(need_vendor_bundle_invoke(invoke_minversion))
|
91
|
+
# -- REQUIRE: path.py
|
92
|
+
try:
|
93
|
+
import path
|
94
|
+
need_bundle = False
|
95
|
+
except ImportError:
|
96
|
+
need_bundle = True
|
97
|
+
need_vendor_answers.append(need_bundle)
|
98
|
+
|
99
|
+
# -- DIAG: print("INVOKE: need_bundle=%s" % need_bundle1)
|
100
|
+
# return need_bundle1 or need_bundle2
|
101
|
+
return any(need_vendor_answers)
|
102
|
+
|
103
|
+
def need_vendor_bundle_invoke(invoke_minversion="0.0.0"):
|
104
|
+
# -- REQUIRE: invoke
|
105
|
+
try:
|
106
|
+
import invoke
|
107
|
+
need_bundle = invoke.__version__ < invoke_minversion
|
108
|
+
if need_bundle:
|
109
|
+
del sys.modules["invoke"]
|
110
|
+
del invoke
|
111
|
+
except ImportError:
|
112
|
+
need_bundle = True
|
113
|
+
except Exception:
|
114
|
+
need_bundle = True
|
115
|
+
return need_bundle
|
116
|
+
|
117
|
+
# -----------------------------------------------------------------------------
|
118
|
+
# UTILITY FUNCTIONS:
|
119
|
+
# -----------------------------------------------------------------------------
|
120
|
+
def setup_path_for_bundle(bundle_path, pos=0):
|
121
|
+
if os.path.exists(bundle_path):
|
122
|
+
syspath_insert(pos, os.path.abspath(bundle_path))
|
123
|
+
return True
|
124
|
+
return False
|
125
|
+
|
126
|
+
def syspath_insert(pos, path):
|
127
|
+
if path in sys.path:
|
128
|
+
sys.path.remove(path)
|
129
|
+
sys.path.insert(pos, path)
|
130
|
+
|
131
|
+
def syspath_append(path):
|
132
|
+
if path in sys.path:
|
133
|
+
sys.path.remove(path)
|
134
|
+
sys.path.append(path)
|
135
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
tasks/_vendor: Bundled vendor parts -- needed by tasks
|
2
|
+
===============================================================================
|
3
|
+
|
4
|
+
This directory contains bundled archives that may be needed to run the tasks.
|
5
|
+
Especially, it contains an executable "invoke.zip" archive.
|
6
|
+
This archive can be used when invoke is not installed.
|
7
|
+
|
8
|
+
To execute invoke from the bundled ZIP archive::
|
9
|
+
|
10
|
+
|
11
|
+
python -m tasks/_vendor/invoke.zip --help
|
12
|
+
python -m tasks/_vendor/invoke.zip --version
|
13
|
+
|
14
|
+
|
15
|
+
Example for a local "bin/invoke" script in a UNIX like platform environment::
|
16
|
+
|
17
|
+
#!/bin/bash
|
18
|
+
# RUN INVOKE: From bundled ZIP file.
|
19
|
+
|
20
|
+
HERE=$(dirname $0)
|
21
|
+
|
22
|
+
python ${HERE}/../tasks/_vendor/invoke.zip $*
|
23
|
+
|
24
|
+
Example for a local "bin/invoke.cmd" script in a Windows environment::
|
25
|
+
|
26
|
+
@echo off
|
27
|
+
REM ==========================================================================
|
28
|
+
REM RUN INVOKE: From bundled ZIP file.
|
29
|
+
REM ==========================================================================
|
30
|
+
|
31
|
+
setlocal
|
32
|
+
set HERE=%~dp0
|
33
|
+
if not defined PYTHON set PYTHON=python
|
34
|
+
|
35
|
+
%PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*"
|
Binary file
|
@@ -0,0 +1,1725 @@
|
|
1
|
+
#
|
2
|
+
# SOURCE: https://pypi.python.org/pypi/path.py
|
3
|
+
# VERSION: 8.2.1
|
4
|
+
# -----------------------------------------------------------------------------
|
5
|
+
# Copyright (c) 2010 Mikhail Gusarov
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in
|
15
|
+
# all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#
|
25
|
+
|
26
|
+
"""
|
27
|
+
path.py - An object representing a path to a file or directory.
|
28
|
+
|
29
|
+
https://github.com/jaraco/path.py
|
30
|
+
|
31
|
+
Example::
|
32
|
+
|
33
|
+
from path import Path
|
34
|
+
d = Path('/home/guido/bin')
|
35
|
+
for f in d.files('*.py'):
|
36
|
+
f.chmod(0o755)
|
37
|
+
"""
|
38
|
+
|
39
|
+
from __future__ import unicode_literals
|
40
|
+
|
41
|
+
import sys
|
42
|
+
import warnings
|
43
|
+
import os
|
44
|
+
import fnmatch
|
45
|
+
import glob
|
46
|
+
import shutil
|
47
|
+
import codecs
|
48
|
+
import hashlib
|
49
|
+
import errno
|
50
|
+
import tempfile
|
51
|
+
import functools
|
52
|
+
import operator
|
53
|
+
import re
|
54
|
+
import contextlib
|
55
|
+
import io
|
56
|
+
from distutils import dir_util
|
57
|
+
import importlib
|
58
|
+
|
59
|
+
try:
|
60
|
+
import win32security
|
61
|
+
except ImportError:
|
62
|
+
pass
|
63
|
+
|
64
|
+
try:
|
65
|
+
import pwd
|
66
|
+
except ImportError:
|
67
|
+
pass
|
68
|
+
|
69
|
+
try:
|
70
|
+
import grp
|
71
|
+
except ImportError:
|
72
|
+
pass
|
73
|
+
|
74
|
+
##############################################################################
|
75
|
+
# Python 2/3 support
|
76
|
+
PY3 = sys.version_info >= (3,)
|
77
|
+
PY2 = not PY3
|
78
|
+
|
79
|
+
string_types = str,
|
80
|
+
text_type = str
|
81
|
+
getcwdu = os.getcwd
|
82
|
+
|
83
|
+
def surrogate_escape(error):
|
84
|
+
"""
|
85
|
+
Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only.
|
86
|
+
"""
|
87
|
+
chars = error.object[error.start:error.end]
|
88
|
+
assert len(chars) == 1
|
89
|
+
val = ord(chars)
|
90
|
+
val += 0xdc00
|
91
|
+
return __builtin__.unichr(val), error.end
|
92
|
+
|
93
|
+
if PY2:
|
94
|
+
import __builtin__
|
95
|
+
string_types = __builtin__.basestring,
|
96
|
+
text_type = __builtin__.unicode
|
97
|
+
getcwdu = os.getcwdu
|
98
|
+
codecs.register_error('surrogateescape', surrogate_escape)
|
99
|
+
|
100
|
+
@contextlib.contextmanager
|
101
|
+
def io_error_compat():
|
102
|
+
try:
|
103
|
+
yield
|
104
|
+
except IOError as io_err:
|
105
|
+
# On Python 2, io.open raises IOError; transform to OSError for
|
106
|
+
# future compatibility.
|
107
|
+
os_err = OSError(*io_err.args)
|
108
|
+
os_err.filename = getattr(io_err, 'filename', None)
|
109
|
+
raise os_err
|
110
|
+
|
111
|
+
##############################################################################
|
112
|
+
|
113
|
+
__all__ = ['Path', 'CaseInsensitivePattern']
|
114
|
+
|
115
|
+
|
116
|
+
LINESEPS = ['\r\n', '\r', '\n']
|
117
|
+
U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029']
|
118
|
+
NEWLINE = re.compile('|'.join(LINESEPS))
|
119
|
+
U_NEWLINE = re.compile('|'.join(U_LINESEPS))
|
120
|
+
NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern))
|
121
|
+
U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern))
|
122
|
+
|
123
|
+
|
124
|
+
try:
|
125
|
+
import pkg_resources
|
126
|
+
__version__ = pkg_resources.require('path.py')[0].version
|
127
|
+
except Exception:
|
128
|
+
__version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown'
|
129
|
+
|
130
|
+
|
131
|
+
class TreeWalkWarning(Warning):
|
132
|
+
pass
|
133
|
+
|
134
|
+
|
135
|
+
# from jaraco.functools
|
136
|
+
def compose(*funcs):
|
137
|
+
compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
|
138
|
+
return functools.reduce(compose_two, funcs)
|
139
|
+
|
140
|
+
|
141
|
+
def simple_cache(func):
|
142
|
+
"""
|
143
|
+
Save results for the :meth:'path.using_module' classmethod.
|
144
|
+
When Python 3.2 is available, use functools.lru_cache instead.
|
145
|
+
"""
|
146
|
+
saved_results = {}
|
147
|
+
|
148
|
+
def wrapper(cls, module):
|
149
|
+
if module in saved_results:
|
150
|
+
return saved_results[module]
|
151
|
+
saved_results[module] = func(cls, module)
|
152
|
+
return saved_results[module]
|
153
|
+
return wrapper
|
154
|
+
|
155
|
+
|
156
|
+
class ClassProperty(property):
|
157
|
+
def __get__(self, cls, owner):
|
158
|
+
return self.fget.__get__(None, owner)()
|
159
|
+
|
160
|
+
|
161
|
+
class multimethod(object):
|
162
|
+
"""
|
163
|
+
Acts like a classmethod when invoked from the class and like an
|
164
|
+
instancemethod when invoked from the instance.
|
165
|
+
"""
|
166
|
+
def __init__(self, func):
|
167
|
+
self.func = func
|
168
|
+
|
169
|
+
def __get__(self, instance, owner):
|
170
|
+
return (
|
171
|
+
functools.partial(self.func, owner) if instance is None
|
172
|
+
else functools.partial(self.func, owner, instance)
|
173
|
+
)
|
174
|
+
|
175
|
+
|
176
|
+
class Path(text_type):
|
177
|
+
"""
|
178
|
+
Represents a filesystem path.
|
179
|
+
|
180
|
+
For documentation on individual methods, consult their
|
181
|
+
counterparts in :mod:`os.path`.
|
182
|
+
|
183
|
+
Some methods are additionally included from :mod:`shutil`.
|
184
|
+
The functions are linked directly into the class namespace
|
185
|
+
such that they will be bound to the Path instance. For example,
|
186
|
+
``Path(src).copy(target)`` is equivalent to
|
187
|
+
``shutil.copy(src, target)``. Therefore, when referencing
|
188
|
+
the docs for these methods, assume `src` references `self`,
|
189
|
+
the Path instance.
|
190
|
+
"""
|
191
|
+
|
192
|
+
module = os.path
|
193
|
+
""" The path module to use for path operations.
|
194
|
+
|
195
|
+
.. seealso:: :mod:`os.path`
|
196
|
+
"""
|
197
|
+
|
198
|
+
def __init__(self, other=''):
|
199
|
+
if other is None:
|
200
|
+
raise TypeError("Invalid initial value for path: None")
|
201
|
+
|
202
|
+
@classmethod
|
203
|
+
@simple_cache
|
204
|
+
def using_module(cls, module):
|
205
|
+
subclass_name = cls.__name__ + '_' + module.__name__
|
206
|
+
if PY2:
|
207
|
+
subclass_name = str(subclass_name)
|
208
|
+
bases = (cls,)
|
209
|
+
ns = {'module': module}
|
210
|
+
return type(subclass_name, bases, ns)
|
211
|
+
|
212
|
+
@ClassProperty
|
213
|
+
@classmethod
|
214
|
+
def _next_class(cls):
|
215
|
+
"""
|
216
|
+
What class should be used to construct new instances from this class
|
217
|
+
"""
|
218
|
+
return cls
|
219
|
+
|
220
|
+
@classmethod
|
221
|
+
def _always_unicode(cls, path):
|
222
|
+
"""
|
223
|
+
Ensure the path as retrieved from a Python API, such as :func:`os.listdir`,
|
224
|
+
is a proper Unicode string.
|
225
|
+
"""
|
226
|
+
if PY3 or isinstance(path, text_type):
|
227
|
+
return path
|
228
|
+
return path.decode(sys.getfilesystemencoding(), 'surrogateescape')
|
229
|
+
|
230
|
+
# --- Special Python methods.
|
231
|
+
|
232
|
+
def __repr__(self):
|
233
|
+
return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__())
|
234
|
+
|
235
|
+
# Adding a Path and a string yields a Path.
|
236
|
+
def __add__(self, more):
|
237
|
+
try:
|
238
|
+
return self._next_class(super(Path, self).__add__(more))
|
239
|
+
except TypeError: # Python bug
|
240
|
+
return NotImplemented
|
241
|
+
|
242
|
+
def __radd__(self, other):
|
243
|
+
if not isinstance(other, string_types):
|
244
|
+
return NotImplemented
|
245
|
+
return self._next_class(other.__add__(self))
|
246
|
+
|
247
|
+
# The / operator joins Paths.
|
248
|
+
def __div__(self, rel):
|
249
|
+
""" fp.__div__(rel) == fp / rel == fp.joinpath(rel)
|
250
|
+
|
251
|
+
Join two path components, adding a separator character if
|
252
|
+
needed.
|
253
|
+
|
254
|
+
.. seealso:: :func:`os.path.join`
|
255
|
+
"""
|
256
|
+
return self._next_class(self.module.join(self, rel))
|
257
|
+
|
258
|
+
# Make the / operator work even when true division is enabled.
|
259
|
+
__truediv__ = __div__
|
260
|
+
|
261
|
+
# The / operator joins Paths the other way around
|
262
|
+
def __rdiv__(self, rel):
|
263
|
+
""" fp.__rdiv__(rel) == rel / fp
|
264
|
+
|
265
|
+
Join two path components, adding a separator character if
|
266
|
+
needed.
|
267
|
+
|
268
|
+
.. seealso:: :func:`os.path.join`
|
269
|
+
"""
|
270
|
+
return self._next_class(self.module.join(rel, self))
|
271
|
+
|
272
|
+
# Make the / operator work even when true division is enabled.
|
273
|
+
__rtruediv__ = __rdiv__
|
274
|
+
|
275
|
+
def __enter__(self):
|
276
|
+
self._old_dir = self.getcwd()
|
277
|
+
os.chdir(self)
|
278
|
+
return self
|
279
|
+
|
280
|
+
def __exit__(self, *_):
|
281
|
+
os.chdir(self._old_dir)
|
282
|
+
|
283
|
+
@classmethod
|
284
|
+
def getcwd(cls):
|
285
|
+
""" Return the current working directory as a path object.
|
286
|
+
|
287
|
+
.. seealso:: :func:`os.getcwdu`
|
288
|
+
"""
|
289
|
+
return cls(getcwdu())
|
290
|
+
|
291
|
+
#
|
292
|
+
# --- Operations on Path strings.
|
293
|
+
|
294
|
+
def abspath(self):
|
295
|
+
""" .. seealso:: :func:`os.path.abspath` """
|
296
|
+
return self._next_class(self.module.abspath(self))
|
297
|
+
|
298
|
+
def normcase(self):
|
299
|
+
""" .. seealso:: :func:`os.path.normcase` """
|
300
|
+
return self._next_class(self.module.normcase(self))
|
301
|
+
|
302
|
+
def normpath(self):
|
303
|
+
""" .. seealso:: :func:`os.path.normpath` """
|
304
|
+
return self._next_class(self.module.normpath(self))
|
305
|
+
|
306
|
+
def realpath(self):
|
307
|
+
""" .. seealso:: :func:`os.path.realpath` """
|
308
|
+
return self._next_class(self.module.realpath(self))
|
309
|
+
|
310
|
+
def expanduser(self):
|
311
|
+
""" .. seealso:: :func:`os.path.expanduser` """
|
312
|
+
return self._next_class(self.module.expanduser(self))
|
313
|
+
|
314
|
+
def expandvars(self):
|
315
|
+
""" .. seealso:: :func:`os.path.expandvars` """
|
316
|
+
return self._next_class(self.module.expandvars(self))
|
317
|
+
|
318
|
+
def dirname(self):
|
319
|
+
""" .. seealso:: :attr:`parent`, :func:`os.path.dirname` """
|
320
|
+
return self._next_class(self.module.dirname(self))
|
321
|
+
|
322
|
+
def basename(self):
|
323
|
+
""" .. seealso:: :attr:`name`, :func:`os.path.basename` """
|
324
|
+
return self._next_class(self.module.basename(self))
|
325
|
+
|
326
|
+
def expand(self):
|
327
|
+
""" Clean up a filename by calling :meth:`expandvars()`,
|
328
|
+
:meth:`expanduser()`, and :meth:`normpath()` on it.
|
329
|
+
|
330
|
+
This is commonly everything needed to clean up a filename
|
331
|
+
read from a configuration file, for example.
|
332
|
+
"""
|
333
|
+
return self.expandvars().expanduser().normpath()
|
334
|
+
|
335
|
+
@property
|
336
|
+
def namebase(self):
|
337
|
+
""" The same as :meth:`name`, but with one file extension stripped off.
|
338
|
+
|
339
|
+
For example,
|
340
|
+
``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``,
|
341
|
+
but
|
342
|
+
``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``.
|
343
|
+
"""
|
344
|
+
base, ext = self.module.splitext(self.name)
|
345
|
+
return base
|
346
|
+
|
347
|
+
@property
|
348
|
+
def ext(self):
|
349
|
+
""" The file extension, for example ``'.py'``. """
|
350
|
+
f, ext = self.module.splitext(self)
|
351
|
+
return ext
|
352
|
+
|
353
|
+
@property
|
354
|
+
def drive(self):
|
355
|
+
""" The drive specifier, for example ``'C:'``.
|
356
|
+
|
357
|
+
This is always empty on systems that don't use drive specifiers.
|
358
|
+
"""
|
359
|
+
drive, r = self.module.splitdrive(self)
|
360
|
+
return self._next_class(drive)
|
361
|
+
|
362
|
+
parent = property(
|
363
|
+
dirname, None, None,
|
364
|
+
""" This path's parent directory, as a new Path object.
|
365
|
+
|
366
|
+
For example,
|
367
|
+
``Path('/usr/local/lib/libpython.so').parent ==
|
368
|
+
Path('/usr/local/lib')``
|
369
|
+
|
370
|
+
.. seealso:: :meth:`dirname`, :func:`os.path.dirname`
|
371
|
+
""")
|
372
|
+
|
373
|
+
name = property(
|
374
|
+
basename, None, None,
|
375
|
+
""" The name of this file or directory without the full path.
|
376
|
+
|
377
|
+
For example,
|
378
|
+
``Path('/usr/local/lib/libpython.so').name == 'libpython.so'``
|
379
|
+
|
380
|
+
.. seealso:: :meth:`basename`, :func:`os.path.basename`
|
381
|
+
""")
|
382
|
+
|
383
|
+
def splitpath(self):
|
384
|
+
""" p.splitpath() -> Return ``(p.parent, p.name)``.
|
385
|
+
|
386
|
+
.. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split`
|
387
|
+
"""
|
388
|
+
parent, child = self.module.split(self)
|
389
|
+
return self._next_class(parent), child
|
390
|
+
|
391
|
+
def splitdrive(self):
|
392
|
+
""" p.splitdrive() -> Return ``(p.drive, <the rest of p>)``.
|
393
|
+
|
394
|
+
Split the drive specifier from this path. If there is
|
395
|
+
no drive specifier, :samp:`{p.drive}` is empty, so the return value
|
396
|
+
is simply ``(Path(''), p)``. This is always the case on Unix.
|
397
|
+
|
398
|
+
.. seealso:: :func:`os.path.splitdrive`
|
399
|
+
"""
|
400
|
+
drive, rel = self.module.splitdrive(self)
|
401
|
+
return self._next_class(drive), rel
|
402
|
+
|
403
|
+
def splitext(self):
|
404
|
+
""" p.splitext() -> Return ``(p.stripext(), p.ext)``.
|
405
|
+
|
406
|
+
Split the filename extension from this path and return
|
407
|
+
the two parts. Either part may be empty.
|
408
|
+
|
409
|
+
The extension is everything from ``'.'`` to the end of the
|
410
|
+
last path segment. This has the property that if
|
411
|
+
``(a, b) == p.splitext()``, then ``a + b == p``.
|
412
|
+
|
413
|
+
.. seealso:: :func:`os.path.splitext`
|
414
|
+
"""
|
415
|
+
filename, ext = self.module.splitext(self)
|
416
|
+
return self._next_class(filename), ext
|
417
|
+
|
418
|
+
def stripext(self):
|
419
|
+
""" p.stripext() -> Remove one file extension from the path.
|
420
|
+
|
421
|
+
For example, ``Path('/home/guido/python.tar.gz').stripext()``
|
422
|
+
returns ``Path('/home/guido/python.tar')``.
|
423
|
+
"""
|
424
|
+
return self.splitext()[0]
|
425
|
+
|
426
|
+
def splitunc(self):
|
427
|
+
""" .. seealso:: :func:`os.path.splitunc` """
|
428
|
+
unc, rest = self.module.splitunc(self)
|
429
|
+
return self._next_class(unc), rest
|
430
|
+
|
431
|
+
@property
|
432
|
+
def uncshare(self):
|
433
|
+
"""
|
434
|
+
The UNC mount point for this path.
|
435
|
+
This is empty for paths on local drives.
|
436
|
+
"""
|
437
|
+
unc, r = self.module.splitunc(self)
|
438
|
+
return self._next_class(unc)
|
439
|
+
|
440
|
+
@multimethod
|
441
|
+
def joinpath(cls, first, *others):
|
442
|
+
"""
|
443
|
+
Join first to zero or more :class:`Path` components, adding a separator
|
444
|
+
character (:samp:`{first}.module.sep`) if needed. Returns a new instance of
|
445
|
+
:samp:`{first}._next_class`.
|
446
|
+
|
447
|
+
.. seealso:: :func:`os.path.join`
|
448
|
+
"""
|
449
|
+
if not isinstance(first, cls):
|
450
|
+
first = cls(first)
|
451
|
+
return first._next_class(first.module.join(first, *others))
|
452
|
+
|
453
|
+
def splitall(self):
|
454
|
+
r""" Return a list of the path components in this path.
|
455
|
+
|
456
|
+
The first item in the list will be a Path. Its value will be
|
457
|
+
either :data:`os.curdir`, :data:`os.pardir`, empty, or the root
|
458
|
+
directory of this path (for example, ``'/'`` or ``'C:\\'``). The
|
459
|
+
other items in the list will be strings.
|
460
|
+
|
461
|
+
``path.Path.joinpath(*result)`` will yield the original path.
|
462
|
+
"""
|
463
|
+
parts = []
|
464
|
+
loc = self
|
465
|
+
while loc != os.curdir and loc != os.pardir:
|
466
|
+
prev = loc
|
467
|
+
loc, child = prev.splitpath()
|
468
|
+
if loc == prev:
|
469
|
+
break
|
470
|
+
parts.append(child)
|
471
|
+
parts.append(loc)
|
472
|
+
parts.reverse()
|
473
|
+
return parts
|
474
|
+
|
475
|
+
def relpath(self, start='.'):
|
476
|
+
""" Return this path as a relative path,
|
477
|
+
based from `start`, which defaults to the current working directory.
|
478
|
+
"""
|
479
|
+
cwd = self._next_class(start)
|
480
|
+
return cwd.relpathto(self)
|
481
|
+
|
482
|
+
def relpathto(self, dest):
|
483
|
+
""" Return a relative path from `self` to `dest`.
|
484
|
+
|
485
|
+
If there is no relative path from `self` to `dest`, for example if
|
486
|
+
they reside on different drives in Windows, then this returns
|
487
|
+
``dest.abspath()``.
|
488
|
+
"""
|
489
|
+
origin = self.abspath()
|
490
|
+
dest = self._next_class(dest).abspath()
|
491
|
+
|
492
|
+
orig_list = origin.normcase().splitall()
|
493
|
+
# Don't normcase dest! We want to preserve the case.
|
494
|
+
dest_list = dest.splitall()
|
495
|
+
|
496
|
+
if orig_list[0] != self.module.normcase(dest_list[0]):
|
497
|
+
# Can't get here from there.
|
498
|
+
return dest
|
499
|
+
|
500
|
+
# Find the location where the two paths start to differ.
|
501
|
+
i = 0
|
502
|
+
for start_seg, dest_seg in zip(orig_list, dest_list):
|
503
|
+
if start_seg != self.module.normcase(dest_seg):
|
504
|
+
break
|
505
|
+
i += 1
|
506
|
+
|
507
|
+
# Now i is the point where the two paths diverge.
|
508
|
+
# Need a certain number of "os.pardir"s to work up
|
509
|
+
# from the origin to the point of divergence.
|
510
|
+
segments = [os.pardir] * (len(orig_list) - i)
|
511
|
+
# Need to add the diverging part of dest_list.
|
512
|
+
segments += dest_list[i:]
|
513
|
+
if len(segments) == 0:
|
514
|
+
# If they happen to be identical, use os.curdir.
|
515
|
+
relpath = os.curdir
|
516
|
+
else:
|
517
|
+
relpath = self.module.join(*segments)
|
518
|
+
return self._next_class(relpath)
|
519
|
+
|
520
|
+
# --- Listing, searching, walking, and matching
|
521
|
+
|
522
|
+
def listdir(self, pattern=None):
|
523
|
+
""" D.listdir() -> List of items in this directory.
|
524
|
+
|
525
|
+
Use :meth:`files` or :meth:`dirs` instead if you want a listing
|
526
|
+
of just files or just subdirectories.
|
527
|
+
|
528
|
+
The elements of the list are Path objects.
|
529
|
+
|
530
|
+
With the optional `pattern` argument, this only lists
|
531
|
+
items whose names match the given pattern.
|
532
|
+
|
533
|
+
.. seealso:: :meth:`files`, :meth:`dirs`
|
534
|
+
"""
|
535
|
+
if pattern is None:
|
536
|
+
pattern = '*'
|
537
|
+
return [
|
538
|
+
self / child
|
539
|
+
for child in map(self._always_unicode, os.listdir(self))
|
540
|
+
if self._next_class(child).fnmatch(pattern)
|
541
|
+
]
|
542
|
+
|
543
|
+
def dirs(self, pattern=None):
|
544
|
+
""" D.dirs() -> List of this directory's subdirectories.
|
545
|
+
|
546
|
+
The elements of the list are Path objects.
|
547
|
+
This does not walk recursively into subdirectories
|
548
|
+
(but see :meth:`walkdirs`).
|
549
|
+
|
550
|
+
With the optional `pattern` argument, this only lists
|
551
|
+
directories whose names match the given pattern. For
|
552
|
+
example, ``d.dirs('build-*')``.
|
553
|
+
"""
|
554
|
+
return [p for p in self.listdir(pattern) if p.isdir()]
|
555
|
+
|
556
|
+
def files(self, pattern=None):
|
557
|
+
""" D.files() -> List of the files in this directory.
|
558
|
+
|
559
|
+
The elements of the list are Path objects.
|
560
|
+
This does not walk into subdirectories (see :meth:`walkfiles`).
|
561
|
+
|
562
|
+
With the optional `pattern` argument, this only lists files
|
563
|
+
whose names match the given pattern. For example,
|
564
|
+
``d.files('*.pyc')``.
|
565
|
+
"""
|
566
|
+
|
567
|
+
return [p for p in self.listdir(pattern) if p.isfile()]
|
568
|
+
|
569
|
+
def walk(self, pattern=None, errors='strict'):
|
570
|
+
""" D.walk() -> iterator over files and subdirs, recursively.
|
571
|
+
|
572
|
+
The iterator yields Path objects naming each child item of
|
573
|
+
this directory and its descendants. This requires that
|
574
|
+
``D.isdir()``.
|
575
|
+
|
576
|
+
This performs a depth-first traversal of the directory tree.
|
577
|
+
Each directory is returned just before all its children.
|
578
|
+
|
579
|
+
The `errors=` keyword argument controls behavior when an
|
580
|
+
error occurs. The default is ``'strict'``, which causes an
|
581
|
+
exception. Other allowed values are ``'warn'`` (which
|
582
|
+
reports the error via :func:`warnings.warn()`), and ``'ignore'``.
|
583
|
+
`errors` may also be an arbitrary callable taking a msg parameter.
|
584
|
+
"""
|
585
|
+
class Handlers:
|
586
|
+
def strict(msg):
|
587
|
+
raise
|
588
|
+
|
589
|
+
def warn(msg):
|
590
|
+
warnings.warn(msg, TreeWalkWarning)
|
591
|
+
|
592
|
+
def ignore(msg):
|
593
|
+
pass
|
594
|
+
|
595
|
+
if not callable(errors) and errors not in vars(Handlers):
|
596
|
+
raise ValueError("invalid errors parameter")
|
597
|
+
errors = vars(Handlers).get(errors, errors)
|
598
|
+
|
599
|
+
try:
|
600
|
+
childList = self.listdir()
|
601
|
+
except Exception:
|
602
|
+
exc = sys.exc_info()[1]
|
603
|
+
tmpl = "Unable to list directory '%(self)s': %(exc)s"
|
604
|
+
msg = tmpl % locals()
|
605
|
+
errors(msg)
|
606
|
+
return
|
607
|
+
|
608
|
+
for child in childList:
|
609
|
+
if pattern is None or child.fnmatch(pattern):
|
610
|
+
yield child
|
611
|
+
try:
|
612
|
+
isdir = child.isdir()
|
613
|
+
except Exception:
|
614
|
+
exc = sys.exc_info()[1]
|
615
|
+
tmpl = "Unable to access '%(child)s': %(exc)s"
|
616
|
+
msg = tmpl % locals()
|
617
|
+
errors(msg)
|
618
|
+
isdir = False
|
619
|
+
|
620
|
+
if isdir:
|
621
|
+
for item in child.walk(pattern, errors):
|
622
|
+
yield item
|
623
|
+
|
624
|
+
def walkdirs(self, pattern=None, errors='strict'):
|
625
|
+
""" D.walkdirs() -> iterator over subdirs, recursively.
|
626
|
+
|
627
|
+
With the optional `pattern` argument, this yields only
|
628
|
+
directories whose names match the given pattern. For
|
629
|
+
example, ``mydir.walkdirs('*test')`` yields only directories
|
630
|
+
with names ending in ``'test'``.
|
631
|
+
|
632
|
+
The `errors=` keyword argument controls behavior when an
|
633
|
+
error occurs. The default is ``'strict'``, which causes an
|
634
|
+
exception. The other allowed values are ``'warn'`` (which
|
635
|
+
reports the error via :func:`warnings.warn()`), and ``'ignore'``.
|
636
|
+
"""
|
637
|
+
if errors not in ('strict', 'warn', 'ignore'):
|
638
|
+
raise ValueError("invalid errors parameter")
|
639
|
+
|
640
|
+
try:
|
641
|
+
dirs = self.dirs()
|
642
|
+
except Exception:
|
643
|
+
if errors == 'ignore':
|
644
|
+
return
|
645
|
+
elif errors == 'warn':
|
646
|
+
warnings.warn(
|
647
|
+
"Unable to list directory '%s': %s"
|
648
|
+
% (self, sys.exc_info()[1]),
|
649
|
+
TreeWalkWarning)
|
650
|
+
return
|
651
|
+
else:
|
652
|
+
raise
|
653
|
+
|
654
|
+
for child in dirs:
|
655
|
+
if pattern is None or child.fnmatch(pattern):
|
656
|
+
yield child
|
657
|
+
for subsubdir in child.walkdirs(pattern, errors):
|
658
|
+
yield subsubdir
|
659
|
+
|
660
|
+
def walkfiles(self, pattern=None, errors='strict'):
|
661
|
+
""" D.walkfiles() -> iterator over files in D, recursively.
|
662
|
+
|
663
|
+
The optional argument `pattern` limits the results to files
|
664
|
+
with names that match the pattern. For example,
|
665
|
+
``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp``
|
666
|
+
extension.
|
667
|
+
"""
|
668
|
+
if errors not in ('strict', 'warn', 'ignore'):
|
669
|
+
raise ValueError("invalid errors parameter")
|
670
|
+
|
671
|
+
try:
|
672
|
+
childList = self.listdir()
|
673
|
+
except Exception:
|
674
|
+
if errors == 'ignore':
|
675
|
+
return
|
676
|
+
elif errors == 'warn':
|
677
|
+
warnings.warn(
|
678
|
+
"Unable to list directory '%s': %s"
|
679
|
+
% (self, sys.exc_info()[1]),
|
680
|
+
TreeWalkWarning)
|
681
|
+
return
|
682
|
+
else:
|
683
|
+
raise
|
684
|
+
|
685
|
+
for child in childList:
|
686
|
+
try:
|
687
|
+
isfile = child.isfile()
|
688
|
+
isdir = not isfile and child.isdir()
|
689
|
+
except:
|
690
|
+
if errors == 'ignore':
|
691
|
+
continue
|
692
|
+
elif errors == 'warn':
|
693
|
+
warnings.warn(
|
694
|
+
"Unable to access '%s': %s"
|
695
|
+
% (self, sys.exc_info()[1]),
|
696
|
+
TreeWalkWarning)
|
697
|
+
continue
|
698
|
+
else:
|
699
|
+
raise
|
700
|
+
|
701
|
+
if isfile:
|
702
|
+
if pattern is None or child.fnmatch(pattern):
|
703
|
+
yield child
|
704
|
+
elif isdir:
|
705
|
+
for f in child.walkfiles(pattern, errors):
|
706
|
+
yield f
|
707
|
+
|
708
|
+
def fnmatch(self, pattern, normcase=None):
|
709
|
+
""" Return ``True`` if `self.name` matches the given `pattern`.
|
710
|
+
|
711
|
+
`pattern` - A filename pattern with wildcards,
|
712
|
+
for example ``'*.py'``. If the pattern contains a `normcase`
|
713
|
+
attribute, it is applied to the name and path prior to comparison.
|
714
|
+
|
715
|
+
`normcase` - (optional) A function used to normalize the pattern and
|
716
|
+
filename before matching. Defaults to :meth:`self.module`, which defaults
|
717
|
+
to :meth:`os.path.normcase`.
|
718
|
+
|
719
|
+
.. seealso:: :func:`fnmatch.fnmatch`
|
720
|
+
"""
|
721
|
+
default_normcase = getattr(pattern, 'normcase', self.module.normcase)
|
722
|
+
normcase = normcase or default_normcase
|
723
|
+
name = normcase(self.name)
|
724
|
+
pattern = normcase(pattern)
|
725
|
+
return fnmatch.fnmatchcase(name, pattern)
|
726
|
+
|
727
|
+
def glob(self, pattern):
|
728
|
+
""" Return a list of Path objects that match the pattern.
|
729
|
+
|
730
|
+
`pattern` - a path relative to this directory, with wildcards.
|
731
|
+
|
732
|
+
For example, ``Path('/users').glob('*/bin/*')`` returns a list
|
733
|
+
of all the files users have in their :file:`bin` directories.
|
734
|
+
|
735
|
+
.. seealso:: :func:`glob.glob`
|
736
|
+
"""
|
737
|
+
cls = self._next_class
|
738
|
+
return [cls(s) for s in glob.glob(self / pattern)]
|
739
|
+
|
740
|
+
#
|
741
|
+
# --- Reading or writing an entire file at once.
|
742
|
+
|
743
|
+
def open(self, *args, **kwargs):
|
744
|
+
""" Open this file and return a corresponding :class:`file` object.
|
745
|
+
|
746
|
+
Keyword arguments work as in :func:`io.open`. If the file cannot be
|
747
|
+
opened, an :class:`~exceptions.OSError` is raised.
|
748
|
+
"""
|
749
|
+
with io_error_compat():
|
750
|
+
return io.open(self, *args, **kwargs)
|
751
|
+
|
752
|
+
def bytes(self):
|
753
|
+
""" Open this file, read all bytes, return them as a string. """
|
754
|
+
with self.open('rb') as f:
|
755
|
+
return f.read()
|
756
|
+
|
757
|
+
def chunks(self, size, *args, **kwargs):
|
758
|
+
""" Returns a generator yielding chunks of the file, so it can
|
759
|
+
be read piece by piece with a simple for loop.
|
760
|
+
|
761
|
+
Any argument you pass after `size` will be passed to :meth:`open`.
|
762
|
+
|
763
|
+
:example:
|
764
|
+
|
765
|
+
>>> hash = hashlib.md5()
|
766
|
+
>>> for chunk in Path("path.py").chunks(8192, mode='rb'):
|
767
|
+
... hash.update(chunk)
|
768
|
+
|
769
|
+
This will read the file by chunks of 8192 bytes.
|
770
|
+
"""
|
771
|
+
with self.open(*args, **kwargs) as f:
|
772
|
+
for chunk in iter(lambda: f.read(size) or None, None):
|
773
|
+
yield chunk
|
774
|
+
|
775
|
+
def write_bytes(self, bytes, append=False):
|
776
|
+
""" Open this file and write the given bytes to it.
|
777
|
+
|
778
|
+
Default behavior is to overwrite any existing file.
|
779
|
+
Call ``p.write_bytes(bytes, append=True)`` to append instead.
|
780
|
+
"""
|
781
|
+
if append:
|
782
|
+
mode = 'ab'
|
783
|
+
else:
|
784
|
+
mode = 'wb'
|
785
|
+
with self.open(mode) as f:
|
786
|
+
f.write(bytes)
|
787
|
+
|
788
|
+
def text(self, encoding=None, errors='strict'):
|
789
|
+
r""" Open this file, read it in, return the content as a string.
|
790
|
+
|
791
|
+
All newline sequences are converted to ``'\n'``. Keyword arguments
|
792
|
+
will be passed to :meth:`open`.
|
793
|
+
|
794
|
+
.. seealso:: :meth:`lines`
|
795
|
+
"""
|
796
|
+
with self.open(mode='r', encoding=encoding, errors=errors) as f:
|
797
|
+
return U_NEWLINE.sub('\n', f.read())
|
798
|
+
|
799
|
+
def write_text(self, text, encoding=None, errors='strict',
|
800
|
+
linesep=os.linesep, append=False):
|
801
|
+
r""" Write the given text to this file.
|
802
|
+
|
803
|
+
The default behavior is to overwrite any existing file;
|
804
|
+
to append instead, use the `append=True` keyword argument.
|
805
|
+
|
806
|
+
There are two differences between :meth:`write_text` and
|
807
|
+
:meth:`write_bytes`: newline handling and Unicode handling.
|
808
|
+
See below.
|
809
|
+
|
810
|
+
Parameters:
|
811
|
+
|
812
|
+
`text` - str/unicode - The text to be written.
|
813
|
+
|
814
|
+
`encoding` - str - The Unicode encoding that will be used.
|
815
|
+
This is ignored if `text` isn't a Unicode string.
|
816
|
+
|
817
|
+
`errors` - str - How to handle Unicode encoding errors.
|
818
|
+
Default is ``'strict'``. See ``help(unicode.encode)`` for the
|
819
|
+
options. This is ignored if `text` isn't a Unicode
|
820
|
+
string.
|
821
|
+
|
822
|
+
`linesep` - keyword argument - str/unicode - The sequence of
|
823
|
+
characters to be used to mark end-of-line. The default is
|
824
|
+
:data:`os.linesep`. You can also specify ``None`` to
|
825
|
+
leave all newlines as they are in `text`.
|
826
|
+
|
827
|
+
`append` - keyword argument - bool - Specifies what to do if
|
828
|
+
the file already exists (``True``: append to the end of it;
|
829
|
+
``False``: overwrite it.) The default is ``False``.
|
830
|
+
|
831
|
+
|
832
|
+
--- Newline handling.
|
833
|
+
|
834
|
+
``write_text()`` converts all standard end-of-line sequences
|
835
|
+
(``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default
|
836
|
+
end-of-line sequence (see :data:`os.linesep`; on Windows, for example,
|
837
|
+
the end-of-line marker is ``'\r\n'``).
|
838
|
+
|
839
|
+
If you don't like your platform's default, you can override it
|
840
|
+
using the `linesep=` keyword argument. If you specifically want
|
841
|
+
``write_text()`` to preserve the newlines as-is, use ``linesep=None``.
|
842
|
+
|
843
|
+
This applies to Unicode text the same as to 8-bit text, except
|
844
|
+
there are three additional standard Unicode end-of-line sequences:
|
845
|
+
``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``.
|
846
|
+
|
847
|
+
(This is slightly different from when you open a file for
|
848
|
+
writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')``
|
849
|
+
in Python.)
|
850
|
+
|
851
|
+
|
852
|
+
--- Unicode
|
853
|
+
|
854
|
+
If `text` isn't Unicode, then apart from newline handling, the
|
855
|
+
bytes are written verbatim to the file. The `encoding` and
|
856
|
+
`errors` arguments are not used and must be omitted.
|
857
|
+
|
858
|
+
If `text` is Unicode, it is first converted to :func:`bytes` using the
|
859
|
+
specified `encoding` (or the default encoding if `encoding`
|
860
|
+
isn't specified). The `errors` argument applies only to this
|
861
|
+
conversion.
|
862
|
+
|
863
|
+
"""
|
864
|
+
if isinstance(text, text_type):
|
865
|
+
if linesep is not None:
|
866
|
+
text = U_NEWLINE.sub(linesep, text)
|
867
|
+
text = text.encode(encoding or sys.getdefaultencoding(), errors)
|
868
|
+
else:
|
869
|
+
assert encoding is None
|
870
|
+
text = NEWLINE.sub(linesep, text)
|
871
|
+
self.write_bytes(text, append=append)
|
872
|
+
|
873
|
+
def lines(self, encoding=None, errors='strict', retain=True):
|
874
|
+
r""" Open this file, read all lines, return them in a list.
|
875
|
+
|
876
|
+
Optional arguments:
|
877
|
+
`encoding` - The Unicode encoding (or character set) of
|
878
|
+
the file. The default is ``None``, meaning the content
|
879
|
+
of the file is read as 8-bit characters and returned
|
880
|
+
as a list of (non-Unicode) str objects.
|
881
|
+
`errors` - How to handle Unicode errors; see help(str.decode)
|
882
|
+
for the options. Default is ``'strict'``.
|
883
|
+
`retain` - If ``True``, retain newline characters; but all newline
|
884
|
+
character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are
|
885
|
+
translated to ``'\n'``. If ``False``, newline characters are
|
886
|
+
stripped off. Default is ``True``.
|
887
|
+
|
888
|
+
This uses ``'U'`` mode.
|
889
|
+
|
890
|
+
.. seealso:: :meth:`text`
|
891
|
+
"""
|
892
|
+
if encoding is None and retain:
|
893
|
+
with self.open('U') as f:
|
894
|
+
return f.readlines()
|
895
|
+
else:
|
896
|
+
return self.text(encoding, errors).splitlines(retain)
|
897
|
+
|
898
|
+
def write_lines(self, lines, encoding=None, errors='strict',
|
899
|
+
linesep=os.linesep, append=False):
|
900
|
+
r""" Write the given lines of text to this file.
|
901
|
+
|
902
|
+
By default this overwrites any existing file at this path.
|
903
|
+
|
904
|
+
This puts a platform-specific newline sequence on every line.
|
905
|
+
See `linesep` below.
|
906
|
+
|
907
|
+
`lines` - A list of strings.
|
908
|
+
|
909
|
+
`encoding` - A Unicode encoding to use. This applies only if
|
910
|
+
`lines` contains any Unicode strings.
|
911
|
+
|
912
|
+
`errors` - How to handle errors in Unicode encoding. This
|
913
|
+
also applies only to Unicode strings.
|
914
|
+
|
915
|
+
linesep - The desired line-ending. This line-ending is
|
916
|
+
applied to every line. If a line already has any
|
917
|
+
standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``,
|
918
|
+
``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will
|
919
|
+
be stripped off and this will be used instead. The
|
920
|
+
default is os.linesep, which is platform-dependent
|
921
|
+
(``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.).
|
922
|
+
Specify ``None`` to write the lines as-is, like
|
923
|
+
:meth:`file.writelines`.
|
924
|
+
|
925
|
+
Use the keyword argument ``append=True`` to append lines to the
|
926
|
+
file. The default is to overwrite the file.
|
927
|
+
|
928
|
+
.. warning ::
|
929
|
+
|
930
|
+
When you use this with Unicode data, if the encoding of the
|
931
|
+
existing data in the file is different from the encoding
|
932
|
+
you specify with the `encoding=` parameter, the result is
|
933
|
+
mixed-encoding data, which can really confuse someone trying
|
934
|
+
to read the file later.
|
935
|
+
"""
|
936
|
+
with self.open('ab' if append else 'wb') as f:
|
937
|
+
for l in lines:
|
938
|
+
isUnicode = isinstance(l, text_type)
|
939
|
+
if linesep is not None:
|
940
|
+
pattern = U_NL_END if isUnicode else NL_END
|
941
|
+
l = pattern.sub('', l) + linesep
|
942
|
+
if isUnicode:
|
943
|
+
l = l.encode(encoding or sys.getdefaultencoding(), errors)
|
944
|
+
f.write(l)
|
945
|
+
|
946
|
+
def read_md5(self):
|
947
|
+
""" Calculate the md5 hash for this file.
|
948
|
+
|
949
|
+
This reads through the entire file.
|
950
|
+
|
951
|
+
.. seealso:: :meth:`read_hash`
|
952
|
+
"""
|
953
|
+
return self.read_hash('md5')
|
954
|
+
|
955
|
+
def _hash(self, hash_name):
|
956
|
+
""" Returns a hash object for the file at the current path.
|
957
|
+
|
958
|
+
`hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``)
|
959
|
+
that's available in the :mod:`hashlib` module.
|
960
|
+
"""
|
961
|
+
m = hashlib.new(hash_name)
|
962
|
+
for chunk in self.chunks(8192, mode="rb"):
|
963
|
+
m.update(chunk)
|
964
|
+
return m
|
965
|
+
|
966
|
+
def read_hash(self, hash_name):
|
967
|
+
""" Calculate given hash for this file.
|
968
|
+
|
969
|
+
List of supported hashes can be obtained from :mod:`hashlib` package.
|
970
|
+
This reads the entire file.
|
971
|
+
|
972
|
+
.. seealso:: :meth:`hashlib.hash.digest`
|
973
|
+
"""
|
974
|
+
return self._hash(hash_name).digest()
|
975
|
+
|
976
|
+
def read_hexhash(self, hash_name):
|
977
|
+
""" Calculate given hash for this file, returning hexdigest.
|
978
|
+
|
979
|
+
List of supported hashes can be obtained from :mod:`hashlib` package.
|
980
|
+
This reads the entire file.
|
981
|
+
|
982
|
+
.. seealso:: :meth:`hashlib.hash.hexdigest`
|
983
|
+
"""
|
984
|
+
return self._hash(hash_name).hexdigest()
|
985
|
+
|
986
|
+
# --- Methods for querying the filesystem.
|
987
|
+
# N.B. On some platforms, the os.path functions may be implemented in C
|
988
|
+
# (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
|
989
|
+
# bound. Playing it safe and wrapping them all in method calls.
|
990
|
+
|
991
|
+
def isabs(self):
|
992
|
+
""" .. seealso:: :func:`os.path.isabs` """
|
993
|
+
return self.module.isabs(self)
|
994
|
+
|
995
|
+
def exists(self):
|
996
|
+
""" .. seealso:: :func:`os.path.exists` """
|
997
|
+
return self.module.exists(self)
|
998
|
+
|
999
|
+
def isdir(self):
|
1000
|
+
""" .. seealso:: :func:`os.path.isdir` """
|
1001
|
+
return self.module.isdir(self)
|
1002
|
+
|
1003
|
+
def isfile(self):
|
1004
|
+
""" .. seealso:: :func:`os.path.isfile` """
|
1005
|
+
return self.module.isfile(self)
|
1006
|
+
|
1007
|
+
def islink(self):
|
1008
|
+
""" .. seealso:: :func:`os.path.islink` """
|
1009
|
+
return self.module.islink(self)
|
1010
|
+
|
1011
|
+
def ismount(self):
|
1012
|
+
""" .. seealso:: :func:`os.path.ismount` """
|
1013
|
+
return self.module.ismount(self)
|
1014
|
+
|
1015
|
+
def samefile(self, other):
|
1016
|
+
""" .. seealso:: :func:`os.path.samefile` """
|
1017
|
+
if not hasattr(self.module, 'samefile'):
|
1018
|
+
other = Path(other).realpath().normpath().normcase()
|
1019
|
+
return self.realpath().normpath().normcase() == other
|
1020
|
+
return self.module.samefile(self, other)
|
1021
|
+
|
1022
|
+
def getatime(self):
|
1023
|
+
""" .. seealso:: :attr:`atime`, :func:`os.path.getatime` """
|
1024
|
+
return self.module.getatime(self)
|
1025
|
+
|
1026
|
+
atime = property(
|
1027
|
+
getatime, None, None,
|
1028
|
+
""" Last access time of the file.
|
1029
|
+
|
1030
|
+
.. seealso:: :meth:`getatime`, :func:`os.path.getatime`
|
1031
|
+
""")
|
1032
|
+
|
1033
|
+
def getmtime(self):
|
1034
|
+
""" .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """
|
1035
|
+
return self.module.getmtime(self)
|
1036
|
+
|
1037
|
+
mtime = property(
|
1038
|
+
getmtime, None, None,
|
1039
|
+
""" Last-modified time of the file.
|
1040
|
+
|
1041
|
+
.. seealso:: :meth:`getmtime`, :func:`os.path.getmtime`
|
1042
|
+
""")
|
1043
|
+
|
1044
|
+
def getctime(self):
|
1045
|
+
""" .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """
|
1046
|
+
return self.module.getctime(self)
|
1047
|
+
|
1048
|
+
ctime = property(
|
1049
|
+
getctime, None, None,
|
1050
|
+
""" Creation time of the file.
|
1051
|
+
|
1052
|
+
.. seealso:: :meth:`getctime`, :func:`os.path.getctime`
|
1053
|
+
""")
|
1054
|
+
|
1055
|
+
def getsize(self):
|
1056
|
+
""" .. seealso:: :attr:`size`, :func:`os.path.getsize` """
|
1057
|
+
return self.module.getsize(self)
|
1058
|
+
|
1059
|
+
size = property(
|
1060
|
+
getsize, None, None,
|
1061
|
+
""" Size of the file, in bytes.
|
1062
|
+
|
1063
|
+
.. seealso:: :meth:`getsize`, :func:`os.path.getsize`
|
1064
|
+
""")
|
1065
|
+
|
1066
|
+
if hasattr(os, 'access'):
|
1067
|
+
def access(self, mode):
|
1068
|
+
""" Return ``True`` if current user has access to this path.
|
1069
|
+
|
1070
|
+
mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`,
|
1071
|
+
:data:`os.W_OK`, :data:`os.X_OK`
|
1072
|
+
|
1073
|
+
.. seealso:: :func:`os.access`
|
1074
|
+
"""
|
1075
|
+
return os.access(self, mode)
|
1076
|
+
|
1077
|
+
def stat(self):
|
1078
|
+
""" Perform a ``stat()`` system call on this path.
|
1079
|
+
|
1080
|
+
.. seealso:: :meth:`lstat`, :func:`os.stat`
|
1081
|
+
"""
|
1082
|
+
return os.stat(self)
|
1083
|
+
|
1084
|
+
def lstat(self):
|
1085
|
+
""" Like :meth:`stat`, but do not follow symbolic links.
|
1086
|
+
|
1087
|
+
.. seealso:: :meth:`stat`, :func:`os.lstat`
|
1088
|
+
"""
|
1089
|
+
return os.lstat(self)
|
1090
|
+
|
1091
|
+
def __get_owner_windows(self):
|
1092
|
+
"""
|
1093
|
+
Return the name of the owner of this file or directory. Follow
|
1094
|
+
symbolic links.
|
1095
|
+
|
1096
|
+
Return a name of the form ``r'DOMAIN\\User Name'``; may be a group.
|
1097
|
+
|
1098
|
+
.. seealso:: :attr:`owner`
|
1099
|
+
"""
|
1100
|
+
desc = win32security.GetFileSecurity(
|
1101
|
+
self, win32security.OWNER_SECURITY_INFORMATION)
|
1102
|
+
sid = desc.GetSecurityDescriptorOwner()
|
1103
|
+
account, domain, typecode = win32security.LookupAccountSid(None, sid)
|
1104
|
+
return domain + '\\' + account
|
1105
|
+
|
1106
|
+
def __get_owner_unix(self):
|
1107
|
+
"""
|
1108
|
+
Return the name of the owner of this file or directory. Follow
|
1109
|
+
symbolic links.
|
1110
|
+
|
1111
|
+
.. seealso:: :attr:`owner`
|
1112
|
+
"""
|
1113
|
+
st = self.stat()
|
1114
|
+
return pwd.getpwuid(st.st_uid).pw_name
|
1115
|
+
|
1116
|
+
def __get_owner_not_implemented(self):
|
1117
|
+
raise NotImplementedError("Ownership not available on this platform.")
|
1118
|
+
|
1119
|
+
if 'win32security' in globals():
|
1120
|
+
get_owner = __get_owner_windows
|
1121
|
+
elif 'pwd' in globals():
|
1122
|
+
get_owner = __get_owner_unix
|
1123
|
+
else:
|
1124
|
+
get_owner = __get_owner_not_implemented
|
1125
|
+
|
1126
|
+
owner = property(
|
1127
|
+
get_owner, None, None,
|
1128
|
+
""" Name of the owner of this file or directory.
|
1129
|
+
|
1130
|
+
.. seealso:: :meth:`get_owner`""")
|
1131
|
+
|
1132
|
+
if hasattr(os, 'statvfs'):
|
1133
|
+
def statvfs(self):
|
1134
|
+
""" Perform a ``statvfs()`` system call on this path.
|
1135
|
+
|
1136
|
+
.. seealso:: :func:`os.statvfs`
|
1137
|
+
"""
|
1138
|
+
return os.statvfs(self)
|
1139
|
+
|
1140
|
+
if hasattr(os, 'pathconf'):
|
1141
|
+
def pathconf(self, name):
|
1142
|
+
""" .. seealso:: :func:`os.pathconf` """
|
1143
|
+
return os.pathconf(self, name)
|
1144
|
+
|
1145
|
+
#
|
1146
|
+
# --- Modifying operations on files and directories
|
1147
|
+
|
1148
|
+
def utime(self, times):
|
1149
|
+
""" Set the access and modified times of this file.
|
1150
|
+
|
1151
|
+
.. seealso:: :func:`os.utime`
|
1152
|
+
"""
|
1153
|
+
os.utime(self, times)
|
1154
|
+
return self
|
1155
|
+
|
1156
|
+
def chmod(self, mode):
|
1157
|
+
"""
|
1158
|
+
Set the mode. May be the new mode (os.chmod behavior) or a `symbolic
|
1159
|
+
mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_.
|
1160
|
+
|
1161
|
+
.. seealso:: :func:`os.chmod`
|
1162
|
+
"""
|
1163
|
+
if isinstance(mode, string_types):
|
1164
|
+
mask = _multi_permission_mask(mode)
|
1165
|
+
mode = mask(self.stat().st_mode)
|
1166
|
+
os.chmod(self, mode)
|
1167
|
+
return self
|
1168
|
+
|
1169
|
+
def chown(self, uid=-1, gid=-1):
|
1170
|
+
"""
|
1171
|
+
Change the owner and group by names rather than the uid or gid numbers.
|
1172
|
+
|
1173
|
+
.. seealso:: :func:`os.chown`
|
1174
|
+
"""
|
1175
|
+
if hasattr(os, 'chown'):
|
1176
|
+
if 'pwd' in globals() and isinstance(uid, string_types):
|
1177
|
+
uid = pwd.getpwnam(uid).pw_uid
|
1178
|
+
if 'grp' in globals() and isinstance(gid, string_types):
|
1179
|
+
gid = grp.getgrnam(gid).gr_gid
|
1180
|
+
os.chown(self, uid, gid)
|
1181
|
+
else:
|
1182
|
+
raise NotImplementedError("Ownership not available on this platform.")
|
1183
|
+
return self
|
1184
|
+
|
1185
|
+
def rename(self, new):
|
1186
|
+
""" .. seealso:: :func:`os.rename` """
|
1187
|
+
os.rename(self, new)
|
1188
|
+
return self._next_class(new)
|
1189
|
+
|
1190
|
+
def renames(self, new):
|
1191
|
+
""" .. seealso:: :func:`os.renames` """
|
1192
|
+
os.renames(self, new)
|
1193
|
+
return self._next_class(new)
|
1194
|
+
|
1195
|
+
#
|
1196
|
+
# --- Create/delete operations on directories
|
1197
|
+
|
1198
|
+
def mkdir(self, mode=0o777):
|
1199
|
+
""" .. seealso:: :func:`os.mkdir` """
|
1200
|
+
os.mkdir(self, mode)
|
1201
|
+
return self
|
1202
|
+
|
1203
|
+
def mkdir_p(self, mode=0o777):
|
1204
|
+
""" Like :meth:`mkdir`, but does not raise an exception if the
|
1205
|
+
directory already exists. """
|
1206
|
+
try:
|
1207
|
+
self.mkdir(mode)
|
1208
|
+
except OSError:
|
1209
|
+
_, e, _ = sys.exc_info()
|
1210
|
+
if e.errno != errno.EEXIST:
|
1211
|
+
raise
|
1212
|
+
return self
|
1213
|
+
|
1214
|
+
def makedirs(self, mode=0o777):
|
1215
|
+
""" .. seealso:: :func:`os.makedirs` """
|
1216
|
+
os.makedirs(self, mode)
|
1217
|
+
return self
|
1218
|
+
|
1219
|
+
def makedirs_p(self, mode=0o777):
|
1220
|
+
""" Like :meth:`makedirs`, but does not raise an exception if the
|
1221
|
+
directory already exists. """
|
1222
|
+
try:
|
1223
|
+
self.makedirs(mode)
|
1224
|
+
except OSError:
|
1225
|
+
_, e, _ = sys.exc_info()
|
1226
|
+
if e.errno != errno.EEXIST:
|
1227
|
+
raise
|
1228
|
+
return self
|
1229
|
+
|
1230
|
+
def rmdir(self):
|
1231
|
+
""" .. seealso:: :func:`os.rmdir` """
|
1232
|
+
os.rmdir(self)
|
1233
|
+
return self
|
1234
|
+
|
1235
|
+
def rmdir_p(self):
|
1236
|
+
""" Like :meth:`rmdir`, but does not raise an exception if the
|
1237
|
+
directory is not empty or does not exist. """
|
1238
|
+
try:
|
1239
|
+
self.rmdir()
|
1240
|
+
except OSError:
|
1241
|
+
_, e, _ = sys.exc_info()
|
1242
|
+
if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
|
1243
|
+
raise
|
1244
|
+
return self
|
1245
|
+
|
1246
|
+
def removedirs(self):
|
1247
|
+
""" .. seealso:: :func:`os.removedirs` """
|
1248
|
+
os.removedirs(self)
|
1249
|
+
return self
|
1250
|
+
|
1251
|
+
def removedirs_p(self):
|
1252
|
+
""" Like :meth:`removedirs`, but does not raise an exception if the
|
1253
|
+
directory is not empty or does not exist. """
|
1254
|
+
try:
|
1255
|
+
self.removedirs()
|
1256
|
+
except OSError:
|
1257
|
+
_, e, _ = sys.exc_info()
|
1258
|
+
if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
|
1259
|
+
raise
|
1260
|
+
return self
|
1261
|
+
|
1262
|
+
# --- Modifying operations on files
|
1263
|
+
|
1264
|
+
def touch(self):
|
1265
|
+
""" Set the access/modified times of this file to the current time.
|
1266
|
+
Create the file if it does not exist.
|
1267
|
+
"""
|
1268
|
+
fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
|
1269
|
+
os.close(fd)
|
1270
|
+
os.utime(self, None)
|
1271
|
+
return self
|
1272
|
+
|
1273
|
+
def remove(self):
|
1274
|
+
""" .. seealso:: :func:`os.remove` """
|
1275
|
+
os.remove(self)
|
1276
|
+
return self
|
1277
|
+
|
1278
|
+
def remove_p(self):
|
1279
|
+
""" Like :meth:`remove`, but does not raise an exception if the
|
1280
|
+
file does not exist. """
|
1281
|
+
try:
|
1282
|
+
self.unlink()
|
1283
|
+
except OSError:
|
1284
|
+
_, e, _ = sys.exc_info()
|
1285
|
+
if e.errno != errno.ENOENT:
|
1286
|
+
raise
|
1287
|
+
return self
|
1288
|
+
|
1289
|
+
def unlink(self):
|
1290
|
+
""" .. seealso:: :func:`os.unlink` """
|
1291
|
+
os.unlink(self)
|
1292
|
+
return self
|
1293
|
+
|
1294
|
+
def unlink_p(self):
|
1295
|
+
""" Like :meth:`unlink`, but does not raise an exception if the
|
1296
|
+
file does not exist. """
|
1297
|
+
self.remove_p()
|
1298
|
+
return self
|
1299
|
+
|
1300
|
+
# --- Links
|
1301
|
+
|
1302
|
+
if hasattr(os, 'link'):
|
1303
|
+
def link(self, newpath):
|
1304
|
+
""" Create a hard link at `newpath`, pointing to this file.
|
1305
|
+
|
1306
|
+
.. seealso:: :func:`os.link`
|
1307
|
+
"""
|
1308
|
+
os.link(self, newpath)
|
1309
|
+
return self._next_class(newpath)
|
1310
|
+
|
1311
|
+
if hasattr(os, 'symlink'):
|
1312
|
+
def symlink(self, newlink):
|
1313
|
+
""" Create a symbolic link at `newlink`, pointing here.
|
1314
|
+
|
1315
|
+
.. seealso:: :func:`os.symlink`
|
1316
|
+
"""
|
1317
|
+
os.symlink(self, newlink)
|
1318
|
+
return self._next_class(newlink)
|
1319
|
+
|
1320
|
+
if hasattr(os, 'readlink'):
|
1321
|
+
def readlink(self):
|
1322
|
+
""" Return the path to which this symbolic link points.
|
1323
|
+
|
1324
|
+
The result may be an absolute or a relative path.
|
1325
|
+
|
1326
|
+
.. seealso:: :meth:`readlinkabs`, :func:`os.readlink`
|
1327
|
+
"""
|
1328
|
+
return self._next_class(os.readlink(self))
|
1329
|
+
|
1330
|
+
def readlinkabs(self):
|
1331
|
+
""" Return the path to which this symbolic link points.
|
1332
|
+
|
1333
|
+
The result is always an absolute path.
|
1334
|
+
|
1335
|
+
.. seealso:: :meth:`readlink`, :func:`os.readlink`
|
1336
|
+
"""
|
1337
|
+
p = self.readlink()
|
1338
|
+
if p.isabs():
|
1339
|
+
return p
|
1340
|
+
else:
|
1341
|
+
return (self.parent / p).abspath()
|
1342
|
+
|
1343
|
+
# High-level functions from shutil
|
1344
|
+
# These functions will be bound to the instance such that
|
1345
|
+
# Path(name).copy(target) will invoke shutil.copy(name, target)
|
1346
|
+
|
1347
|
+
copyfile = shutil.copyfile
|
1348
|
+
copymode = shutil.copymode
|
1349
|
+
copystat = shutil.copystat
|
1350
|
+
copy = shutil.copy
|
1351
|
+
copy2 = shutil.copy2
|
1352
|
+
copytree = shutil.copytree
|
1353
|
+
if hasattr(shutil, 'move'):
|
1354
|
+
move = shutil.move
|
1355
|
+
rmtree = shutil.rmtree
|
1356
|
+
|
1357
|
+
def rmtree_p(self):
|
1358
|
+
""" Like :meth:`rmtree`, but does not raise an exception if the
|
1359
|
+
directory does not exist. """
|
1360
|
+
try:
|
1361
|
+
self.rmtree()
|
1362
|
+
except OSError:
|
1363
|
+
_, e, _ = sys.exc_info()
|
1364
|
+
if e.errno != errno.ENOENT:
|
1365
|
+
raise
|
1366
|
+
return self
|
1367
|
+
|
1368
|
+
def chdir(self):
|
1369
|
+
""" .. seealso:: :func:`os.chdir` """
|
1370
|
+
os.chdir(self)
|
1371
|
+
|
1372
|
+
cd = chdir
|
1373
|
+
|
1374
|
+
def merge_tree(self, dst, symlinks=False, *args, **kwargs):
|
1375
|
+
"""
|
1376
|
+
Copy entire contents of self to dst, overwriting existing
|
1377
|
+
contents in dst with those in self.
|
1378
|
+
|
1379
|
+
If the additional keyword `update` is True, each
|
1380
|
+
`src` will only be copied if `dst` does not exist,
|
1381
|
+
or `src` is newer than `dst`.
|
1382
|
+
|
1383
|
+
Note that the technique employed stages the files in a temporary
|
1384
|
+
directory first, so this function is not suitable for merging
|
1385
|
+
trees with large files, especially if the temporary directory
|
1386
|
+
is not capable of storing a copy of the entire source tree.
|
1387
|
+
"""
|
1388
|
+
update = kwargs.pop('update', False)
|
1389
|
+
with tempdir() as _temp_dir:
|
1390
|
+
# first copy the tree to a stage directory to support
|
1391
|
+
# the parameters and behavior of copytree.
|
1392
|
+
stage = _temp_dir / str(hash(self))
|
1393
|
+
self.copytree(stage, symlinks, *args, **kwargs)
|
1394
|
+
# now copy everything from the stage directory using
|
1395
|
+
# the semantics of dir_util.copy_tree
|
1396
|
+
dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks,
|
1397
|
+
update=update)
|
1398
|
+
|
1399
|
+
#
|
1400
|
+
# --- Special stuff from os
|
1401
|
+
|
1402
|
+
if hasattr(os, 'chroot'):
|
1403
|
+
def chroot(self):
|
1404
|
+
""" .. seealso:: :func:`os.chroot` """
|
1405
|
+
os.chroot(self)
|
1406
|
+
|
1407
|
+
if hasattr(os, 'startfile'):
|
1408
|
+
def startfile(self):
|
1409
|
+
""" .. seealso:: :func:`os.startfile` """
|
1410
|
+
os.startfile(self)
|
1411
|
+
return self
|
1412
|
+
|
1413
|
+
# in-place re-writing, courtesy of Martijn Pieters
|
1414
|
+
# http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/
|
1415
|
+
@contextlib.contextmanager
|
1416
|
+
def in_place(self, mode='r', buffering=-1, encoding=None, errors=None,
|
1417
|
+
newline=None, backup_extension=None):
|
1418
|
+
"""
|
1419
|
+
A context in which a file may be re-written in-place with new content.
|
1420
|
+
|
1421
|
+
Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable`
|
1422
|
+
replaces `readable`.
|
1423
|
+
|
1424
|
+
If an exception occurs, the old file is restored, removing the
|
1425
|
+
written data.
|
1426
|
+
|
1427
|
+
Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are
|
1428
|
+
allowed. A :exc:`ValueError` is raised on invalid modes.
|
1429
|
+
|
1430
|
+
For example, to add line numbers to a file::
|
1431
|
+
|
1432
|
+
p = Path(filename)
|
1433
|
+
assert p.isfile()
|
1434
|
+
with p.in_place() as (reader, writer):
|
1435
|
+
for number, line in enumerate(reader, 1):
|
1436
|
+
writer.write('{0:3}: '.format(number)))
|
1437
|
+
writer.write(line)
|
1438
|
+
|
1439
|
+
Thereafter, the file at `filename` will have line numbers in it.
|
1440
|
+
"""
|
1441
|
+
import io
|
1442
|
+
|
1443
|
+
if set(mode).intersection('wa+'):
|
1444
|
+
raise ValueError('Only read-only file modes can be used')
|
1445
|
+
|
1446
|
+
# move existing file to backup, create new file with same permissions
|
1447
|
+
# borrowed extensively from the fileinput module
|
1448
|
+
backup_fn = self + (backup_extension or os.extsep + 'bak')
|
1449
|
+
try:
|
1450
|
+
os.unlink(backup_fn)
|
1451
|
+
except os.error:
|
1452
|
+
pass
|
1453
|
+
os.rename(self, backup_fn)
|
1454
|
+
readable = io.open(backup_fn, mode, buffering=buffering,
|
1455
|
+
encoding=encoding, errors=errors, newline=newline)
|
1456
|
+
try:
|
1457
|
+
perm = os.fstat(readable.fileno()).st_mode
|
1458
|
+
except OSError:
|
1459
|
+
writable = open(self, 'w' + mode.replace('r', ''),
|
1460
|
+
buffering=buffering, encoding=encoding, errors=errors,
|
1461
|
+
newline=newline)
|
1462
|
+
else:
|
1463
|
+
os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
|
1464
|
+
if hasattr(os, 'O_BINARY'):
|
1465
|
+
os_mode |= os.O_BINARY
|
1466
|
+
fd = os.open(self, os_mode, perm)
|
1467
|
+
writable = io.open(fd, "w" + mode.replace('r', ''),
|
1468
|
+
buffering=buffering, encoding=encoding, errors=errors,
|
1469
|
+
newline=newline)
|
1470
|
+
try:
|
1471
|
+
if hasattr(os, 'chmod'):
|
1472
|
+
os.chmod(self, perm)
|
1473
|
+
except OSError:
|
1474
|
+
pass
|
1475
|
+
try:
|
1476
|
+
yield readable, writable
|
1477
|
+
except Exception:
|
1478
|
+
# move backup back
|
1479
|
+
readable.close()
|
1480
|
+
writable.close()
|
1481
|
+
try:
|
1482
|
+
os.unlink(self)
|
1483
|
+
except os.error:
|
1484
|
+
pass
|
1485
|
+
os.rename(backup_fn, self)
|
1486
|
+
raise
|
1487
|
+
else:
|
1488
|
+
readable.close()
|
1489
|
+
writable.close()
|
1490
|
+
finally:
|
1491
|
+
try:
|
1492
|
+
os.unlink(backup_fn)
|
1493
|
+
except os.error:
|
1494
|
+
pass
|
1495
|
+
|
1496
|
+
@ClassProperty
|
1497
|
+
@classmethod
|
1498
|
+
def special(cls):
|
1499
|
+
"""
|
1500
|
+
Return a SpecialResolver object suitable referencing a suitable
|
1501
|
+
directory for the relevant platform for the given
|
1502
|
+
type of content.
|
1503
|
+
|
1504
|
+
For example, to get a user config directory, invoke:
|
1505
|
+
|
1506
|
+
dir = Path.special().user.config
|
1507
|
+
|
1508
|
+
Uses the `appdirs
|
1509
|
+
<https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve
|
1510
|
+
the paths in a platform-friendly way.
|
1511
|
+
|
1512
|
+
To create a config directory for 'My App', consider:
|
1513
|
+
|
1514
|
+
dir = Path.special("My App").user.config.makedirs_p()
|
1515
|
+
|
1516
|
+
If the ``appdirs`` module is not installed, invocation
|
1517
|
+
of special will raise an ImportError.
|
1518
|
+
"""
|
1519
|
+
return functools.partial(SpecialResolver, cls)
|
1520
|
+
|
1521
|
+
|
1522
|
+
class SpecialResolver(object):
|
1523
|
+
class ResolverScope:
|
1524
|
+
def __init__(self, paths, scope):
|
1525
|
+
self.paths = paths
|
1526
|
+
self.scope = scope
|
1527
|
+
|
1528
|
+
def __getattr__(self, class_):
|
1529
|
+
return self.paths.get_dir(self.scope, class_)
|
1530
|
+
|
1531
|
+
def __init__(self, path_class, *args, **kwargs):
|
1532
|
+
appdirs = importlib.import_module('appdirs')
|
1533
|
+
|
1534
|
+
# let appname default to None until
|
1535
|
+
# https://github.com/ActiveState/appdirs/issues/55 is solved.
|
1536
|
+
not args and kwargs.setdefault('appname', None)
|
1537
|
+
|
1538
|
+
vars(self).update(
|
1539
|
+
path_class=path_class,
|
1540
|
+
wrapper=appdirs.AppDirs(*args, **kwargs),
|
1541
|
+
)
|
1542
|
+
|
1543
|
+
def __getattr__(self, scope):
|
1544
|
+
return self.ResolverScope(self, scope)
|
1545
|
+
|
1546
|
+
def get_dir(self, scope, class_):
|
1547
|
+
"""
|
1548
|
+
Return the callable function from appdirs, but with the
|
1549
|
+
result wrapped in self.path_class
|
1550
|
+
"""
|
1551
|
+
prop_name = '{scope}_{class_}_dir'.format(**locals())
|
1552
|
+
value = getattr(self.wrapper, prop_name)
|
1553
|
+
MultiPath = Multi.for_class(self.path_class)
|
1554
|
+
return MultiPath.detect(value)
|
1555
|
+
|
1556
|
+
|
1557
|
+
class Multi:
|
1558
|
+
"""
|
1559
|
+
A mix-in for a Path which may contain multiple Path separated by pathsep.
|
1560
|
+
"""
|
1561
|
+
@classmethod
|
1562
|
+
def for_class(cls, path_cls):
|
1563
|
+
name = 'Multi' + path_cls.__name__
|
1564
|
+
if PY2:
|
1565
|
+
name = str(name)
|
1566
|
+
return type(name, (cls, path_cls), {})
|
1567
|
+
|
1568
|
+
@classmethod
|
1569
|
+
def detect(cls, input):
|
1570
|
+
if os.pathsep not in input:
|
1571
|
+
cls = cls._next_class
|
1572
|
+
return cls(input)
|
1573
|
+
|
1574
|
+
def __iter__(self):
|
1575
|
+
return iter(map(self._next_class, self.split(os.pathsep)))
|
1576
|
+
|
1577
|
+
@ClassProperty
|
1578
|
+
@classmethod
|
1579
|
+
def _next_class(cls):
|
1580
|
+
"""
|
1581
|
+
Multi-subclasses should use the parent class
|
1582
|
+
"""
|
1583
|
+
return next(
|
1584
|
+
class_
|
1585
|
+
for class_ in cls.__mro__
|
1586
|
+
if not issubclass(class_, Multi)
|
1587
|
+
)
|
1588
|
+
|
1589
|
+
|
1590
|
+
class tempdir(Path):
|
1591
|
+
"""
|
1592
|
+
A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the
|
1593
|
+
same parameters that you can use as a context manager.
|
1594
|
+
|
1595
|
+
Example:
|
1596
|
+
|
1597
|
+
with tempdir() as d:
|
1598
|
+
# do stuff with the Path object "d"
|
1599
|
+
|
1600
|
+
# here the directory is deleted automatically
|
1601
|
+
|
1602
|
+
.. seealso:: :func:`tempfile.mkdtemp`
|
1603
|
+
"""
|
1604
|
+
|
1605
|
+
@ClassProperty
|
1606
|
+
@classmethod
|
1607
|
+
def _next_class(cls):
|
1608
|
+
return Path
|
1609
|
+
|
1610
|
+
def __new__(cls, *args, **kwargs):
|
1611
|
+
dirname = tempfile.mkdtemp(*args, **kwargs)
|
1612
|
+
return super(tempdir, cls).__new__(cls, dirname)
|
1613
|
+
|
1614
|
+
def __init__(self, *args, **kwargs):
|
1615
|
+
pass
|
1616
|
+
|
1617
|
+
def __enter__(self):
|
1618
|
+
return self
|
1619
|
+
|
1620
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
1621
|
+
if not exc_value:
|
1622
|
+
self.rmtree()
|
1623
|
+
|
1624
|
+
|
1625
|
+
def _multi_permission_mask(mode):
|
1626
|
+
"""
|
1627
|
+
Support multiple, comma-separated Unix chmod symbolic modes.
|
1628
|
+
|
1629
|
+
>>> _multi_permission_mask('a=r,u+w')(0) == 0o644
|
1630
|
+
True
|
1631
|
+
"""
|
1632
|
+
compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs))
|
1633
|
+
return functools.reduce(compose, map(_permission_mask, mode.split(',')))
|
1634
|
+
|
1635
|
+
|
1636
|
+
def _permission_mask(mode):
|
1637
|
+
"""
|
1638
|
+
Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function
|
1639
|
+
suitable for applying to a mask to affect that change.
|
1640
|
+
|
1641
|
+
>>> mask = _permission_mask('ugo+rwx')
|
1642
|
+
>>> mask(0o554) == 0o777
|
1643
|
+
True
|
1644
|
+
|
1645
|
+
>>> _permission_mask('go-x')(0o777) == 0o766
|
1646
|
+
True
|
1647
|
+
|
1648
|
+
>>> _permission_mask('o-x')(0o445) == 0o444
|
1649
|
+
True
|
1650
|
+
|
1651
|
+
>>> _permission_mask('a+x')(0) == 0o111
|
1652
|
+
True
|
1653
|
+
|
1654
|
+
>>> _permission_mask('a=rw')(0o057) == 0o666
|
1655
|
+
True
|
1656
|
+
|
1657
|
+
>>> _permission_mask('u=x')(0o666) == 0o166
|
1658
|
+
True
|
1659
|
+
|
1660
|
+
>>> _permission_mask('g=')(0o157) == 0o107
|
1661
|
+
True
|
1662
|
+
"""
|
1663
|
+
# parse the symbolic mode
|
1664
|
+
parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode)
|
1665
|
+
if not parsed:
|
1666
|
+
raise ValueError("Unrecognized symbolic mode", mode)
|
1667
|
+
|
1668
|
+
# generate a mask representing the specified permission
|
1669
|
+
spec_map = dict(r=4, w=2, x=1)
|
1670
|
+
specs = (spec_map[perm] for perm in parsed.group('what'))
|
1671
|
+
spec = functools.reduce(operator.or_, specs, 0)
|
1672
|
+
|
1673
|
+
# now apply spec to each subject in who
|
1674
|
+
shift_map = dict(u=6, g=3, o=0)
|
1675
|
+
who = parsed.group('who').replace('a', 'ugo')
|
1676
|
+
masks = (spec << shift_map[subj] for subj in who)
|
1677
|
+
mask = functools.reduce(operator.or_, masks)
|
1678
|
+
|
1679
|
+
op = parsed.group('op')
|
1680
|
+
|
1681
|
+
# if op is -, invert the mask
|
1682
|
+
if op == '-':
|
1683
|
+
mask ^= 0o777
|
1684
|
+
|
1685
|
+
# if op is =, retain extant values for unreferenced subjects
|
1686
|
+
if op == '=':
|
1687
|
+
masks = (0o7 << shift_map[subj] for subj in who)
|
1688
|
+
retain = functools.reduce(operator.or_, masks) ^ 0o777
|
1689
|
+
|
1690
|
+
op_map = {
|
1691
|
+
'+': operator.or_,
|
1692
|
+
'-': operator.and_,
|
1693
|
+
'=': lambda mask, target: target & retain ^ mask,
|
1694
|
+
}
|
1695
|
+
return functools.partial(op_map[op], mask)
|
1696
|
+
|
1697
|
+
|
1698
|
+
class CaseInsensitivePattern(text_type):
|
1699
|
+
"""
|
1700
|
+
A string with a ``'normcase'`` property, suitable for passing to
|
1701
|
+
:meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`,
|
1702
|
+
:meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive.
|
1703
|
+
|
1704
|
+
For example, to get all files ending in .py, .Py, .pY, or .PY in the
|
1705
|
+
current directory::
|
1706
|
+
|
1707
|
+
from path import Path, CaseInsensitivePattern as ci
|
1708
|
+
Path('.').files(ci('*.py'))
|
1709
|
+
"""
|
1710
|
+
|
1711
|
+
@property
|
1712
|
+
def normcase(self):
|
1713
|
+
return __import__('ntpath').normcase
|
1714
|
+
|
1715
|
+
########################
|
1716
|
+
# Backward-compatibility
|
1717
|
+
class path(Path):
|
1718
|
+
def __new__(cls, *args, **kwargs):
|
1719
|
+
msg = "path is deprecated. Use Path instead."
|
1720
|
+
warnings.warn(msg, DeprecationWarning)
|
1721
|
+
return Path.__new__(cls, *args, **kwargs)
|
1722
|
+
|
1723
|
+
|
1724
|
+
__all__ += ['path']
|
1725
|
+
########################
|