# cmdline.py - functions for handling command-line arguments # # Copyright (C) 2015 Arthur de Jong # # 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 St, Fifth Floor, Boston, MA 02110-1301 USA # # The files produced as output from the software do not automatically fall # under the copyright of the software, unless explicitly stated otherwise. import argparse import re class VersionAction(argparse.Action): def __init__(self, option_strings, dest, help='output version information and exit'): super(VersionAction, self).__init__( option_strings=option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, nargs=0, help=help) def __call__(self, parser, namespace, values, option_string=None): print(''' backupscript 0.0 Written by Arthur de Jong. Copyright (C) 2015 Arthur de Jong. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. '''.strip()) parser.exit() # command-line options that are shared between all commands global_options = argparse.ArgumentParser(add_help=False) global_options.add_argument( '-V', '--version', action=VersionAction) global_options.add_argument( '--cache-dir', metavar='DIR', help='directory containing local meta-data cache') # set up command line parser parser = argparse.ArgumentParser(parents=[global_options]) parser.add_argument( 'repository', metavar='URL', help='the location of the backup repository') subparsers = parser.add_subparsers(metavar='COMMAND', dest='command') # the set-keys command set_keys_command = subparsers.add_parser( 'set-keys', parents=[global_options], help='configure keys that can decrypt the backup') set_keys_command.add_argument( 'keys', metavar='KEYID', nargs='+', help='the GnuPG key IDs used for encryption') def size(value): m = re.match('^([0-9]+)([kMGT]?)', value) if m is None: raise argparse.ArgumentTypeError('invalid size: %r' % value) value = int(m.group(1)) if m.group(2) == 'k': return value * 1024 elif m.group(2) == 'M': return value * 1024 * 1024 elif m.group(2) == 'G': return value * 1024 * 1024 * 1024 elif m.group(2) == 'T': return value * 1024 * 1024 * 1024 * 1024 else: return value # the backup command backup_command = subparsers.add_parser( 'backup', parents=[global_options], help='backup files to the repository') backup_command.add_argument( 'files', metavar='PATH', nargs='+', help='directories and files to backup') backup_command.add_argument( '--encryption', choices=('gpg', 'none'), help='the encryption mechanism to use') backup_command.add_argument( '--compression', choices=('gzip', 'bzip2', 'xz', 'none'), help='the compression mechanism to use') backup_command.add_argument( '--block-size', metavar='SIZE', type=size, help='target size of newly created archive files') backup_command.add_argument( '--archive-min-files', metavar='MINFILES', type=int, help='minimal number of files in newly created archive files') backup_command.add_argument( '--archive-min-perc', metavar='PERCENTAGE', help='ignore existing archives that have lower useful data') backup_command.add_argument( '--archive-use-extractlist', metavar='PERCENTAGE', help='use an extractlist when use drops below percentage') backup_command.add_argument( '--exclude', action='append', metavar='PATTERN', dest='excludes', help='file patterns to exclude') backup_command.add_argument( '--exclude-from', action='append', metavar='FILE', dest='exclude_files', help='read exclude patterns from FILE') backup_command.add_argument( '--no-extractlists', action='store_true', help='do not require extractlists for extracting archives') # the backups command backups_command = subparsers.add_parser( 'backups', parents=[global_options], help='list backups in the repository') def backup_name(value): if not re.match(r'^[0-9a-zA-Z_-]+$', value): raise argparse.ArgumentTypeError('invalid backup name: %r' % value) return value # the list command list_command = subparsers.add_parser( 'ls', parents=[global_options], help='list contents of a backup') list_command.add_argument( 'backup', metavar='BACKUP', type=backup_name, help='name of the backup') list_command.add_argument( 'files', metavar='PATH', nargs='*', help='directories and files to list') # the find command find_command = subparsers.add_parser( 'find', parents=[global_options], help='find backups containing the specified paths') find_command.add_argument( 'files', metavar='PATTERN', nargs='+', help='directory and file patterns to find') # the restore command restore_command = subparsers.add_parser( 'restore', parents=[global_options], help='restore files from a backup') restore_command.add_argument( 'backup', metavar='BACKUP', type=backup_name, help='name of the backup to restore') restore_command.add_argument( 'files', metavar='PATH', nargs='*', help='directories and files to restore') def keep_policy(value): values = value.split(',') for value in values: if not re.match(r'^[0-9]+[hdwmy]$', value): raise argparse.ArgumentTypeError('unknown policy: %r' % value) return ','.join(values) # the remove backup command remove_command = subparsers.add_parser( 'rm', parents=[global_options], help='remove backups from repository') remove_command.add_argument( '--keep', metavar='POLICY', action='append', type=keep_policy, default=[], dest='keeps', help='remove all backups except as specified') remove_command.add_argument( '-n', '--no-act', '--dry-run', action='store_true', dest='no_act', help='print backups to be removed instead of removing') remove_command.add_argument( 'backups', metavar='BACKUP', type=backup_name, nargs='*', help='names of the backups to remove') # the fsck command fsck_command = subparsers.add_parser( 'fsck', parents=[global_options], help='check the consistency of the repository') fsck_command.add_argument( '--clean', action='store_true', help='remove superfluous files')