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-ext
qubes-log
qubes-tools/index
qubes-tests
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
import qubes.events
class VMMConnection(object):
'''Connection to Virtual Machine Manager (libvirt)'''

View File

@ -29,8 +29,10 @@ particularly our custom Sphinx extension.
'''
import csv
import os
import posixpath
import re
import StringIO
import urllib2
import docutils
@ -39,9 +41,13 @@ import docutils.parsers.rst
import docutils.parsers.rst.roles
import docutils.statemachine
import sphinx
import sphinx.errors
import sphinx.locale
import sphinx.util.docfields
import qubes.tools
def fetch_ticket_info(uri):
'''Fetch info about particular trac ticket given
@ -75,7 +81,7 @@ def ticket(name, rawtext, text, lineno, inliner, options=None, content=None):
options = {}
ticketno = text.lstrip('#')
if not ticket.isdigit():
if not ticketno.isdigit():
msg = inliner.reporter.error(
'Invalid ticket identificator: {!r}'.format(text), line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
@ -147,6 +153,119 @@ class VersionCheck(docutils.parsers.rst.Directive):
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
#
@ -172,10 +291,19 @@ def parse_event(env, sig, signode):
# 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):
app.add_role('ticket', ticket)
app.add_config_value('ticket_base_uri',
'https://wiki.qubes-os.org/ticket/', 'env')
app.add_config_value('break_to_pdb', False, 'env')
app.add_node(versioncheck,
html=(visit, depart),
man=(visit, depart))
@ -186,5 +314,8 @@ def setup(app):
app.add_object_type('event', 'event', 'pair: %s; event', parse_event,
doc_field_types=[fdesc])
app.connect('doctree-resolved', break_to_pdb)
app.connect('doctree-resolved', check_man_args)
# 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