busser-behave 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (418) hide show
  1. checksums.yaml +7 -0
  2. data/.cane +0 -0
  3. data/.gitignore +17 -0
  4. data/.tailor +4 -0
  5. data/.travis.yml +11 -0
  6. data/CHANGELOG.md +3 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +15 -0
  9. data/README.md +41 -0
  10. data/Rakefile +68 -0
  11. data/busser-behave.gemspec +30 -0
  12. data/features/plugin_install_command.feature +11 -0
  13. data/features/plugin_list_command.feature +8 -0
  14. data/features/support/env.rb +13 -0
  15. data/features/test_command.feature +31 -0
  16. data/lib/busser/behave/version.rb +26 -0
  17. data/lib/busser/runner_plugin/behave.rb +37 -0
  18. data/vendor/behave/CHANGES.rst +483 -0
  19. data/vendor/behave/LICENSE +23 -0
  20. data/vendor/behave/MANIFEST.in +37 -0
  21. data/vendor/behave/PROJECT_INFO.rst +21 -0
  22. data/vendor/behave/README.rst +112 -0
  23. data/vendor/behave/VERSION.txt +1 -0
  24. data/vendor/behave/behave.ini +22 -0
  25. data/vendor/behave/behave/__init__.py +30 -0
  26. data/vendor/behave/behave/__main__.py +187 -0
  27. data/vendor/behave/behave/_stepimport.py +185 -0
  28. data/vendor/behave/behave/_types.py +134 -0
  29. data/vendor/behave/behave/api/__init__.py +7 -0
  30. data/vendor/behave/behave/api/async_step.py +283 -0
  31. data/vendor/behave/behave/capture.py +227 -0
  32. data/vendor/behave/behave/compat/__init__.py +5 -0
  33. data/vendor/behave/behave/compat/collections.py +20 -0
  34. data/vendor/behave/behave/configuration.py +788 -0
  35. data/vendor/behave/behave/contrib/__init__.py +0 -0
  36. data/vendor/behave/behave/contrib/scenario_autoretry.py +73 -0
  37. data/vendor/behave/behave/formatter/__init__.py +12 -0
  38. data/vendor/behave/behave/formatter/_builtins.py +39 -0
  39. data/vendor/behave/behave/formatter/_registry.py +135 -0
  40. data/vendor/behave/behave/formatter/ansi_escapes.py +91 -0
  41. data/vendor/behave/behave/formatter/base.py +200 -0
  42. data/vendor/behave/behave/formatter/formatters.py +57 -0
  43. data/vendor/behave/behave/formatter/json.py +253 -0
  44. data/vendor/behave/behave/formatter/null.py +12 -0
  45. data/vendor/behave/behave/formatter/plain.py +158 -0
  46. data/vendor/behave/behave/formatter/pretty.py +351 -0
  47. data/vendor/behave/behave/formatter/progress.py +287 -0
  48. data/vendor/behave/behave/formatter/rerun.py +114 -0
  49. data/vendor/behave/behave/formatter/sphinx_steps.py +372 -0
  50. data/vendor/behave/behave/formatter/sphinx_util.py +118 -0
  51. data/vendor/behave/behave/formatter/steps.py +497 -0
  52. data/vendor/behave/behave/formatter/tags.py +178 -0
  53. data/vendor/behave/behave/i18n.py +614 -0
  54. data/vendor/behave/behave/importer.py +102 -0
  55. data/vendor/behave/behave/json_parser.py +264 -0
  56. data/vendor/behave/behave/log_capture.py +233 -0
  57. data/vendor/behave/behave/matchers.py +402 -0
  58. data/vendor/behave/behave/model.py +1737 -0
  59. data/vendor/behave/behave/model_core.py +416 -0
  60. data/vendor/behave/behave/model_describe.py +105 -0
  61. data/vendor/behave/behave/parser.py +615 -0
  62. data/vendor/behave/behave/reporter/__init__.py +0 -0
  63. data/vendor/behave/behave/reporter/base.py +45 -0
  64. data/vendor/behave/behave/reporter/junit.py +473 -0
  65. data/vendor/behave/behave/reporter/summary.py +94 -0
  66. data/vendor/behave/behave/runner.py +753 -0
  67. data/vendor/behave/behave/runner_util.py +417 -0
  68. data/vendor/behave/behave/step_registry.py +112 -0
  69. data/vendor/behave/behave/tag_expression.py +111 -0
  70. data/vendor/behave/behave/tag_matcher.py +465 -0
  71. data/vendor/behave/behave/textutil.py +137 -0
  72. data/vendor/behave/behave/userdata.py +130 -0
  73. data/vendor/behave/behave4cmd0/__all_steps__.py +12 -0
  74. data/vendor/behave/behave4cmd0/__init__.py +5 -0
  75. data/vendor/behave/behave4cmd0/__setup.py +11 -0
  76. data/vendor/behave/behave4cmd0/command_shell.py +216 -0
  77. data/vendor/behave/behave4cmd0/command_shell_proc.py +256 -0
  78. data/vendor/behave/behave4cmd0/command_steps.py +532 -0
  79. data/vendor/behave/behave4cmd0/command_util.py +147 -0
  80. data/vendor/behave/behave4cmd0/failing_steps.py +49 -0
  81. data/vendor/behave/behave4cmd0/log/__init__.py +1 -0
  82. data/vendor/behave/behave4cmd0/log/steps.py +395 -0
  83. data/vendor/behave/behave4cmd0/note_steps.py +29 -0
  84. data/vendor/behave/behave4cmd0/passing_steps.py +36 -0
  85. data/vendor/behave/behave4cmd0/pathutil.py +146 -0
  86. data/vendor/behave/behave4cmd0/setup_command_shell.py +24 -0
  87. data/vendor/behave/behave4cmd0/textutil.py +304 -0
  88. data/vendor/behave/bin/behave +44 -0
  89. data/vendor/behave/bin/behave.cmd +10 -0
  90. data/vendor/behave/bin/behave.junit_filter.py +85 -0
  91. data/vendor/behave/bin/behave.step_durations.py +163 -0
  92. data/vendor/behave/bin/behave2cucumber_json.py +63 -0
  93. data/vendor/behave/bin/behave_cmd.py +44 -0
  94. data/vendor/behave/bin/convert_i18n_yaml.py +77 -0
  95. data/vendor/behave/bin/explore_platform_encoding.py +24 -0
  96. data/vendor/behave/bin/i18n.yml +621 -0
  97. data/vendor/behave/bin/invoke +8 -0
  98. data/vendor/behave/bin/invoke.cmd +9 -0
  99. data/vendor/behave/bin/json.format.py +167 -0
  100. data/vendor/behave/bin/jsonschema_validate.py +122 -0
  101. data/vendor/behave/bin/make_localpi.py +279 -0
  102. data/vendor/behave/bin/project_bootstrap.sh +30 -0
  103. data/vendor/behave/bin/toxcmd.py +270 -0
  104. data/vendor/behave/bin/toxcmd3.py +270 -0
  105. data/vendor/behave/conftest.py +27 -0
  106. data/vendor/behave/docs/Makefile +154 -0
  107. data/vendor/behave/docs/_static/agogo.css +501 -0
  108. data/vendor/behave/docs/_static/behave_logo.png +0 -0
  109. data/vendor/behave/docs/_static/behave_logo1.png +0 -0
  110. data/vendor/behave/docs/_static/behave_logo2.png +0 -0
  111. data/vendor/behave/docs/_static/behave_logo3.png +0 -0
  112. data/vendor/behave/docs/_themes/LICENSE +45 -0
  113. data/vendor/behave/docs/_themes/kr/layout.html +17 -0
  114. data/vendor/behave/docs/_themes/kr/relations.html +19 -0
  115. data/vendor/behave/docs/_themes/kr/static/flasky.css_t +480 -0
  116. data/vendor/behave/docs/_themes/kr/static/small_flask.css +90 -0
  117. data/vendor/behave/docs/_themes/kr/theme.conf +7 -0
  118. data/vendor/behave/docs/_themes/kr_small/layout.html +22 -0
  119. data/vendor/behave/docs/_themes/kr_small/static/flasky.css_t +287 -0
  120. data/vendor/behave/docs/_themes/kr_small/theme.conf +10 -0
  121. data/vendor/behave/docs/api.rst +408 -0
  122. data/vendor/behave/docs/appendix.rst +19 -0
  123. data/vendor/behave/docs/behave.rst +640 -0
  124. data/vendor/behave/docs/behave.rst-template +86 -0
  125. data/vendor/behave/docs/behave_ecosystem.rst +81 -0
  126. data/vendor/behave/docs/comparison.rst +85 -0
  127. data/vendor/behave/docs/conf.py +293 -0
  128. data/vendor/behave/docs/context_attributes.rst +66 -0
  129. data/vendor/behave/docs/django.rst +192 -0
  130. data/vendor/behave/docs/formatters.rst +61 -0
  131. data/vendor/behave/docs/gherkin.rst +673 -0
  132. data/vendor/behave/docs/index.rst +57 -0
  133. data/vendor/behave/docs/install.rst +60 -0
  134. data/vendor/behave/docs/more_info.rst +184 -0
  135. data/vendor/behave/docs/new_and_noteworthy.rst +18 -0
  136. data/vendor/behave/docs/new_and_noteworthy_v1.2.4.rst +11 -0
  137. data/vendor/behave/docs/new_and_noteworthy_v1.2.5.rst +814 -0
  138. data/vendor/behave/docs/new_and_noteworthy_v1.2.6.rst +255 -0
  139. data/vendor/behave/docs/parse_builtin_types.rst +59 -0
  140. data/vendor/behave/docs/philosophy.rst +235 -0
  141. data/vendor/behave/docs/regular_expressions.rst +71 -0
  142. data/vendor/behave/docs/related.rst +14 -0
  143. data/vendor/behave/docs/test_domains.rst +62 -0
  144. data/vendor/behave/docs/tutorial.rst +636 -0
  145. data/vendor/behave/docs/update_behave_rst.py +100 -0
  146. data/vendor/behave/etc/json/behave.json-schema +172 -0
  147. data/vendor/behave/etc/junit.xml/behave_junit.xsd +103 -0
  148. data/vendor/behave/etc/junit.xml/junit-4.xsd +92 -0
  149. data/vendor/behave/examples/async_step/README.txt +8 -0
  150. data/vendor/behave/examples/async_step/behave.ini +14 -0
  151. data/vendor/behave/examples/async_step/features/async_dispatch.feature +8 -0
  152. data/vendor/behave/examples/async_step/features/async_run.feature +6 -0
  153. data/vendor/behave/examples/async_step/features/environment.py +28 -0
  154. data/vendor/behave/examples/async_step/features/steps/async_dispatch_steps.py +26 -0
  155. data/vendor/behave/examples/async_step/features/steps/async_steps34.py +10 -0
  156. data/vendor/behave/examples/async_step/features/steps/async_steps35.py +10 -0
  157. data/vendor/behave/examples/async_step/testrun_example.async_dispatch.txt +11 -0
  158. data/vendor/behave/examples/async_step/testrun_example.async_run.txt +9 -0
  159. data/vendor/behave/examples/env_vars/README.rst +26 -0
  160. data/vendor/behave/examples/env_vars/behave.ini +15 -0
  161. data/vendor/behave/examples/env_vars/behave_run.output_example.txt +12 -0
  162. data/vendor/behave/examples/env_vars/features/env_var.feature +6 -0
  163. data/vendor/behave/examples/env_vars/features/steps/env_var_steps.py +38 -0
  164. data/vendor/behave/features/README.txt +12 -0
  165. data/vendor/behave/features/background.feature +392 -0
  166. data/vendor/behave/features/capture_stderr.feature +172 -0
  167. data/vendor/behave/features/capture_stdout.feature +125 -0
  168. data/vendor/behave/features/cmdline.lang_list.feature +33 -0
  169. data/vendor/behave/features/configuration.default_paths.feature +116 -0
  170. data/vendor/behave/features/context.global_params.feature +35 -0
  171. data/vendor/behave/features/context.local_params.feature +17 -0
  172. data/vendor/behave/features/directory_layout.advanced.feature +147 -0
  173. data/vendor/behave/features/directory_layout.basic.feature +75 -0
  174. data/vendor/behave/features/directory_layout.basic2.feature +87 -0
  175. data/vendor/behave/features/environment.py +53 -0
  176. data/vendor/behave/features/exploratory_testing.with_table.feature +141 -0
  177. data/vendor/behave/features/feature.description.feature +0 -0
  178. data/vendor/behave/features/feature.exclude_from_run.feature +96 -0
  179. data/vendor/behave/features/formatter.help.feature +30 -0
  180. data/vendor/behave/features/formatter.json.feature +420 -0
  181. data/vendor/behave/features/formatter.progress3.feature +235 -0
  182. data/vendor/behave/features/formatter.rerun.feature +296 -0
  183. data/vendor/behave/features/formatter.steps.feature +181 -0
  184. data/vendor/behave/features/formatter.steps_catalog.feature +100 -0
  185. data/vendor/behave/features/formatter.steps_doc.feature +140 -0
  186. data/vendor/behave/features/formatter.steps_usage.feature +404 -0
  187. data/vendor/behave/features/formatter.tags.feature +134 -0
  188. data/vendor/behave/features/formatter.tags_location.feature +183 -0
  189. data/vendor/behave/features/formatter.user_defined.feature +196 -0
  190. data/vendor/behave/features/i18n.unicode_problems.feature +445 -0
  191. data/vendor/behave/features/logcapture.clear_handlers.feature +114 -0
  192. data/vendor/behave/features/logcapture.feature +188 -0
  193. data/vendor/behave/features/logcapture.filter.feature +130 -0
  194. data/vendor/behave/features/logging.no_capture.feature +99 -0
  195. data/vendor/behave/features/logging.setup_format.feature +157 -0
  196. data/vendor/behave/features/logging.setup_level.feature +168 -0
  197. data/vendor/behave/features/logging.setup_with_configfile.feature +137 -0
  198. data/vendor/behave/features/parser.background.sad_cases.feature +129 -0
  199. data/vendor/behave/features/parser.feature.sad_cases.feature +144 -0
  200. data/vendor/behave/features/runner.abort_by_user.feature +305 -0
  201. data/vendor/behave/features/runner.continue_after_failed_step.feature +136 -0
  202. data/vendor/behave/features/runner.default_format.feature +175 -0
  203. data/vendor/behave/features/runner.dry_run.feature +184 -0
  204. data/vendor/behave/features/runner.feature_listfile.feature +223 -0
  205. data/vendor/behave/features/runner.hook_errors.feature +382 -0
  206. data/vendor/behave/features/runner.multiple_formatters.feature +285 -0
  207. data/vendor/behave/features/runner.scenario_autoretry.feature +131 -0
  208. data/vendor/behave/features/runner.select_files_by_regexp.example.feature +71 -0
  209. data/vendor/behave/features/runner.select_files_by_regexp.feature +84 -0
  210. data/vendor/behave/features/runner.select_scenarios_by_file_location.feature +403 -0
  211. data/vendor/behave/features/runner.select_scenarios_by_name.feature +289 -0
  212. data/vendor/behave/features/runner.select_scenarios_by_tag.feature +225 -0
  213. data/vendor/behave/features/runner.stop_after_failure.feature +122 -0
  214. data/vendor/behave/features/runner.tag_logic.feature +67 -0
  215. data/vendor/behave/features/runner.unknown_formatter.feature +23 -0
  216. data/vendor/behave/features/runner.use_stage_implementations.feature +126 -0
  217. data/vendor/behave/features/scenario.description.feature +171 -0
  218. data/vendor/behave/features/scenario.exclude_from_run.feature +217 -0
  219. data/vendor/behave/features/scenario_outline.basics.feature +100 -0
  220. data/vendor/behave/features/scenario_outline.improved.feature +177 -0
  221. data/vendor/behave/features/scenario_outline.name_annotation.feature +157 -0
  222. data/vendor/behave/features/scenario_outline.parametrized.feature +401 -0
  223. data/vendor/behave/features/scenario_outline.tagged_examples.feature +118 -0
  224. data/vendor/behave/features/step.async_steps.feature +225 -0
  225. data/vendor/behave/features/step.duplicated_step.feature +106 -0
  226. data/vendor/behave/features/step.execute_steps.feature +59 -0
  227. data/vendor/behave/features/step.execute_steps.with_table.feature +65 -0
  228. data/vendor/behave/features/step.import_other_step_module.feature +103 -0
  229. data/vendor/behave/features/step.pending_steps.feature +128 -0
  230. data/vendor/behave/features/step.undefined_steps.feature +307 -0
  231. data/vendor/behave/features/step.use_step_library.feature +44 -0
  232. data/vendor/behave/features/step_dialect.generic_steps.feature +189 -0
  233. data/vendor/behave/features/step_dialect.given_when_then.feature +89 -0
  234. data/vendor/behave/features/step_param.builtin_types.with_float.feature +239 -0
  235. data/vendor/behave/features/step_param.builtin_types.with_integer.feature +305 -0
  236. data/vendor/behave/features/step_param.custom_types.feature +134 -0
  237. data/vendor/behave/features/steps/behave_active_tags_steps.py +86 -0
  238. data/vendor/behave/features/steps/behave_context_steps.py +67 -0
  239. data/vendor/behave/features/steps/behave_model_tag_logic_steps.py +105 -0
  240. data/vendor/behave/features/steps/behave_model_util.py +105 -0
  241. data/vendor/behave/features/steps/behave_select_files_steps.py +83 -0
  242. data/vendor/behave/features/steps/behave_tag_expression_steps.py +166 -0
  243. data/vendor/behave/features/steps/behave_undefined_steps.py +101 -0
  244. data/vendor/behave/features/steps/use_steplib_behave4cmd.py +12 -0
  245. data/vendor/behave/features/summary.undefined_steps.feature +114 -0
  246. data/vendor/behave/features/tags.active_tags.feature +385 -0
  247. data/vendor/behave/features/tags.default_tags.feature +104 -0
  248. data/vendor/behave/features/tags.tag_expression.feature +105 -0
  249. data/vendor/behave/features/userdata.feature +331 -0
  250. data/vendor/behave/invoke.yaml +21 -0
  251. data/vendor/behave/issue.features/README.txt +17 -0
  252. data/vendor/behave/issue.features/environment.py +97 -0
  253. data/vendor/behave/issue.features/issue0030.feature +21 -0
  254. data/vendor/behave/issue.features/issue0031.feature +16 -0
  255. data/vendor/behave/issue.features/issue0032.feature +28 -0
  256. data/vendor/behave/issue.features/issue0035.feature +74 -0
  257. data/vendor/behave/issue.features/issue0040.feature +154 -0
  258. data/vendor/behave/issue.features/issue0041.feature +135 -0
  259. data/vendor/behave/issue.features/issue0042.feature +230 -0
  260. data/vendor/behave/issue.features/issue0044.feature +51 -0
  261. data/vendor/behave/issue.features/issue0046.feature +77 -0
  262. data/vendor/behave/issue.features/issue0052.feature +66 -0
  263. data/vendor/behave/issue.features/issue0059.feature +29 -0
  264. data/vendor/behave/issue.features/issue0063.feature +102 -0
  265. data/vendor/behave/issue.features/issue0064.feature +97 -0
  266. data/vendor/behave/issue.features/issue0065.feature +18 -0
  267. data/vendor/behave/issue.features/issue0066.feature +80 -0
  268. data/vendor/behave/issue.features/issue0067.feature +90 -0
  269. data/vendor/behave/issue.features/issue0069.feature +64 -0
  270. data/vendor/behave/issue.features/issue0072.feature +32 -0
  271. data/vendor/behave/issue.features/issue0073.feature +228 -0
  272. data/vendor/behave/issue.features/issue0075.feature +18 -0
  273. data/vendor/behave/issue.features/issue0077.feature +89 -0
  274. data/vendor/behave/issue.features/issue0080.feature +49 -0
  275. data/vendor/behave/issue.features/issue0081.feature +138 -0
  276. data/vendor/behave/issue.features/issue0083.feature +69 -0
  277. data/vendor/behave/issue.features/issue0084.feature +69 -0
  278. data/vendor/behave/issue.features/issue0085.feature +119 -0
  279. data/vendor/behave/issue.features/issue0092.feature +66 -0
  280. data/vendor/behave/issue.features/issue0096.feature +173 -0
  281. data/vendor/behave/issue.features/issue0099.feature +130 -0
  282. data/vendor/behave/issue.features/issue0109.feature +60 -0
  283. data/vendor/behave/issue.features/issue0111.feature +53 -0
  284. data/vendor/behave/issue.features/issue0112.feature +64 -0
  285. data/vendor/behave/issue.features/issue0114.feature +118 -0
  286. data/vendor/behave/issue.features/issue0116.feature +71 -0
  287. data/vendor/behave/issue.features/issue0125.feature +49 -0
  288. data/vendor/behave/issue.features/issue0127.feature +64 -0
  289. data/vendor/behave/issue.features/issue0139.feature +67 -0
  290. data/vendor/behave/issue.features/issue0142.feature +37 -0
  291. data/vendor/behave/issue.features/issue0143.feature +54 -0
  292. data/vendor/behave/issue.features/issue0145.feature +63 -0
  293. data/vendor/behave/issue.features/issue0148.feature +105 -0
  294. data/vendor/behave/issue.features/issue0152.feature +52 -0
  295. data/vendor/behave/issue.features/issue0159.feature +74 -0
  296. data/vendor/behave/issue.features/issue0162.feature +86 -0
  297. data/vendor/behave/issue.features/issue0171.feature +16 -0
  298. data/vendor/behave/issue.features/issue0172.feature +51 -0
  299. data/vendor/behave/issue.features/issue0175.feature +91 -0
  300. data/vendor/behave/issue.features/issue0177.feature +40 -0
  301. data/vendor/behave/issue.features/issue0181.feature +36 -0
  302. data/vendor/behave/issue.features/issue0184.feature +144 -0
  303. data/vendor/behave/issue.features/issue0186.feature +12 -0
  304. data/vendor/behave/issue.features/issue0188.feature +60 -0
  305. data/vendor/behave/issue.features/issue0191.feature +178 -0
  306. data/vendor/behave/issue.features/issue0194.feature +215 -0
  307. data/vendor/behave/issue.features/issue0197.feature +11 -0
  308. data/vendor/behave/issue.features/issue0216.feature +129 -0
  309. data/vendor/behave/issue.features/issue0226.feature +51 -0
  310. data/vendor/behave/issue.features/issue0228.feature +41 -0
  311. data/vendor/behave/issue.features/issue0230.feature +46 -0
  312. data/vendor/behave/issue.features/issue0231.feature +77 -0
  313. data/vendor/behave/issue.features/issue0238.feature +52 -0
  314. data/vendor/behave/issue.features/issue0251.feature +15 -0
  315. data/vendor/behave/issue.features/issue0280.feature +118 -0
  316. data/vendor/behave/issue.features/issue0288.feature +95 -0
  317. data/vendor/behave/issue.features/issue0300.feature +49 -0
  318. data/vendor/behave/issue.features/issue0302.feature +91 -0
  319. data/vendor/behave/issue.features/issue0309.feature +52 -0
  320. data/vendor/behave/issue.features/issue0330.feature +124 -0
  321. data/vendor/behave/issue.features/issue0349.feature +9 -0
  322. data/vendor/behave/issue.features/issue0361.feature +79 -0
  323. data/vendor/behave/issue.features/issue0383.feature +76 -0
  324. data/vendor/behave/issue.features/issue0384.feature +103 -0
  325. data/vendor/behave/issue.features/issue0385.feature +109 -0
  326. data/vendor/behave/issue.features/issue0424.feature +66 -0
  327. data/vendor/behave/issue.features/issue0446.feature +116 -0
  328. data/vendor/behave/issue.features/issue0449.feature +42 -0
  329. data/vendor/behave/issue.features/issue0453.feature +42 -0
  330. data/vendor/behave/issue.features/issue0457.feature +65 -0
  331. data/vendor/behave/issue.features/issue0462.feature +38 -0
  332. data/vendor/behave/issue.features/issue0476.feature +39 -0
  333. data/vendor/behave/issue.features/issue0487.feature +92 -0
  334. data/vendor/behave/issue.features/issue0506.feature +77 -0
  335. data/vendor/behave/issue.features/issue0510.feature +51 -0
  336. data/vendor/behave/issue.features/requirements.txt +12 -0
  337. data/vendor/behave/issue.features/steps/ansi_steps.py +20 -0
  338. data/vendor/behave/issue.features/steps/behave_hooks_steps.py +10 -0
  339. data/vendor/behave/issue.features/steps/use_steplib_behave4cmd.py +13 -0
  340. data/vendor/behave/more.features/formatter.json.validate_output.feature +37 -0
  341. data/vendor/behave/more.features/steps/tutorial_steps.py +16 -0
  342. data/vendor/behave/more.features/steps/use_steplib_behave4cmd.py +7 -0
  343. data/vendor/behave/more.features/tutorial.feature +6 -0
  344. data/vendor/behave/py.requirements/README.txt +5 -0
  345. data/vendor/behave/py.requirements/all.txt +16 -0
  346. data/vendor/behave/py.requirements/basic.txt +21 -0
  347. data/vendor/behave/py.requirements/develop.txt +28 -0
  348. data/vendor/behave/py.requirements/docs.txt +6 -0
  349. data/vendor/behave/py.requirements/json.txt +7 -0
  350. data/vendor/behave/py.requirements/more_py26.txt +8 -0
  351. data/vendor/behave/py.requirements/testing.txt +10 -0
  352. data/vendor/behave/pytest.ini +24 -0
  353. data/vendor/behave/setup.cfg +29 -0
  354. data/vendor/behave/setup.py +118 -0
  355. data/vendor/behave/setuptools_behave.py +130 -0
  356. data/vendor/behave/tasks/__behave.py +45 -0
  357. data/vendor/behave/tasks/__init__.py +55 -0
  358. data/vendor/behave/tasks/__main__.py +70 -0
  359. data/vendor/behave/tasks/_setup.py +135 -0
  360. data/vendor/behave/tasks/_vendor/README.rst +35 -0
  361. data/vendor/behave/tasks/_vendor/invoke.zip +0 -0
  362. data/vendor/behave/tasks/_vendor/path.py +1725 -0
  363. data/vendor/behave/tasks/_vendor/pathlib.py +1280 -0
  364. data/vendor/behave/tasks/_vendor/six.py +868 -0
  365. data/vendor/behave/tasks/clean.py +246 -0
  366. data/vendor/behave/tasks/docs.py +97 -0
  367. data/vendor/behave/tasks/requirements.txt +17 -0
  368. data/vendor/behave/tasks/test.py +192 -0
  369. data/vendor/behave/test/__init__.py +0 -0
  370. data/vendor/behave/test/_importer_candidate.py +3 -0
  371. data/vendor/behave/test/reporters/__init__.py +0 -0
  372. data/vendor/behave/test/reporters/test_summary.py +240 -0
  373. data/vendor/behave/test/test_ansi_escapes.py +73 -0
  374. data/vendor/behave/test/test_configuration.py +172 -0
  375. data/vendor/behave/test/test_formatter.py +265 -0
  376. data/vendor/behave/test/test_formatter_progress.py +39 -0
  377. data/vendor/behave/test/test_formatter_rerun.py +97 -0
  378. data/vendor/behave/test/test_formatter_tags.py +57 -0
  379. data/vendor/behave/test/test_importer.py +151 -0
  380. data/vendor/behave/test/test_log_capture.py +29 -0
  381. data/vendor/behave/test/test_matchers.py +236 -0
  382. data/vendor/behave/test/test_model.py +871 -0
  383. data/vendor/behave/test/test_parser.py +1590 -0
  384. data/vendor/behave/test/test_runner.py +1074 -0
  385. data/vendor/behave/test/test_step_registry.py +96 -0
  386. data/vendor/behave/test/test_tag_expression.py +506 -0
  387. data/vendor/behave/test/test_tag_expression2.py +462 -0
  388. data/vendor/behave/test/test_tag_matcher.py +729 -0
  389. data/vendor/behave/test/test_userdata.py +184 -0
  390. data/vendor/behave/tests/README.txt +12 -0
  391. data/vendor/behave/tests/__init__.py +0 -0
  392. data/vendor/behave/tests/api/__ONLY_PY34_or_newer.txt +0 -0
  393. data/vendor/behave/tests/api/__init__.py +0 -0
  394. data/vendor/behave/tests/api/_test_async_step34.py +130 -0
  395. data/vendor/behave/tests/api/_test_async_step35.py +75 -0
  396. data/vendor/behave/tests/api/test_async_step.py +18 -0
  397. data/vendor/behave/tests/api/testing_support.py +94 -0
  398. data/vendor/behave/tests/api/testing_support_async.py +21 -0
  399. data/vendor/behave/tests/issues/test_issue0336.py +66 -0
  400. data/vendor/behave/tests/issues/test_issue0449.py +55 -0
  401. data/vendor/behave/tests/issues/test_issue0453.py +62 -0
  402. data/vendor/behave/tests/issues/test_issue0458.py +54 -0
  403. data/vendor/behave/tests/issues/test_issue0495.py +65 -0
  404. data/vendor/behave/tests/unit/__init__.py +0 -0
  405. data/vendor/behave/tests/unit/test_behave4cmd_command_shell_proc.py +135 -0
  406. data/vendor/behave/tests/unit/test_capture.py +280 -0
  407. data/vendor/behave/tests/unit/test_model_core.py +56 -0
  408. data/vendor/behave/tests/unit/test_textutil.py +267 -0
  409. data/vendor/behave/tools/test-features/background.feature +9 -0
  410. data/vendor/behave/tools/test-features/environment.py +8 -0
  411. data/vendor/behave/tools/test-features/french.feature +11 -0
  412. data/vendor/behave/tools/test-features/outline.feature +39 -0
  413. data/vendor/behave/tools/test-features/parse.feature +10 -0
  414. data/vendor/behave/tools/test-features/step-data.feature +60 -0
  415. data/vendor/behave/tools/test-features/steps/steps.py +120 -0
  416. data/vendor/behave/tools/test-features/tags.feature +18 -0
  417. data/vendor/behave/tox.ini +159 -0
  418. 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 "%*"
@@ -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
+ ########################