def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) mount_point = cmdline['<mount_point>'] archive = cmdline['--archive'] date = cmdline['--date'] mount_all = cmdline['--all'] include_external_archives = cmdline['--include-external'] # get the desired archive if not archive: if date: archive = get_name_of_nearest_archive(settings, date) elif not mount_all: archive = get_name_of_latest_archive(settings) # create mount point if it does not exist try: mkdir(mount_point) except OSError as e: raise Error(os_error(e)) # run borg borg = settings.run_borg( cmd='mount', args=[settings.destination(archive), mount_point], emborg_opts=options, strip_prefix=include_external_archives, ) out = borg.stdout if out: output(out.rstrip())
def test_wring(): with messenger() as (msg, stdout, stderr, logfile): output('hey now!', flush=True) codicil('baby', 'bird', sep='\n') msg.flush_logfile() expected = dedent(''' hey now! baby bird ''').strip() assert msg.errors_accrued() == 0 assert errors_accrued() == 0 assert strip(stdout) == expected assert strip(stderr) == '' assert log_strip(logfile) == dedent(''' ack: invoked as: <exe> ack: invoked on: <date> {expected} ''').strip().format(expected=expected) try: terminate_if_errors() assert True except SystemExit: assert False
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) archive = cmdline["<archive>"] check_all = cmdline["--all"] include_external_archives = cmdline["--include-external"] verify = ["--verify-data"] if cmdline["--verify-data"] else [] repair = ['--repair'] if cmdline['--repair'] else [] if repair: require_env_var('BORG_CHECK_I_KNOW_WHAT_I_AM_DOING', repair, command, args) # identify archive or archives to check if check_all: archive = None elif not archive: archive = get_name_of_latest_archive(settings) # run borg borg = settings.run_borg( cmd="check", args=verify + repair + [settings.destination(archive)], emborg_opts=options, strip_prefix=include_external_archives, ) out = borg.stdout if out: output(out.rstrip())
def list_plugins(config): # Work out the width of each column: stages = 'archive', 'publish', 'auth' defaults = {'auth': ['getpass']} max_on = 2 max_type = max(len(x) for x in stages) max_name = max(len(k) for stage in stages for k in load_plugins(stage)) max_width = get_terminal_size().columns - 1 max_desc = max_width - max_on - max_type - max_name - 3 * 2 row = "{:%ds} {:%ds} {:%ds} {:%ds}" % (max_on, max_type, max_name, max_desc) header = row.format("On", "Stage", "Name", "Description") rule = '─' * max_width output(rule) output(header) output(rule) for stage in stages: installed = load_plugins(stage).values() enabled = select_plugins(config, stage, defaults.get(stage)) plugins = enabled + [x for x in installed if x not in enabled] for i, plugin in enumerate(plugins): summary = (plugin.__doc__ or "No summary").strip().split('\n')[0] output( row.format( '*' if plugin in enabled else '', stage if i == 0 else '', plugin.name, shorten(summary, width=max_desc, placeholder='...'), )) output(rule)
def main(): try: # read config file read_config() # read command line cmdline = docopt( __doc__.format(commands=Command.summarize()), version='avendesora {} ({})'.format(__version__, __released__), options_first=True, ) # start logging logfile = BufferedFile(get_setting('log_file'), True) Inform(logfile=logfile, hanging_indent=False, stream_policy='header', notify_if_no_tty=True) shlib.set_prefs(use_inform=True, log_cmd=True) # run the requested command Command.execute(cmdline['<command>'], cmdline['<args>']) done() except KeyboardInterrupt: output('\nTerminated by user.') terminate() except (PasswordError, Error) as e: e.terminate() except OSError as e: fatal(os_error(e)) done()
def run(cls, command, args): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) # output the version from .__init__ import __version__ output('Avendesora version: %s' % __version__)
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) archive = cmdline['<archive>'] check_all = cmdline['--all'] verify = ['--verify-data'] if cmdline['--verify-data'] else [] # repair = ['--repair'] if cmdline['--repair'] else [] repair = [] # repair has been deleted because it requires the user to # interactively respond to a query, and emborg does not support that # yet. include_external_archives = cmdline['--include-external'] # identify archive or archives to check if check_all: archive = None elif not archive: archive = get_name_of_latest_archive(settings) # run borg borg = settings.run_borg( cmd='check', args=verify + repair + [settings.destination(archive)], emborg_opts=options, strip_prefix=include_external_archives, ) out = borg.stdout if out: output(out.rstrip())
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) paths = cmdline['<path>'] archive = cmdline['--archive'] date = cmdline['--date'] # add cwd to paths paths = [to_path(cwd(), p) for p in paths] # assure that paths correspond to src_dirs src_dirs = settings.src_dirs unknown_path = False # get the desired archive if date and not archive: archive = get_name_of_nearest_archive(settings, date) if not archive: archive = get_name_of_latest_archive(settings) output('Archive:', archive) # run borg borg = settings.run_borg( cmd='extract', args=[settings.destination(archive)] + paths, emborg_opts=options, ) out = borg.stdout if out: output(out.rstrip())
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) include_external_archives = cmdline["--include-external"] show_stats = cmdline["--stats"] or settings.show_stats # checking the settings intervals = "within last minutely hourly daily weekly monthly yearly" prune_settings = [("keep_" + s) for s in intervals.split()] if not any(settings.value(s) for s in prune_settings): prune_settings = conjoin(prune_settings, ", or ") raise Error( "No prune settings available", codicil=f"At least one of {prune_settings} must be specified.", ) # run borg borg = settings.run_borg( cmd="prune", args=[settings.destination()], emborg_opts=options, strip_prefix=include_external_archives, show_borg_output=show_stats, ) out = borg.stdout if out: output(out.rstrip())
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) mount_point = cmdline['<mount_point>'] archive = cmdline['--archive'] date = cmdline['--date'] # get the desired archive if date and not archive: archive = get_nearest_archive(settings, date) if not archive: raise Error('archive not available.', culprit=date) # create mount point if it does not exist try: mkdir(mount_point) except OSError as e: raise Error(os_error(e)) # run borg borg = settings.run_borg( cmd='mount', args=[settings.destination(archive), mount_point], emborg_opts=options, ) out = borg.stdout if out: output(out.rstrip())
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) archive = cmdline['--archive'] date = cmdline['--date'] filenames_only = cmdline['--name'] # get the desired archive if date and not archive: archive = get_nearest_archive(settings, date) if not archive: raise Error('archive not available.', culprit=date) if not archive: archives = get_available_archives(settings) if not archives: raise Error('no archives are available.') archive = archives[-1]['name'] output('Archive:', archive) # run borg list_opts = ['--short'] if filenames_only else [] borg = settings.run_borg( cmd='list', args=list_opts + [settings.destination(archive)], emborg_opts=options, ) out = borg.stdout if out: output(out.rstrip())
def generate(self, field_name, field_key, account): try: if self.secret: return except AttributeError: pass account_name = account.get_name() account_seed = account.get_seed() if self.master is None: master = account.get_field("master", default=None) master_source = account.get_field("_master_source", default=None) else: master = self.master master_source = "secret" if not master: master = get_setting("user_key") master_source = "user_key" if not master: try: try: master = getpass.getpass("master password for %s: " % account_name) master_source = "user" except EOFError: output() if not master: warn("master password is empty.") except (EOFError, KeyboardInterrupt): terminate() log("Generating secret, source of master seed:", master_source) field_key = self.get_key(field_key) if self.version: version = self.version else: version = account.get_field("version", default="") if account.request_seed(): try: try: interactive_seed = getpass.getpass("seed for %s: " % account_name) except EOFError: output() if not interactive_seed: warn("seed is empty.") except (EOFError, KeyboardInterrupt): terminate() else: interactive_seed = "" seeds = [master, account_seed, field_name, field_key, version, interactive_seed] key = " ".join([str(seed) for seed in seeds]) # Convert the key into 512 bit number digest = hashlib.sha512((key).encode("utf-8")).digest() bits_per_byte = 8 radix = 1 << bits_per_byte bits = 0 for byte in digest: bits = radix * bits + byte self.pool = bits
def query_user(msg): highlight_color = get_setting('_highlight_color') msg = highlight_color(msg) try: return input(msg + ' ').strip() except EOFError: output()
def help(cls, desc): if desc: output(desc.strip() + "\n") output("Available commands:") output(Command.summarize()) output("\nAvailable topics:") output(cls.summarize())
def query_user(msg): msg = HighlightColor(msg) try: if sys.version_info.major < 3: return raw_input(msg + ' ').strip() else: return input(msg + ' ').strip() except EOFError: output()
def help(cls, desc): if desc: output(desc.strip() + '\n') output('Available commands:') output(Command.summarize()) output('\nAvailable topics:') output(cls.summarize())
def run(cls, command, args, settings, options): # read command line docopt(cls.USAGE, argv=[command] + args) configurations = Collection(settings.configurations) if configurations: output('Available Configurations:', *configurations, sep='\n ') else: output('No configurations available.')
def run(cls, command, args, settings, options): # read command line docopt(cls.USAGE, argv=[command] + args) try: prev_log = settings.prev_logfile.read_text() output(prev_log) except FileNotFoundError as e: narrate(os_error(e))
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) highlight = Color('yellow') normal = Color('cyan') for k, v in settings: key = f'{k:>22s}' key = normal(key) if k in KNOWN_SETTINGS else highlight(key) output(f'{key}: {render(v, level=6)}')
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args, options_first=True) borg_args = cmdline['<borg_args>'] # run borg borg = settings.run_borg_raw(borg_args) out = borg.stdout if out: output(out.rstrip())
def write_summary(cls, all=False, sort=False): # present all account values that are not explicitly secret to the user label_color = get_setting('_label_color') highlight_color = get_setting('_highlight_color') def fmt_field(name, value='', key=None, level=0): hl = False # resolve values if isinstance(value, Script): hl = True value = value.script elif cls.is_secret(name, key): reveal = "reveal with: {}".format( highlight_color( join('avendesora', 'value', cls.get_name(), cls.combine_field(name, key)))) value = ', '.join(cull([value.get_description(), reveal])) elif isinstance(value, (GeneratedSecret, ObscuredSecret)): v = cls.get_scalar(name, key) value = ', '.join(cull([value.get_description(), str(v)])) else: value = str(value) # format values if '\n' in value: value = indent(dedent(value), get_setting('indent')).strip('\n') sep = '\n' elif value: sep = ' ' else: sep = '' if hl: value = highlight_color(value) name = str(name).replace('_', ' ') leader = level * get_setting('indent') return indent( label_color((name if key is None else str(key)) + ':') + sep + value, leader) # preload list with the names associated with this account names = [cls.get_name()] + getattr(cls, 'aliases', []) lines = [fmt_field('names', ', '.join(names))] for key, value in cls.items(all=all, sort=sort): if is_collection(value): lines.append(fmt_field(key)) for k, v in Collection(value).items(): lines.append(fmt_field(key, v, k, level=1)) else: lines.append(fmt_field(key, value)) output(*lines, sep='\n')
def pager(text): program = get_setting('use_pager') if not is_str(program): program = os.environ.get('PAGER', 'less') if program: try: Run([program], stdin=text, modes='WoEs') return except Error as e: e.report(culprit=program) output(text)
def run(cls, command, args): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) # get the text text = cmdline['<text>'] if not text: output('Enter the obscured text, type ctrl-d to terminate.') text = sys.stdin.read() # transform and output the string output(Obscure.show(text))
def run(cls, command, args): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) # get the text text = cmdline['<text>'] symmetric = cmdline['--symmetric'] if not text: output('Enter text to obscure, type ctrl-d to terminate.') text = sys.stdin.read()[:-1] # transform and output the string output(Obscure.hide(text, cmdline['--encoding'], True, symmetric))
def run(cls, command, args, settings, options): # read command line docopt(cls.USAGE, argv=[command] + args) # run borg borg = settings.run_borg( cmd='init', args=[settings.destination()], emborg_opts=options, ) out = borg.stdout if out: output(out.rstrip())
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) archive = cmdline['<archive>'] # run borg borg = settings.run_borg( cmd='delete', args=[settings.destination(archive)], emborg_opts=options, ) out = borg.stdout if out: output(out.rstrip())
def display_field(self, account, field): # get string to display value, is_secret, name, desc = tuple(account.get_value(field)) label = '%s (%s)' % (name, desc) if desc else name value = dedent(str(value)).strip() label_color = get_setting('_label_color') # indent multiline outputs sep = ' ' if '\n' in value: if is_secret: warn('secret contains newlines, will not be fully concealed.') value = indent(value, get_setting('indent')).strip('\n') sep = '\n' if label: if label[0] == '_': # hidden field label = '!' + label[1:] text = label_color(label.replace('_', ' ') + ':') + sep + value else: text = value label = field log('Writing to TTY:', label) if is_secret: if Color.isTTY(): # Write only if output is a TTY. This is a security feature. # The ideas is that when the TTY writer is called it is because # the user is expecting the output to go to the tty. This # eliminates the chance that the output can be intercepted and # recorded by replacing Avendesora with an alias or shell # script. If the user really want the output to go to something # other than the TTY, the user should use the --stdout option. try: cursor.write(text) cursor.conceal() sleep(get_setting('display_time')) except KeyboardInterrupt: pass cursor.reveal() cursor.clear() else: error('output is not a TTY.') codicil( 'Use --stdout option if you want to send secret', 'to a file or a pipe.' ) else: output(text)
def run(cls, command, args, settings, options): # get the Python version python = "Python %s.%s.%s" % ( sys.version_info.major, sys.version_info.minor, sys.version_info.micro, ) # output the SSHconfig version along with the Python version from .__init__ import __version__, __released__ output("sshconfig version: %s (%s) [%s]." % (__version__, __released__, python))
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) verify = ['--verify-data'] if cmdline['--verify-data'] else [] # run borg borg = settings.run_borg( cmd='check', args=verify + [settings.destination()], emborg_opts=options, ) out = borg.stdout if out: output(out.rstrip())
def run_script(self, script, dryrun=False): out = [] scrubbed = [] sleep(INITIAL_AUTOTYPE_DELAY) ms_per_char = None for cmd, val in script.components(True): if cmd in ['tab', 'return', 'text', 'value']: out.append(val) scrubbed.append(val) elif cmd.startswith('sleep '): scrubbed.append('<%s>' % cmd) try: kw, seconds = cmd.split() assert kw == 'sleep' if out: # drain the buffer before sleeping if not dryrun: self._autotype(''.join(out), ms_per_char) out = [] sleep(float(seconds)) except (TypeError, ValueError): raise PasswordError('syntax error in keyboard script.', culprit=cmd) elif cmd.startswith('rate '): scrubbed.append('<%s>' % cmd) try: kw, rate = cmd.split() assert kw == 'rate' if out: # drain the buffer before changing rate if not dryrun: self._autotype(''.join(out), ms_per_char) out = [] ms_per_char = int(rate) except (TypeError, ValueError): raise PasswordError('syntax error in keyboard script.', culprit=cmd) elif cmd.startswith('remind '): notify(cmd[7:]) else: out.append(val) scrubbed.append('<%s>' % cmd) scrubbed = ''.join(scrubbed).replace('\t', '→').replace('\n', '↲') log('Autotyping "%s"%s.' % (scrubbed, ' -- dry run' if dryrun else '')) if dryrun: output(script.account.get_name(), scrubbed, sep=': ') else: self._autotype(''.join(out), ms_per_char)
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) # get the Python version python = 'Python %s.%s.%s' % ( sys.version_info.major, sys.version_info.minor, sys.version_info.micro, ) # output the Avendesora version along with the Python version from .__init__ import __version__, __released__ output('embalm version: %s (%s) [%s].' % (__version__, __released__, python))
def run(cls, command, args): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) # run the generator generator = PasswordGenerator() # search for accounts that match search criteria to_print = [] for acct in generator.search_accounts(cmdline['<text>']): aliases = ', '.join(Collection(getattr(acct, 'aliases', [])).values()) aliases = ' (%s)' % (aliases) if aliases else '' to_print += [acct.get_name() + aliases] output(cmdline['<text>']+ ':') output(' ' + ('\n '.join(sorted(to_print))))
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) archive1 = cmdline["<archive1>"] archive2 = cmdline["<archive2>"] # run borg borg = settings.run_borg( cmd="diff", args=[settings.destination(archive1), archive2], emborg_opts=options, ) out = borg.stdout if out: output(out.rstrip())
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) include_external_archives = cmdline['--include-external'] # run borg borg = settings.run_borg( cmd='list', args=['--short', settings.destination()], emborg_opts=options, strip_prefix=include_external_archives, ) out = borg.stdout if out: output(out.rstrip())
def get_seed(cls): # need to handle case where stdin/stdout is not available. # perhaps write generic password getter that supports both gui and tui. # Then have global option that indicates which should be used. # Separate name from seed. Only request seed when generating a password. import getpass try: name = getpass.getpass("account name: ") except EOFError: output() name = "" if not name: warn("null account name.") return name
def run(cls, command, args, settings, options): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) paths = cmdline['<path>'] archive = cmdline['--archive'] date = cmdline['--date'] # make sure source directories are given as absolute paths for src_dir in settings.src_dirs: if not src_dir.is_absolute(): raise Error('restore command cannot be used', 'with relative source directories', culprit=src_dir) # convert to absolute resolved paths paths = [to_path(p).resolve() for p in paths] # assure that paths correspond to src_dirs src_dirs = settings.src_dirs unknown_path = False for path in paths: if not any([str(path).startswith(str(sd)) for sd in src_dirs]): unknown_path = True warn('unknown path.', culprit=path) if unknown_path: codicil('Paths should start with:', conjoin(src_dirs, conj=', or ')) # remove leading / from paths paths = [str(p).lstrip('/') for p in paths] # get the desired archive if date and not archive: archive = get_name_of_nearest_archive(settings, date) if not archive: archive = get_name_of_latest_archive(settings) output('Archive:', archive) # run borg cd('/') borg = settings.run_borg( cmd='extract', args=[settings.destination(archive)] + paths, emborg_opts=options, ) out = borg.stdout if out: output(out.rstrip())
def write_summary(cls): # present all account values that are not explicitly secret to the user def fmt_field(key, value="", level=0): if "\n" in value: value = indent(dedent(value), get_setting("indent")).strip("\n") sep = "\n" elif value: sep = " " else: sep = "" key = str(key).replace("_", " ") leader = level * get_setting("indent") return indent(LabelColor(key + ":") + sep + value, leader) def reveal(name, key=None): return "<reveal with 'avendesora value %s %s'>" % (cls.get_name(), cls.combine_name(name, key)) def extract_collection(name, collection): lines = [fmt_field(key)] for k, v in Collection(collection).items(): if hasattr(v, "generate"): # is a secret, get description if available try: v = "%s %s" % (v.get_key(), reveal(name, k)) except AttributeError: v = reveal(name, k) lines.append(fmt_field(k, v, level=1)) return lines # preload list with the names associated with this account names = [cls.get_name()] if hasattr(cls, "aliases"): names += Collection(cls.aliases) lines = [fmt_field("names", ", ".join(names))] for key, value in cls.items(): if key in TOOL_FIELDS: pass # is an Avendesora field elif is_collection(value): lines += extract_collection(key, value) elif hasattr(value, "generate"): lines.append(fmt_field(key, reveal(key))) else: lines.append(fmt_field(key, value)) output(*lines, sep="\n")
def display_field(self, account, field): name, key = account.split_name(field) is_secret = account.is_secret(name, key) try: value = account.get_field(name, key) tvalue = dedent(str(value)).strip() except Error as err: err.terminate() sep = ' ' if '\n' in tvalue: if is_secret: warn( 'secret contains newlines, will not be fully concealed.', culprit=key ) else: tvalue = indent(dedent(tvalue), get_setting('indent')).strip('\n') sep = '\n' # build output string label = account.combine_name(name, key) log('Writing to TTY:', label) try: alt_name = value.get_key() if alt_name: label += ' (%s)' % alt_name except AttributeError: pass text = LabelColor(label + ':') + sep + tvalue if is_secret: try: cursor.write(text) cursor.conceal() sleep(get_setting('display_time')) except KeyboardInterrupt: pass cursor.reveal() cursor.clear() else: output(text)
def main(): # read config file read_config() # read command line cmdline = docopt( __doc__.format(commands=Command.summarize()), options_first=True ) # start logging logfile = BufferedFile(get_setting('log_file'), True) with Inform(logfile=logfile, hanging_indent=False): try: Command.execute(cmdline['<command>'], cmdline['<args>']) except KeyboardInterrupt: output('Terminated by user.') except Error as err: err.terminate() except OSError as err: fatal(os_error(err)) terminate()
def help(cls): output('Available commands:') output(Command.summarize()) output('\nAvailable topics:') output(cls.summarize())
def run(cls, command, args): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) # read archive file archive_path = get_setting('archive_file') f = PythonFile(archive_path) archive = f.run() import arrow created = archive.get('CREATED') if created: created = arrow.get(created).format('YYYY-MM-DD hh:mm:ss A ZZ') output('archive created: %s' % created) archive_accounts = archive.get('ACCOUNTS') if not archive_accounts: raise Error( 'corrupt archive, ACCOUNTS missing.', culprit=archive_path ) # run the generator generator = PasswordGenerator() # determine the account and open the URL current_accounts = {} for account in generator.all_accounts: entry = account.archive() if entry: current_accounts[account.get_name()] = entry # report any new or missing accounts new = current_accounts.keys() - archive_accounts.keys() missing = archive_accounts.keys() - current_accounts.keys() for each in sorted(new): output('new account:', each) for each in sorted(missing): output('missing account:', each) # for the common accounts, report any differences in the fields common = archive_accounts.keys() & current_accounts.keys() for account_name in sorted(common): archive_account = archive_accounts[account_name] current_account = current_accounts[account_name] # report any new or missing fields new = current_account.keys() - archive_account.keys() missing = archive_account.keys() - current_account.keys() for each in sorted(new): output(account_name, 'new field', each, sep=': ') for each in sorted(missing): output(account_name, 'new field', each, sep=': ') # for the common fields, report any differences in the values shared = archive_account.keys() & current_account.keys() for field_name in sorted(shared): try: archive_value = archive_account[field_name] current_value = current_account[field_name] if is_collection(current_value) != is_collection(archive_value): output(account_name, 'field dimension differs', field_name, sep=': ') elif is_collection(current_value): archive_items = Collection(archive_account[field_name]).items() current_items = Collection(current_account[field_name]).items() archive_keys = set(k for k, v in archive_items) current_keys = set(k for k, v in current_items) new = current_keys - archive_keys missing = archive_keys - current_keys for each in sorted(new): output(account_name, field_name, 'new member', each, sep=': ') for each in sorted(missing): output(account_name, field_name, 'missing member', each, sep=': ') for k in sorted(archive_keys & current_keys): if str(archive_value[k]) != str(current_value[k]): output(account_name, 'member differs', '%s[%s]' % (field_name, k), sep=': ') else: if dedent(str(archive_value)) != dedent(str(current_value)): output(account_name, 'field differs', field_name, sep=': ') except Exception: error( 'unanticipated situation.', culprit=(account_name, field_name) ) raise
def run(cls, command, args): # read command line cmdline = docopt(cls.USAGE, argv=[command] + args) try: # get the specified template templates = get_setting('account_templates') if cmdline['<template>']: template_name = cmdline['<template>'] else: template_name = get_setting('default_account_template') template = dedent(templates[template_name]).strip() + '\n' # save template to tmp file and open it in the editor from tempfile import mktemp tmpfile = GnuPG(mktemp(suffix='_avendesora.gpg')) tmpfile.save(template, get_setting('gpg_ids')) GenericEditor.open_and_search(tmpfile.path) # read the tmp file and determine if it has changed new = tmpfile.read() tmpfile.remove() if new == template: return output('Unchanged, and so ignored.') # hide the values that should be hidden def hide(match): return 'Hidden(%r)' % Obscure.hide(match.group(1)) new = re.sub("<<(.*?)>>", hide, new) # determine the accounts file prefix = cmdline['--file'] if prefix: candidates = [ p for p in get_setting('accounts_files') if p.startswith(prefix) ] if not candidates: raise Error('not found.', cuplrit=cmdline['--file']) if len(candidates) > 1: raise Error( 'ambiguous, matches %s.' % conjoin(candidates), cuplrit=prefix ) filename = candidates[0] else: filename = get_setting('accounts_files')[0] path = to_path(get_setting('settings_dir'), filename) # get original contents of accounts file orig_accounts_file = PythonFile(path) accounts = orig_accounts_file.run() gpg_ids = accounts.get('gpg_ids') # add new account to the contents accounts = orig_accounts_file.code + new + '\n' # rename the original file and then save the new version orig_accounts_file.rename('.saved') new_accounts_file = GnuPG(path) new_accounts_file.save(accounts, gpg_ids) except OSError as err: error(os_error(err)) except KeyError as err: error( 'unknown account template, choose from %s.' % conjoin( sorted(templates.keys()) ), culprit=template_name )