Commits

Vadim Fint committed 3f289e4 Merge

merge: release-0.1@release -> master

version bump: 0.1.dev -> 0.1.0.dev1

Comments (0)

Files changed (10)

+^(docs/)?build/
+^\w+\.egg-info/
+btb
+===
+
+Fast and effective backup system using btrfs and rsync.
+#!/usr/bin/env python
+
+from __future__ import print_function
+
+try:
+    from setuptools import setup
+    from setuptools.command.test import test as TestCommand
+
+    class PyTestCommand(TestCommand):
+        user_options = [
+            ('opts=', 'o', 'Options for pytest'),
+        ]
+
+        def initialize_options(self):
+            self.opts = ''
+
+        def finalize_options(self):
+            import shlex
+            self.opts_str = self.opts
+            self.opts = shlex.split(self.opts)
+
+        def run(self):
+            if self.distribution.install_requires:
+                self.distribution.fetch_build_eggs(self.distribution.install_requires)
+            if self.distribution.tests_require:
+                self.distribution.fetch_build_eggs(self.distribution.tests_require)
+
+            self.with_project_on_sys_path(self.run_pytest)
+
+        def run_pytest(self):
+            import sys
+            import pytest
+            import os
+
+            if os.fork():
+                status = os.wait()[1]
+                if os.WIFEXITED(status):
+                    raise SystemExit(os.WEXITSTATUS(status))
+                else:
+                    self.announce('Killed by signal: %d' % (os.WSTOPSIG(status), ))
+                    raise SystemExit(1)
+            else:
+                sys.argv[:] = ['setup.py test'] + self.opts
+                self.announce('Running pytest %s' % (self.opts_str, ))
+                raise SystemExit(pytest.main())
+
+except ImportError:
+    from distutils.core import setup  # NOQA
+
+
+try:
+    variables = {}
+    execfile('src/__version__.py', variables, variables)
+    VERSION = str(variables['VERSION'])
+except:
+    VERSION = 'unknown'
+
+
+setup(
+    name='btb',
+    version=VERSION,
+    description='Backup system using rsync and btrfs snapshots',
+    long_description=open('README.rst').read(),
+    url='http://bitbucket.org/mocksoul/btb',
+    author='Vadim Fint',
+    author_email='mocksoul@gmail.com',
+    license='BSD',
+    zip_safe=True,
+    install_requires=[
+        'distribute',
+    ],
+    tests_require=['pytest'],
+    packages=[
+        'btb',
+    ],
+    entry_points={
+        'console_scripts': [
+            'btb = btb.__main__:main',
+        ]
+    },
+    package_dir={'btb': 'src'},
+    cmdclass={
+        'test': PyTestCommand,
+    },
+    classifiers=[
+        'Development Status :: 1 - Planning',
+        'License :: OSI Approved :: BSD License',
+        'Natural Language :: English',
+        'Operating System :: POSIX',
+        'Operating System :: POSIX :: BSD',
+        'Operating System :: POSIX :: Linux',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 2 :: Only',
+        'Programming Language :: Python :: Implementation :: CPython',
+        'Topic :: Communications',
+    ]
+)

src/__init__.py

Empty file added.
+import sys
+
+from .ui.console import enter_ui
+
+
+def main():
+    return enter_ui(sys.argv[1:])
+
+
+if __name__ == '__main__':
+    raise SystemExit(main())

src/__version__.py

+from collections import namedtuple
+
+
+class Version(namedtuple('VersionBase', 'major minor maintainance extra')):
+    def __str__(self):
+        if all([i is None for i in self][:3]):
+            return self[3]
+        dotted = '.'.join([str(i) for i in self[:3] if i is not None])
+        if not self[3]:
+            return dotted
+        return '{}{}'.format(dotted, self[3])
+
+
+PROGRAM = 'btb'
+VERSION = Version(0, 1, 0, '.dev1')

src/ui/__init__.py

Empty file added.

src/ui/console/__init__.py

+import argparse
+import textwrap
+
+from ...__version__ import PROGRAM, VERSION
+
+
+SUBCOMMANDS = (
+    'commands.sync',
+)
+
+
+class ArgumentParser(argparse.ArgumentParser):
+    def _get_formatter(self):
+        return self.formatter_class(prog=self.prog, max_help_position=80, width=120)
+
+
+class HelpFormatter(
+    argparse.ArgumentDefaultsHelpFormatter,
+    argparse.RawTextHelpFormatter
+):
+    pass
+
+
+def import_by_name(name):
+    module_name, fromlist = name.rsplit('.', 1)
+    module = __import__(module_name, globals(), locals(), [fromlist])
+    return getattr(module, fromlist)
+
+
+def iterate_commands_mods(submod=None, only_commands=None):
+    for module_name in SUBCOMMANDS:
+        if only_commands and module_name not in only_commands:
+            continue
+        if submod:
+            fromlist = []
+            for submod_part in submod.split('.'):
+                if not fromlist:
+                    current = submod_part
+                else:
+                    current = '.' + submod_part
+                fromlist.append(current)
+        else:
+            fromlist = []
+
+        cmd_module = __import__(module_name, globals(), locals(), fromlist)
+
+        if submod:
+            current_item = cmd_module
+            for submod_part in submod.split('.'):
+                try:
+                    current_item = getattr(current_item, submod_part)
+                except AttributeError:
+                    raise AttributeError('Module %s does not have attribute %s' % (
+                        current_item.__name__, submod_part
+                    ))
+            yield current_item
+        else:
+            yield cmd_module
+
+
+def create_main_parser(prog, version):
+    parser = ArgumentParser(
+        prog=prog,
+        formatter_class=HelpFormatter,
+        description=textwrap.dedent('''
+            Modern and lightweight backup system
+        '''),
+        epilog=None,
+        add_help=False
+    )
+
+    # Add info options (--help and --version)
+    info_options = parser.add_argument_group(title='Informational')
+    info_options_group = info_options.add_mutually_exclusive_group()
+    info_options_group.add_argument(
+        '-h', '--help', action='help',
+        help='show this help message and exit'
+    )
+    info_options_group.add_argument(
+        '-V', '--version', action='version',
+        version=(
+            '%(prog)s v{}'.format(version)
+            if version[0].isdigit() else
+            '%(prog)s {}'.format(version)
+        ),
+        help='show program version number and exit'
+    )
+
+    # Create subparsers
+    subparsers = parser.add_subparsers(
+        dest='cmd',
+        title='Commands',
+        description=textwrap.dedent('''
+            %(prog)s functionality is splitted into different commands,
+            they are named as "CMD" in this help.
+
+            Each command has its own set of options (find them by running
+            "%(prog)s CMD --help") and there are some global options
+            mentioned earlier which can be applied to all commands.
+
+            Only one command can be executed at a time.
+        ''').strip(),
+        metavar='COMMAND'
+    )
+
+    return parser, subparsers
+
+
+def parse_args(args):
+    parser, subparsers = create_main_parser(PROGRAM, str(VERSION))
+    for create_cmd_parser in iterate_commands_mods('create_parser'):
+        create_cmd_parser(subparsers)
+    return parser.parse_args(args)
+
+
+def enter_ui(args):
+    args = parse_args(args)
+    if isinstance(args.main, str):
+        args.main = import_by_name(args.main)
+
+    return args.main('ctx')

src/ui/console/commands/__init__.py

Empty file added.

src/ui/console/commands/sync.py

+import textwrap
+
+from .. import HelpFormatter
+
+
+def create_parser(parser):
+    parser = parser.add_parser(
+        'sync',
+        formatter_class=HelpFormatter,
+        help='run synchronization',
+        add_help=False,
+        description=textwrap.dedent('''
+            Run backup
+        '''),
+        epilog=None
+    )
+
+    parser.set_defaults(
+        main='{}.main'.format(__name__),
+        config='x.y'
+    )
+
+    path_selection_group = parser.add_argument_group(
+        title='Path selection'
+    )
+    path_selection_group.add_argument(
+        'source',
+        metavar='SOURCE',
+        help='source path'
+    )
+    path_selection_group.add_argument(
+        'target',
+        metavar='TARGET',
+        help='target path'
+    )
+    path_selection_group.add_argument(
+        '--exclude', type=list, nargs='*',
+        metavar='PATTERN',
+        default=[],
+        help='exclude pattern (rsync-like), can be specified multiple times'
+    )
+
+    historic_group = parser.add_argument_group(
+        title='Backup history management'
+    )
+    historic_group.add_argument(
+        '--max-age', metavar='AGE',
+        default='1w',
+        help=textwrap.dedent('''
+            Max snapshots age.
+            Examples:
+             - 30 (30 days)
+             - 2d10h (2 days 10 hours)
+             - 10m (invalid! cant distinguish between months and minutes)
+             - 10mi (10 minutes)
+             - 10mo (10 months)
+             - 10s1h (stupid form, but valid: 1 hour 10 seconds)
+        ''').strip()
+    )
+
+    options = parser.add_argument_group(title='Options')
+    options.add_argument(
+        '-h', '--help',
+        action='help',
+        help='show this help message and exit'
+    )
+
+
+def main(ctx):
+    raise NotImplementedError('Not yed made')