Merge remote-tracking branch 'woju/pull/12/head' into core3-devel

Conflicts:
    doc/manpages/qvm-kill.rst
This commit is contained in:
Wojtek Porczyk 2016-05-25 11:01:19 +02:00
commit 0484be518c
7 changed files with 207 additions and 51 deletions

View File

@ -6,7 +6,7 @@
Synopsis Synopsis
-------- --------
:command:`qubes-prefs` [-h] [--xml *XMLFILE*] [--verbose] [--quiet] [--force-root] [--help-properties] [*PROPERTY* [*VALUE*\|--delete]] :command:`qubes-prefs` [-h] [--verbose] [--quiet] [--force-root] [--help-properties] [*PROPERTY* [*VALUE*\|--delete]]
Options Options
------- -------
@ -19,10 +19,6 @@ Options
List available properties with short descriptions and exit. List available properties with short descriptions and exit.
.. option:: --qubesxml=XMLFILE
Qubes OS store file.
.. option:: --verbose, -v .. option:: --verbose, -v
Increase verbosity. Increase verbosity.

View File

@ -6,7 +6,7 @@
Synopsis Synopsis
-------- --------
:command:`qvm-create` [-h] [--qubesxml *XMLFILE*] [--force-root] [--class *CLS*] [--property *NAME*=*VALUE*] [--template *VALUE*] [--label *VALUE*] [--root-copy-from *FILENAME* | --root-move-from *FILENAME*] *VMNAME* :command:`qvm-create` [-h] [--verbose] [--quiet] [--force-root] [--class *CLS*] [--property *NAME*=*VALUE*] [--pool *POOL_NAME:VOLUME_NAME*] [--template *VALUE*] --label *VALUE* [--root-copy-from *FILENAME* | --root-move-from *FILENAME*] *VMNAME*
Options Options
------- -------
@ -15,9 +15,13 @@ Options
show help message and exit show help message and exit
.. option:: --qubesxml=XMLFILE .. option:: --verbose, -v
Qubes OS store file Increase verbosity.
.. option:: --quiet, -q
Decrease verbosity.
.. option:: --force-root .. option:: --force-root

View File

@ -6,8 +6,7 @@
Synopsis Synopsis
-------- --------
:command:`qvm-ls` [*options*] :command:`qvm-ls` [-h] [--verbose] [--quiet] [--help-columns] [--help-formats] [--format *FORMAT* | --fields *FIELD*,...]
Options Options
------- -------
@ -35,10 +34,13 @@ Options
:option:`--format`. All columns along with short descriptions can be listed :option:`--format`. All columns along with short descriptions can be listed
with :option:`--help-columns`. with :option:`--help-columns`.
.. option:: --qubesxml=XMLFILE .. option:: --verbose, -v
Qubes store file Increase verbosity.
.. option:: --quiet, -q
Decrease verbosity.
Authors Authors
------- -------

View File

@ -6,7 +6,7 @@
Synopsis Synopsis
-------- --------
:command:`qvm-prefs` qvm-prefs [-h] [--xml *XMLFILE*] [--verbose] [--quiet] [--force-root] [--help-properties] *VMNAME* [*PROPERTY* [*VALUE*\|--delete]] :command:`qvm-prefs` qvm-prefs [-h] [--verbose] [--quiet] [--force-root] [--help-properties] *VMNAME* [*PROPERTY* [*VALUE* \| --delete \| --default ]]
Options Options
------- -------
@ -19,10 +19,6 @@ Options
List available properties with short descriptions and exit. List available properties with short descriptions and exit.
.. option:: --qubesxml=XMLFILE
Qubes OS store file.
.. option:: --verbose, -v .. option:: --verbose, -v
Increase verbosity. Increase verbosity.

View File

@ -24,10 +24,6 @@ Options
Show help message and exit. Show help message and exit.
.. option:: --qubesxml=XMLFILE
Use another :file:`qubes.xml` file.
.. option:: --verbose, -v .. option:: --verbose, -v
Increase verbosity. Increase verbosity.
@ -65,6 +61,10 @@ Options
Do actions necessary when preparing DVM image. Do actions necessary when preparing DVM image.
.. option:: --skip-if-running
Do not fail if the qube is already runnning
.. option:: --no-start-guid .. option:: --no-start-guid
Do not start GUI daemon. Do not start GUI daemon.

View File

@ -28,6 +28,7 @@ This module contains classes and functions which help to maintain documentation,
particularly our custom Sphinx extension. particularly our custom Sphinx extension.
''' '''
import argparse
import csv import csv
import os import os
import posixpath import posixpath
@ -40,12 +41,14 @@ import docutils.nodes
import docutils.parsers.rst import docutils.parsers.rst
import docutils.parsers.rst.roles import docutils.parsers.rst.roles
import docutils.statemachine import docutils.statemachine
import qubes.tools
import sphinx import sphinx
import sphinx.errors import sphinx.errors
import sphinx.locale import sphinx.locale
import sphinx.util.docfields import sphinx.util.docfields
import qubes.tools SUBCOMMANDS_TITLE = 'COMMANDS'
OPTIONS_TITLE = 'OPTIONS'
def fetch_ticket_info(uri): def fetch_ticket_info(uri):
@ -209,39 +212,24 @@ def prepare_manpage(command):
return stream.getvalue() return stream.getvalue()
class ArgumentCheckVisitor(docutils.nodes.SparseNodeVisitor): class OptionsCheckVisitor(docutils.nodes.SparseNodeVisitor):
def __init__(self, app, command, document): ''' Checks if the visited option nodes and the specified args are in sync.
'''
def __init__(self, command, args, document):
assert isinstance(args, set)
docutils.nodes.SparseNodeVisitor.__init__(self, document) docutils.nodes.SparseNodeVisitor.__init__(self, document)
self.app = app
self.command = command self.command = command
self.args = set() self.args = args
try:
parser = qubes.tools.get_parser_for_command(command)
except ImportError:
self.app.warn('cannot import module for command')
self.command = None
return
except AttributeError:
raise sphinx.errors.SphinxError('cannot find parser in module')
# pylint: disable=protected-access
for action in parser._actions:
self.args.update(action.option_strings)
# pylint: disable=no-self-use,unused-argument
def visit_desc(self, node): def visit_desc(self, node):
''' Skips all but 'option' elements '''
# pylint: disable=no-self-use
if not node.get('desctype', None) == 'option': if not node.get('desctype', None) == 'option':
raise docutils.nodes.SkipChildren raise docutils.nodes.SkipChildren
def visit_desc_name(self, node): def visit_desc_name(self, node):
if self.command is None: ''' Checks if the option is defined `self.args` '''
return
if not isinstance(node[0], docutils.nodes.Text): if not isinstance(node[0], docutils.nodes.Text):
raise sphinx.errors.SphinxError('first child should be Text') raise sphinx.errors.SphinxError('first child should be Text')
@ -252,18 +240,149 @@ class ArgumentCheckVisitor(docutils.nodes.SparseNodeVisitor):
raise sphinx.errors.SphinxError( raise sphinx.errors.SphinxError(
'No such argument for {!r}: {!r}'.format(self.command, arg)) 'No such argument for {!r}: {!r}'.format(self.command, arg))
def check_undocumented_arguments(self, ignored_options=set()):
''' Call this to check if any undocumented arguments are left.
def depart_document(self, node): While the documentation talks about a
if self.args: 'SparseNodeVisitor.depart_document()' function, this function does
not exists. (For details see implementation of
:py:method:`NodeVisitor.dispatch_departure()`) So we need to
manually call this.
'''
left_over_args = self.args - ignored_options
if left_over_args:
raise sphinx.errors.SphinxError( raise sphinx.errors.SphinxError(
'Undocumented arguments: {!r}'.format( 'Undocumented arguments for command {!r}: {!r}'.format(
', '.join(sorted(self.args)))) self.command, ', '.join(sorted(left_over_args))))
class CommandCheckVisitor(docutils.nodes.SparseNodeVisitor):
''' Checks if the visited sub command section nodes and the specified sub
command args are in sync.
'''
def __init__(self, command, sub_commands, document):
docutils.nodes.SparseNodeVisitor.__init__(self, document)
self.command = command
self.sub_commands = sub_commands
def visit_section(self, node):
''' Checks if the visited sub-command section nodes exists and it
options are in sync.
Uses :py:class:`OptionsCheckVisitor` for checking
sub-commands options
'''
# pylint: disable=no-self-use
title = str(node[0][0])
if title.upper() == SUBCOMMANDS_TITLE:
return
sub_cmd = self.command + ' ' + title
try:
args = self.sub_commands[title]
options_visitor = OptionsCheckVisitor(sub_cmd, args, self.document)
node.walkabout(options_visitor)
options_visitor.check_undocumented_arguments(
{'--help', '--quiet', '--verbose', '-h', '-q', '-v'})
del self.sub_commands[title]
except KeyError:
raise sphinx.errors.SphinxError(
'No such sub-command {!r}'.format(sub_cmd))
def visit_Text(self, node):
''' If the visited text node starts with 'alias: ', all the provided
comma separted alias in this node, are removed from
`self.sub_commands`
'''
# pylint: disable=invalid-name
text = str(node).strip()
if text.startswith('aliases:'):
aliases = {a.strip() for a in text.split('aliases:')[1].split(',')}
for alias in aliases:
assert alias in self.sub_commands
del self.sub_commands[alias]
def check_undocumented_sub_commands(self):
''' Call this to check if any undocumented sub_commands are left.
While the documentation talks about a
'SparseNodeVisitor.depart_document()' function, this function does
not exists. (For details see implementation of
:py:method:`NodeVisitor.dispatch_departure()`) So we need to
manually call this.
'''
if self.sub_commands:
raise sphinx.errors.SphinxError(
'Undocumented commands for {!r}: {!r}'.format(
self.command, ', '.join(sorted(self.sub_commands.keys()))))
class ManpageCheckVisitor(docutils.nodes.SparseNodeVisitor):
''' Checks if the sub-commands and options specified in the 'COMMAND' and
'OPTIONS' (case insensitve) sections in sync the command parser.
'''
def __init__(self, app, command, document):
docutils.nodes.SparseNodeVisitor.__init__(self, document)
try:
parser = qubes.tools.get_parser_for_command(command)
except ImportError:
app.warn('cannot import module for command')
self.parser = None
return
except AttributeError:
raise sphinx.errors.SphinxError('cannot find parser in module')
self.command = command
self.parser = parser
self.options = set()
self.sub_commands = {}
self.app = app
# pylint: disable=protected-access
for action in parser._actions:
if action.help == argparse.SUPPRESS:
continue
if issubclass(action.__class__,
qubes.tools.AliasedSubParsersAction):
for cmd, cmd_parser in action._name_parser_map.items():
self.sub_commands[cmd] = set()
for sub_action in cmd_parser._actions:
if sub_action.help != argparse.SUPPRESS:
self.sub_commands[cmd].update(
sub_action.option_strings)
else:
self.options.update(action.option_strings)
def visit_section(self, node):
''' If section title is OPTIONS or COMMANDS dispatch the apropriate
`NodeVisitor`.
'''
if self.parser is None:
return
section_title = str(node[0][0]).upper()
if section_title == OPTIONS_TITLE:
options_visitor = OptionsCheckVisitor(self.command, self.options,
self.document)
node.walkabout(options_visitor)
options_visitor.check_undocumented_arguments()
elif section_title == SUBCOMMANDS_TITLE:
sub_cmd_visitor = CommandCheckVisitor(
self.command, self.sub_commands, self.document)
node.walkabout(sub_cmd_visitor)
sub_cmd_visitor.check_undocumented_sub_commands()
def check_man_args(app, doctree, docname): def check_man_args(app, doctree, docname):
''' Checks the manpage for undocumented or obsolete sub-commands and
options.
'''
command = os.path.split(docname)[1] command = os.path.split(docname)[1]
app.info('Checking arguments for {!r}'.format(command)) app.info('Checking arguments for {!r}'.format(command))
doctree.walk(ArgumentCheckVisitor(app, command, doctree)) doctree.walk(ManpageCheckVisitor(app, command, doctree))
# #

View File

@ -319,6 +319,45 @@ class QubesArgumentParser(argparse.ArgumentParser):
print(*args, file=sys.stderr, **kwargs) print(*args, file=sys.stderr, **kwargs)
class AliasedSubParsersAction(argparse._SubParsersAction):
# source https://gist.github.com/sampsyo/471779
# pylint: disable=protected-access,too-few-public-methods
class _AliasedPseudoAction(argparse.Action):
# pylint: disable=redefined-builtin
def __init__(self, name, aliases, help):
dest = name
if aliases:
dest += ' (%s)' % ','.join(aliases)
sup = super(AliasedSubParsersAction._AliasedPseudoAction, self)
sup.__init__(option_strings=[], dest=dest, help=help)
def __call__(self, **kwargs):
super(AliasedSubParsersAction._AliasedPseudoAction, self).__call__(
**kwargs)
def add_parser(self, name, **kwargs):
if 'aliases' in kwargs:
aliases = kwargs['aliases']
del kwargs['aliases']
else:
aliases = []
local_parser = super(AliasedSubParsersAction, self).add_parser(
name, **kwargs)
# Make the aliases work.
for alias in aliases:
self._name_parser_map[alias] = local_parser
# Make the help text reflect them, first removing old help entry.
if 'help' in kwargs:
self._choices_actions.pop()
pseudo_action = self._AliasedPseudoAction(name, aliases,
kwargs.pop('help'))
self._choices_actions.append(pseudo_action)
return local_parser
def get_parser_for_command(command): def get_parser_for_command(command):
'''Get parser for given qvm-tool. '''Get parser for given qvm-tool.