Skip to content

Commit

Permalink
Merge pull request #19 from enkore/docs
Browse files Browse the repository at this point in the history
docs: generate usage and some smaller stuff
  • Loading branch information
enkore committed Feb 18, 2017
2 parents 33714e5 + 5b2d033 commit 671d33a
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 17 deletions.
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,4 +1,4 @@
Copyright (C) 2016 The Borg Collective (see AUTHORS file)
Copyright (C) 2016-2017 The Borg Collective (see AUTHORS file)
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
4 changes: 4 additions & 0 deletions docs/authors.rst
@@ -1,5 +1,9 @@
.. include:: global.rst.inc

=================
Authors & License
=================

.. include:: ../AUTHORS

License
Expand Down
8 changes: 5 additions & 3 deletions docs/conf.py
Expand Up @@ -18,8 +18,9 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import sys

sys.path.insert(0, os.path.abspath('.'))

on_rtd = os.environ.get('READTHEDOCS', None) == 'True'

Expand All @@ -34,6 +35,7 @@
# ones.
extensions = [
'sphinx.ext.autodoc',
'usage',
]

# Add any paths that contain templates here, relative to this directory.
Expand All @@ -54,7 +56,7 @@

# General information about the project.
project = 'Borg - Import'
copyright = '2016 The Borg Collective (see AUTHORS file)'
copyright = '2016-2017 The Borg Collective (see AUTHORS file)'
author = 'The Borg Collective'

# The version info for the project you're documenting, acts as replacement for
Expand Down
1 change: 0 additions & 1 deletion docs/index.rst
@@ -1,6 +1,5 @@
.. include:: global.rst.inc


Borg-Import Documentation
=========================

Expand Down
109 changes: 109 additions & 0 deletions docs/usage.py
@@ -0,0 +1,109 @@
from textwrap import dedent, indent

import sphinx.application
from docutils.parsers.rst import Directive
from docutils import nodes
from docutils.statemachine import StringList
from sphinx.util.nodes import nested_parse_with_titles

from borg_import.main import build_parser


class GenerateUsageDirective(Directive):
required_arguments = 1
has_content = False

@staticmethod
def _get_command_parser(parser, command):
for action in parser._actions:
if action.choices is not None and 'SubParsersAction' in str(action.__class__):
return action.choices[command]
raise ValueError('No parser for %s found' % command)

def write_options_group(self, group, contents, with_title=True):
def is_positional_group(group):
return any(not o.option_strings for o in group._group_actions)

def get_help(option):
text = dedent((option.help or '') % option.__dict__)
return '\n'.join('| ' + line for line in text.splitlines())

def shipout(text):
for line in text:
contents.append(indent(line, ' ' * 4))

if not group._group_actions:
return

if with_title:
contents.append(group.title)
text = []

if is_positional_group(group):
for option in group._group_actions:
text.append(option.metavar)
text.append(indent(option.help or '', ' ' * 4))
shipout(text)
return

options = []
for option in group._group_actions:
if option.metavar:
option_fmt = '``%%s %s``' % option.metavar
else:
option_fmt = '``%s``'
option_str = ', '.join(option_fmt % s for s in option.option_strings)
options.append((option_str, option))
for option_str, option in options:
help = indent(get_help(option), ' ' * 4)
text.append(option_str)
text.append(help)

text.append("")
shipout(text)

def run(self):
command = self.arguments[0]
parser = self._get_command_parser(build_parser(), command)

full_command = 'borg-import ' + command
headline = '::\n\n ' + full_command

if any(len(o.option_strings) for o in parser._actions):
headline += ' <options>'

# Add the metavars of the parameters to the synopsis line
for option in parser._actions:
if not option.option_strings:
headline += ' ' + option.metavar

headline += '\n\n'

# Final result will look like:
# borg-import something <options> FOO_BAR REPOSITORY
contents = headline.splitlines()

for group in parser._action_groups:
self.write_options_group(group, contents)

if parser.epilog:
contents.append('Description')
contents.append('~~~~~~~~~~~')
contents.append('')

node = nodes.paragraph()
nested_parse_with_titles(self.state, StringList(contents), node)
gen_nodes = [node]

if parser.epilog:
paragraphs = parser.epilog.split('\n\n')
for paragraph in paragraphs:
node = nodes.paragraph()
nested_parse_with_titles(self.state, StringList(paragraph.split('\n')), node)
gen_nodes.append(node)

return gen_nodes


def setup(app: sphinx.application.Sphinx):
app.add_directive('generate-usage', GenerateUsageDirective)
12 changes: 8 additions & 4 deletions docs/usage.rst
Expand Up @@ -5,12 +5,16 @@
Usage
=====

|project_name| consists of a number of commands. Each command accepts
a number of arguments and options. The following sections will describe each
command in detail.
``borg-import`` consists of a number of commands, one for each
backup system supported. Each accepts a number of arguments and
options. The following sections will describe each in detail.

General
-------

...
.. _rsnapshot:

borg-import rsnapshot
---------------------

.. generate-usage:: rsnapshot
39 changes: 31 additions & 8 deletions src/borg_import/main.py
Expand Up @@ -4,6 +4,7 @@
import shlex
import subprocess
import sys
import textwrap
from pathlib import Path

from .rsnapshots import get_snapshots
Expand Down Expand Up @@ -43,6 +44,7 @@ def list_borg_archives(args):
class Importer:
name = 'name-of-command'
description = 'descriptive description describing this importer'
epilog = 'epilog-y epilog epiloging about this importer (docstringy for multiple lines)'

def populate_parser(self, parser):
"""
Expand All @@ -62,14 +64,27 @@ def import_something(self, args):
class rsnapshotImporter(Importer):
name = 'rsnapshot'
description = 'import rsnapshot backups'
epilog = """
Imports from rsnapshot backup sets by renaming each snapshot
to a common name independent of the snapshot (and the backup set),
which allows the Borg files cache to work with maximum efficiency.
The directory is called "borg-import-dir" inside the rsnapshot root,
and borg-import will note which snapshot is currently located there
in a file called "borg-import-dir.snapshot" besides it, in case
things go wrong.
Otherwise nothing in the rsnapshot root is modified, and neither
are the contents of the snapshots.
"""

def populate_parser(self, parser):
parser.add_argument('--backup-set', help='Only consider given backup set (can be given multiple times).',
action='append', dest='backup_sets')
parser.add_argument('rsnapshot_root', metavar='RSNAPSHOT_ROOT',
help='Path to rsnapshot root directory', type=Path)
# TODO: support the full wealth of borg possibilities
parser.add_argument('repository', metavar='REPOSITORY', help='Borg repository', type=Path)
parser.add_argument('repository', metavar='BORG_REPOSITORY', help='Borg repository', type=Path)
parser.set_defaults(function=self.import_rsnapshot)

def import_rsnapshot(self, args):
Expand Down Expand Up @@ -119,12 +134,7 @@ def import_rsnapshot(self, args):
import_journal.unlink()


def main():
if not shutil.which('borg'):
print('The \'borg\' command can\'t be found in the PATH. Please correctly install borgbackup first.')
print('See instructions at https://borgbackup.readthedocs.io/en/stable/installation.html')
return 1

def build_parser():
common_parser = argparse.ArgumentParser(add_help=False)
common_group = common_parser.add_argument_group('Common options')

Expand All @@ -143,9 +153,22 @@ def main():

for importer_class in Importer.__subclasses__():
importer = importer_class()
subparser = subparsers.add_parser(importer.name, help=importer.description, parents=[common_parser])
subparser = subparsers.add_parser(importer.name,
help=importer.description, epilog=textwrap.dedent(importer.epilog),
formatter_class=argparse.RawDescriptionHelpFormatter,
parents=[common_parser])
importer.populate_parser(subparser)

return parser


def main():
if not shutil.which('borg'):
print('The \'borg\' command can\'t be found in the PATH. Please correctly install borgbackup first.')
print('See instructions at https://borgbackup.readthedocs.io/en/stable/installation.html')
return 1

parser = build_parser()
args = parser.parse_args()
logging.basicConfig(level=args.log_level, format='%(message)s')

Expand Down

0 comments on commit 671d33a

Please sign in to comment.