From f827b99ffe960c46d8ff71325f1444f3d460f82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Borgstr=C3=B6m?= Date: Fri, 5 Jul 2013 12:32:56 +0200 Subject: [PATCH] Improved documentation. --- COPYING => LICENSE | 0 README => README.rst | 33 ++--- darc/archiver.py | 101 +++++++++------ docs/Makefile | 3 +- docs/detailedusage.rst | 11 ++ docs/generalusage.rst | 3 +- docs/index.rst | 5 +- docs/{definitions.rst => terminology.rst} | 4 +- docs/update_usage.sh | 6 + docs/usage.rst | 147 ++++++++++++++++++++++ setup.py | 5 +- 11 files changed, 260 insertions(+), 58 deletions(-) rename COPYING => LICENSE (100%) rename README => README.rst (66%) create mode 100644 docs/detailedusage.rst rename docs/{definitions.rst => terminology.rst} (96%) create mode 100755 docs/update_usage.sh create mode 100644 docs/usage.rst diff --git a/COPYING b/LICENSE similarity index 100% rename from COPYING rename to LICENSE diff --git a/README b/README.rst similarity index 66% rename from README rename to README.rst index 1f45ddd71..47a900f44 100644 --- a/README +++ b/README.rst @@ -1,14 +1,19 @@ -.. -*- restructuredtext -*- - - What is darc? -============= +------------- Darc is a Deduplicating ARChiver written in Python. The main goal of darc is to provide an efficient and secure way to backup data. The data deduplication -technique used makes darc suitable for taking daily backups. +technique used makes darc suitable for daily backups since only actual changes +are stored. + +Easy to use +~~~~~~~~~~~ +Initialze backup repository and create a backup archive:: + + $ darc init /usbdrive/my-backup.darc + $ darc create -v /usbdrive/my-backup.darc::documents ~/Documents Main features -------------- +~~~~~~~~~~~~~ Space efficient storage Variable block size deduplication is used to reduce the number of bytes stored by detecting redundant data. Each file is split into a number of @@ -24,28 +29,28 @@ Off-site backups darc is installed. What do I need? -=============== +--------------- Darc requires Python 3.2 or above to work. Besides Python darc also requires msgpack-python and sufficiently recent OpenSSL (>= 1.0.0). How do I install it? -==================== +-------------------- :: $ python setup.py install Where are the docs? -=================== -Go to http://pythonhosted.org/darc/ for a prebuilt version of the docs. You +------------------- +Go to https://pythonhosted.org/darc/ for a prebuilt version of the docs. You can also build them yourself form the docs folder. Where are the tests? -==================== +-------------------- The tests are in the darc/testsuite package. To run the test suite use the following command:: $ python -m darc.testsuite.run Where can I get help? -===================== - -Send wishes, comments, patches, etc. to jonas@borgstrom.se +--------------------- +Send questions, comments, patches, etc. to jonas@borgstrom.se. Issues and +pull requests can also be created on https://github.com/jborg/darc diff --git a/darc/archiver.py b/darc/archiver.py index 11b079ad9..7bf99520e 100644 --- a/darc/archiver.py +++ b/darc/archiver.py @@ -46,6 +46,8 @@ class Archiver: return RepositoryServer().serve() def do_init(self, args): + """Initialize a new repository + """ print('Initializing repository at "%s"' % args.repository.orig) repository = self.open_repository(args.repository, create=True) key = key_creator(repository, args) @@ -57,12 +59,16 @@ class Archiver: return self.exit_code def do_change_passphrase(self, args): + """Change passphrase on repository key file + """ repository = self.open_repository(Location(args.repository)) manifest, key = Manifest.load(repository) key.change_passphrase() return self.exit_code def do_create(self, args): + """Create new archive + """ t0 = datetime.now() repository = self.open_repository(args.archive) manifest, key = Manifest.load(repository) @@ -150,6 +156,8 @@ class Archiver: self.print_error('Unknown file type: %s', path) def do_extract(self, args): + """Extract archive contents + """ repository = self.open_repository(args.archive) manifest, key = Manifest.load(repository) archive = Archive(repository, key, manifest, args.archive.archive, @@ -174,6 +182,8 @@ class Archiver: return self.exit_code def do_delete(self, args): + """Delete archive + """ repository = self.open_repository(args.archive) manifest, key = Manifest.load(repository) cache = Cache(repository, key, manifest) @@ -182,6 +192,8 @@ class Archiver: return self.exit_code def do_list(self, args): + """List archive or repository contents + """ repository = self.open_repository(args.src) manifest, key = Manifest.load(repository) if args.src.archive: @@ -214,6 +226,8 @@ class Archiver: return self.exit_code def do_verify(self, args): + """Verify archive consistency + """ repository = self.open_repository(args.archive) manifest, key = Manifest.load(repository) archive = Archive(repository, key, manifest, args.archive.archive) @@ -234,6 +248,8 @@ class Archiver: return self.exit_code def do_info(self, args): + """Show archive details such as disk space used + """ repository = self.open_repository(args.archive) manifest, key = Manifest.load(repository) cache = Cache(repository, key, manifest) @@ -249,6 +265,8 @@ class Archiver: return self.exit_code def do_prune(self, args): + """Prune repository archives according to specified rules + """ repository = self.open_repository(args.repository) manifest, key = Manifest.load(repository) cache = Cache(repository, key, manifest) @@ -294,7 +312,7 @@ class Archiver: common_parser = argparse.ArgumentParser(add_help=False) common_parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, - help='Verbose output') + help='verbose output') parser = argparse.ArgumentParser(description='Darc - Deduplicating Archiver') subparsers = parser.add_subparsers(title='Available subcommands') @@ -302,104 +320,113 @@ class Archiver: subparser = subparsers.add_parser('serve', parents=[common_parser]) subparser.set_defaults(func=self.do_serve) - subparser = subparsers.add_parser('init', parents=[common_parser]) + subparser = subparsers.add_parser('init', parents=[common_parser], + description=self.do_init.__doc__) subparser.set_defaults(func=self.do_init) subparser.add_argument('repository', type=location_validator(archive=False), - help='Repository to create') + help='repository to create') subparser.add_argument('--key-file', dest='keyfile', action='store_true', default=False, - help='Encrypt data using key file') + help='enable key file based encryption') subparser.add_argument('--passphrase', dest='passphrase', action='store_true', default=False, - help='Encrypt data using passphrase derived keys') + help='enable passphrase based encryption') - subparser = subparsers.add_parser('change-passphrase', parents=[common_parser]) + subparser = subparsers.add_parser('change-passphrase', parents=[common_parser], + description=self.do_change_passphrase.__doc__) subparser.set_defaults(func=self.do_change_passphrase) subparser.add_argument('repository', type=location_validator(archive=False)) - subparser = subparsers.add_parser('create', parents=[common_parser]) + subparser = subparsers.add_parser('create', parents=[common_parser], + description=self.do_create.__doc__) subparser.set_defaults(func=self.do_create) subparser.add_argument('-s', '--stats', dest='stats', action='store_true', default=False, - help='Print statistics for the created archive') + help='print statistics for the created archive') subparser.add_argument('-e', '--exclude', dest='excludes', type=ExcludePattern, action='append', - help='Exclude condition') + metavar="PATTERN", help='exclude paths matching PATTERN') subparser.add_argument('-c', '--checkpoint-interval', dest='checkpoint_interval', type=int, default=300, metavar='SECONDS', - help='Write checkpointe ever SECONDS seconds (Default: 300)') + help='write checkpointe ever SECONDS seconds (Default: 300)') subparser.add_argument('--do-not-cross-mountpoints', dest='dontcross', action='store_true', default=False, - help='Do not cross mount points') + help='do not cross mount points') subparser.add_argument('--numeric-owner', dest='numeric_owner', action='store_true', default=False, - help='Only store numeric user and group identifiers') + help='only store numeric user and group identifiers') subparser.add_argument('archive', metavar='ARCHIVE', type=location_validator(archive=True), - help='Archive to create') + help='archive to create') subparser.add_argument('paths', metavar='PATH', nargs='+', type=str, - help='Paths to archive') + help='paths to archive') - subparser = subparsers.add_parser('extract', parents=[common_parser]) + subparser = subparsers.add_parser('extract', parents=[common_parser], + description=self.do_extract.__doc__) subparser.set_defaults(func=self.do_extract) subparser.add_argument('-e', '--exclude', dest='excludes', type=ExcludePattern, action='append', - help='Exclude condition') + metavar="PATTERN", help='exclude paths matching PATTERN') subparser.add_argument('--numeric-owner', dest='numeric_owner', action='store_true', default=False, - help='Only obey numeric user and group identifiers') + help='only obey numeric user and group identifiers') subparser.add_argument('archive', metavar='ARCHIVE', type=location_validator(archive=True), - help='Archive to extract') + help='archive to extract') subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, - help='Paths to extract') + help='paths to extract') - subparser = subparsers.add_parser('delete', parents=[common_parser]) + subparser = subparsers.add_parser('delete', parents=[common_parser], + description=self.do_delete.__doc__) subparser.set_defaults(func=self.do_delete) subparser.add_argument('archive', metavar='ARCHIVE', type=location_validator(archive=True), - help='Archive to delete') + help='archive to delete') - subparser = subparsers.add_parser('list', parents=[common_parser]) + subparser = subparsers.add_parser('list', parents=[common_parser], + description=self.do_list.__doc__) subparser.set_defaults(func=self.do_list) subparser.add_argument('src', metavar='SRC', type=location_validator(), - help='Repository/Archive to list contents of') + help='repository/Archive to list contents of') - subparser = subparsers.add_parser('verify', parents=[common_parser]) + subparser = subparsers.add_parser('verify', parents=[common_parser], + description=self.do_verify.__doc__) subparser.set_defaults(func=self.do_verify) subparser.add_argument('-e', '--exclude', dest='excludes', type=ExcludePattern, action='append', - help='Include condition') + metavar="PATTERN", help='exclude paths matching PATTERN') subparser.add_argument('archive', metavar='ARCHIVE', type=location_validator(archive=True), - help='Archive to verity integrity of') + help='archive to verity integrity of') subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, - help='Paths to verify') + help='paths to verify') - subparser = subparsers.add_parser('info', parents=[common_parser]) + subparser = subparsers.add_parser('info', parents=[common_parser], + description=self.do_info.__doc__) subparser.set_defaults(func=self.do_info) subparser.add_argument('archive', metavar='ARCHIVE', type=location_validator(archive=True), - help='Archive to display information about') + help='archive to display information about') - subparser = subparsers.add_parser('prune', parents=[common_parser]) + subparser = subparsers.add_parser('prune', parents=[common_parser], + description=self.do_prune.__doc__) subparser.set_defaults(func=self.do_prune) subparser.add_argument('-H', '--hourly', dest='hourly', type=int, default=0, - help='Number of hourly archives to keep') + help='number of hourly archives to keep') subparser.add_argument('-d', '--daily', dest='daily', type=int, default=0, - help='Number of daily archives to keep') + help='number of daily archives to keep') subparser.add_argument('-w', '--weekly', dest='weekly', type=int, default=0, - help='Number of daily archives to keep') + help='number of daily archives to keep') subparser.add_argument('-m', '--monthly', dest='monthly', type=int, default=0, - help='Number of monthly archives to keep') + help='number of monthly archives to keep') subparser.add_argument('-y', '--yearly', dest='yearly', type=int, default=0, - help='Number of yearly archives to keep') + help='number of yearly archives to keep') subparser.add_argument('-p', '--prefix', dest='prefix', type=str, - help='Only consider archive names starting with this prefix') + help='only consider archive names starting with this prefix') subparser.add_argument('repository', metavar='REPOSITORY', type=location_validator(archive=False), - help='Repository to prune') + help='repository to prune') args = parser.parse_args(args or ['-h']) self.verbose = args.verbose return args.func(args) diff --git a/docs/Makefile b/docs/Makefile index af4ac9ab2..02c94644a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -37,6 +37,7 @@ clean: -rm -rf $(BUILDDIR)/* html: + ./update_usage.sh $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @@ -138,4 +139,4 @@ gh-pages: html rm -rf $$GH_PAGES_CLONE inotify: - while inotifywait -r .; do make html ; done + while inotifywait -r . --exclude usage.rst --exclude '_build/*' ; do make html ; done diff --git a/docs/detailedusage.rst b/docs/detailedusage.rst new file mode 100644 index 000000000..9e0f6ecc9 --- /dev/null +++ b/docs/detailedusage.rst @@ -0,0 +1,11 @@ +.. include:: global.rst.inc +.. _detailed_usage: + +Detailed Usage +-------------- + +|project_name| consists of a number of subcommands. Each subcommand accepts +a number of arguments and options. The following sections will describe each +subcommand in detail. + +.. include:: usage.rst diff --git a/docs/generalusage.rst b/docs/generalusage.rst index d45fe9db8..71ad0b314 100644 --- a/docs/generalusage.rst +++ b/docs/generalusage.rst @@ -5,7 +5,8 @@ General Usage ============= The following examples showcases how to use |project_name| to backup some -important files from a users home directory. +important files from a users home directory (for more detailed information +about each subcommand see the :ref:`detailed_usage` section). Initialize a local :ref:`repository ` to store backup :ref:`archives ` in (See :ref:`encrypted_repos` and diff --git a/docs/index.rst b/docs/index.rst index bd7239913..34e92284f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,7 +5,7 @@ Darc |project_name| is a Deduplicating ARChiver written in Python. The main goal of |project_name| is to provide an efficient and secure way to backup data. The data deduplication technique used makes |project_name| -suitable for taking daily backups. +suitable for daily backups since only actual changes are stored. Main Features ------------- @@ -49,8 +49,9 @@ User's Guide installation generalusage + detailedusage faq - definitions + terminology Contribute ========== diff --git a/docs/definitions.rst b/docs/terminology.rst similarity index 96% rename from docs/definitions.rst rename to docs/terminology.rst index 91469c06e..021f160e3 100644 --- a/docs/definitions.rst +++ b/docs/terminology.rst @@ -1,7 +1,7 @@ -.. _definitions: +.. _terminology: .. include:: global.rst.inc -Definitions +Terminology =========== .. _deduplication_def: diff --git a/docs/update_usage.sh b/docs/update_usage.sh new file mode 100755 index 000000000..d9b75be19 --- /dev/null +++ b/docs/update_usage.sh @@ -0,0 +1,6 @@ +#!/usr/bin/bash +echo -n > usage.rst +for cmd in init create extract delete prune verify change-passphrase; do + echo -e ".. _usage_darc_$cmd:\n\ndarc $cmd\n~~~~~~\n::\n" >> usage.rst + darc $cmd -h >> usage.rst +done diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 000000000..aa201e449 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,147 @@ +.. _usage_darc_init: + +darc init +~~~~~~ +:: + +usage: darc init [-h] [-v] [--key-file] [--passphrase] repository + +Initialize a new repository + +positional arguments: + repository repository to create + +optional arguments: + -h, --help show this help message and exit + -v, --verbose verbose output + --key-file enable key file based encryption + --passphrase enable passphrase based encryption +.. _usage_darc_create: + +darc create +~~~~~~ +:: + +usage: darc create [-h] [-v] [-s] [-e PATTERN] [-c SECONDS] + [--do-not-cross-mountpoints] [--numeric-owner] + ARCHIVE PATH [PATH ...] + +Create new archive + +positional arguments: + ARCHIVE archive to create + PATH paths to archive + +optional arguments: + -h, --help show this help message and exit + -v, --verbose verbose output + -s, --stats print statistics for the created archive + -e PATTERN, --exclude PATTERN + exclude paths matching PATTERN + -c SECONDS, --checkpoint-interval SECONDS + write checkpointe ever SECONDS seconds (Default: 300) + --do-not-cross-mountpoints + do not cross mount points + --numeric-owner only store numeric user and group identifiers +.. _usage_darc_extract: + +darc extract +~~~~~~ +:: + +usage: darc extract [-h] [-v] [-e PATTERN] [--numeric-owner] + ARCHIVE [PATH [PATH ...]] + +Extract archive contents + +positional arguments: + ARCHIVE archive to extract + PATH paths to extract + +optional arguments: + -h, --help show this help message and exit + -v, --verbose verbose output + -e PATTERN, --exclude PATTERN + exclude paths matching PATTERN + --numeric-owner only obey numeric user and group identifiers +.. _usage_darc_delete: + +darc delete +~~~~~~ +:: + +usage: darc delete [-h] [-v] ARCHIVE + +Delete archive + +positional arguments: + ARCHIVE archive to delete + +optional arguments: + -h, --help show this help message and exit + -v, --verbose verbose output +.. _usage_darc_prune: + +darc prune +~~~~~~ +:: + +usage: darc prune [-h] [-v] [-H HOURLY] [-d DAILY] [-w WEEKLY] [-m MONTHLY] + [-y YEARLY] [-p PREFIX] + REPOSITORY + +Prune repository archives according to specified rules + +positional arguments: + REPOSITORY repository to prune + +optional arguments: + -h, --help show this help message and exit + -v, --verbose verbose output + -H HOURLY, --hourly HOURLY + number of hourly archives to keep + -d DAILY, --daily DAILY + number of daily archives to keep + -w WEEKLY, --weekly WEEKLY + number of daily archives to keep + -m MONTHLY, --monthly MONTHLY + number of monthly archives to keep + -y YEARLY, --yearly YEARLY + number of yearly archives to keep + -p PREFIX, --prefix PREFIX + only consider archive names starting with this prefix +.. _usage_darc_verify: + +darc verify +~~~~~~ +:: + +usage: darc verify [-h] [-v] [-e PATTERN] ARCHIVE [PATH [PATH ...]] + +Verify archive consistency + +positional arguments: + ARCHIVE archive to verity integrity of + PATH paths to verify + +optional arguments: + -h, --help show this help message and exit + -v, --verbose verbose output + -e PATTERN, --exclude PATTERN + exclude paths matching PATTERN +.. _usage_darc_change-passphrase: + +darc change-passphrase +~~~~~~ +:: + +usage: darc change-passphrase [-h] [-v] repository + +Change passphrase on repository key file + +positional arguments: + repository + +optional arguments: + -h, --help show this help message and exit + -v, --verbose verbose output diff --git a/setup.py b/setup.py index f90aef57f..0c403b5bc 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ # -*- encoding: utf-8 *-* -#!/usr/bin/env python import os import sys from glob import glob @@ -45,6 +44,9 @@ except ImportError: if not os.path.exists(chunker_source) or not os.path.exists(hashindex_source): raise ImportError('The GIT version of darc needs Cython. Install Cython or use a released version') +with open('README.rst', 'r') as fd: + long_description = fd.read() + setup( name='darc', version=darc.__release__, @@ -52,6 +54,7 @@ setup( author_email='jonas@borgstrom.se', url='http://github.com/jborg/darc/', description='Deduplicating ARChiver written in Python', + long_description=long_description, license='BSD', platforms=['Linux', 'MacOS X'], classifiers=[