qubes: Documentation for qubes.tools

And first documentation for qubes.tools.qvm_ls.
This commit is contained in:
Wojtek Porczyk 2015-01-26 17:33:11 +01:00
parent e50ca5131f
commit 142521317c
7 changed files with 246 additions and 2 deletions

View File

@ -19,6 +19,7 @@ manpages and API documentation. For primary user documentation, see
qubes-plugins qubes-plugins
qubes-ext qubes-ext
qubes-log qubes-log
qubes-tools/index
qubes-tests qubes-tests
qubes-dochelpers qubes-dochelpers

32
doc/qubes-tools/index.rst Normal file
View File

@ -0,0 +1,32 @@
:py:mod:`qubes.tools` -- Command line utilities
===============================================
Those are Python modules that house actual functionality of CLI tools -- the
files installed in :file:`/usr/bin` only import these modules and run ``main()``
function.
The modules should make available for import theirs command line parsers
(instances of :py:class:`argparse.ArgumentParser`) as either ``.parser``
attribute or function ``get_parser()``, which returns parser. Manual page will
be automatically checked during generation if its "Options" section contains all
options from this parser (and only those).
Module contents
---------------
.. automodule:: qubes.tools
:members:
:show-inheritance:
All CLI tools
-------------
.. toctree::
:maxdepth: 1
:glob:
*
.. vim: ts=3 sw=3 et tw=80

View File

@ -0,0 +1,8 @@
:py:mod:`qubes.tools.qvm_ls` -- VM listing
==========================================
.. automodule:: qubes.tools.qvm_ls
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et

20
doc/skel-manpage.py Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/python
import os
import sys
sys.path.insert(0, os.path.abspath('../'))
import argparse
import qubes.dochelpers
parser = argparse.ArgumentParser(description='prepare new manpage for command')
parser.add_argument('command', metavar='COMMAND',
help='program\'s command name; this should translate to '
'qubes.tools.<command_name>')
def main():
args = parser.parse_args()
sys.stdout.write(qubes.dochelpers.prepare_manpage(args.command))
if __name__ == '__main__':
main()

View File

@ -81,6 +81,9 @@ class QubesException(Exception):
pass pass
import qubes.events
class VMMConnection(object): class VMMConnection(object):
'''Connection to Virtual Machine Manager (libvirt)''' '''Connection to Virtual Machine Manager (libvirt)'''

View File

@ -29,8 +29,10 @@ particularly our custom Sphinx extension.
''' '''
import csv import csv
import os
import posixpath import posixpath
import re import re
import StringIO
import urllib2 import urllib2
import docutils import docutils
@ -39,9 +41,13 @@ import docutils.parsers.rst
import docutils.parsers.rst.roles import docutils.parsers.rst.roles
import docutils.statemachine import docutils.statemachine
import sphinx import sphinx
import sphinx.errors
import sphinx.locale import sphinx.locale
import sphinx.util.docfields import sphinx.util.docfields
import qubes.tools
def fetch_ticket_info(uri): def fetch_ticket_info(uri):
'''Fetch info about particular trac ticket given '''Fetch info about particular trac ticket given
@ -75,7 +81,7 @@ def ticket(name, rawtext, text, lineno, inliner, options=None, content=None):
options = {} options = {}
ticketno = text.lstrip('#') ticketno = text.lstrip('#')
if not ticket.isdigit(): if not ticketno.isdigit():
msg = inliner.reporter.error( msg = inliner.reporter.error(
'Invalid ticket identificator: {!r}'.format(text), line=lineno) 'Invalid ticket identificator: {!r}'.format(text), line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg) prb = inliner.problematic(rawtext, rawtext, msg)
@ -147,6 +153,119 @@ class VersionCheck(docutils.parsers.rst.Directive):
return [node] return [node]
def make_rst_section(heading, char):
return '{}\n{}\n\n'.format(heading, char[0] * len(heading))
def prepare_manpage(command):
parser = qubes.tools.get_parser_for_command(command)
stream = StringIO.StringIO()
stream.write('.. program:: {}\n\n'.format(command))
stream.write(make_rst_section(
':program:`{}` -- {}'.format(command, parser.description), '='))
stream.write('''.. warning::
This page was autogenerated from command-line parser. It shouldn't be 1:1
conversion, because it would add little value. Please revise it and add
more descriptive help, which normally won't fit in standard ``--help``
option.
After rewrite, please remove this admonition.\n\n''')
stream.write(make_rst_section('Synopsis', '-'))
usage = ' '.join(parser.format_usage().strip().split())
if usage.startswith('usage: '):
usage = usage[len('usage: '):]
# replace METAVARS with *METAVARS*
usage = re.sub(r'\b([A-Z]{2,})\b', r'*\1*', usage)
stream.write(':command:`{}` {}\n\n'.format(command, usage))
stream.write(make_rst_section('Options', '-'))
for action in parser._actions: # pylint: disable=protected-access
stream.write('.. option:: ')
if action.metavar:
stream.write(', '.join('{}{}{}'.format(
option,
'=' if option.startswith('--') else ' ',
action.metavar)
for option in sorted(action.option_strings)))
else:
stream.write(', '.join(sorted(action.option_strings)))
stream.write('\n\n {}\n\n'.format(action.help))
stream.write(make_rst_section('Authors', '-'))
stream.write('''\
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
| Wojtek Porczyk <woju at invisiblethingslab dot com>
.. vim: ts=3 sw=3 et tw=80
''')
return stream.getvalue()
class ArgumentCheckVisitor(docutils.nodes.SparseNodeVisitor):
def __init__(self, app, command, document):
docutils.nodes.SparseNodeVisitor.__init__(self, document)
self.app = app
self.command = command
self.args = set()
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):
if not node.get('desctype', None) == 'option':
raise docutils.nodes.SkipChildren
def visit_desc_name(self, node):
if self.command is None:
return
if not isinstance(node[0], docutils.nodes.Text):
raise sphinx.errors.SphinxError('first child should be Text')
arg = str(node[0])
try:
self.args.remove(arg)
except KeyError:
raise sphinx.errors.SphinxError(
'No such argument for {!r}: {!r}'.format(self.command, arg))
def depart_document(self, node):
if self.args:
raise sphinx.errors.SphinxError(
'Undocumented arguments: {!r}'.format(
', '.join(sorted(self.args))))
def check_man_args(app, doctree, docname):
command = os.path.split(docname)[1]
app.info('Checking arguments for {!r}'.format(command))
doctree.walk(ArgumentCheckVisitor(app, command, doctree))
# #
# this is lifted from sphinx' own conf.py # this is lifted from sphinx' own conf.py
# #
@ -172,10 +291,19 @@ def parse_event(env, sig, signode):
# end of codelifting # end of codelifting
# #
def break_to_pdb(app, *dummy):
if not app.config.break_to_pdb:
return
import pdb
pdb.set_trace()
def setup(app): def setup(app):
app.add_role('ticket', ticket) app.add_role('ticket', ticket)
app.add_config_value('ticket_base_uri', app.add_config_value('ticket_base_uri',
'https://wiki.qubes-os.org/ticket/', 'env') 'https://wiki.qubes-os.org/ticket/', 'env')
app.add_config_value('break_to_pdb', False, 'env')
app.add_node(versioncheck, app.add_node(versioncheck,
html=(visit, depart), html=(visit, depart),
man=(visit, depart)) man=(visit, depart))
@ -186,5 +314,8 @@ def setup(app):
app.add_object_type('event', 'event', 'pair: %s; event', parse_event, app.add_object_type('event', 'event', 'pair: %s; event', parse_event,
doc_field_types=[fdesc]) doc_field_types=[fdesc])
app.connect('doctree-resolved', break_to_pdb)
app.connect('doctree-resolved', check_man_args)
# vim: ts=4 sw=4 et # vim: ts=4 sw=4 et

View File

@ -1 +1,50 @@
# pylint: skip-file #!/usr/bin/python2 -O
# vim: fileencoding=utf-8
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
'''Qubes' command line tools
'''
import importlib
def get_parser_for_command(command):
'''Get parser for given qvm-tool.
:param str command: command name
:rtype: argparse.ArgumentParser
:raises ImportError: when command's module is not found
:raises AttributeError: when parser was not found
'''
module = importlib.import_module(
'.' + command.replace('-', '_'), 'qubes.tools')
try:
parser = module.parser
except AttributeError:
try:
parser = module.get_parser()
except AttributeError:
raise AttributeError('cannot find parser in module')
return parser