Add `man` documentation target

Fixes #1131
This commit is contained in:
ZyX 2014-10-25 21:31:23 +04:00
parent 99e531ec0e
commit 9c57255dec
9 changed files with 456 additions and 3 deletions

View File

@ -7,7 +7,7 @@ BUILDDIR = _build
# Internal variables
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
ALLSPHINXOPTS = -T -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
GH_PAGES_SOURCES = source Makefile
GH_SOURCE_BRANCH = develop
@ -32,3 +32,8 @@ html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."

View File

@ -0,0 +1,12 @@
:orphan:
powerline-config manual page
============================
.. automan:: powerline.commands.config
:prog: powerline-config
See also
--------
:manpage:`powerline(1)`

View File

@ -0,0 +1,12 @@
:orphan:
powerline-daemon manual page
============================
.. automan:: powerline.commands.daemon
:prog: powerline-daemon
See also
--------
:manpage:`powerline(1)`

View File

@ -0,0 +1,12 @@
:orphan:
powerline manual page
=====================
.. automan:: powerline.commands.main
:prog: powerline
See also
--------
:manpage:`powerline-daemon(1)`, :manpage:`powerline-config(1)`

View File

@ -8,7 +8,10 @@ import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(os.getcwd()))))
sys.path.insert(0, os.path.abspath(os.getcwd()))
extensions = ['powerline_autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
extensions = [
'powerline_autodoc', 'powerline_automan',
'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode',
]
source_suffix = '.rst'
master_doc = 'index'
project = 'Powerline'
@ -31,6 +34,22 @@ latex_elements = {
''',
}
man_pages = []
for doc in os.listdir(os.path.join(os.path.dirname(__file__), 'commands')):
if doc.endswith('.rst'):
name = doc[:-4]
module = 'powerline.commands.{0}'.format(name)
get_argparser = __import__(str(module), fromlist=[str('get_argparser')]).get_argparser
parser = get_argparser()
description = parser.description
man_pages.append([
'commands/' + name,
'powerline' if name == 'main' else 'powerline-' + name,
description,
'',
1
])
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if were building docs locally

View File

@ -5,6 +5,14 @@ License and credits
Powerline is licensed under the `MIT license
<https://raw.github.com/Lokaltog/powerline/develop/LICENSE>`_.
..
This document is parsed by powerline_automan.py module. Do not forget to
check that file before altering this one. Specifically it expects
``Authors`` and ``Contributors`` sections underlined by ``---``, a list of
authors in format ``* `{name} <`` in the “Authors” section and fonts
contributor name in format ``The glyphs in the font patcher are created by
{name},`` in the “Contributors” section.
Authors
-------

View File

@ -0,0 +1,384 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import os
import re
import codecs
from collections import namedtuple
from docutils.parsers.rst import Directive
from docutils.parsers.rst.directives import unchanged_required
from docutils import nodes
from powerline.lib.unicode import u
AUTHOR_LINE_START = '* `'
GLYPHS_AUTHOR_LINE_START = '* The glyphs in the font patcher are created by '
def get_authors():
credits_file = os.path.join(os.path.dirname(__file__), 'license-and-credits.rst')
authors = []
glyphs_author = None
with codecs.open(credits_file, encoding='utf-8') as CF:
section = None
prev_line = None
for line in CF:
line = line[:-1]
if line and not line.replace('-', ''):
section = prev_line
elif section == 'Authors':
if line.startswith(AUTHOR_LINE_START):
authors.append(line[len(AUTHOR_LINE_START):line.index('<')].strip())
elif section == 'Contributors':
if line.startswith(GLYPHS_AUTHOR_LINE_START):
assert(not glyphs_author)
glyphs_author = line[len(GLYPHS_AUTHOR_LINE_START):line.index(',')].strip()
prev_line = line
return {
'authors': ', '.join(authors),
'glyphs_author': glyphs_author,
}
class AutoManSubparsers(object):
def __init__(self):
self.parsers = []
def add_parser(self, command, *args, **kwargs):
self.parsers.append((command, AutoManParser(*args, **kwargs)))
return self.parsers[-1][1]
Argument = namedtuple('Argument', ('names', 'help', 'choices', 'metavar', 'required', 'nargs', 'is_option', 'is_long_option', 'is_short_option', 'multi', 'can_be_joined'))
def parse_argument(*args, **kwargs):
is_option = args[0].startswith('-')
is_long_option = args[0].startswith('--')
is_short_option = is_option and not is_long_option
action = kwargs.get('action', 'store_true')
multi = kwargs.get('action') in ('append',)
nargs = kwargs.get('nargs') or (1 if kwargs.get('metavar') or action in ('append',) else 0)
return Argument(
names=args,
help=u(kwargs.get('help', '')),
choices=[str(choice) for choice in kwargs.get('choices', [])],
metavar=kwargs.get('metavar') or args[-1].lstrip('-').replace('-', '_').upper(),
required=kwargs.get('required', False) if is_option else (
kwargs.get('nargs') not in ('?',)),
nargs=nargs,
multi=multi,
is_option=is_option,
is_long_option=is_long_option,
is_short_option=is_short_option,
can_be_joined=(is_short_option and not multi and not nargs)
)
class AutoManGroup(object):
is_short_option = False
is_option = False
is_long_option = False
can_be_joined = False
def __init__(self):
self.arguments = []
self.required = False
def add_argument(self, *args, **kwargs):
self.arguments.append(parse_argument(*args, **kwargs))
class SurroundWith():
def __init__(self, ret, condition, start='[', end=']'):
self.ret = ret
self.condition = condition
self.start = start
self.end = end
def __enter__(self, *args):
if self.condition:
self.ret.append(nodes.Text(self.start))
def __exit__(self, *args):
if self.condition:
self.ret.append(nodes.Text(self.end))
def insert_separators(ret, sep):
for i in range(len(ret) - 1, 0, -1):
ret.insert(i, nodes.Text(sep))
return ret
def format_usage_arguments(arguments, base_length=None):
line = []
prev_argument = None
arg_indexes = [0]
arguments = arguments[:]
while arguments:
argument = arguments.pop(0)
if isinstance(argument, nodes.Text):
line += [argument]
continue
can_join_arguments = (
argument.is_short_option
and prev_argument
and prev_argument.can_be_joined
and prev_argument.required == argument.required
)
if (
prev_argument
and not prev_argument.required
and prev_argument.can_be_joined
and not can_join_arguments
):
line.append(nodes.Text(']'))
arg_indexes.append(len(line))
if isinstance(argument, AutoManGroup):
arguments = (
[nodes.Text(' (')]
+ insert_separators(argument.arguments[:], nodes.Text(' |'))
+ [nodes.Text(' )')]
+ arguments
)
else:
if not can_join_arguments:
line.append(nodes.Text(' '))
with SurroundWith(line, not argument.required and not argument.can_be_joined):
if argument.can_be_joined and not can_join_arguments and not argument.required:
line.append(nodes.Text('['))
if argument.is_option:
line.append(nodes.strong())
name = argument.names[0]
if can_join_arguments:
name = name[1:]
# `--` is automatically transformed into &#8211; (EN DASH)
# when parsing into HTML. We do not need this.
line[-1] += [nodes.Text(char) for char in name]
if argument.nargs:
assert(argument.nargs in (1, '?'))
with SurroundWith(line, argument.nargs == '?' and argument.is_option):
if argument.is_long_option:
line.append(nodes.Text('='))
line.append(nodes.emphasis(text=argument.metavar))
elif not argument.is_option:
line.append(nodes.strong(text=argument.metavar))
if argument.multi:
line.append(nodes.Text(''))
prev_argument = argument
if (
prev_argument
and prev_argument.can_be_joined
and not prev_argument.required
):
line.append(nodes.Text(']'))
arg_indexes.append(len(line))
ret = []
if base_length is None:
ret = line
else:
length = base_length
prev_arg_idx = arg_indexes.pop(0)
while arg_indexes:
next_arg_idx = arg_indexes.pop(0)
arg_length = sum((len(element.astext()) for element in line[prev_arg_idx:next_arg_idx]))
if length + arg_length > 68:
ret.append(nodes.Text('\n' + (' ' * base_length)))
length = base_length
ret += line[prev_arg_idx:next_arg_idx]
length += arg_length
prev_arg_idx = next_arg_idx
return ret
LITERAL_RE = re.compile(r"`(.*?)'")
def parse_argparse_text(text):
rst_text = LITERAL_RE.subn(r'``\1``', text)[0]
ret = []
for i, text in enumerate(rst_text.split('``')):
if i % 2 == 0:
ret.append(nodes.Text(text))
else:
ret.append(nodes.literal(text=text))
return ret
def flatten_groups(arguments):
for argument in arguments:
if isinstance(argument, AutoManGroup):
for group_argument in flatten_groups(argument.arguments):
yield group_argument
else:
yield argument
def format_arguments(arguments):
return [nodes.definition_list(
'', *[
nodes.definition_list_item(
'',
nodes.term(
# node.Text('') is required because otherwise for some
# reason first name node is seen in HTML output as
# `<strong>abc</strong>`.
'', *([nodes.Text('')] + (
insert_separators([
nodes.strong('', '', *[nodes.Text(ch) for ch in name])
for name in argument.names
], ', ')
if argument.is_option else
# Unless node.Text('') is here metavar is written in
# bold in the man page.
[nodes.Text(''), nodes.emphasis(text=argument.metavar)]
) + (
[] if not argument.is_option or not argument.nargs else
[nodes.Text(' '), nodes.emphasis('', argument.metavar)]
))
),
nodes.definition('', nodes.paragraph('', *parse_argparse_text(argument.help or ''))),
)
for argument in flatten_groups(arguments)
] + [
nodes.definition_list_item(
'',
nodes.term(
'', nodes.Text(''),
nodes.strong(text='-h'),
nodes.Text(', '),
nodes.strong('', '', nodes.Text('-'), nodes.Text('-help')),
),
nodes.definition('', nodes.paragraph('', nodes.Text('Display help and exit.')))
)
]
)]
def format_subcommand_usage(arguments, subcommands, progname, base_length):
return reduce((lambda a, b: a + reduce((lambda c, d: c + d), b, [])), [
[
[progname]
+ format_usage_arguments(arguments)
+ [nodes.Text(' '), nodes.strong(text=subcmd)]
+ format_usage_arguments(subparser.arguments)
+ [nodes.Text('\n')]
for subcmd, subparser in subparsers.parsers
]
for subparsers in subcommands
], [])
def format_subcommands(subcommands):
return reduce((lambda a, b: a + reduce((lambda c, d: c + d), b, [])), [
[
[
nodes.section(
'',
nodes.title(text='Arguments specific to ' + subcmd + ' subcommand'),
*format_arguments(subparser.arguments),
ids=['subcmd-' + subcmd]
)
]
for subcmd, subparser in subparsers.parsers
]
for subparsers in subcommands
], [])
class AutoManParser(object):
def __init__(self, description=None, help=None):
self.description = description
self.help = help
self.arguments = []
self.subcommands = []
def add_argument(self, *args, **kwargs):
self.arguments.append(parse_argument(*args, **kwargs))
def add_subparsers(self):
self.subcommands.append(AutoManSubparsers())
return self.subcommands[-1]
def add_mutually_exclusive_group(self):
self.arguments.append(AutoManGroup())
return self.arguments[-1]
def automan_usage(self, prog):
block = nodes.literal_block()
progname = nodes.strong()
progname += [nodes.Text(prog)]
base_length = len(prog)
if self.subcommands:
block += format_subcommand_usage(self.arguments, self.subcommands, progname, base_length)
else:
block += [progname]
block += format_usage_arguments(self.arguments, base_length)
return [block]
def automan_description(self):
ret = []
if self.help:
ret += parse_argparse_text(self.help)
ret += format_arguments(self.arguments) + format_subcommands(self.subcommands)
return ret
class AutoMan(Directive):
required_arguments = 1
optional_arguments = 0
option_spec = dict(prog=unchanged_required)
has_content = False
def run(self):
module = self.arguments[0]
template_args = {}
template_args.update(get_authors())
get_argparser = __import__(str(module), fromlist=[str('get_argparser')]).get_argparser
parser = get_argparser(AutoManParser)
synopsis_section = nodes.section(
'',
nodes.title(text='Synopsis'),
ids=['synopsis-section'],
)
synopsis_section += parser.automan_usage(self.options['prog'])
description_section = nodes.section(
'', nodes.title(text='Description'),
ids=['description-section'],
)
description_section += parser.automan_description()
author_section = nodes.section(
'', nodes.title(text='Author'),
nodes.paragraph(
'',
nodes.Text('Written by {authors} and contributors. The glyphs in the font patcher are created by {glyphs_author}.'.format(
**get_authors()
))
),
ids=['author-section']
)
issues_url = 'https://github.com/Lokaltog/powerline/issues'
reporting_bugs_section = nodes.section(
'', nodes.title(text='Reporting bugs'),
nodes.paragraph(
'',
nodes.Text('Report {prog} bugs to '.format(
prog=self.options['prog'])),
nodes.reference(
issues_url, issues_url,
refuri=issues_url,
internal=False,
),
nodes.Text('.'),
),
ids=['reporting-bugs-section']
)
return [synopsis_section, description_section, author_section, reporting_bugs_section]
def setup(app):
app.add_directive('automan', AutoMan)

View File

@ -65,6 +65,7 @@ def get_argparser(ArgumentParser=ConfigArgParser):
nargs='?',
choices=('tmux', 'prompt'),
metavar='COMPONENT',
help='Only applicable for `uses\' subcommand: makes `powerline-config\' exit with 0 if specific component is enabled and with 1 otherwise. `tmux\' component stands for tmux bindings (e.g. those that notify tmux about current directory changes), `prompt\' component stands for shell prompt.'
)
shell_parser.add_argument(
'-s', '--shell',

View File

@ -41,7 +41,7 @@ def get_argparser(ArgumentParser=argparse.ArgumentParser):
parser.add_argument('--jobnum', metavar='INT', type=int, help='Number of jobs.')
parser.add_argument('-c', '--config', metavar='KEY.KEY=VALUE', action='append', help='Configuration overrides for `config.json\'. Is translated to a dictionary and merged with the dictionary obtained from actual JSON configuration: KEY.KEY=VALUE is translated to `{"KEY": {"KEY": VALUE}}\' and then merged recursively. VALUE may be any JSON value, values that are not `null\', `true\', `false\', start with digit, `{\', `[\' are treated like strings. If VALUE is omitted then corresponding key is removed.')
parser.add_argument('-t', '--theme_option', metavar='THEME.KEY.KEY=VALUE', action='append', help='Like above, but theme-specific. THEME should point to an existing and used theme to have any effect, but it is fine to use any theme here.')
parser.add_argument('-R', '--renderer_arg', metavar='KEY=VAL', action='append', help='Like above, but provides argument for renderer. Is supposed to be used only by shell bindings to provide various data like last_exit_code or last_pipe_status (they are not using --renderer_arg for historical resons: renderer_arg was added later).')
parser.add_argument('-R', '--renderer_arg', metavar='KEY=VAL', action='append', help='Like above, but provides argument for renderer. Is supposed to be used only by shell bindings to provide various data like last_exit_code or last_pipe_status (they are not using `--renderer_arg\' for historical resons: `--renderer_arg\' was added later).')
parser.add_argument('-p', '--config_path', action='append', metavar='PATH', help='Path to configuration directory. If it is present then configuration files will only be seeked in the provided path. May be provided multiple times to search in a list of directories.')
parser.add_argument('--socket', metavar='ADDRESS', type=str, help='Socket address to use in daemon clients. Is always UNIX domain socket on linux and file socket on Mac OS X. Not used here, present only for compatibility with other powerline clients. This argument must always be the first one and be in a form `--socket ADDRESS\': no `=\' or short form allowed (in other powerline clients, not here).')
return parser