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.
 
 
 
 

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