ansible-core 2.16.7__py3-none-any.whl → 2.17.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ansible-core might be problematic. Click here for more details.

Files changed (597) hide show
  1. ansible/__init__.py +1 -3
  2. ansible/__main__.py +1 -0
  3. ansible/_vendor/__init__.py +1 -2
  4. ansible/cli/__init__.py +7 -8
  5. ansible/cli/adhoc.py +1 -2
  6. ansible/cli/arguments/__init__.py +1 -2
  7. ansible/cli/arguments/option_helpers.py +1 -2
  8. ansible/cli/config.py +1 -2
  9. ansible/cli/console.py +1 -2
  10. ansible/cli/doc.py +326 -202
  11. ansible/cli/galaxy.py +9 -3
  12. ansible/cli/inventory.py +4 -6
  13. ansible/cli/playbook.py +1 -2
  14. ansible/cli/pull.py +4 -5
  15. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -4
  16. ansible/cli/vault.py +1 -2
  17. ansible/collections/list.py +1 -0
  18. ansible/compat/__init__.py +1 -4
  19. ansible/compat/importlib_resources.py +1 -2
  20. ansible/compat/{selectors/__init__.py → selectors.py} +12 -12
  21. ansible/config/ansible_builtin_runtime.yml +2 -0
  22. ansible/config/base.yml +154 -149
  23. ansible/config/manager.py +33 -25
  24. ansible/constants.py +1 -2
  25. ansible/context.py +1 -4
  26. ansible/errors/__init__.py +1 -3
  27. ansible/errors/yaml_strings.py +1 -3
  28. ansible/executor/__init__.py +1 -3
  29. ansible/executor/discovery/python_target.py +1 -2
  30. ansible/executor/interpreter_discovery.py +9 -8
  31. ansible/executor/module_common.py +1 -3
  32. ansible/executor/play_iterator.py +1 -3
  33. ansible/executor/playbook_executor.py +1 -3
  34. ansible/executor/powershell/module_manifest.py +2 -3
  35. ansible/executor/process/__init__.py +1 -3
  36. ansible/executor/process/worker.py +1 -3
  37. ansible/executor/stats.py +1 -3
  38. ansible/executor/task_executor.py +2 -3
  39. ansible/executor/task_queue_manager.py +1 -3
  40. ansible/executor/task_result.py +2 -3
  41. ansible/galaxy/__init__.py +1 -2
  42. ansible/galaxy/api.py +10 -2
  43. ansible/galaxy/collection/__init__.py +38 -24
  44. ansible/galaxy/collection/concrete_artifact_manager.py +1 -2
  45. ansible/galaxy/collection/galaxy_api_proxy.py +1 -2
  46. ansible/galaxy/collection/gpg.py +4 -2
  47. ansible/galaxy/data/apb/meta/main.yml.j2 +0 -15
  48. ansible/galaxy/data/container/meta/main.yml.j2 +0 -18
  49. ansible/galaxy/data/default/role/meta/main.yml.j2 +0 -18
  50. ansible/galaxy/data/network/cliconf_plugins/example.py.j2 +1 -2
  51. ansible/galaxy/data/network/library/example_command.py.j2 +1 -2
  52. ansible/galaxy/data/network/library/example_config.py.j2 +1 -2
  53. ansible/galaxy/data/network/library/example_facts.py.j2 +1 -2
  54. ansible/galaxy/data/network/meta/main.yml.j2 +0 -15
  55. ansible/galaxy/data/network/module_utils/example.py.j2 +1 -2
  56. ansible/galaxy/data/network/netconf_plugins/example.py.j2 +1 -2
  57. ansible/galaxy/data/network/terminal_plugins/example.py.j2 +1 -2
  58. ansible/galaxy/dependency_resolution/__init__.py +1 -2
  59. ansible/galaxy/dependency_resolution/dataclasses.py +5 -6
  60. ansible/galaxy/dependency_resolution/errors.py +1 -2
  61. ansible/galaxy/dependency_resolution/providers.py +3 -4
  62. ansible/galaxy/dependency_resolution/reporters.py +2 -3
  63. ansible/galaxy/dependency_resolution/resolvers.py +1 -2
  64. ansible/galaxy/dependency_resolution/versioning.py +1 -2
  65. ansible/galaxy/role.py +2 -3
  66. ansible/galaxy/token.py +1 -2
  67. ansible/galaxy/user_agent.py +1 -2
  68. ansible/inventory/data.py +1 -2
  69. ansible/inventory/group.py +1 -2
  70. ansible/inventory/helpers.py +1 -2
  71. ansible/inventory/host.py +1 -3
  72. ansible/inventory/manager.py +1 -2
  73. ansible/module_utils/_text.py +1 -2
  74. ansible/module_utils/ansible_release.py +3 -5
  75. ansible/module_utils/api.py +1 -2
  76. ansible/module_utils/basic.py +113 -158
  77. ansible/module_utils/common/_collections_compat.py +1 -2
  78. ansible/module_utils/common/_utils.py +1 -3
  79. ansible/module_utils/common/arg_spec.py +1 -2
  80. ansible/module_utils/common/collections.py +1 -2
  81. ansible/module_utils/common/dict_transformations.py +1 -2
  82. ansible/module_utils/common/file.py +10 -5
  83. ansible/module_utils/common/json.py +1 -3
  84. ansible/module_utils/common/locale.py +1 -2
  85. ansible/module_utils/common/network.py +2 -3
  86. ansible/module_utils/common/parameters.py +9 -9
  87. ansible/module_utils/common/process.py +12 -5
  88. ansible/module_utils/common/respawn.py +2 -3
  89. ansible/module_utils/common/sys_info.py +1 -2
  90. ansible/module_utils/common/text/converters.py +1 -2
  91. ansible/module_utils/common/text/formatters.py +1 -2
  92. ansible/module_utils/common/validation.py +5 -6
  93. ansible/module_utils/common/warnings.py +1 -2
  94. ansible/module_utils/common/yaml.py +1 -2
  95. ansible/module_utils/compat/datetime.py +1 -3
  96. ansible/module_utils/compat/importlib.py +21 -13
  97. ansible/module_utils/compat/paramiko.py +1 -2
  98. ansible/module_utils/compat/selectors.py +10 -34
  99. ansible/module_utils/compat/selinux.py +1 -2
  100. ansible/module_utils/compat/typing.py +1 -2
  101. ansible/module_utils/compat/version.py +1 -2
  102. ansible/module_utils/connection.py +1 -2
  103. ansible/module_utils/csharp/Ansible.Basic.cs +20 -3
  104. ansible/module_utils/distro/__init__.py +3 -4
  105. ansible/module_utils/distro/_distro.py +230 -234
  106. ansible/module_utils/errors.py +1 -2
  107. ansible/module_utils/facts/__init__.py +1 -2
  108. ansible/module_utils/facts/ansible_collector.py +1 -2
  109. ansible/module_utils/facts/collector.py +1 -2
  110. ansible/module_utils/facts/compat.py +1 -2
  111. ansible/module_utils/facts/default_collectors.py +1 -2
  112. ansible/module_utils/facts/hardware/aix.py +1 -2
  113. ansible/module_utils/facts/hardware/base.py +1 -2
  114. ansible/module_utils/facts/hardware/darwin.py +1 -2
  115. ansible/module_utils/facts/hardware/dragonfly.py +1 -2
  116. ansible/module_utils/facts/hardware/freebsd.py +1 -2
  117. ansible/module_utils/facts/hardware/hpux.py +1 -2
  118. ansible/module_utils/facts/hardware/hurd.py +1 -2
  119. ansible/module_utils/facts/hardware/linux.py +8 -6
  120. ansible/module_utils/facts/hardware/netbsd.py +1 -2
  121. ansible/module_utils/facts/hardware/openbsd.py +1 -2
  122. ansible/module_utils/facts/hardware/sunos.py +2 -3
  123. ansible/module_utils/facts/namespace.py +1 -2
  124. ansible/module_utils/facts/network/aix.py +1 -2
  125. ansible/module_utils/facts/network/base.py +1 -2
  126. ansible/module_utils/facts/network/darwin.py +1 -2
  127. ansible/module_utils/facts/network/dragonfly.py +1 -2
  128. ansible/module_utils/facts/network/fc_wwn.py +1 -2
  129. ansible/module_utils/facts/network/freebsd.py +1 -2
  130. ansible/module_utils/facts/network/generic_bsd.py +1 -2
  131. ansible/module_utils/facts/network/hpux.py +1 -2
  132. ansible/module_utils/facts/network/hurd.py +1 -2
  133. ansible/module_utils/facts/network/iscsi.py +1 -2
  134. ansible/module_utils/facts/network/linux.py +1 -2
  135. ansible/module_utils/facts/network/netbsd.py +1 -2
  136. ansible/module_utils/facts/network/nvme.py +1 -2
  137. ansible/module_utils/facts/network/openbsd.py +1 -2
  138. ansible/module_utils/facts/network/sunos.py +1 -2
  139. ansible/module_utils/facts/other/facter.py +1 -2
  140. ansible/module_utils/facts/other/ohai.py +1 -2
  141. ansible/module_utils/facts/packages.py +1 -2
  142. ansible/module_utils/facts/sysctl.py +1 -2
  143. ansible/module_utils/facts/system/apparmor.py +1 -2
  144. ansible/module_utils/facts/system/caps.py +1 -2
  145. ansible/module_utils/facts/system/chroot.py +1 -2
  146. ansible/module_utils/facts/system/cmdline.py +1 -2
  147. ansible/module_utils/facts/system/date_time.py +1 -2
  148. ansible/module_utils/facts/system/distribution.py +4 -5
  149. ansible/module_utils/facts/system/dns.py +1 -2
  150. ansible/module_utils/facts/system/env.py +1 -2
  151. ansible/module_utils/facts/system/fips.py +1 -2
  152. ansible/module_utils/facts/system/loadavg.py +1 -2
  153. ansible/module_utils/facts/system/local.py +1 -2
  154. ansible/module_utils/facts/system/lsb.py +1 -2
  155. ansible/module_utils/facts/system/pkg_mgr.py +7 -30
  156. ansible/module_utils/facts/system/platform.py +1 -2
  157. ansible/module_utils/facts/system/python.py +1 -2
  158. ansible/module_utils/facts/system/selinux.py +1 -2
  159. ansible/module_utils/facts/system/service_mgr.py +1 -2
  160. ansible/module_utils/facts/system/ssh_pub_keys.py +1 -2
  161. ansible/module_utils/facts/system/user.py +1 -2
  162. ansible/module_utils/facts/timeout.py +1 -2
  163. ansible/module_utils/facts/utils.py +1 -2
  164. ansible/module_utils/facts/virtual/base.py +1 -2
  165. ansible/module_utils/facts/virtual/dragonfly.py +1 -2
  166. ansible/module_utils/facts/virtual/freebsd.py +1 -2
  167. ansible/module_utils/facts/virtual/hpux.py +1 -2
  168. ansible/module_utils/facts/virtual/linux.py +1 -2
  169. ansible/module_utils/facts/virtual/netbsd.py +1 -2
  170. ansible/module_utils/facts/virtual/openbsd.py +1 -2
  171. ansible/module_utils/facts/virtual/sunos.py +1 -2
  172. ansible/module_utils/facts/virtual/sysctl.py +1 -2
  173. ansible/module_utils/json_utils.py +1 -2
  174. ansible/module_utils/parsing/convert_bool.py +1 -2
  175. ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 +5 -2
  176. ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
  177. ansible/module_utils/pycompat24.py +24 -4
  178. ansible/module_utils/service.py +31 -5
  179. ansible/module_utils/six/__init__.py +1 -1
  180. ansible/module_utils/splitter.py +1 -2
  181. ansible/module_utils/urls.py +335 -1052
  182. ansible/module_utils/yumdnf.py +13 -35
  183. ansible/modules/add_host.py +1 -2
  184. ansible/modules/apt.py +38 -22
  185. ansible/modules/apt_key.py +1 -2
  186. ansible/modules/apt_repository.py +18 -10
  187. ansible/modules/assemble.py +1 -2
  188. ansible/modules/assert.py +8 -4
  189. ansible/modules/async_status.py +17 -14
  190. ansible/modules/async_wrapper.py +11 -9
  191. ansible/modules/blockinfile.py +2 -3
  192. ansible/modules/command.py +1 -2
  193. ansible/modules/copy.py +4 -76
  194. ansible/modules/cron.py +3 -3
  195. ansible/modules/deb822_repository.py +5 -5
  196. ansible/modules/debconf.py +19 -8
  197. ansible/modules/debug.py +1 -2
  198. ansible/modules/dnf.py +39 -46
  199. ansible/modules/dnf5.py +28 -26
  200. ansible/modules/dpkg_selections.py +1 -2
  201. ansible/modules/expect.py +23 -15
  202. ansible/modules/fail.py +1 -2
  203. ansible/modules/fetch.py +1 -2
  204. ansible/modules/file.py +4 -3
  205. ansible/modules/find.py +18 -5
  206. ansible/modules/gather_facts.py +1 -2
  207. ansible/modules/get_url.py +2 -3
  208. ansible/modules/getent.py +2 -3
  209. ansible/modules/git.py +28 -17
  210. ansible/modules/group.py +1 -2
  211. ansible/modules/group_by.py +1 -2
  212. ansible/modules/hostname.py +2 -19
  213. ansible/modules/import_playbook.py +1 -2
  214. ansible/modules/import_role.py +9 -2
  215. ansible/modules/import_tasks.py +1 -2
  216. ansible/modules/include_role.py +1 -2
  217. ansible/modules/include_tasks.py +1 -2
  218. ansible/modules/include_vars.py +1 -2
  219. ansible/modules/iptables.py +38 -21
  220. ansible/modules/known_hosts.py +15 -8
  221. ansible/modules/lineinfile.py +1 -6
  222. ansible/modules/meta.py +3 -2
  223. ansible/modules/package.py +6 -5
  224. ansible/modules/package_facts.py +1 -2
  225. ansible/modules/pause.py +2 -3
  226. ansible/modules/ping.py +1 -2
  227. ansible/modules/pip.py +40 -20
  228. ansible/modules/raw.py +1 -2
  229. ansible/modules/reboot.py +1 -2
  230. ansible/modules/replace.py +7 -5
  231. ansible/modules/rpm_key.py +1 -2
  232. ansible/modules/script.py +1 -2
  233. ansible/modules/service.py +3 -21
  234. ansible/modules/service_facts.py +3 -12
  235. ansible/modules/set_fact.py +1 -2
  236. ansible/modules/set_stats.py +1 -2
  237. ansible/modules/setup.py +1 -2
  238. ansible/modules/shell.py +1 -2
  239. ansible/modules/slurp.py +1 -2
  240. ansible/modules/stat.py +1 -2
  241. ansible/modules/subversion.py +2 -3
  242. ansible/modules/systemd.py +12 -9
  243. ansible/modules/systemd_service.py +12 -9
  244. ansible/modules/sysvinit.py +7 -2
  245. ansible/modules/tempfile.py +7 -2
  246. ansible/modules/template.py +2 -3
  247. ansible/modules/unarchive.py +13 -3
  248. ansible/modules/uri.py +12 -15
  249. ansible/modules/user.py +11 -4
  250. ansible/modules/validate_argument_spec.py +15 -16
  251. ansible/modules/wait_for.py +1 -2
  252. ansible/modules/wait_for_connection.py +1 -2
  253. ansible/modules/yum_repository.py +2 -3
  254. ansible/parsing/__init__.py +1 -3
  255. ansible/parsing/ajson.py +1 -3
  256. ansible/parsing/dataloader.py +23 -12
  257. ansible/parsing/mod_args.py +6 -4
  258. ansible/parsing/plugin_docs.py +1 -2
  259. ansible/parsing/quoting.py +1 -3
  260. ansible/parsing/splitter.py +1 -3
  261. ansible/parsing/utils/__init__.py +1 -3
  262. ansible/parsing/utils/addresses.py +1 -3
  263. ansible/parsing/utils/jsonify.py +1 -3
  264. ansible/parsing/utils/yaml.py +1 -3
  265. ansible/parsing/vault/__init__.py +6 -8
  266. ansible/parsing/yaml/__init__.py +1 -3
  267. ansible/parsing/yaml/constructor.py +1 -3
  268. ansible/parsing/yaml/dumper.py +1 -3
  269. ansible/parsing/yaml/loader.py +1 -3
  270. ansible/parsing/yaml/objects.py +1 -3
  271. ansible/playbook/__init__.py +1 -3
  272. ansible/playbook/attribute.py +1 -3
  273. ansible/playbook/base.py +2 -3
  274. ansible/playbook/block.py +1 -3
  275. ansible/playbook/collectionsearch.py +1 -2
  276. ansible/playbook/conditional.py +1 -3
  277. ansible/playbook/delegatable.py +1 -0
  278. ansible/playbook/handler.py +2 -4
  279. ansible/playbook/handler_task_include.py +1 -3
  280. ansible/playbook/helpers.py +1 -4
  281. ansible/playbook/included_file.py +4 -4
  282. ansible/playbook/loop_control.py +1 -3
  283. ansible/playbook/notifiable.py +1 -0
  284. ansible/playbook/play.py +1 -3
  285. ansible/playbook/play_context.py +1 -3
  286. ansible/playbook/playbook_include.py +1 -3
  287. ansible/playbook/role/__init__.py +1 -3
  288. ansible/playbook/role/definition.py +1 -3
  289. ansible/playbook/role/include.py +1 -3
  290. ansible/playbook/role/metadata.py +1 -3
  291. ansible/playbook/role/requirement.py +1 -3
  292. ansible/playbook/role_include.py +2 -10
  293. ansible/playbook/taggable.py +1 -3
  294. ansible/playbook/task.py +2 -4
  295. ansible/playbook/task_include.py +1 -3
  296. ansible/plugins/__init__.py +4 -5
  297. ansible/plugins/action/__init__.py +19 -13
  298. ansible/plugins/action/add_host.py +2 -4
  299. ansible/plugins/action/assemble.py +2 -3
  300. ansible/plugins/action/assert.py +1 -2
  301. ansible/plugins/action/async_status.py +1 -2
  302. ansible/plugins/action/command.py +1 -2
  303. ansible/plugins/action/copy.py +12 -9
  304. ansible/plugins/action/debug.py +1 -2
  305. ansible/plugins/action/dnf.py +4 -4
  306. ansible/plugins/action/fail.py +1 -2
  307. ansible/plugins/action/fetch.py +2 -3
  308. ansible/plugins/action/gather_facts.py +3 -4
  309. ansible/plugins/action/group_by.py +1 -2
  310. ansible/plugins/action/include_vars.py +1 -2
  311. ansible/plugins/action/normal.py +1 -2
  312. ansible/plugins/action/package.py +36 -21
  313. ansible/plugins/action/pause.py +1 -2
  314. ansible/plugins/action/raw.py +2 -3
  315. ansible/plugins/action/reboot.py +30 -15
  316. ansible/plugins/action/script.py +3 -4
  317. ansible/plugins/action/service.py +1 -2
  318. ansible/plugins/action/set_fact.py +1 -2
  319. ansible/plugins/action/set_stats.py +1 -2
  320. ansible/plugins/action/shell.py +1 -2
  321. ansible/plugins/action/template.py +1 -2
  322. ansible/plugins/action/unarchive.py +1 -2
  323. ansible/plugins/action/uri.py +2 -3
  324. ansible/plugins/action/validate_argument_spec.py +1 -2
  325. ansible/plugins/action/wait_for_connection.py +2 -3
  326. ansible/plugins/become/__init__.py +1 -2
  327. ansible/plugins/become/runas.py +1 -2
  328. ansible/plugins/become/su.py +1 -2
  329. ansible/plugins/become/sudo.py +1 -2
  330. ansible/plugins/cache/__init__.py +3 -3
  331. ansible/plugins/cache/base.py +1 -2
  332. ansible/plugins/cache/jsonfile.py +1 -3
  333. ansible/plugins/cache/memory.py +1 -2
  334. ansible/plugins/callback/__init__.py +2 -4
  335. ansible/plugins/callback/default.py +2 -3
  336. ansible/plugins/callback/junit.py +1 -2
  337. ansible/plugins/callback/minimal.py +1 -3
  338. ansible/plugins/callback/oneline.py +1 -3
  339. ansible/plugins/callback/tree.py +1 -2
  340. ansible/plugins/cliconf/__init__.py +1 -2
  341. ansible/plugins/connection/__init__.py +4 -5
  342. ansible/plugins/connection/local.py +3 -4
  343. ansible/plugins/connection/paramiko_ssh.py +1 -2
  344. ansible/plugins/connection/psrp.py +1 -2
  345. ansible/plugins/connection/ssh.py +18 -54
  346. ansible/plugins/connection/winrm.py +3 -4
  347. ansible/plugins/doc_fragments/action_common_attributes.py +2 -3
  348. ansible/plugins/doc_fragments/action_core.py +3 -4
  349. ansible/plugins/doc_fragments/backup.py +1 -2
  350. ansible/plugins/doc_fragments/connection_pipelining.py +1 -2
  351. ansible/plugins/doc_fragments/constructed.py +1 -2
  352. ansible/plugins/doc_fragments/decrypt.py +2 -3
  353. ansible/plugins/doc_fragments/default_callback.py +1 -2
  354. ansible/plugins/doc_fragments/files.py +1 -2
  355. ansible/plugins/doc_fragments/inventory_cache.py +1 -2
  356. ansible/plugins/doc_fragments/result_format_callback.py +1 -2
  357. ansible/plugins/doc_fragments/return_common.py +1 -2
  358. ansible/plugins/doc_fragments/shell_common.py +1 -2
  359. ansible/plugins/doc_fragments/shell_windows.py +1 -2
  360. ansible/plugins/doc_fragments/template_common.py +1 -2
  361. ansible/plugins/doc_fragments/url.py +1 -2
  362. ansible/plugins/doc_fragments/url_windows.py +1 -2
  363. ansible/plugins/doc_fragments/validate.py +1 -2
  364. ansible/plugins/doc_fragments/vars_plugin_staging.py +1 -2
  365. ansible/plugins/filter/__init__.py +1 -2
  366. ansible/plugins/filter/b64decode.yml +8 -8
  367. ansible/plugins/filter/b64encode.yml +4 -4
  368. ansible/plugins/filter/comment.yml +1 -1
  369. ansible/plugins/filter/core.py +11 -9
  370. ansible/plugins/filter/encryption.py +1 -3
  371. ansible/plugins/filter/extract.yml +1 -1
  372. ansible/plugins/filter/from_yaml_all.yml +1 -1
  373. ansible/plugins/filter/human_readable.yml +3 -3
  374. ansible/plugins/filter/human_to_bytes.yml +2 -2
  375. ansible/plugins/filter/mandatory.yml +1 -1
  376. ansible/plugins/filter/mathstuff.py +2 -2
  377. ansible/plugins/filter/password_hash.yml +3 -2
  378. ansible/plugins/filter/regex_replace.yml +15 -0
  379. ansible/plugins/filter/regex_search.yml +12 -0
  380. ansible/plugins/filter/strftime.yml +2 -9
  381. ansible/plugins/filter/to_datetime.yml +17 -2
  382. ansible/plugins/filter/to_nice_json.yml +4 -0
  383. ansible/plugins/filter/union.yml +1 -1
  384. ansible/plugins/filter/urls.py +1 -2
  385. ansible/plugins/filter/urlsplit.py +1 -2
  386. ansible/plugins/filter/zip.yml +2 -2
  387. ansible/plugins/filter/zip_longest.yml +1 -1
  388. ansible/plugins/httpapi/__init__.py +1 -2
  389. ansible/plugins/inventory/__init__.py +2 -4
  390. ansible/plugins/inventory/advanced_host_list.py +1 -2
  391. ansible/plugins/inventory/auto.py +2 -3
  392. ansible/plugins/inventory/constructed.py +3 -2
  393. ansible/plugins/inventory/generator.py +1 -2
  394. ansible/plugins/inventory/host_list.py +1 -2
  395. ansible/plugins/inventory/ini.py +1 -2
  396. ansible/plugins/inventory/script.py +122 -3
  397. ansible/plugins/inventory/toml.py +1 -2
  398. ansible/plugins/inventory/yaml.py +3 -4
  399. ansible/plugins/list.py +2 -3
  400. ansible/plugins/loader.py +10 -11
  401. ansible/plugins/lookup/__init__.py +1 -3
  402. ansible/plugins/lookup/config.py +19 -15
  403. ansible/plugins/lookup/csvfile.py +16 -7
  404. ansible/plugins/lookup/dict.py +2 -3
  405. ansible/plugins/lookup/env.py +4 -4
  406. ansible/plugins/lookup/file.py +1 -2
  407. ansible/plugins/lookup/fileglob.py +1 -2
  408. ansible/plugins/lookup/first_found.py +8 -7
  409. ansible/plugins/lookup/indexed_items.py +1 -2
  410. ansible/plugins/lookup/ini.py +6 -3
  411. ansible/plugins/lookup/inventory_hostnames.py +1 -2
  412. ansible/plugins/lookup/items.py +1 -2
  413. ansible/plugins/lookup/lines.py +1 -2
  414. ansible/plugins/lookup/list.py +1 -3
  415. ansible/plugins/lookup/nested.py +1 -2
  416. ansible/plugins/lookup/password.py +16 -14
  417. ansible/plugins/lookup/pipe.py +1 -2
  418. ansible/plugins/lookup/random_choice.py +1 -2
  419. ansible/plugins/lookup/sequence.py +21 -52
  420. ansible/plugins/lookup/subelements.py +1 -2
  421. ansible/plugins/lookup/template.py +1 -2
  422. ansible/plugins/lookup/together.py +1 -2
  423. ansible/plugins/lookup/unvault.py +1 -2
  424. ansible/plugins/lookup/url.py +9 -11
  425. ansible/plugins/lookup/varnames.py +1 -2
  426. ansible/plugins/lookup/vars.py +1 -2
  427. ansible/plugins/netconf/__init__.py +1 -2
  428. ansible/plugins/shell/__init__.py +3 -4
  429. ansible/plugins/shell/cmd.py +1 -2
  430. ansible/plugins/shell/powershell.py +1 -2
  431. ansible/plugins/shell/sh.py +1 -2
  432. ansible/plugins/strategy/__init__.py +33 -21
  433. ansible/plugins/strategy/debug.py +1 -2
  434. ansible/plugins/strategy/free.py +17 -7
  435. ansible/plugins/strategy/host_pinned.py +1 -3
  436. ansible/plugins/strategy/linear.py +22 -22
  437. ansible/plugins/terminal/__init__.py +2 -3
  438. ansible/plugins/test/__init__.py +1 -2
  439. ansible/plugins/test/change.yml +1 -1
  440. ansible/plugins/test/changed.yml +1 -1
  441. ansible/plugins/test/contains.yml +1 -1
  442. ansible/plugins/test/core.py +2 -4
  443. ansible/plugins/test/exists.yml +1 -1
  444. ansible/plugins/test/failed.yml +1 -1
  445. ansible/plugins/test/failure.yml +1 -1
  446. ansible/plugins/test/files.py +1 -3
  447. ansible/plugins/test/finished.yml +3 -3
  448. ansible/plugins/test/issuperset.yml +1 -1
  449. ansible/plugins/test/match.yml +1 -1
  450. ansible/plugins/test/mathstuff.py +1 -2
  451. ansible/plugins/test/reachable.yml +1 -1
  452. ansible/plugins/test/regex.yml +1 -1
  453. ansible/plugins/test/search.yml +1 -1
  454. ansible/plugins/test/skip.yml +1 -1
  455. ansible/plugins/test/skipped.yml +1 -1
  456. ansible/plugins/test/started.yml +1 -1
  457. ansible/plugins/test/succeeded.yml +1 -1
  458. ansible/plugins/test/success.yml +1 -1
  459. ansible/plugins/test/successful.yml +1 -1
  460. ansible/plugins/test/superset.yml +1 -1
  461. ansible/plugins/test/unreachable.yml +1 -1
  462. ansible/plugins/test/uri.py +1 -3
  463. ansible/plugins/vars/__init__.py +1 -2
  464. ansible/plugins/vars/host_group_vars.py +2 -3
  465. ansible/release.py +3 -5
  466. ansible/template/__init__.py +14 -27
  467. ansible/template/native_helpers.py +1 -3
  468. ansible/template/template.py +1 -3
  469. ansible/template/vars.py +1 -0
  470. ansible/utils/__init__.py +1 -3
  471. ansible/utils/cmd_functions.py +1 -2
  472. ansible/utils/collection_loader/__init__.py +1 -2
  473. ansible/utils/collection_loader/_collection_config.py +1 -2
  474. ansible/utils/collection_loader/_collection_finder.py +1 -2
  475. ansible/utils/collection_loader/_collection_meta.py +1 -2
  476. ansible/utils/color.py +1 -2
  477. ansible/utils/context_objects.py +1 -4
  478. ansible/utils/display.py +89 -30
  479. ansible/utils/encrypt.py +4 -105
  480. ansible/utils/fqcn.py +1 -2
  481. ansible/utils/galaxy.py +1 -3
  482. ansible/utils/hashing.py +1 -3
  483. ansible/utils/helpers.py +1 -3
  484. ansible/utils/jsonrpc.py +1 -2
  485. ansible/utils/listify.py +1 -3
  486. ansible/utils/lock.py +1 -3
  487. ansible/utils/multiprocessing.py +1 -3
  488. ansible/utils/native_jinja.py +1 -3
  489. ansible/utils/path.py +1 -2
  490. ansible/utils/plugin_docs.py +7 -6
  491. ansible/utils/py3compat.py +17 -55
  492. ansible/utils/sentinel.py +1 -3
  493. ansible/utils/shlex.py +1 -3
  494. ansible/utils/singleton.py +1 -3
  495. ansible/utils/ssh_functions.py +1 -3
  496. ansible/utils/unicode.py +1 -3
  497. ansible/utils/unsafe_proxy.py +1 -2
  498. ansible/utils/vars.py +6 -8
  499. ansible/utils/version.py +1 -3
  500. ansible/vars/clean.py +1 -3
  501. ansible/vars/fact_cache.py +1 -2
  502. ansible/vars/hostvars.py +3 -7
  503. ansible/vars/manager.py +24 -6
  504. ansible/vars/reserved.py +1 -3
  505. {ansible_core-2.16.7.data → ansible_core-2.17.0.data}/scripts/ansible-test +1 -2
  506. {ansible_core-2.16.7.dist-info → ansible_core-2.17.0.dist-info}/METADATA +3 -3
  507. ansible_core-2.17.0.dist-info/RECORD +987 -0
  508. ansible_test/__init__.py +1 -2
  509. ansible_test/_data/completion/docker.txt +7 -9
  510. ansible_test/_data/completion/remote.txt +6 -7
  511. ansible_test/_data/requirements/ansible-test.txt +0 -2
  512. ansible_test/_data/requirements/constraints.txt +1 -6
  513. ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
  514. ansible_test/_data/requirements/sanity.changelog.txt +3 -3
  515. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  516. ansible_test/_data/requirements/sanity.mypy.txt +12 -12
  517. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  518. ansible_test/_data/requirements/sanity.pylint.txt +7 -7
  519. ansible_test/_data/requirements/sanity.runtime-metadata.txt +1 -1
  520. ansible_test/_data/requirements/sanity.validate-modules.txt +3 -3
  521. ansible_test/_data/requirements/sanity.yamllint.txt +2 -2
  522. ansible_test/_internal/bootstrap.py +2 -2
  523. ansible_test/_internal/classification/__init__.py +0 -5
  524. ansible_test/_internal/commands/integration/cloud/cs.py +1 -1
  525. ansible_test/_internal/commands/integration/cloud/nios.py +1 -1
  526. ansible_test/_internal/commands/sanity/__init__.py +1 -27
  527. ansible_test/_internal/commands/sanity/import.py +0 -18
  528. ansible_test/_internal/commands/sanity/mypy.py +7 -10
  529. ansible_test/_internal/commands/units/__init__.py +1 -1
  530. ansible_test/_internal/config.py +0 -1
  531. ansible_test/_internal/content_config.py +0 -5
  532. ansible_test/_internal/coverage_util.py +0 -1
  533. ansible_test/_internal/docker_util.py +1 -1
  534. ansible_test/_internal/host_profiles.py +5 -4
  535. ansible_test/_internal/pypi_proxy.py +1 -8
  536. ansible_test/_internal/python_requirements.py +1 -119
  537. ansible_test/_internal/ssh.py +1 -0
  538. ansible_test/_internal/util.py +1 -1
  539. ansible_test/_internal/util_common.py +1 -1
  540. ansible_test/_internal/venv.py +10 -108
  541. ansible_test/_util/__init__.py +1 -2
  542. ansible_test/_util/controller/sanity/mypy/ansible-core.ini +0 -6
  543. ansible_test/_util/controller/sanity/mypy/modules.ini +0 -6
  544. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +0 -1
  545. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +0 -1
  546. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +0 -1
  547. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +0 -1
  548. ansible_test/_util/controller/sanity/pylint/config/default.cfg +0 -1
  549. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -2
  550. ansible_test/_util/controller/sanity/shellcheck/exclude.txt +0 -1
  551. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -59
  552. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +0 -7
  553. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +10 -2
  554. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -1
  555. ansible_test/_util/controller/sanity/yamllint/yamllinter.py +16 -4
  556. ansible_test/_util/target/__init__.py +1 -2
  557. ansible_test/_util/target/cli/ansible_test_cli_stub.py +1 -2
  558. ansible_test/_util/target/common/constants.py +1 -4
  559. ansible_test/_util/target/injector/python.py +4 -19
  560. ansible_test/_util/target/pytest/plugins/ansible_forked.py +2 -9
  561. ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +1 -5
  562. ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -2
  563. ansible_test/_util/target/sanity/compile/compile.py +3 -12
  564. ansible_test/_util/target/sanity/import/importer.py +1 -12
  565. ansible_test/_util/target/setup/bootstrap.sh +49 -105
  566. ansible_test/_util/target/setup/probe_cgroups.py +1 -2
  567. ansible_test/_util/target/setup/quiet_pip.py +1 -16
  568. ansible_test/_util/target/setup/requirements.py +9 -2
  569. ansible_test/_util/target/tools/virtualenvcheck.py +1 -2
  570. ansible_test/_util/target/tools/yamlcheck.py +1 -2
  571. ansible/module_utils/common/_json_compat.py +0 -16
  572. ansible/module_utils/compat/_selectors2.py +0 -655
  573. ansible/modules/yum.py +0 -1821
  574. ansible/plugins/action/yum.py +0 -111
  575. ansible_core-2.16.7.dist-info/RECORD +0 -1009
  576. ansible_test/_util/controller/sanity/code-smell/future-import-boilerplate.json +0 -7
  577. ansible_test/_util/controller/sanity/code-smell/future-import-boilerplate.py +0 -46
  578. ansible_test/_util/controller/sanity/code-smell/metaclass-boilerplate.json +0 -7
  579. ansible_test/_util/controller/sanity/code-smell/metaclass-boilerplate.py +0 -44
  580. ansible_test/_util/controller/sanity/code-smell/no-basestring.json +0 -7
  581. ansible_test/_util/controller/sanity/code-smell/no-basestring.py +0 -21
  582. ansible_test/_util/controller/sanity/code-smell/no-dict-iteritems.json +0 -7
  583. ansible_test/_util/controller/sanity/code-smell/no-dict-iteritems.py +0 -21
  584. ansible_test/_util/controller/sanity/code-smell/no-dict-iterkeys.json +0 -7
  585. ansible_test/_util/controller/sanity/code-smell/no-dict-iterkeys.py +0 -21
  586. ansible_test/_util/controller/sanity/code-smell/no-dict-itervalues.json +0 -7
  587. ansible_test/_util/controller/sanity/code-smell/no-dict-itervalues.py +0 -21
  588. ansible_test/_util/controller/sanity/code-smell/no-main-display.json +0 -10
  589. ansible_test/_util/controller/sanity/code-smell/no-main-display.py +0 -21
  590. ansible_test/_util/controller/sanity/code-smell/no-unicode-literals.json +0 -7
  591. ansible_test/_util/controller/sanity/code-smell/no-unicode-literals.py +0 -21
  592. ansible_test/_util/controller/tools/sslcheck.py +0 -22
  593. ansible_test/_util/target/common/__init__.py +0 -2
  594. {ansible_core-2.16.7.dist-info → ansible_core-2.17.0.dist-info}/COPYING +0 -0
  595. {ansible_core-2.16.7.dist-info → ansible_core-2.17.0.dist-info}/WHEEL +0 -0
  596. {ansible_core-2.16.7.dist-info → ansible_core-2.17.0.dist-info}/entry_points.txt +0 -0
  597. {ansible_core-2.16.7.dist-info → ansible_core-2.17.0.dist-info}/top_level.txt +0 -0
ansible/cli/doc.py CHANGED
@@ -4,12 +4,12 @@
4
4
  # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
5
  # PYTHON_ARGCOMPLETE_OK
6
6
 
7
- from __future__ import (absolute_import, division, print_function)
8
- __metaclass__ = type
7
+ from __future__ import annotations
9
8
 
10
9
  # ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
11
10
  from ansible.cli import CLI
12
11
 
12
+ import importlib
13
13
  import pkgutil
14
14
  import os
15
15
  import os.path
@@ -30,7 +30,6 @@ from ansible.module_utils.common.text.converters import to_native, to_text
30
30
  from ansible.module_utils.common.collections import is_sequence
31
31
  from ansible.module_utils.common.json import json_dump
32
32
  from ansible.module_utils.common.yaml import yaml_dump
33
- from ansible.module_utils.compat import importlib
34
33
  from ansible.module_utils.six import string_types
35
34
  from ansible.parsing.plugin_docs import read_docstub
36
35
  from ansible.parsing.utils.yaml import from_yaml
@@ -39,6 +38,7 @@ from ansible.plugins.list import list_plugins
39
38
  from ansible.plugins.loader import action_loader, fragment_loader
40
39
  from ansible.utils.collection_loader import AnsibleCollectionConfig, AnsibleCollectionRef
41
40
  from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path
41
+ from ansible.utils.color import stringc
42
42
  from ansible.utils.display import Display
43
43
  from ansible.utils.plugin_docs import get_plugin_docs, get_docstring, get_versioned_doclink
44
44
 
@@ -46,14 +46,33 @@ display = Display()
46
46
 
47
47
 
48
48
  TARGET_OPTIONS = C.DOCUMENTABLE_PLUGINS + ('role', 'keyword',)
49
- PB_OBJECTS = ['Play', 'Role', 'Block', 'Task']
49
+ PB_OBJECTS = ['Play', 'Role', 'Block', 'Task', 'Handler']
50
50
  PB_LOADED = {}
51
51
  SNIPPETS = ['inventory', 'lookup', 'module']
52
52
 
53
-
54
- def add_collection_plugins(plugin_list, plugin_type, coll_filter=None):
55
- display.deprecated("add_collection_plugins method, use ansible.plugins.list functions instead.", version='2.17')
56
- plugin_list.update(list_plugins(plugin_type, coll_filter))
53
+ # harcoded from ascii values
54
+ STYLE = {
55
+ 'BLINK': '\033[5m',
56
+ 'BOLD': '\033[1m',
57
+ 'HIDE': '\033[8m',
58
+ # 'NORMAL': '\x01b[0m', # newer?
59
+ 'NORMAL': '\033[0m',
60
+ 'RESET': "\033[0;0m",
61
+ # 'REVERSE':"\033[;7m", # newer?
62
+ 'REVERSE': "\033[7m",
63
+ 'UNDERLINE': '\033[4m',
64
+ }
65
+
66
+ # previously existing string identifiers
67
+ NOCOLOR = {
68
+ 'BOLD': r'*%s*',
69
+ 'UNDERLINE': r'`%s`',
70
+ 'MODULE': r'[%s]',
71
+ 'PLUGIN': r'[%s]',
72
+ }
73
+
74
+ # TODO: make configurable
75
+ ref_style = {'MODULE': 'yellow', 'REF': 'magenta', 'LINK': 'cyan', 'DEP': 'magenta', 'CONSTANT': 'dark gray', 'PLUGIN': 'yellow'}
57
76
 
58
77
 
59
78
  def jdump(text):
@@ -72,37 +91,27 @@ class RoleMixin(object):
72
91
 
73
92
  # Potential locations of the role arg spec file in the meta subdir, with main.yml
74
93
  # having the lowest priority.
75
- ROLE_ARGSPEC_FILES = ['argument_specs' + e for e in C.YAML_FILENAME_EXTENSIONS] + ["main" + e for e in C.YAML_FILENAME_EXTENSIONS]
94
+ ROLE_METADATA_FILES = ["main" + e for e in C.YAML_FILENAME_EXTENSIONS]
95
+ ROLE_ARGSPEC_FILES = ['argument_specs' + e for e in C.YAML_FILENAME_EXTENSIONS] + ROLE_METADATA_FILES
76
96
 
77
- def _load_argspec(self, role_name, collection_path=None, role_path=None):
78
- """Load the role argument spec data from the source file.
97
+ def _load_role_data(self, root, files, role_name, collection):
98
+ """ Load and process the YAML for the first found of a set of role files
79
99
 
100
+ :param str root: The root path to get the files from
101
+ :param str files: List of candidate file names in order of precedence
80
102
  :param str role_name: The name of the role for which we want the argspec data.
81
- :param str collection_path: Path to the collection containing the role. This
82
- will be None for standard roles.
83
- :param str role_path: Path to the standard role. This will be None for
84
- collection roles.
85
-
86
- We support two files containing the role arg spec data: either meta/main.yml
87
- or meta/argument_spec.yml. The argument_spec.yml file will take precedence
88
- over the meta/main.yml file, if it exists. Data is NOT combined between the
89
- two files.
103
+ :param str collection: collection name or None in case of stand alone roles
90
104
 
91
- :returns: A dict of all data underneath the ``argument_specs`` top-level YAML
92
- key in the argspec data file. Empty dict is returned if there is no data.
105
+ :returns: A dict that contains the data requested, empty if no data found
93
106
  """
94
107
 
95
- if collection_path:
96
- meta_path = os.path.join(collection_path, 'roles', role_name, 'meta')
97
- elif role_path:
98
- meta_path = os.path.join(role_path, 'meta')
108
+ if collection:
109
+ meta_path = os.path.join(root, 'roles', role_name, 'meta')
99
110
  else:
100
- raise AnsibleError("A path is required to load argument specs for role '%s'" % role_name)
101
-
102
- path = None
111
+ meta_path = os.path.join(root, 'meta')
103
112
 
104
113
  # Check all potential spec files
105
- for specfile in self.ROLE_ARGSPEC_FILES:
114
+ for specfile in files:
106
115
  full_path = os.path.join(meta_path, specfile)
107
116
  if os.path.exists(full_path):
108
117
  path = full_path
@@ -116,9 +125,50 @@ class RoleMixin(object):
116
125
  data = from_yaml(f.read(), file_name=path)
117
126
  if data is None:
118
127
  data = {}
119
- return data.get('argument_specs', {})
120
128
  except (IOError, OSError) as e:
121
- raise AnsibleParserError("An error occurred while trying to read the file '%s': %s" % (path, to_native(e)), orig_exc=e)
129
+ raise AnsibleParserError("Could not read the role '%s' (at %s)" % (role_name, path), orig_exc=e)
130
+
131
+ return data
132
+
133
+ def _load_metadata(self, role_name, role_path, collection):
134
+ """Load the roles metadata from the source file.
135
+
136
+ :param str role_name: The name of the role for which we want the argspec data.
137
+ :param str role_path: Path to the role/collection root.
138
+ :param str collection: collection name or None in case of stand alone roles
139
+
140
+ :returns: A dict of all role meta data, except ``argument_specs`` or an empty dict
141
+ """
142
+
143
+ data = self._load_role_data(role_path, self.ROLE_METADATA_FILES, role_name, collection)
144
+ del data['argument_specs']
145
+
146
+ return data
147
+
148
+ def _load_argspec(self, role_name, role_path, collection):
149
+ """Load the role argument spec data from the source file.
150
+
151
+ :param str role_name: The name of the role for which we want the argspec data.
152
+ :param str role_path: Path to the role/collection root.
153
+ :param str collection: collection name or None in case of stand alone roles
154
+
155
+ We support two files containing the role arg spec data: either meta/main.yml
156
+ or meta/argument_spec.yml. The argument_spec.yml file will take precedence
157
+ over the meta/main.yml file, if it exists. Data is NOT combined between the
158
+ two files.
159
+
160
+ :returns: A dict of all data underneath the ``argument_specs`` top-level YAML
161
+ key in the argspec data file. Empty dict is returned if there is no data.
162
+ """
163
+
164
+ try:
165
+ data = self._load_role_data(role_path, self.ROLE_ARGSPEC_FILES, role_name, collection)
166
+ data = data.get('argument_specs', {})
167
+
168
+ except Exception as e:
169
+ # we keep error info, but let caller deal with it
170
+ data = {'error': 'Failed to process role (%s): %s' % (role_name, to_native(e)), 'exception': e}
171
+ return data
122
172
 
123
173
  def _find_all_normal_roles(self, role_paths, name_filters=None):
124
174
  """Find all non-collection roles that have an argument spec file.
@@ -147,10 +197,13 @@ class RoleMixin(object):
147
197
  full_path = os.path.join(role_path, 'meta', specfile)
148
198
  if os.path.exists(full_path):
149
199
  if name_filters is None or entry in name_filters:
200
+ # select first-found role
150
201
  if entry not in found_names:
151
- found.add((entry, role_path))
152
- found_names.add(entry)
153
- # select first-found
202
+ found_names.add(entry)
203
+ # None here stands for 'colleciton', which stand alone roles dont have
204
+ # makes downstream code simpler by having same structure as collection roles
205
+ found.add((entry, None, role_path))
206
+ # only read first existing spec
154
207
  break
155
208
  return found
156
209
 
@@ -196,7 +249,7 @@ class RoleMixin(object):
196
249
  break
197
250
  return found
198
251
 
199
- def _build_summary(self, role, collection, argspec):
252
+ def _build_summary(self, role, collection, meta, argspec):
200
253
  """Build a summary dict for a role.
201
254
 
202
255
  Returns a simplified role arg spec containing only the role entry points and their
@@ -204,17 +257,24 @@ class RoleMixin(object):
204
257
 
205
258
  :param role: The simple role name.
206
259
  :param collection: The collection containing the role (None or empty string if N/A).
260
+ :param meta: dictionary with galaxy information (None or empty string if N/A).
207
261
  :param argspec: The complete role argspec data dict.
208
262
 
209
263
  :returns: A tuple with the FQCN role name and a summary dict.
210
264
  """
265
+
266
+ if meta and meta.get('galaxy_info'):
267
+ summary = meta['galaxy_info']
268
+ else:
269
+ summary = {'description': 'UNDOCUMENTED'}
270
+ summary['entry_points'] = {}
271
+
211
272
  if collection:
212
273
  fqcn = '.'.join([collection, role])
274
+ summary['collection'] = collection
213
275
  else:
214
276
  fqcn = role
215
- summary = {}
216
- summary['collection'] = collection
217
- summary['entry_points'] = {}
277
+
218
278
  for ep in argspec.keys():
219
279
  entry_spec = argspec[ep] or {}
220
280
  summary['entry_points'][ep] = entry_spec.get('short_description', '')
@@ -228,15 +288,18 @@ class RoleMixin(object):
228
288
  doc = {}
229
289
  doc['path'] = path
230
290
  doc['collection'] = collection
231
- doc['entry_points'] = {}
232
- for ep in argspec.keys():
233
- if entry_point is None or ep == entry_point:
234
- entry_spec = argspec[ep] or {}
235
- doc['entry_points'][ep] = entry_spec
291
+ if 'error' in argspec:
292
+ doc.update(argspec)
293
+ else:
294
+ doc['entry_points'] = {}
295
+ for ep in argspec.keys():
296
+ if entry_point is None or ep == entry_point:
297
+ entry_spec = argspec[ep] or {}
298
+ doc['entry_points'][ep] = entry_spec
236
299
 
237
- # If we didn't add any entry points (b/c of filtering), ignore this entry.
238
- if len(doc['entry_points'].keys()) == 0:
239
- doc = None
300
+ # If we didn't add any entry points (b/c of filtering), ignore this entry.
301
+ if len(doc['entry_points'].keys()) == 0:
302
+ doc = None
240
303
 
241
304
  return (fqcn, doc)
242
305
 
@@ -275,34 +338,29 @@ class RoleMixin(object):
275
338
  if not collection_filter:
276
339
  roles = self._find_all_normal_roles(roles_path)
277
340
  else:
278
- roles = []
341
+ roles = set()
279
342
  collroles = self._find_all_collection_roles(collection_filter=collection_filter)
280
343
 
281
344
  result = {}
282
345
 
283
- for role, role_path in roles:
284
- try:
285
- argspec = self._load_argspec(role, role_path=role_path)
286
- fqcn, summary = self._build_summary(role, '', argspec)
287
- result[fqcn] = summary
288
- except Exception as e:
289
- if fail_on_errors:
290
- raise
291
- result[role] = {
292
- 'error': 'Error while loading role argument spec: %s' % to_native(e),
293
- }
346
+ for role, collection, role_path in (roles | collroles):
294
347
 
295
- for role, collection, collection_path in collroles:
296
348
  try:
297
- argspec = self._load_argspec(role, collection_path=collection_path)
298
- fqcn, summary = self._build_summary(role, collection, argspec)
299
- result[fqcn] = summary
349
+ meta = self._load_metadata(role, role_path, collection)
300
350
  except Exception as e:
351
+ display.vvv('No metadata for role (%s) due to: %s' % (role, to_native(e)), True)
352
+ meta = {}
353
+
354
+ argspec = self._load_argspec(role, role_path, collection)
355
+ if 'error' in argspec:
301
356
  if fail_on_errors:
302
- raise
303
- result['%s.%s' % (collection, role)] = {
304
- 'error': 'Error while loading role argument spec: %s' % to_native(e),
305
- }
357
+ raise argspec['exception']
358
+ else:
359
+ display.warning('Skipping role (%s) due to: %s' % (role, argspec['error']), True)
360
+ continue
361
+
362
+ fqcn, summary = self._build_summary(role, collection, meta, argspec)
363
+ result[fqcn] = summary
306
364
 
307
365
  return result
308
366
 
@@ -321,31 +379,47 @@ class RoleMixin(object):
321
379
 
322
380
  result = {}
323
381
 
324
- for role, role_path in roles:
325
- try:
326
- argspec = self._load_argspec(role, role_path=role_path)
327
- fqcn, doc = self._build_doc(role, role_path, '', argspec, entry_point)
328
- if doc:
329
- result[fqcn] = doc
330
- except Exception as e: # pylint:disable=broad-except
331
- result[role] = {
332
- 'error': 'Error while processing role: %s' % to_native(e),
333
- }
334
-
335
- for role, collection, collection_path in collroles:
336
- try:
337
- argspec = self._load_argspec(role, collection_path=collection_path)
338
- fqcn, doc = self._build_doc(role, collection_path, collection, argspec, entry_point)
339
- if doc:
340
- result[fqcn] = doc
341
- except Exception as e: # pylint:disable=broad-except
342
- result['%s.%s' % (collection, role)] = {
343
- 'error': 'Error while processing role: %s' % to_native(e),
344
- }
382
+ for role, collection, role_path in (roles | collroles):
383
+ argspec = self._load_argspec(role, role_path, collection)
384
+ fqcn, doc = self._build_doc(role, role_path, collection, argspec, entry_point)
385
+ if doc:
386
+ result[fqcn] = doc
345
387
 
346
388
  return result
347
389
 
348
390
 
391
+ def _doclink(url):
392
+ # assume that if it is relative, it is for docsite, ignore rest
393
+ if not url.startswith(("http", "..")):
394
+ url = get_versioned_doclink(url)
395
+ return url
396
+
397
+
398
+ def _format(string, *args):
399
+
400
+ ''' add ascii formatting or delimiters '''
401
+
402
+ for style in args:
403
+
404
+ if style not in ref_style and style.upper() not in STYLE and style not in C.COLOR_CODES:
405
+ raise KeyError("Invalid format value supplied: %s" % style)
406
+
407
+ if C.ANSIBLE_NOCOLOR:
408
+ # ignore most styles, but some already had 'identifier strings'
409
+ if style in NOCOLOR:
410
+ string = NOCOLOR[style] % string
411
+ elif style in C.COLOR_CODES:
412
+ string = stringc(string, style)
413
+ elif style in ref_style:
414
+ # assumes refs are also always colors
415
+ string = stringc(string, ref_style[style])
416
+ else:
417
+ # start specific style and 'end' with normal
418
+ string = '%s%s%s' % (STYLE[style.upper()], string, STYLE['NORMAL'])
419
+
420
+ return string
421
+
422
+
349
423
  class DocCLI(CLI, RoleMixin):
350
424
  ''' displays information on modules installed in Ansible libraries.
351
425
  It displays a terse listing of plugins and their short descriptions,
@@ -355,7 +429,8 @@ class DocCLI(CLI, RoleMixin):
355
429
  name = 'ansible-doc'
356
430
 
357
431
  # default ignore list for detailed views
358
- IGNORE = ('module', 'docuri', 'version_added', 'version_added_collection', 'short_description', 'now_date', 'plainexamples', 'returndocs', 'collection')
432
+ IGNORE = ('module', 'docuri', 'version_added', 'version_added_collection', 'short_description',
433
+ 'now_date', 'plainexamples', 'returndocs', 'collection', 'plugin_name')
359
434
 
360
435
  # Warning: If you add more elements here, you also need to add it to the docsite build (in the
361
436
  # ansible-community/antsibull repo)
@@ -424,23 +499,20 @@ class DocCLI(CLI, RoleMixin):
424
499
  return f"`{text}' (of {plugin})"
425
500
  return f"`{text}'"
426
501
 
427
- @classmethod
428
- def find_plugins(cls, path, internal, plugin_type, coll_filter=None):
429
- display.deprecated("find_plugins method as it is incomplete/incorrect. use ansible.plugins.list functions instead.", version='2.17')
430
- return list_plugins(plugin_type, coll_filter, [path]).keys()
431
-
432
502
  @classmethod
433
503
  def tty_ify(cls, text):
434
504
 
435
505
  # general formatting
436
- t = cls._ITALIC.sub(r"`\1'", text) # I(word) => `word'
437
- t = cls._BOLD.sub(r"*\1*", t) # B(word) => *word*
438
- t = cls._MODULE.sub("[" + r"\1" + "]", t) # M(word) => [word]
506
+ t = cls._ITALIC.sub(_format(r"\1", 'UNDERLINE'), text) # no ascii code for this
507
+ t = cls._BOLD.sub(_format(r"\1", 'BOLD'), t)
508
+ t = cls._MODULE.sub(_format(r"\1", 'MODULE'), t) # M(word) => [word]
439
509
  t = cls._URL.sub(r"\1", t) # U(word) => word
440
510
  t = cls._LINK.sub(r"\1 <\2>", t) # L(word, url) => word <url>
441
- t = cls._PLUGIN.sub("[" + r"\1" + "]", t) # P(word#type) => [word]
442
- t = cls._REF.sub(r"\1", t) # R(word, sphinx-ref) => word
443
- t = cls._CONST.sub(r"`\1'", t) # C(word) => `word'
511
+
512
+ t = cls._PLUGIN.sub(_format("[" + r"\1" + "]", 'PLUGIN'), t) # P(word#type) => [word]
513
+
514
+ t = cls._REF.sub(_format(r"\1", 'REF'), t) # R(word, sphinx-ref) => word
515
+ t = cls._CONST.sub(_format(r"`\1'", 'CONSTANT'), t)
444
516
  t = cls._SEM_OPTION_NAME.sub(cls._tty_ify_sem_complex, t) # O(expr)
445
517
  t = cls._SEM_OPTION_VALUE.sub(cls._tty_ify_sem_simle, t) # V(expr)
446
518
  t = cls._SEM_ENV_VARIABLE.sub(cls._tty_ify_sem_simle, t) # E(expr)
@@ -449,10 +521,16 @@ class DocCLI(CLI, RoleMixin):
449
521
 
450
522
  # remove rst
451
523
  t = cls._RST_SEEALSO.sub(r"See also:", t) # seealso to See also:
452
- t = cls._RST_NOTE.sub(r"Note:", t) # .. note:: to note:
524
+ t = cls._RST_NOTE.sub(_format(r"Note:", 'bold'), t) # .. note:: to note:
453
525
  t = cls._RST_ROLES.sub(r"`", t) # remove :ref: and other tags, keep tilde to match ending one
454
526
  t = cls._RST_DIRECTIVES.sub(r"", t) # remove .. stuff:: in general
455
527
 
528
+ # handle docsite refs
529
+ # U(word) => word
530
+ t = re.sub(cls._URL, lambda m: _format(r"%s" % _doclink(m.group(1)), 'LINK'), t)
531
+ # L(word, url) => word <url>
532
+ t = re.sub(cls._LINK, lambda m: r"%s <%s>" % (m.group(1), _format(_doclink(m.group(2)), 'LINK')), t)
533
+
456
534
  return t
457
535
 
458
536
  def init_parser(self):
@@ -485,8 +563,9 @@ class DocCLI(CLI, RoleMixin):
485
563
  action=opt_help.PrependListAction,
486
564
  help='The path to the directory containing your roles.')
487
565
 
488
- # modifiers
566
+ # exclusive modifiers
489
567
  exclusive = self.parser.add_mutually_exclusive_group()
568
+
490
569
  # TODO: warn if not used with -t roles
491
570
  exclusive.add_argument("-e", "--entry-point", dest="entry_point",
492
571
  help="Select the entry point for role(s).")
@@ -503,6 +582,7 @@ class DocCLI(CLI, RoleMixin):
503
582
  exclusive.add_argument("--metadata-dump", action="store_true", default=False, dest='dump',
504
583
  help='**For internal use only** Dump json metadata for all entries, ignores other options.')
505
584
 
585
+ # generic again
506
586
  self.parser.add_argument("--no-fail-on-errors", action="store_true", default=False, dest='no_fail_on_errors',
507
587
  help='**For internal use only** Only used for --metadata-dump. '
508
588
  'Do not fail on errors. Report the error message in the JSON instead.')
@@ -567,7 +647,7 @@ class DocCLI(CLI, RoleMixin):
567
647
  Output is: fqcn role name, entry point, short description
568
648
  """
569
649
  roles = list(list_json.keys())
570
- entry_point_names = set()
650
+ entry_point_names = set() # to find max len
571
651
  for role in roles:
572
652
  for entry_point in list_json[role]['entry_points'].keys():
573
653
  entry_point_names.add(entry_point)
@@ -575,8 +655,6 @@ class DocCLI(CLI, RoleMixin):
575
655
  max_role_len = 0
576
656
  max_ep_len = 0
577
657
 
578
- if roles:
579
- max_role_len = max(len(x) for x in roles)
580
658
  if entry_point_names:
581
659
  max_ep_len = max(len(x) for x in entry_point_names)
582
660
 
@@ -584,12 +662,15 @@ class DocCLI(CLI, RoleMixin):
584
662
  text = []
585
663
 
586
664
  for role in sorted(roles):
587
- for entry_point, desc in list_json[role]['entry_points'].items():
588
- if len(desc) > linelimit:
589
- desc = desc[:linelimit] + '...'
590
- text.append("%-*s %-*s %s" % (max_role_len, role,
591
- max_ep_len, entry_point,
592
- desc))
665
+ if list_json[role]['entry_points']:
666
+ text.append('%s:' % role)
667
+ text.append(' specs:')
668
+ for entry_point, desc in list_json[role]['entry_points'].items():
669
+ if len(desc) > linelimit:
670
+ desc = desc[:linelimit] + '...'
671
+ text.append(" %-*s: %s" % (max_ep_len, entry_point, desc))
672
+ else:
673
+ text.append('%s' % role)
593
674
 
594
675
  # display results
595
676
  DocCLI.pager("\n".join(text))
@@ -598,7 +679,14 @@ class DocCLI(CLI, RoleMixin):
598
679
  roles = list(role_json.keys())
599
680
  text = []
600
681
  for role in roles:
601
- text += self.get_role_man_text(role, role_json[role])
682
+ try:
683
+ if 'error' in role_json[role]:
684
+ display.warning("Skipping role '%s' due to: %s" % (role, role_json[role]['error']), True)
685
+ continue
686
+ text += self.get_role_man_text(role, role_json[role])
687
+ except AnsibleParserError as e:
688
+ # TODO: warn and skip role?
689
+ raise AnsibleParserError("Role '%s" % (role), orig_exc=e)
602
690
 
603
691
  # display results
604
692
  DocCLI.pager("\n".join(text))
@@ -825,12 +913,12 @@ class DocCLI(CLI, RoleMixin):
825
913
  else:
826
914
  plugin_names = self._list_plugins(ptype, None)
827
915
  docs['all'][ptype] = self._get_plugins_docs(ptype, plugin_names, fail_ok=(ptype in ('test', 'filter')), fail_on_errors=no_fail)
828
- # reset list after each type to avoid polution
916
+ # reset list after each type to avoid pollution
829
917
  elif listing:
830
918
  if plugin_type == 'keyword':
831
919
  docs = DocCLI._list_keywords()
832
920
  elif plugin_type == 'role':
833
- docs = self._create_role_list()
921
+ docs = self._create_role_list(fail_on_errors=False)
834
922
  else:
835
923
  docs = self._list_plugins(plugin_type, content)
836
924
  else:
@@ -1070,7 +1158,16 @@ class DocCLI(CLI, RoleMixin):
1070
1158
  return 'version %s' % (version_added, )
1071
1159
 
1072
1160
  @staticmethod
1073
- def add_fields(text, fields, limit, opt_indent, return_values=False, base_indent=''):
1161
+ def warp_fill(text, limit, initial_indent='', subsequent_indent='', **kwargs):
1162
+ result = []
1163
+ for paragraph in text.split('\n\n'):
1164
+ result.append(textwrap.fill(paragraph, limit, initial_indent=initial_indent, subsequent_indent=subsequent_indent,
1165
+ break_on_hyphens=False, break_long_words=False, drop_whitespace=True, **kwargs))
1166
+ initial_indent = subsequent_indent
1167
+ return '\n'.join(result)
1168
+
1169
+ @staticmethod
1170
+ def add_fields(text, fields, limit, opt_indent, return_values=False, base_indent='', man=False):
1074
1171
 
1075
1172
  for o in sorted(fields):
1076
1173
  # Create a copy so we don't modify the original (in case YAML anchors have been used)
@@ -1080,25 +1177,38 @@ class DocCLI(CLI, RoleMixin):
1080
1177
  required = opt.pop('required', False)
1081
1178
  if not isinstance(required, bool):
1082
1179
  raise AnsibleError("Incorrect value for 'Required', a boolean is needed.: %s" % required)
1180
+
1181
+ opt_leadin = ' '
1182
+ key = ''
1083
1183
  if required:
1084
- opt_leadin = "="
1184
+ if C.ANSIBLE_NOCOLOR:
1185
+ opt_leadin = "="
1186
+ key = "%s%s %s" % (base_indent, opt_leadin, _format(o, 'bold', 'red'))
1085
1187
  else:
1086
- opt_leadin = "-"
1087
-
1088
- text.append("%s%s %s" % (base_indent, opt_leadin, o))
1188
+ if C.ANSIBLE_NOCOLOR:
1189
+ opt_leadin = "-"
1190
+ key = "%s%s %s" % (base_indent, opt_leadin, _format(o, 'yellow'))
1089
1191
 
1090
1192
  # description is specifically formated and can either be string or list of strings
1091
1193
  if 'description' not in opt:
1092
1194
  raise AnsibleError("All (sub-)options and return values must have a 'description' field")
1195
+ text.append('')
1196
+
1197
+ # TODO: push this to top of for and sort by size, create indent on largest key?
1198
+ inline_indent = base_indent + ' ' * max((len(opt_indent) - len(o)) - len(base_indent), 2)
1199
+ sub_indent = inline_indent + ' ' * (len(o) + 3)
1093
1200
  if is_sequence(opt['description']):
1094
1201
  for entry_idx, entry in enumerate(opt['description'], 1):
1095
1202
  if not isinstance(entry, string_types):
1096
1203
  raise AnsibleError("Expected string in description of %s at index %s, got %s" % (o, entry_idx, type(entry)))
1097
- text.append(textwrap.fill(DocCLI.tty_ify(entry), limit, initial_indent=opt_indent, subsequent_indent=opt_indent))
1204
+ if entry_idx == 1:
1205
+ text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(entry), limit, initial_indent=inline_indent, subsequent_indent=sub_indent))
1206
+ else:
1207
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(entry), limit, initial_indent=sub_indent, subsequent_indent=sub_indent))
1098
1208
  else:
1099
1209
  if not isinstance(opt['description'], string_types):
1100
1210
  raise AnsibleError("Expected string in description of %s, got %s" % (o, type(opt['description'])))
1101
- text.append(textwrap.fill(DocCLI.tty_ify(opt['description']), limit, initial_indent=opt_indent, subsequent_indent=opt_indent))
1211
+ text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(opt['description']), limit, initial_indent=inline_indent, subsequent_indent=sub_indent))
1102
1212
  del opt['description']
1103
1213
 
1104
1214
  suboptions = []
@@ -1117,6 +1227,8 @@ class DocCLI(CLI, RoleMixin):
1117
1227
  conf[config] = [dict(item) for item in opt.pop(config)]
1118
1228
  for ignore in DocCLI.IGNORE:
1119
1229
  for item in conf[config]:
1230
+ if display.verbosity > 0 and 'version_added' in item:
1231
+ item['added_in'] = DocCLI._format_version_added(item['version_added'], item.get('version_added_colleciton', 'ansible-core'))
1120
1232
  if ignore in item:
1121
1233
  del item[ignore]
1122
1234
 
@@ -1148,15 +1260,12 @@ class DocCLI(CLI, RoleMixin):
1148
1260
  else:
1149
1261
  text.append(DocCLI._indent_lines(DocCLI._dump_yaml({k: opt[k]}), opt_indent))
1150
1262
 
1151
- if version_added:
1152
- text.append("%sadded in: %s\n" % (opt_indent, DocCLI._format_version_added(version_added, version_added_collection)))
1263
+ if version_added and not man:
1264
+ text.append("%sadded in: %s" % (opt_indent, DocCLI._format_version_added(version_added, version_added_collection)))
1153
1265
 
1154
1266
  for subkey, subdata in suboptions:
1155
- text.append('')
1156
- text.append("%s%s:\n" % (opt_indent, subkey.upper()))
1157
- DocCLI.add_fields(text, subdata, limit, opt_indent + ' ', return_values, opt_indent)
1158
- if not suboptions:
1159
- text.append('')
1267
+ text.append("%s%s:" % (opt_indent, subkey))
1268
+ DocCLI.add_fields(text, subdata, limit, opt_indent + ' ', return_values, opt_indent)
1160
1269
 
1161
1270
  def get_role_man_text(self, role, role_json):
1162
1271
  '''Generate text for the supplied role suitable for display.
@@ -1170,52 +1279,65 @@ class DocCLI(CLI, RoleMixin):
1170
1279
  :returns: A array of text suitable for displaying to screen.
1171
1280
  '''
1172
1281
  text = []
1173
- opt_indent = " "
1282
+ opt_indent = " "
1174
1283
  pad = display.columns * 0.20
1175
1284
  limit = max(display.columns - int(pad), 70)
1176
1285
 
1177
- text.append("> %s (%s)\n" % (role.upper(), role_json.get('path')))
1286
+ text.append("> ROLE: %s (%s)" % (_format(role, 'BOLD'), role_json.get('path')))
1178
1287
 
1179
1288
  for entry_point in role_json['entry_points']:
1180
1289
  doc = role_json['entry_points'][entry_point]
1181
-
1290
+ desc = ''
1182
1291
  if doc.get('short_description'):
1183
- text.append("ENTRY POINT: %s - %s\n" % (entry_point, doc.get('short_description')))
1184
- else:
1185
- text.append("ENTRY POINT: %s\n" % entry_point)
1292
+ desc = "- %s" % (doc.get('short_description'))
1293
+ text.append('')
1294
+ text.append("ENTRY POINT: %s %s" % (_format(entry_point, "BOLD"), desc))
1295
+ text.append('')
1186
1296
 
1187
1297
  if doc.get('description'):
1188
1298
  if isinstance(doc['description'], list):
1189
- desc = " ".join(doc['description'])
1299
+ descs = doc['description']
1190
1300
  else:
1191
- desc = doc['description']
1301
+ descs = [doc['description']]
1302
+ for desc in descs:
1303
+ text.append("%s" % DocCLI.warp_fill(DocCLI.tty_ify(desc), limit, initial_indent=opt_indent, subsequent_indent=opt_indent))
1304
+ text.append('')
1192
1305
 
1193
- text.append("%s\n" % textwrap.fill(DocCLI.tty_ify(desc),
1194
- limit, initial_indent=opt_indent,
1195
- subsequent_indent=opt_indent))
1196
1306
  if doc.get('options'):
1197
- text.append("OPTIONS (= is mandatory):\n")
1307
+ text.append(_format("Options", 'bold') + " (%s indicates it is required):" % ("=" if C.ANSIBLE_NOCOLOR else 'red'))
1198
1308
  DocCLI.add_fields(text, doc.pop('options'), limit, opt_indent)
1199
- text.append('')
1200
1309
 
1201
- if doc.get('attributes'):
1202
- text.append("ATTRIBUTES:\n")
1203
- text.append(DocCLI._indent_lines(DocCLI._dump_yaml(doc.pop('attributes')), opt_indent))
1204
- text.append('')
1310
+ if doc.get('attributes', False):
1311
+ display.deprecated(
1312
+ f'The role {role}\'s argument spec {entry_point} contains the key "attributes", '
1313
+ 'which will not be displayed by ansible-doc in the future. '
1314
+ 'This was unintentionally allowed when plugin attributes were added, '
1315
+ 'but the feature does not map well to role argument specs.',
1316
+ version='2.20',
1317
+ collection_name='ansible.builtin',
1318
+ )
1319
+ text.append("")
1320
+ text.append(_format("ATTRIBUTES:", 'bold'))
1321
+ for k in doc['attributes'].keys():
1322
+ text.append('')
1323
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(_format('%s:' % k, 'UNDERLINE')), limit - 6, initial_indent=opt_indent,
1324
+ subsequent_indent=opt_indent))
1325
+ text.append(DocCLI._indent_lines(DocCLI._dump_yaml(doc['attributes'][k]), opt_indent))
1326
+ del doc['attributes']
1205
1327
 
1206
1328
  # generic elements we will handle identically
1207
1329
  for k in ('author',):
1208
1330
  if k not in doc:
1209
1331
  continue
1332
+ text.append('')
1210
1333
  if isinstance(doc[k], string_types):
1211
- text.append('%s: %s' % (k.upper(), textwrap.fill(DocCLI.tty_ify(doc[k]),
1334
+ text.append('%s: %s' % (k.upper(), DocCLI.warp_fill(DocCLI.tty_ify(doc[k]),
1212
1335
  limit - (len(k) + 2), subsequent_indent=opt_indent)))
1213
1336
  elif isinstance(doc[k], (list, tuple)):
1214
1337
  text.append('%s: %s' % (k.upper(), ', '.join(doc[k])))
1215
1338
  else:
1216
1339
  # use empty indent since this affects the start of the yaml doc, not it's keys
1217
1340
  text.append(DocCLI._indent_lines(DocCLI._dump_yaml({k.upper(): doc[k]}), ''))
1218
- text.append('')
1219
1341
 
1220
1342
  return text
1221
1343
 
@@ -1226,31 +1348,27 @@ class DocCLI(CLI, RoleMixin):
1226
1348
 
1227
1349
  DocCLI.IGNORE = DocCLI.IGNORE + (context.CLIARGS['type'],)
1228
1350
  opt_indent = " "
1351
+ base_indent = " "
1229
1352
  text = []
1230
1353
  pad = display.columns * 0.20
1231
1354
  limit = max(display.columns - int(pad), 70)
1232
1355
 
1233
- plugin_name = doc.get(context.CLIARGS['type'], doc.get('name')) or doc.get('plugin_type') or plugin_type
1234
- if collection_name:
1235
- plugin_name = '%s.%s' % (collection_name, plugin_name)
1236
-
1237
- text.append("> %s (%s)\n" % (plugin_name.upper(), doc.pop('filename')))
1356
+ text.append("> %s %s (%s)" % (plugin_type.upper(), _format(doc.pop('plugin_name'), 'bold'), doc.pop('filename')))
1238
1357
 
1239
1358
  if isinstance(doc['description'], list):
1240
- desc = " ".join(doc.pop('description'))
1359
+ descs = doc.pop('description')
1241
1360
  else:
1242
- desc = doc.pop('description')
1361
+ descs = [doc.pop('description')]
1243
1362
 
1244
- text.append("%s\n" % textwrap.fill(DocCLI.tty_ify(desc), limit, initial_indent=opt_indent,
1245
- subsequent_indent=opt_indent))
1363
+ text.append('')
1364
+ for desc in descs:
1365
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(desc), limit, initial_indent=base_indent, subsequent_indent=base_indent))
1246
1366
 
1247
- if 'version_added' in doc:
1248
- version_added = doc.pop('version_added')
1249
- version_added_collection = doc.pop('version_added_collection', None)
1250
- text.append("ADDED IN: %s\n" % DocCLI._format_version_added(version_added, version_added_collection))
1367
+ if display.verbosity > 0:
1368
+ doc['added_in'] = DocCLI._format_version_added(doc.pop('version_added', 'historical'), doc.pop('version_added_collection', 'ansible-core'))
1251
1369
 
1252
1370
  if doc.get('deprecated', False):
1253
- text.append("DEPRECATED: \n")
1371
+ text.append(_format("DEPRECATED: ", 'bold', 'DEP'))
1254
1372
  if isinstance(doc['deprecated'], dict):
1255
1373
  if 'removed_at_date' in doc['deprecated']:
1256
1374
  text.append(
@@ -1262,100 +1380,106 @@ class DocCLI(CLI, RoleMixin):
1262
1380
  text.append("\tReason: %(why)s\n\tWill be removed in: Ansible %(removed_in)s\n\tAlternatives: %(alternative)s" % doc.pop('deprecated'))
1263
1381
  else:
1264
1382
  text.append("%s" % doc.pop('deprecated'))
1265
- text.append("\n")
1266
1383
 
1267
1384
  if doc.pop('has_action', False):
1268
- text.append(" * note: %s\n" % "This module has a corresponding action plugin.")
1385
+ text.append("")
1386
+ text.append(_format(" * note:", 'bold') + " This module has a corresponding action plugin.")
1269
1387
 
1270
1388
  if doc.get('options', False):
1271
- text.append("OPTIONS (= is mandatory):\n")
1272
- DocCLI.add_fields(text, doc.pop('options'), limit, opt_indent)
1273
- text.append('')
1389
+ text.append("")
1390
+ text.append(_format("OPTIONS", 'bold') + " (%s indicates it is required):" % ("=" if C.ANSIBLE_NOCOLOR else 'red'))
1391
+ DocCLI.add_fields(text, doc.pop('options'), limit, opt_indent, man=(display.verbosity == 0))
1274
1392
 
1275
1393
  if doc.get('attributes', False):
1276
- text.append("ATTRIBUTES:\n")
1277
- text.append(DocCLI._indent_lines(DocCLI._dump_yaml(doc.pop('attributes')), opt_indent))
1278
- text.append('')
1394
+ text.append("")
1395
+ text.append(_format("ATTRIBUTES:", 'bold'))
1396
+ for k in doc['attributes'].keys():
1397
+ text.append('')
1398
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(_format('%s:' % k, 'UNDERLINE')), limit - 6, initial_indent=opt_indent,
1399
+ subsequent_indent=opt_indent))
1400
+ text.append(DocCLI._indent_lines(DocCLI._dump_yaml(doc['attributes'][k]), opt_indent))
1401
+ del doc['attributes']
1279
1402
 
1280
1403
  if doc.get('notes', False):
1281
- text.append("NOTES:")
1404
+ text.append("")
1405
+ text.append(_format("NOTES:", 'bold'))
1282
1406
  for note in doc['notes']:
1283
- text.append(textwrap.fill(DocCLI.tty_ify(note), limit - 6,
1284
- initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1285
- text.append('')
1286
- text.append('')
1407
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(note), limit - 6,
1408
+ initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1287
1409
  del doc['notes']
1288
1410
 
1289
1411
  if doc.get('seealso', False):
1290
- text.append("SEE ALSO:")
1412
+ text.append("")
1413
+ text.append(_format("SEE ALSO:", 'bold'))
1291
1414
  for item in doc['seealso']:
1292
1415
  if 'module' in item:
1293
- text.append(textwrap.fill(DocCLI.tty_ify('Module %s' % item['module']),
1416
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify('Module %s' % item['module']),
1294
1417
  limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1295
1418
  description = item.get('description')
1296
1419
  if description is None and item['module'].startswith('ansible.builtin.'):
1297
1420
  description = 'The official documentation on the %s module.' % item['module']
1298
1421
  if description is not None:
1299
- text.append(textwrap.fill(DocCLI.tty_ify(description),
1422
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1300
1423
  limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1301
1424
  if item['module'].startswith('ansible.builtin.'):
1302
1425
  relative_url = 'collections/%s_module.html' % item['module'].replace('.', '/', 2)
1303
- text.append(textwrap.fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1426
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1304
1427
  limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1305
1428
  elif 'plugin' in item and 'plugin_type' in item:
1306
1429
  plugin_suffix = ' plugin' if item['plugin_type'] not in ('module', 'role') else ''
1307
- text.append(textwrap.fill(DocCLI.tty_ify('%s%s %s' % (item['plugin_type'].title(), plugin_suffix, item['plugin'])),
1430
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify('%s%s %s' % (item['plugin_type'].title(), plugin_suffix, item['plugin'])),
1308
1431
  limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1309
1432
  description = item.get('description')
1310
1433
  if description is None and item['plugin'].startswith('ansible.builtin.'):
1311
1434
  description = 'The official documentation on the %s %s%s.' % (item['plugin'], item['plugin_type'], plugin_suffix)
1312
1435
  if description is not None:
1313
- text.append(textwrap.fill(DocCLI.tty_ify(description),
1436
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(description),
1314
1437
  limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1315
1438
  if item['plugin'].startswith('ansible.builtin.'):
1316
1439
  relative_url = 'collections/%s_%s.html' % (item['plugin'].replace('.', '/', 2), item['plugin_type'])
1317
- text.append(textwrap.fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1440
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink(relative_url)),
1318
1441
  limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent))
1319
1442
  elif 'name' in item and 'link' in item and 'description' in item:
1320
- text.append(textwrap.fill(DocCLI.tty_ify(item['name']),
1443
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['name']),
1321
1444
  limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1322
- text.append(textwrap.fill(DocCLI.tty_ify(item['description']),
1445
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1323
1446
  limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1324
- text.append(textwrap.fill(DocCLI.tty_ify(item['link']),
1447
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['link']),
1325
1448
  limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1326
1449
  elif 'ref' in item and 'description' in item:
1327
- text.append(textwrap.fill(DocCLI.tty_ify('Ansible documentation [%s]' % item['ref']),
1450
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify('Ansible documentation [%s]' % item['ref']),
1328
1451
  limit - 6, initial_indent=opt_indent[:-2] + "* ", subsequent_indent=opt_indent))
1329
- text.append(textwrap.fill(DocCLI.tty_ify(item['description']),
1452
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(item['description']),
1330
1453
  limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1331
- text.append(textwrap.fill(DocCLI.tty_ify(get_versioned_doclink('/#stq=%s&stp=1' % item['ref'])),
1454
+ text.append(DocCLI.warp_fill(DocCLI.tty_ify(get_versioned_doclink('/#stq=%s&stp=1' % item['ref'])),
1332
1455
  limit - 6, initial_indent=opt_indent + ' ', subsequent_indent=opt_indent + ' '))
1333
1456
 
1334
- text.append('')
1335
- text.append('')
1336
1457
  del doc['seealso']
1337
1458
 
1338
1459
  if doc.get('requirements', False):
1460
+ text.append('')
1339
1461
  req = ", ".join(doc.pop('requirements'))
1340
- text.append("REQUIREMENTS:%s\n" % textwrap.fill(DocCLI.tty_ify(req), limit - 16, initial_indent=" ", subsequent_indent=opt_indent))
1462
+ text.append(_format("REQUIREMENTS:", 'bold') + "%s\n" % DocCLI.warp_fill(DocCLI.tty_ify(req), limit - 16, initial_indent=" ",
1463
+ subsequent_indent=opt_indent))
1341
1464
 
1342
1465
  # Generic handler
1343
1466
  for k in sorted(doc):
1344
- if k in DocCLI.IGNORE or not doc[k]:
1467
+ if not doc[k] or k in DocCLI.IGNORE:
1345
1468
  continue
1469
+ text.append('')
1470
+ header = _format(k.upper(), 'bold')
1346
1471
  if isinstance(doc[k], string_types):
1347
- text.append('%s: %s' % (k.upper(), textwrap.fill(DocCLI.tty_ify(doc[k]), limit - (len(k) + 2), subsequent_indent=opt_indent)))
1472
+ text.append('%s: %s' % (header, DocCLI.warp_fill(DocCLI.tty_ify(doc[k]), limit - (len(k) + 2), subsequent_indent=opt_indent)))
1348
1473
  elif isinstance(doc[k], (list, tuple)):
1349
- text.append('%s: %s' % (k.upper(), ', '.join(doc[k])))
1474
+ text.append('%s: %s' % (header, ', '.join(doc[k])))
1350
1475
  else:
1351
1476
  # use empty indent since this affects the start of the yaml doc, not it's keys
1352
- text.append(DocCLI._indent_lines(DocCLI._dump_yaml({k.upper(): doc[k]}), ''))
1477
+ text.append('%s: ' % header + DocCLI._indent_lines(DocCLI._dump_yaml(doc[k]), ' ' * (len(k) + 2)))
1353
1478
  del doc[k]
1354
- text.append('')
1355
1479
 
1356
1480
  if doc.get('plainexamples', False):
1357
- text.append("EXAMPLES:")
1358
1481
  text.append('')
1482
+ text.append(_format("EXAMPLES:", 'bold'))
1359
1483
  if isinstance(doc['plainexamples'], string_types):
1360
1484
  text.append(doc.pop('plainexamples').strip())
1361
1485
  else:
@@ -1363,13 +1487,13 @@ class DocCLI(CLI, RoleMixin):
1363
1487
  text.append(yaml_dump(doc.pop('plainexamples'), indent=2, default_flow_style=False))
1364
1488
  except Exception as e:
1365
1489
  raise AnsibleParserError("Unable to parse examples section", orig_exc=e)
1366
- text.append('')
1367
- text.append('')
1368
1490
 
1369
1491
  if doc.get('returndocs', False):
1370
- text.append("RETURN VALUES:")
1371
- DocCLI.add_fields(text, doc.pop('returndocs'), limit, opt_indent, return_values=True)
1492
+ text.append('')
1493
+ text.append(_format("RETURN VALUES:", 'bold'))
1494
+ DocCLI.add_fields(text, doc.pop('returndocs'), limit, opt_indent, return_values=True, man=(display.verbosity == 0))
1372
1495
 
1496
+ text.append('\n')
1373
1497
  return "\n".join(text)
1374
1498
 
1375
1499
 
@@ -1406,14 +1530,14 @@ def _do_yaml_snippet(doc):
1406
1530
  if module:
1407
1531
  if required:
1408
1532
  desc = "(required) %s" % desc
1409
- text.append(" %-20s # %s" % (o, textwrap.fill(desc, limit, subsequent_indent=subdent)))
1533
+ text.append(" %-20s # %s" % (o, DocCLI.warp_fill(desc, limit, subsequent_indent=subdent)))
1410
1534
  else:
1411
1535
  if required:
1412
1536
  default = '(required)'
1413
1537
  else:
1414
1538
  default = opt.get('default', 'None')
1415
1539
 
1416
- text.append("%s %-9s # %s" % (o, default, textwrap.fill(desc, limit, subsequent_indent=subdent, max_lines=3)))
1540
+ text.append("%s %-9s # %s" % (o, default, DocCLI.warp_fill(desc, limit, subsequent_indent=subdent, max_lines=3)))
1417
1541
 
1418
1542
  return text
1419
1543