Skip to content

Commit

Permalink
Merge pull request #14 from zyga/master
Browse files Browse the repository at this point in the history
Add manpage-specific output mode
  • Loading branch information
alex-rudakov committed Jul 22, 2014
2 parents 2f9a37c + 87d25f5 commit 04113ff
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 43 deletions.
184 changes: 163 additions & 21 deletions sphinxarg/ext.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from argparse import ArgumentParser
import os

from docutils import nodes
from sphinx.util.compat import Directive
from docutils.parsers.rst.directives import flag, unchanged
from sphinx.util.compat import Directive
from sphinx.util.nodes import nested_parse_with_titles

from sphinxarg.parser import parse_parser, parser_navigate


Expand All @@ -24,11 +28,9 @@ def map_nested_definitions(nested_content):
ci = subitem[idx]
if len(ci.children) > 0:
classifier = ci.children[0].astext()

if classifier is not None and not classifier in (
if classifier is not None and classifier not in (
'@replace', '@before', '@after'):
raise Exception('Unknown classifier: %s' % classifier)

idx = subitem.first_child_matching_class(nodes.term)
if idx is not None:
ch = subitem[idx]
Expand Down Expand Up @@ -58,9 +60,7 @@ def print_arg_list(data, nested_content):
items.append(
nodes.option_list_item(
'', nodes.option_group('', nodes.option_string(text=name)),
nodes.description('', *my_def)
)
)
nodes.description('', *my_def)))
return nodes.option_list('', *items) if items else None


Expand All @@ -87,9 +87,7 @@ def print_opt_list(data, nested_content):
items.append(
nodes.option_list_item(
'', nodes.option_group('', *names),
nodes.description('', *my_def)
)
)
nodes.description('', *my_def)))
return nodes.option_list('', *items) if items else None


Expand All @@ -98,18 +96,15 @@ def print_command_args_and_opts(arg_list, opt_list, sub_list=None):
if arg_list:
items.append(nodes.definition_list_item(
'', nodes.term(text='Positional arguments:'),
nodes.definition('', arg_list)
))
nodes.definition('', arg_list)))
if opt_list:
items.append(nodes.definition_list_item(
'', nodes.term(text='Options:'),
nodes.definition('', opt_list)
))
nodes.definition('', opt_list)))
if sub_list and len(sub_list):
items.append(nodes.definition_list_item(
'', nodes.term(text='Sub-commands:'),
nodes.definition('', sub_list)
))
nodes.definition('', sub_list)))
return nodes.definition_list('', *items)


Expand Down Expand Up @@ -156,7 +151,151 @@ def print_subcommand_list(data, nested_content):
class ArgParseDirective(Directive):
has_content = True
option_spec = dict(module=unchanged, func=unchanged, ref=unchanged,
prog=unchanged, path=unchanged, nodefault=flag)
prog=unchanged, path=unchanged, nodefault=flag,
manpage=unchanged, nosubcommands=unchanged)

def _construct_manpage_specific_structure(self, parser_info):
"""
Construct a typical man page consisting of the following elements:
NAME (automatically generated, out of our control)
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
SEE ALSO
BUGS
"""
# SYNOPSIS section
synopsis_section = nodes.section(
'',
nodes.title(text='Synopsis'),
nodes.literal_block(text=parser_info["bare_usage"]),
ids=['synopsis-section'])
# DESCRIPTION section
description_section = nodes.section(
'',
nodes.title(text='Description'),
nodes.paragraph(text=parser_info.get(
'description', parser_info.get(
'help', "undocumented").capitalize())),
ids=['description-section'])
nested_parse_with_titles(
self.state, self.content, description_section)
if parser_info.get('epilog'):
# TODO: do whatever sphinx does to understand ReST inside
# docstrings magically imported from other places. The nested
# parse method invoked above seem to be able to do this but
# I haven't found a way to do it for arbitrary text
description_section += nodes.paragraph(
text=parser_info['epilog'])
# OPTIONS section
options_section = nodes.section(
'',
nodes.title(text='Options'),
ids=['options-section'])
if 'args' in parser_info:
options_section += nodes.paragraph()
options_section += nodes.subtitle(text='Positional arguments:')
options_section += self._format_positional_arguments(parser_info)
if 'options' in parser_info:
options_section += nodes.paragraph()
options_section += nodes.subtitle(text='Optional arguments:')
options_section += self._format_optional_arguments(parser_info)
if 'children' in parser_info:
subcommands_section += self._format_subcommands(parser_info)
items = [
# NOTE: we cannot generate NAME ourselves. It is generated by
# docutils.writers.manpage
synopsis_section,
description_section,
# TODO: files
# TODO: see also
# TODO: bugs
]
if len(options_section.children) > 1:
items.append(options_section)
if 'nosubcommands' not in self.options:
# SUBCOMMANDS section (non-standard)
subcommands_section = nodes.section(
'',
nodes.title(text='Sub-Commands'),
ids=['subcommands-section'])
if len(subcommands_section) > 1:
items.append(subcommands_section)
if os.getenv("INCLUDE_DEBUG_SECTION"):
import json
# DEBUG section (non-standard)
debug_section = nodes.section(
'',
nodes.title(text="Argparse + Sphinx Debugging"),
nodes.literal_block(text=json.dumps(parser_info, indent=' ')),
ids=['debug-section'])
items.append(debug_section)
return items

def _format_positional_arguments(self, parser_info):
assert 'args' in parser_info
items = []
for arg in parser_info['args']:
arg_items = []
if arg['help']:
arg_items.append(nodes.paragraph(text=arg['help']))
else:
arg_items.append(nodes.paragraph(text='Undocumented'))
if 'choices' in arg:
arg_items.append(
nodes.paragraph(
text='Possible choices: ' + ', '.join(arg['choices'])))
items.append(
nodes.option_list_item(
'', nodes.option_group(
'', nodes.description(text=arg['metavar'])),
nodes.description('', *arg_items)))
return nodes.option_list('', *items)

def _format_optional_arguments(self, parser_info):
assert 'options' in parser_info
items = []
for opt in parser_info['options']:
names = []
opt_items = []
for name in opt['name']:
option_declaration = [nodes.option_string(text=name)]
if opt['default'] is not None \
and opt['default'] != '==SUPPRESS==':
option_declaration += nodes.option_argument(
'', text='=' + str(opt['default']))
names.append(nodes.option('', *option_declaration))
if opt['help']:
opt_items.append(nodes.paragraph(text=opt['help']))
else:
opt_items.append(nodes.paragraph(text='Undocumented'))
if 'choices' in opt:
opt_items.append(
nodes.paragraph(
text='Possible choices: ' + ', '.join(opt['choices'])))
items.append(
nodes.option_list_item(
'', nodes.option_group('', *names),
nodes.description('', *opt_items)))
return nodes.option_list('', *items)

def _format_subcommands(self, parser_info):
assert 'children' in parser_info
items = []
for subcmd in parser_info['children']:
subcmd_items = []
if subcmd['help']:
subcmd_items.append(nodes.paragraph(text=subcmd['help']))
else:
subcmd_items.append(nodes.paragraph(text='Undocumented'))
items.append(
nodes.definition_list_item(
'',
nodes.term('', '', nodes.strong(
text=subcmd['bare_usage'])),
nodes.definition('', *subcmd_items)))
return nodes.definition_list('', *items)

def run(self):
if 'module' in self.options and 'func' in self.options:
Expand All @@ -180,13 +319,16 @@ def run(self):
parser = func
else:
parser = func()
if not 'path' in self.options:
if 'path' not in self.options:
self.options['path'] = ''
path = str(self.options['path'])
parser.prog = self.options['prog']
result = parse_parser(parser,
skip_default_values='nodefault' in self.options)
if 'prog' in self.options:
parser.prog = self.options['prog']
result = parse_parser(
parser, skip_default_values='nodefault' in self.options)
result = parser_navigate(result, path)
if 'manpage' in self.options:
return self._construct_manpage_specific_structure(result)
nested_content = nodes.paragraph()
self.state.nested_parse(
self.content, self.content_offset, nested_content)
Expand Down
37 changes: 28 additions & 9 deletions sphinxarg/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def parser_navigate(parser_result, path, current_path=None):
current_path = current_path or []
if len(path) == 0:
return parser_result
if not 'children' in parser_result:
if 'children' not in parser_result:
raise NavigationException(
'Current parser have no children elements. (path: %s)' %
' '.join(current_path))
Expand All @@ -38,9 +38,25 @@ def _try_add_parser_attribute(data, parser, attribname):
data[attribname] = attribval


def _format_usage_without_prefix(parser):
"""
Use private argparse APIs to get the usage string without
the 'usage: ' prefix.
"""
fmt = parser._get_formatter()
fmt.add_usage(parser.usage, parser._actions,
parser._mutually_exclusive_groups, prefix='')
return fmt.format_help().strip()


def parse_parser(parser, data=None, **kwargs):
if data is None:
data = {'name': '', 'usage': parser.format_usage().strip()}
data = {
'name': '',
'usage': parser.format_usage().strip(),
'bare_usage': _format_usage_without_prefix(parser),
'prog': parser.prog,
}
_try_add_parser_attribute(data, parser, 'description')
_try_add_parser_attribute(data, parser, 'epilog')
for action in parser._get_positional_actions():
Expand All @@ -55,29 +71,31 @@ def parse_parser(parser, data=None, **kwargs):
subdata = {
'name': name,
'help': helps[name] if name in helps else '',
'usage': subaction.format_usage().strip()
'usage': subaction.format_usage().strip(),
'bare_usage': _format_usage_without_prefix(subaction),
}
parse_parser(subaction, subdata, **kwargs)
if not 'children' in data:
if 'children' not in data:
data['children'] = []
data['children'].append(subdata)
continue
if not 'args' in data:
if 'args' not in data:
data['args'] = []
arg = {
'name': action.dest,
'help': action.help or ''
'help': action.help or '',
'metavar': action.metavar
}
if action.choices:
arg['choices'] = action.choices
data['args'].append(arg)
show_defaults = (
(not 'skip_default_values' in kwargs)
('skip_default_values' not in kwargs)
or (kwargs['skip_default_values'] is False))
for action in parser._get_optional_actions():
if isinstance(action, _HelpAction):
continue
if not 'options' in data:
if 'options' not in data:
data['options'] = []
option = {
'name': action.option_strings,
Expand All @@ -86,5 +104,6 @@ def parse_parser(parser, data=None, **kwargs):
}
if action.choices:
option['choices'] = action.choices
data['options'].append(option)
if "==SUPPRESS==" not in option['help']:
data['options'].append(option)
return data
Loading

0 comments on commit 04113ff

Please sign in to comment.