qubes: Documentation for qubes.tools
And first documentation for qubes.tools.qvm_ls.
This commit is contained in:
parent
e50ca5131f
commit
142521317c
@ -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
32
doc/qubes-tools/index.rst
Normal 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
|
8
doc/qubes-tools/qvm_ls.rst
Normal file
8
doc/qubes-tools/qvm_ls.rst
Normal 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
20
doc/skel-manpage.py
Executable 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()
|
@ -81,6 +81,9 @@ class QubesException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
import qubes.events
|
||||
|
||||
|
||||
class VMMConnection(object):
|
||||
'''Connection to Virtual Machine Manager (libvirt)'''
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user