Tools used by OpenStack Documentation
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

824 lines
30 KiB

  1. #!/usr/bin/env python
  2. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  3. # not use this file except in compliance with the License. You may obtain
  4. # a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software
  9. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  10. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  11. # License for the specific language governing permissions and limitations
  12. # under the License.
  13. import argparse
  14. import os
  15. import re
  16. import subprocess
  17. import sys
  18. import yaml
  19. import os_doc_tools
  20. DEVNULL = open(os.devnull, 'wb')
  21. MAXLINELENGTH = 78
  22. def use_help_flag(os_command):
  23. """Use --help flag (instead of help keyword)
  24. Returns true if the command requires a --help flag instead
  25. of a help keyword.
  26. """
  27. return os_command == "swift" or "-manage" in os_command
  28. def quote_rst(line):
  29. """Convert special characters for RST output."""
  30. line = line.replace('\\', '\\\\').replace('`', '\\`').replace('*', '\\*')
  31. if '--' in line:
  32. line = re.sub(r'(--[^ .\'\\]*)', r":option:`\1`", line)
  33. # work around for "`--`" at murano
  34. line = line.replace('\\`:option:`--`\\`', '```--```')
  35. if 'DEPRECATED!' in line:
  36. line = line.replace('DEPRECATED!', '**DEPRECATED!**')
  37. elif 'DEPRECATED' in line:
  38. line = line.replace('DEPRECATED', '**DEPRECATED**')
  39. if 'env[' in line:
  40. line = line.replace('env[', '``env[').replace(']', ']``')
  41. # work around for "Default=env[...]" at cinder
  42. line = line.replace('=``', '= ``')
  43. return line
  44. def generate_heading(os_command, api_name, title,
  45. output_dir, os_filename, continue_on_error):
  46. """Write RST file header.
  47. :param os_command: client command to document
  48. :param api_name: string description of the API of os_command
  49. :param output_dir: directory to write output file to
  50. :param os_filename: name to create current output file as
  51. :param continue_on_error: continue even if there's an error
  52. """
  53. try:
  54. version = subprocess.check_output([os_command, "--version"],
  55. universal_newlines=True,
  56. stderr=subprocess.STDOUT)
  57. except OSError as e:
  58. if e.errno == os.errno.ENOENT:
  59. action = 'skipping' if continue_on_error else 'aborting'
  60. print("Command %s not found, %s." % (os_command, action))
  61. if continue_on_error:
  62. return
  63. else:
  64. sys.exit(1)
  65. # Extract version from "swift 0.3"
  66. version = version.splitlines()[-1].strip().rpartition(' ')[2]
  67. print("Documenting '%s help (version %s)'" % (os_command, version))
  68. os_file = open(os.path.join(output_dir, os_filename), 'w')
  69. os_file.write(".. ## WARNING #####################################\n")
  70. os_file.write(".. This file is tool-generated. Do not edit manually.\n")
  71. os_file.write(".. ##################################################\n\n")
  72. format_heading(title, 1, os_file)
  73. if os_command == "heat":
  74. os_file.write(".. warning::\n\n")
  75. os_file.write(" The " + os_command + " CLI is deprecated\n")
  76. os_file.write(" in favor of python-openstackclient.\n")
  77. os_file.write(" For more information, see :doc:`openstack`.\n")
  78. os_file.write(" For a Python library, continue using\n")
  79. os_file.write(" python-" + os_command + "client.\n\n")
  80. if os_command == "openstack":
  81. os_file.write("The openstack client is a common OpenStack")
  82. os_file.write("command-line interface (CLI).\n\n")
  83. else:
  84. os_file.write("The " + os_command + " client is the command-line ")
  85. os_file.write("interface (CLI) for\n")
  86. os_file.write("the " + api_name + " and its extensions.\n\n")
  87. os_file.write("This chapter documents :command:`" + os_command + "` ")
  88. os_file.write("version ``" + version + "``.\n\n")
  89. os_file.write("For help on a specific :command:`" + os_command + "` ")
  90. os_file.write("command, enter:\n\n")
  91. os_file.write(".. code-block:: console\n\n")
  92. if use_help_flag(os_command):
  93. os_file.write(" $ " + os_command + " COMMAND --help\n\n")
  94. else:
  95. os_file.write(" $ " + os_command + " help COMMAND\n\n")
  96. os_file.write(".. _" + os_command + "_command_usage:\n\n")
  97. format_heading(os_command + " usage", 2, os_file)
  98. return os_file
  99. def is_option(string):
  100. """Returns True if string specifies an argument."""
  101. for x in string:
  102. if not (x.isupper() or x == '_' or x == ','):
  103. return False
  104. if string.startswith('DEPRECATED'):
  105. return False
  106. return True
  107. def extract_options(line):
  108. """Extract command or option from line."""
  109. # We have a command or parameter to handle
  110. # Differentiate:
  111. # 1. --version
  112. # 2. --timeout <seconds>
  113. # 3. --service <service>, --service-id <service>
  114. # 4. -v, --verbose
  115. # 5. -p PORT, --port PORT
  116. # 6. <backup> ID of the backup to restore.
  117. # 7. --alarm-action <Webhook URL>
  118. # 8. <NAME or ID> Name or ID of stack to resume.
  119. # 9. --json JSON JSON representation of node group template.
  120. # 10. --id <cluster_id> ID of the cluster to show.
  121. # 11. --instance "<opt=value,opt=value,...>"
  122. split_line = line.split(None, 2)
  123. if split_line[0].startswith("-"):
  124. last_was_option = True
  125. else:
  126. last_was_option = False
  127. if (len(split_line) > 1 and
  128. ('<' in split_line[0] or
  129. '<' in split_line[1] or
  130. '--' in split_line[1] or
  131. split_line[1].startswith(("-", '<', '{', '[')) or
  132. is_option(split_line[1]))):
  133. words = line.split(None)
  134. i = 0
  135. while i < len(words) - 1:
  136. if (('<' in words[i] and
  137. '>' not in words[i]) or
  138. ('[' in words[i] and
  139. ']' not in words[i])):
  140. words[i] += ' ' + words[i + 1]
  141. del words[i + 1]
  142. else:
  143. i += 1
  144. skip_is_option = False
  145. while len(words) > 1:
  146. if words[1].startswith('DEPRECATED'):
  147. break
  148. if last_was_option:
  149. if (words[1].startswith(("-", '<', '{', '[', '"')) or
  150. (is_option(words[1]) and skip_is_option is False)):
  151. skip_is_option = False
  152. if words[1].isupper() or words[1].startswith('<'):
  153. skip_is_option = True
  154. words[0] = words[0] + ' ' + words[1]
  155. del words[1]
  156. else:
  157. break
  158. else:
  159. if words[1].startswith("-"):
  160. words[0] = words[0] + ' ' + words[1]
  161. del words[1]
  162. else:
  163. break
  164. w0 = words[0]
  165. del words[0]
  166. w1 = ''
  167. if words:
  168. w1 = words[0]
  169. del words[0]
  170. for w in words:
  171. w1 += " " + w
  172. if not w1:
  173. split_line = [w0]
  174. else:
  175. split_line = [w0, w1]
  176. else:
  177. split_line = line.split(None, 1)
  178. return split_line
  179. def format_heading(heading, level, os_file):
  180. """Nicely print heading.
  181. :param heading: heading strings
  182. :param level: heading level
  183. :param os_file: open filehandle for output of RST file
  184. """
  185. if level == 1:
  186. os_file.write("=" * len(heading) + "\n")
  187. os_file.write(heading + "\n")
  188. if level == 1:
  189. os_file.write("=" * len(heading) + "\n\n")
  190. elif level == 2:
  191. os_file.write("~" * len(heading) + "\n\n")
  192. elif level == 3:
  193. os_file.write("-" * len(heading) + "\n\n")
  194. else:
  195. os_file.write("\n")
  196. return
  197. def format_help(title, lines, os_file):
  198. """Nicely print section of lines.
  199. :param title: help title, if exist
  200. :param lines: strings to format
  201. :param os_file: open filehandle for output of RST file
  202. """
  203. close_entry = False
  204. if title:
  205. os_file.write("**" + title + ":**" + "\n\n")
  206. continued_line = ''
  207. for line in lines:
  208. if not line or line[0] != ' ':
  209. break
  210. # We have to handle these cases:
  211. # 1. command Explanation
  212. # 2. command
  213. # Explanation on next line
  214. # 3. command Explanation continued
  215. # on next line
  216. # If there are more than 8 spaces, let's treat it as
  217. # explanation.
  218. if line.startswith(' '):
  219. # Explanation
  220. xline = continued_line + quote_rst(line.lstrip(' '))
  221. continued_line = ''
  222. # Concatenate the command options with "-"
  223. # For example:
  224. # see 'glance image-
  225. # show'
  226. if xline.endswith('-'):
  227. continued_line = xline
  228. continue
  229. # check niceness
  230. if len(xline) > (MAXLINELENGTH - 2):
  231. xline = xline.replace(' ', '\n ')
  232. os_file.write(" " + xline + "\n")
  233. continue
  234. # Now we have a command or parameter to handle
  235. split_line = extract_options(line)
  236. if not close_entry:
  237. close_entry = True
  238. else:
  239. os_file.write("\n")
  240. xline = split_line[0]
  241. # check niceness work around for long option name, glance
  242. xline = xline.replace('[<RESOURCE_TYPE_ASSOCIATIONS> ...]',
  243. '[...]')
  244. os_file.write("``" + xline + "``\n")
  245. if len(split_line) > 1:
  246. # Explanation
  247. xline = continued_line + quote_rst(split_line[1])
  248. continued_line = ''
  249. # Concatenate the command options with "-"
  250. # For example:
  251. # see 'glance image-
  252. # show'
  253. if xline.endswith('-'):
  254. continued_line = xline
  255. continue
  256. # check niceness
  257. if len(xline) > (MAXLINELENGTH - 2):
  258. # check niceness
  259. xline = xline.replace(' ', '\n ')
  260. os_file.write(" " + xline + "\n")
  261. os_file.write("\n")
  262. return
  263. def generate_command(os_command, os_file):
  264. """Convert os_command --help to RST.
  265. :param os_command: client command to document
  266. :param os_file: open filehandle for output of RST file
  267. """
  268. if use_help_flag(os_command):
  269. help_lines = subprocess.check_output([os_command, "--help"],
  270. universal_newlines=True,
  271. stderr=DEVNULL).split('\n')
  272. else:
  273. help_lines = subprocess.check_output([os_command, "help"],
  274. universal_newlines=True,
  275. stderr=DEVNULL).split('\n')
  276. ignore_next_lines = False
  277. next_line_screen = True
  278. line_index = -1
  279. in_screen = False
  280. subcommands = 'complete'
  281. for line in help_lines:
  282. line_index += 1
  283. if line and line[0] != ' ':
  284. # XXX: Might have whitespace before!!
  285. if '<subcommands>' in line:
  286. ignore_next_lines = False
  287. continue
  288. if 'Positional arguments' in line:
  289. ignore_next_lines = True
  290. next_line_screen = True
  291. os_file.write("\n\n")
  292. in_screen = False
  293. if os_command != "glance":
  294. format_help('Subcommands',
  295. help_lines[line_index + 2:], os_file)
  296. continue
  297. if line.startswith(('Optional arguments:', 'Optional:',
  298. 'Options:', 'optional arguments')):
  299. if in_screen:
  300. os_file.write("\n\n")
  301. in_screen = False
  302. os_file.write(".. _" + os_command + "_command_options:\n\n")
  303. format_heading(os_command + " optional arguments", 2, os_file)
  304. format_help('', help_lines[line_index + 1:], os_file)
  305. next_line_screen = True
  306. ignore_next_lines = True
  307. continue
  308. # magnum and sahara
  309. if line.startswith('Common auth options'):
  310. if in_screen:
  311. os_file.write("\n\n")
  312. in_screen = False
  313. os_file.write("\n")
  314. os_file.write(os_command)
  315. os_file.write(".. _" + os_command + "_common_auth:\n\n")
  316. format_heading(os_command + " common authentication arguments",
  317. 2, os_file)
  318. format_help('', help_lines[line_index + 1:], os_file)
  319. next_line_screen = True
  320. ignore_next_lines = True
  321. continue
  322. # neutron
  323. if line.startswith('Commands for API v2.0:'):
  324. if in_screen:
  325. os_file.write("\n\n")
  326. in_screen = False
  327. os_file.write(".. _" + os_command + "_common_api_v2:\n\n")
  328. format_heading(os_command + " API v2.0 commands", 2, os_file)
  329. format_help('', help_lines[line_index + 1:], os_file)
  330. next_line_screen = True
  331. ignore_next_lines = True
  332. continue
  333. # swift
  334. if line.startswith('Examples:'):
  335. os_file.write(".. _" + os_command + "_examples:\n\n")
  336. format_heading(os_command + " examples", 2, os_file)
  337. next_line_screen = True
  338. ignore_next_lines = False
  339. continue
  340. # all
  341. if not line.startswith('usage'):
  342. continue
  343. if not ignore_next_lines:
  344. if next_line_screen:
  345. os_file.write(".. code-block:: console\n\n")
  346. os_file.write(" " + line)
  347. next_line_screen = False
  348. in_screen = True
  349. elif line:
  350. os_file.write("\n " + line.rstrip())
  351. # subcommands (select bash-completion, complete for bash-completion)
  352. if 'bash-completion' in line:
  353. subcommands = 'bash-completion'
  354. if in_screen:
  355. os_file.write("\n\n")
  356. return subcommands
  357. def generate_subcommand(os_command, os_subcommand, os_file, extra_params,
  358. suffix, title_suffix):
  359. """Convert os_command help os_subcommand to RST.
  360. :param os_command: client command to document
  361. :param os_subcommand: client subcommand to document
  362. :param os_file: open filehandle for output of RST file
  363. :param extra_params: Extra parameter to pass to os_command
  364. :param suffix: Extra suffix to add to link ID
  365. :param title_suffix: Extra suffix for title
  366. """
  367. print("Documenting subcommand '%s'..." % os_subcommand)
  368. args = [os_command]
  369. if extra_params:
  370. args.extend(extra_params)
  371. if use_help_flag(os_command):
  372. args.append(os_subcommand)
  373. args.append("--help")
  374. else:
  375. args.append("help")
  376. args.append(os_subcommand)
  377. help_lines = subprocess.check_output(args,
  378. universal_newlines=True,
  379. stderr=DEVNULL)
  380. help_lines_lower = help_lines.lower()
  381. if 'positional arguments' in help_lines_lower:
  382. index = help_lines_lower.index('positional arguments')
  383. elif 'optional arguments' in help_lines_lower:
  384. index = help_lines_lower.index('optional arguments')
  385. else:
  386. index = len(help_lines_lower)
  387. if 'deprecated' in (help_lines_lower[0:index]):
  388. print("Subcommand '%s' is deprecated, skipping." % os_subcommand)
  389. return
  390. help_lines = help_lines.split('\n')
  391. os_subcommandid = os_subcommand.replace(' ', '_')
  392. os_file.write(".. _" + os_command + "_" + os_subcommandid + suffix)
  393. os_file.write(":\n\n")
  394. format_heading(os_command + " " + os_subcommand + title_suffix, 3, os_file)
  395. if os_command == "swift":
  396. next_line_screen = False
  397. os_file.write(".. code-block:: console\n\n")
  398. os_file.write("Usage: swift " + os_subcommand + "\n\n")
  399. in_para = True
  400. else:
  401. next_line_screen = True
  402. in_para = False
  403. if extra_params:
  404. extra_paramstr = ' '.join(extra_params)
  405. help_lines[0] = help_lines[0].replace(os_command, "%s %s" %
  406. (os_command, extra_paramstr))
  407. line_index = -1
  408. # Content is:
  409. # usage...
  410. #
  411. # Description
  412. #
  413. # Arguments
  414. skip_lines = False
  415. for line in help_lines:
  416. line_index += 1
  417. if line.startswith('Usage:') and os_command == "swift":
  418. line = line[len("Usage: "):]
  419. if line.startswith(('Arguments:', 'Positional arguments:',
  420. 'positional arguments', 'Optional arguments',
  421. 'optional arguments')):
  422. if in_para:
  423. in_para = False
  424. os_file.write("\n")
  425. if line.startswith(('Positional arguments',
  426. 'positional arguments')):
  427. format_help('Positional arguments',
  428. help_lines[line_index + 1:], os_file)
  429. skip_lines = True
  430. continue
  431. elif line.startswith(('Optional arguments:',
  432. 'optional arguments')):
  433. format_help('Optional arguments',
  434. help_lines[line_index + 1:], os_file)
  435. break
  436. else:
  437. format_help('Arguments', help_lines[line_index + 1:], os_file)
  438. break
  439. if skip_lines:
  440. continue
  441. if not line:
  442. if not in_para:
  443. os_file.write("\n")
  444. in_para = True
  445. continue
  446. if next_line_screen:
  447. os_file.write(".. code-block:: console\n\n")
  448. os_file.write(" " + line + "\n")
  449. next_line_screen = False
  450. elif line.startswith(' '):
  451. # ceilometer alarm-gnocchi-aggregation-by-metrics-threshold-create
  452. # has 7 white space indentation
  453. if not line.isspace():
  454. # skip blank line, such as "trove help cluster-grow" command.
  455. os_file.write(" " + line + "\n")
  456. else:
  457. xline = quote_rst(line)
  458. if (len(xline) > MAXLINELENGTH):
  459. # check niceness
  460. xline = xline.replace(' ', '\n')
  461. os_file.write(xline + "\n")
  462. if in_para:
  463. os_file.write("\n")
  464. def discover_subcommands(os_command, subcommands, extra_params):
  465. """Discover all help subcommands for the given command"
  466. :param os_command: client command whose subcommands need to be discovered
  467. :param subcommands: list or type ('complete' or 'bash-completion')
  468. of subcommands to document
  469. :param extra_params: Extra parameter to pass to os_command.
  470. :return: the list of subcommands discovered
  471. :rtype: list(str)
  472. """
  473. if extra_params is None:
  474. extra_params = ''
  475. print(("Discovering subcommands of '%s' %s ..."
  476. % (os_command, extra_params)))
  477. blacklist = ['bash-completion', 'complete', 'help']
  478. if type(subcommands) is str:
  479. args = [os_command]
  480. if extra_params:
  481. args.extend(extra_params)
  482. if subcommands == 'complete':
  483. subcommands = []
  484. args.append('complete')
  485. lines = subprocess.check_output(
  486. args, universal_newlines=True, stderr=DEVNULL).split('\n')
  487. delim = ' '
  488. # if the cmds= line contains '-' then use that as a delim
  489. for line in lines:
  490. if '-' in line and 'cmds=' in line:
  491. delim = '-'
  492. break
  493. for line in [x.strip() for x in lines
  494. if x.strip().startswith('cmds_') and '-' in x]:
  495. subcommand, _ = line.split('=')
  496. subcommand = subcommand.replace(
  497. 'cmds_', '').replace('_', delim)
  498. subcommands.append(subcommand)
  499. else:
  500. args.append('bash-completion')
  501. subcommands = subprocess.check_output(
  502. args,
  503. universal_newlines=True).strip().split('\n')[-1].split()
  504. subcommands = sorted([o for o in subcommands if not (o.startswith('-') or
  505. o in blacklist)])
  506. print("%d subcommands discovered." % len(subcommands))
  507. return subcommands
  508. def generate_subcommands(os_command, os_file, subcommands, extra_params,
  509. suffix, title_suffix):
  510. """Convert os_command help subcommands for all subcommands to RST.
  511. :param os_command: client command to document
  512. :param os_file: open filehandle for output of RST file
  513. :param subcommands: list or type ('complete' or 'bash-completion')
  514. of subcommands to document
  515. :param extra_params: Extra parameter to pass to os_command.
  516. :param suffix: Extra suffix to add to link ID
  517. :param title_suffix: Extra suffix for title
  518. """
  519. for subcommand in subcommands:
  520. generate_subcommand(os_command, subcommand, os_file, extra_params,
  521. suffix, title_suffix)
  522. print("%d subcommands documented." % len(subcommands))
  523. def discover_and_generate_subcommands(os_command, os_file, subcommands,
  524. extra_params, suffix, title_suffix):
  525. """Convert os_command help subcommands for all subcommands to RST.
  526. :param os_command: client command to document
  527. :param os_file: open filehandle for output of RST file
  528. :param subcommands: list or type ('complete' or 'bash-completion')
  529. of subcommands to document
  530. :param extra_params: Extra parameter to pass to os_command.
  531. :param suffix: Extra suffix to add to link ID
  532. :param title_suffix: Extra suffix for title
  533. """
  534. subcommands = discover_subcommands(os_command, subcommands, extra_params)
  535. generate_subcommands(os_command, os_file, subcommands, extra_params,
  536. suffix, title_suffix)
  537. def _get_clients_filename():
  538. return os.path.join(os.path.dirname(__file__),
  539. 'resources/clients.yaml')
  540. def get_clients():
  541. """Load client definitions from the resource file."""
  542. fname = _get_clients_filename()
  543. clients = yaml.load(open(fname, 'r'))
  544. return clients
  545. def document_single_project(os_command, output_dir, continue_on_error):
  546. """Create documentation for os_command."""
  547. clients = get_clients()
  548. if os_command not in clients:
  549. print("'%s' command not yet handled" % os_command)
  550. print("(Command must be defined in '%s')" % _get_clients_filename())
  551. if continue_on_error:
  552. return False
  553. else:
  554. sys.exit(-1)
  555. print("Documenting '%s'" % os_command)
  556. data = clients[os_command]
  557. if 'name' in data:
  558. api_name = "%s API" % data['name']
  559. title = "%s command-line client" % data.get('title', data['name'])
  560. else:
  561. api_name = ''
  562. title = data.get('title', '')
  563. out_filename = os_command + ".rst"
  564. out_file = generate_heading(os_command, api_name, title,
  565. output_dir, out_filename,
  566. continue_on_error)
  567. if not out_file:
  568. if continue_on_error:
  569. return False
  570. else:
  571. sys.exit(-1)
  572. subcommands = generate_command(os_command, out_file)
  573. if subcommands == 'complete' and data.get('subcommands'):
  574. subcommands = data.get('subcommands')
  575. if os_command == 'cinder':
  576. format_heading("Block Storage API v2 commands", 2, out_file)
  577. out_file.write("You can select an API version to use by adding the\n")
  578. out_file.write(":option:`--os-volume-api-version` parameter or by\n")
  579. out_file.write("setting the corresponding environment variable:\n\n")
  580. out_file.write(".. code-block:: console\n\n")
  581. out_file.write(" export OS_VOLUME_API_VERSION=2\n\n")
  582. discover_and_generate_subcommands(os_command, out_file, subcommands,
  583. ["--os-volume-api-version", "2"],
  584. "", "")
  585. elif os_command == 'openstack':
  586. format_heading("OpenStack with Identity API v3 commands", 2, out_file)
  587. out_file.write(".. important::\n\n")
  588. out_file.write(" OpenStack Identity API v2 is deprecated in\n")
  589. out_file.write(" the Mitaka release and later.\n\n")
  590. out_file.write(" You can select the Identity API version to use\n")
  591. out_file.write(" by adding the\n")
  592. out_file.write(" :option:`--os-identity-api-version`\n")
  593. out_file.write(" parameter or by setting the corresponding\n")
  594. out_file.write(" environment variable:\n\n")
  595. out_file.write(" .. code-block:: console\n\n")
  596. out_file.write(" export OS_IDENTITY_API_VERSION=3\n\n")
  597. extra_params = ["--os-auth-type", "token"]
  598. subcommands = discover_subcommands(os_command, subcommands,
  599. extra_params)
  600. generate_subcommands(os_command, out_file, subcommands,
  601. extra_params, "", "")
  602. elif os_command == 'glance':
  603. format_heading("Image service API v2 commands", 2, out_file)
  604. out_file.write("You can select an API version to use by adding the\n")
  605. out_file.write(":option:`--os-image-api-version` parameter or by\n")
  606. out_file.write("setting the corresponding environment variable:\n\n")
  607. out_file.write(".. code-block:: console\n\n")
  608. out_file.write(" export OS_IMAGE_API_VERSION=2\n\n")
  609. discover_and_generate_subcommands(os_command, out_file, subcommands,
  610. ["--os-image-api-version", "2"],
  611. "_v2", " (v2)")
  612. else:
  613. discover_and_generate_subcommands(os_command, out_file, subcommands,
  614. None, "", "")
  615. # Print subcommands for different API versions
  616. if os_command == 'cinder':
  617. out_file.write("\n")
  618. format_heading("Block Storage API v1 commands (DEPRECATED)",
  619. 2, out_file)
  620. discover_and_generate_subcommands(os_command, out_file, subcommands,
  621. None, "_v1", " (v1)")
  622. if os_command == 'glance':
  623. out_file.write("\n")
  624. format_heading("Image service API v1 commands", 2, out_file)
  625. discover_and_generate_subcommands(os_command, out_file, subcommands,
  626. ["--os-image-api-version", "1"],
  627. "_v1", " (v1)")
  628. print("Finished.\n")
  629. out_file.close()
  630. return True
  631. def main():
  632. clients = get_clients()
  633. api_clients = sorted([x for x in clients if not x.endswith('-manage')])
  634. manage_clients = sorted([x for x in clients if x.endswith('-manage')])
  635. all_clients = api_clients + manage_clients
  636. parser = argparse.ArgumentParser(description="Generate RST files "
  637. "to document python-PROJECTclients.")
  638. parser.add_argument('clients', metavar='client', nargs='*',
  639. help="OpenStack command to document. Specify "
  640. "multiple times to generate documentation for "
  641. "multiple clients. One of: " +
  642. ", ".join(all_clients) + ".")
  643. parser.add_argument("--all", help="Document all clients. "
  644. "Namely " + ", ".join(all_clients) + ".",
  645. action="store_true")
  646. parser.add_argument("--all-api", help="Document all API clients. "
  647. "Namely " + ", ".join(clients.keys()) + ".",
  648. action="store_true")
  649. parser.add_argument("--all-manage", help="Document all manage clients. "
  650. "Namely " + ", ".join(manage_clients) + ".",
  651. action="store_true")
  652. parser.add_argument("--output-dir", default=".",
  653. help="Directory to write generated files to")
  654. parser.add_argument("--continue-on-error", default=False,
  655. help="Continue with remaining clients even if an "
  656. "error occurs generating a client file.",
  657. action="store_true")
  658. parser.add_argument("--version", default=False,
  659. help="Show program's version number and exit.",
  660. action="store_true")
  661. prog_args = parser.parse_args()
  662. client_list = []
  663. if prog_args.all or prog_args.all_api or prog_args.all_manage:
  664. if prog_args.all or prog_args.all_api:
  665. client_list = api_clients
  666. if prog_args.all or prog_args.all_manage:
  667. client_list.extend(manage_clients)
  668. elif prog_args.clients:
  669. client_list = prog_args.clients
  670. if not prog_args or 'help' in [client.lower() for client in client_list]:
  671. parser.print_help()
  672. sys.exit(0)
  673. elif prog_args.version:
  674. print(os_doc_tools.__version__)
  675. sys.exit(0)
  676. if not client_list:
  677. parser.print_help()
  678. sys.exit(1)
  679. print("OpenStack Auto Documenting of Commands (using "
  680. "openstack-doc-tools version %s)\n"
  681. % os_doc_tools.__version__)
  682. success_list = []
  683. error_list = []
  684. for client in client_list:
  685. if document_single_project(
  686. client, prog_args.output_dir, prog_args.continue_on_error):
  687. success_list.append(client)
  688. else:
  689. error_list.append(client)
  690. if success_list:
  691. print("Generated documentation for: %s" % ", ".join(success_list))
  692. if error_list:
  693. print("Generation failed for: %s" % ", ".join(error_list))
  694. sys.exit(1)
  695. if __name__ == "__main__":
  696. sys.exit(main())