From 49a7771336ce09f6d42c7699ef32aecea0e83182 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 7 Dec 2009 20:07:55 -0500 Subject: Initial directory restructuring to clarify dependencies --- libbe/ui/util/cmdutil.py | 356 +++++++++++++++++++++++++++++++++++++++++++++++ libbe/ui/util/editor.py | 113 +++++++++++++++ libbe/ui/util/pager.py | 65 +++++++++ 3 files changed, 534 insertions(+) create mode 100644 libbe/ui/util/cmdutil.py create mode 100644 libbe/ui/util/editor.py create mode 100644 libbe/ui/util/pager.py (limited to 'libbe/ui') diff --git a/libbe/ui/util/cmdutil.py b/libbe/ui/util/cmdutil.py new file mode 100644 index 0000000..c567984 --- /dev/null +++ b/libbe/ui/util/cmdutil.py @@ -0,0 +1,356 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi +# Oleg Romanyshyn +# W. Trevor King +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Define assorted utilities to make command-line handling easier. +""" + +import glob +import optparse +import os +from textwrap import TextWrapper +from StringIO import StringIO +import sys + +import libbe +import bugdir +import comment +import plugin +import encoding +if libbe.TESTING == True: + import doctest + + +class UserError(Exception): + def __init__(self, msg): + Exception.__init__(self, msg) + +class UnknownCommand(UserError): + def __init__(self, cmd): + Exception.__init__(self, "Unknown command '%s'" % cmd) + self.cmd = cmd + +class UsageError(Exception): + pass + +class GetHelp(Exception): + pass + +class GetCompletions(Exception): + def __init__(self, completions=[]): + msg = "Get allowed completions" + Exception.__init__(self, msg) + self.completions = completions + +def iter_commands(): + for name, module in plugin.iter_plugins("becommands"): + yield name.replace("_", "-"), module + +def get_command(command_name): + """Retrieves the module for a user command + + >>> try: + ... get_command("asdf") + ... except UnknownCommand, e: + ... print e + Unknown command 'asdf' + >>> repr(get_command("list")).startswith(" 0: + max_pos_arg = max(bugid_args.keys()) + else: + max_pos_arg = -1 + for pos,value in enumerate(args): + if value == "--complete": + filter = None + if pos in bugid_args: + filter = bugid_args[pos] + if pos > max_pos_arg and -1 in bugid_args: + filter = bugid_args[-1] + if filter != None: + bugshortnames = [] + try: + bd = bugdir.BugDir(from_disk=True, + manipulate_encodings=False) + bd.load_all_bugs() + bugs = [bug for bug in bd if filter(bug) == True] + bugshortnames = [bd.bug_shortname(bug) for bug in bugs] + except bugdir.NoBugDir: + pass + raise GetCompletions(bugshortnames) + raise GetCompletions() + +def complete_path(path): + """List possible path completions for path.""" + comps = glob.glob(path+"*") + glob.glob(path+"/*") + if len(comps) == 1 and os.path.isdir(comps[0]): + comps.extend(glob.glob(comps[0]+"/*")) + return comps + +def underlined(instring): + """Produces a version of a string that is underlined with '=' + + >>> underlined("Underlined String") + 'Underlined String\\n=================' + """ + + return "%s\n%s" % (instring, "="*len(instring)) + +def select_values(string, possible_values, name="unkown"): + """ + This function allows the user to select values from a list of + possible values. The default is to select all the values: + + >>> select_values(None, ['abc', 'def', 'hij']) + ['abc', 'def', 'hij'] + + The user selects values with a comma-separated limit_string. + Prepending a minus sign to such a list denotes blacklist mode: + + >>> select_values('-abc,hij', ['abc', 'def', 'hij']) + ['def'] + + Without the leading -, the selection is in whitelist mode: + + >>> select_values('abc,hij', ['abc', 'def', 'hij']) + ['abc', 'hij'] + + In either case, appropriate errors are raised if on of the + user-values is not in the list of possible values. The name + parameter lets you make the error message more clear: + + >>> select_values('-xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + >>> select_values('xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + """ + possible_values = list(possible_values) # don't alter the original + if string == None: + pass + elif string.startswith('-'): + blacklisted_values = set(string[1:].split(',')) + for value in blacklisted_values: + if value not in possible_values: + raise UserError('Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values.remove(value) + else: + whitelisted_values = string.split(',') + for value in whitelisted_values: + if value not in possible_values: + raise UserError('Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values = whitelisted_values + return possible_values + +def restrict_file_access(bugdir, path): + """ + Check that the file at path is inside bugdir.root. This is + important if you allow other users to execute becommands with your + username (e.g. if you're running be-handle-mail through your + ~/.procmailrc). If this check wasn't made, a user could e.g. + run + be commit -b ~/.ssh/id_rsa "Hack to expose ssh key" + which would expose your ssh key to anyone who could read the VCS + log. + """ + in_root = bugdir.vcs.path_in_root(path, bugdir.root) + if in_root == False: + raise UserError('file access restricted!\n %s not in %s' + % (path, bugdir.root)) + +def parse_id(id): + """ + Return (bug_id, comment_id) tuple. + Basically inverts Comment.comment_shortnames() + >>> parse_id('XYZ') + ('XYZ', None) + >>> parse_id('XYZ:123') + ('XYZ', ':123') + >>> parse_id('') + Traceback (most recent call last): + ... + UserError: invalid id ''. + >>> parse_id('::') + Traceback (most recent call last): + ... + UserError: invalid id '::'. + """ + if len(id) == 0: + raise UserError("invalid id '%s'." % id) + if id.count(':') > 1: + raise UserError("invalid id '%s'." % id) + elif id.count(':') == 1: + # Split shortname generated by Comment.comment_shortnames() + bug_id,comment_id = id.split(':') + comment_id = ':'+comment_id + else: + bug_id = id + comment_id = None + return (bug_id, comment_id) + +def bug_from_id(bdir, id): + """ + Exception translation for the command-line interface. + id can be either the bug shortname or the full uuid. + """ + try: + bug = bdir.bug_from_shortname(id) + except (bugdir.MultipleBugMatches, bugdir.NoBugMatches), e: + raise UserError(e.message) + return bug + +def bug_comment_from_id(bdir, id): + """ + Return (bug,comment) tuple matching shortname. id can be either + the bug/comment shortname or the full uuid. If there is no + comment part to the id, the returned comment is the bug's + .comment_root. + """ + bug_id,comment_id = parse_id(id) + try: + bug = bdir.bug_from_shortname(bug_id) + except (bugdir.MultipleBugMatches, bugdir.NoBugMatches), e: + raise UserError(e.message) + if comment_id == None: + comm = bug.comment_root + else: + #bug.load_comments(load_full=False) + try: + comm = bug.comment_root.comment_from_shortname(comment_id) + except comment.InvalidShortname, e: + raise UserError(e.message) + return (bug, comm) + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/ui/util/editor.py b/libbe/ui/util/editor.py new file mode 100644 index 0000000..859cedc --- /dev/null +++ b/libbe/ui/util/editor.py @@ -0,0 +1,113 @@ +# Bugs Everywhere, a distributed bugtracker +# Copyright (C) 2008-2009 Gianluca Montecchi +# W. Trevor King +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Define editor_string(), a function that invokes an editor to accept +user-produced text as a string. +""" + +import codecs +import locale +import os +import sys +import tempfile + +import libbe +if libbe.TESTING == True: + import doctest + + +default_encoding = sys.getfilesystemencoding() or locale.getpreferredencoding() + +comment_marker = u"== Anything below this line will be ignored\n" + +class CantFindEditor(Exception): + def __init__(self): + Exception.__init__(self, "Can't find editor to get string from") + +def editor_string(comment=None, encoding=None): + """Invokes the editor, and returns the user-produced text as a string + + >>> if "EDITOR" in os.environ: + ... del os.environ["EDITOR"] + >>> if "VISUAL" in os.environ: + ... del os.environ["VISUAL"] + >>> editor_string() + Traceback (most recent call last): + CantFindEditor: Can't find editor to get string from + >>> os.environ["EDITOR"] = "echo bar > " + >>> editor_string() + u'bar\\n' + >>> os.environ["VISUAL"] = "echo baz > " + >>> editor_string() + u'baz\\n' + >>> del os.environ["EDITOR"] + >>> del os.environ["VISUAL"] + """ + if encoding == None: + encoding = default_encoding + for name in ('VISUAL', 'EDITOR'): + try: + editor = os.environ[name] + break + except KeyError: + pass + else: + raise CantFindEditor() + fhandle, fname = tempfile.mkstemp() + try: + if comment is not None: + cstring = u'\n'+comment_string(comment) + os.write(fhandle, cstring.encode(encoding)) + os.close(fhandle) + oldmtime = os.path.getmtime(fname) + os.system("%s %s" % (editor, fname)) + f = codecs.open(fname, "r", encoding) + output = trimmed_string(f.read()) + f.close() + if output.rstrip('\n') == "": + output = None + finally: + os.unlink(fname) + return output + + +def comment_string(comment): + """ + >>> comment_string('hello') == comment_marker+"hello" + True + """ + return comment_marker + comment + + +def trimmed_string(instring): + """ + >>> trimmed_string("hello\\n"+comment_marker) + u'hello\\n' + >>> trimmed_string("hi!\\n" + comment_string('Booga')) + u'hi!\\n' + """ + out = [] + for line in instring.splitlines(True): + if line.startswith(comment_marker): + break + out.append(line) + return ''.join(out) + +if libbe.TESTING == True: + suite = doctest.DocTestSuite() diff --git a/libbe/ui/util/pager.py b/libbe/ui/util/pager.py new file mode 100644 index 0000000..1ddc3fa --- /dev/null +++ b/libbe/ui/util/pager.py @@ -0,0 +1,65 @@ +# Copyright (C) 2009 W. Trevor King +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +Automatic pager for terminal output (a la Git). +""" + +import sys, os, select + +# see http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby +def run_pager(paginate='auto'): + """ + paginate should be one of 'never', 'auto', or 'always'. + + usage: just call this function and continue using sys.stdout like + you normally would. + """ + if paginate == 'never' \ + or sys.platform == 'win32' \ + or not hasattr(sys.stdout, 'isatty') \ + or sys.stdout.isatty() == False: + return + + if paginate == 'auto': + if 'LESS' not in os.environ: + os.environ['LESS'] = '' # += doesn't work on undefined var + # don't page if the input is short enough + os.environ['LESS'] += ' -FRX' + if 'PAGER' in os.environ: + pager = os.environ['PAGER'] + else: + pager = 'less' + + read_fd, write_fd = os.pipe() + if os.fork() == 0: + # child process + os.close(read_fd) + os.close(0) + os.dup2(write_fd, 1) + os.close(write_fd) + if hasattr(sys.stderr, 'isatty') and sys.stderr.isatty() == True: + os.dup2(1, 2) + return + + # parent process, become pager + os.close(write_fd) + os.dup2(read_fd, 0) + os.close(read_fd) + + # Wait until we have input before we start the pager + select.select([0], [], []) + os.execlp(pager, pager) -- cgit From bf3d434b244c57556bec979acbc658c30eb58221 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 12 Dec 2009 00:31:55 -0500 Subject: Added libbe.command.base (with Command class) and moved list command to new format. --- libbe/ui/__init__.py | 1 + libbe/ui/base.py | 23 ++++++++ libbe/ui/util/__init__.py | 69 +++++++++++++++++++++++ libbe/ui/util/cmdutil.py | 141 ++++++---------------------------------------- libbe/ui/util/repo.py | 4 ++ 5 files changed, 115 insertions(+), 123 deletions(-) create mode 100644 libbe/ui/__init__.py create mode 100644 libbe/ui/base.py create mode 100644 libbe/ui/util/__init__.py create mode 100644 libbe/ui/util/repo.py (limited to 'libbe/ui') diff --git a/libbe/ui/__init__.py b/libbe/ui/__init__.py new file mode 100644 index 0000000..b98f164 --- /dev/null +++ b/libbe/ui/__init__.py @@ -0,0 +1 @@ +# Copyright diff --git a/libbe/ui/base.py b/libbe/ui/base.py new file mode 100644 index 0000000..d26115f --- /dev/null +++ b/libbe/ui/base.py @@ -0,0 +1,23 @@ + def _setup_user_id(self, user_id): + if isinstance(self.storage, storage.vcs.base.VCS): + self.storage.user_id = user_id + def _guess_user_id(self): + if isinstance(self.storage, storage.vcs.base.VCS): + return self.storage.get_user_id() + def _set_user_id(self, old_user_id, new_user_id): + self._setup_user_id(new_user_id) + self._prop_save_settings(old_user_id, new_user_id) + + @_versioned_property(name="user_id", + doc= +"""The user's prefered name, e.g. 'John Doe '. Note +that the Arch VCS backend *enforces* ids with this format.""", + change_hook=_set_user_id, + generator=_guess_user_id) + def user_id(): return {} + + @_versioned_property(name="default_assignee", + doc= +"""The default assignee for new bugs e.g. 'John Doe '.""") + def default_assignee(): return {} + diff --git a/libbe/ui/util/__init__.py b/libbe/ui/util/__init__.py new file mode 100644 index 0000000..a650d33 --- /dev/null +++ b/libbe/ui/util/__init__.py @@ -0,0 +1,69 @@ +# Copyright + +class Completer (object): + def __init__(self, options): + self.options = options + def __call__(self, bugdir, fragment=None): + return [fragment] + +def complete_status(bugdir, fragment=None): + return [fragment] +def complete_severity(bugdir, fragment=None): + return [fragment] +def complete_assigned(bugdir, fragment=None): + return [fragment] +def complete_extra_strings(bugdir, fragment=None): + return [fragment] + +def select_values(string, possible_values, name="unkown"): + """ + This function allows the user to select values from a list of + possible values. The default is to select all the values: + + >>> select_values(None, ['abc', 'def', 'hij']) + ['abc', 'def', 'hij'] + + The user selects values with a comma-separated limit_string. + Prepending a minus sign to such a list denotes blacklist mode: + + >>> select_values('-abc,hij', ['abc', 'def', 'hij']) + ['def'] + + Without the leading -, the selection is in whitelist mode: + + >>> select_values('abc,hij', ['abc', 'def', 'hij']) + ['abc', 'hij'] + + In either case, appropriate errors are raised if on of the + user-values is not in the list of possible values. The name + parameter lets you make the error message more clear: + + >>> select_values('-xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + >>> select_values('xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + """ + possible_values = list(possible_values) # don't alter the original + if string == None: + pass + elif string.startswith('-'): + blacklisted_values = set(string[1:].split(',')) + for value in blacklisted_values: + if value not in possible_values: + raise UserError('Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values.remove(value) + else: + whitelisted_values = string.split(',') + for value in whitelisted_values: + if value not in possible_values: + raise UserError('Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values = whitelisted_values + return possible_values diff --git a/libbe/ui/util/cmdutil.py b/libbe/ui/util/cmdutil.py index c567984..b2d8a99 100644 --- a/libbe/ui/util/cmdutil.py +++ b/libbe/ui/util/cmdutil.py @@ -37,48 +37,11 @@ if libbe.TESTING == True: import doctest -class UserError(Exception): - def __init__(self, msg): - Exception.__init__(self, msg) - -class UnknownCommand(UserError): - def __init__(self, cmd): - Exception.__init__(self, "Unknown command '%s'" % cmd) - self.cmd = cmd - -class UsageError(Exception): - pass - -class GetHelp(Exception): - pass - -class GetCompletions(Exception): - def __init__(self, completions=[]): - msg = "Get allowed completions" - Exception.__init__(self, msg) - self.completions = completions def iter_commands(): for name, module in plugin.iter_plugins("becommands"): yield name.replace("_", "-"), module -def get_command(command_name): - """Retrieves the module for a user command - - >>> try: - ... get_command("asdf") - ... except UnknownCommand, e: - ... print e - Unknown command 'asdf' - >>> repr(get_command("list")).startswith(">> select_values(None, ['abc', 'def', 'hij']) - ['abc', 'def', 'hij'] - - The user selects values with a comma-separated limit_string. - Prepending a minus sign to such a list denotes blacklist mode: - - >>> select_values('-abc,hij', ['abc', 'def', 'hij']) - ['def'] - - Without the leading -, the selection is in whitelist mode: - - >>> select_values('abc,hij', ['abc', 'def', 'hij']) - ['abc', 'hij'] - - In either case, appropriate errors are raised if on of the - user-values is not in the list of possible values. The name - parameter lets you make the error message more clear: - - >>> select_values('-xyz,hij', ['abc', 'def', 'hij'], name="foobar") - Traceback (most recent call last): - ... - UserError: Invalid foobar xyz - ['abc', 'def', 'hij'] - >>> select_values('xyz,hij', ['abc', 'def', 'hij'], name="foobar") - Traceback (most recent call last): - ... - UserError: Invalid foobar xyz - ['abc', 'def', 'hij'] - """ - possible_values = list(possible_values) # don't alter the original - if string == None: - pass - elif string.startswith('-'): - blacklisted_values = set(string[1:].split(',')) - for value in blacklisted_values: - if value not in possible_values: - raise UserError('Invalid %s %s\n %s' - % (name, value, possible_values)) - possible_values.remove(value) - else: - whitelisted_values = string.split(',') - for value in whitelisted_values: - if value not in possible_values: - raise UserError('Invalid %s %s\n %s' - % (name, value, possible_values)) - possible_values = whitelisted_values - return possible_values def restrict_file_access(bugdir, path): """ @@ -352,5 +245,7 @@ def bug_comment_from_id(bdir, id): raise UserError(e.message) return (bug, comm) + + if libbe.TESTING == True: suite = doctest.DocTestSuite() diff --git a/libbe/ui/util/repo.py b/libbe/ui/util/repo.py new file mode 100644 index 0000000..174c5b1 --- /dev/null +++ b/libbe/ui/util/repo.py @@ -0,0 +1,4 @@ +# Copyright + +def complete(string): + pass -- cgit From 8b4ad37815cbef1e06532179f9ca098588d9cb44 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 12 Dec 2009 01:12:17 -0500 Subject: Moved command completion from libbe.ui.util to libbe.command.util --- libbe/ui/util/__init__.py | 68 ----------------------------------------------- 1 file changed, 68 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/util/__init__.py b/libbe/ui/util/__init__.py index a650d33..b98f164 100644 --- a/libbe/ui/util/__init__.py +++ b/libbe/ui/util/__init__.py @@ -1,69 +1 @@ # Copyright - -class Completer (object): - def __init__(self, options): - self.options = options - def __call__(self, bugdir, fragment=None): - return [fragment] - -def complete_status(bugdir, fragment=None): - return [fragment] -def complete_severity(bugdir, fragment=None): - return [fragment] -def complete_assigned(bugdir, fragment=None): - return [fragment] -def complete_extra_strings(bugdir, fragment=None): - return [fragment] - -def select_values(string, possible_values, name="unkown"): - """ - This function allows the user to select values from a list of - possible values. The default is to select all the values: - - >>> select_values(None, ['abc', 'def', 'hij']) - ['abc', 'def', 'hij'] - - The user selects values with a comma-separated limit_string. - Prepending a minus sign to such a list denotes blacklist mode: - - >>> select_values('-abc,hij', ['abc', 'def', 'hij']) - ['def'] - - Without the leading -, the selection is in whitelist mode: - - >>> select_values('abc,hij', ['abc', 'def', 'hij']) - ['abc', 'hij'] - - In either case, appropriate errors are raised if on of the - user-values is not in the list of possible values. The name - parameter lets you make the error message more clear: - - >>> select_values('-xyz,hij', ['abc', 'def', 'hij'], name="foobar") - Traceback (most recent call last): - ... - UserError: Invalid foobar xyz - ['abc', 'def', 'hij'] - >>> select_values('xyz,hij', ['abc', 'def', 'hij'], name="foobar") - Traceback (most recent call last): - ... - UserError: Invalid foobar xyz - ['abc', 'def', 'hij'] - """ - possible_values = list(possible_values) # don't alter the original - if string == None: - pass - elif string.startswith('-'): - blacklisted_values = set(string[1:].split(',')) - for value in blacklisted_values: - if value not in possible_values: - raise UserError('Invalid %s %s\n %s' - % (name, value, possible_values)) - possible_values.remove(value) - else: - whitelisted_values = string.split(',') - for value in whitelisted_values: - if value not in possible_values: - raise UserError('Invalid %s %s\n %s' - % (name, value, possible_values)) - possible_values = whitelisted_values - return possible_values -- cgit From f8a498f76d7bbcb42cf7bbc80164d98bfe57f8ab Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 12 Dec 2009 01:43:20 -0500 Subject: Added libbe.ui.util.user for managing user ids. --- libbe/ui/base.py | 23 -------------- libbe/ui/util/user.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 23 deletions(-) create mode 100644 libbe/ui/util/user.py (limited to 'libbe/ui') diff --git a/libbe/ui/base.py b/libbe/ui/base.py index d26115f..e69de29 100644 --- a/libbe/ui/base.py +++ b/libbe/ui/base.py @@ -1,23 +0,0 @@ - def _setup_user_id(self, user_id): - if isinstance(self.storage, storage.vcs.base.VCS): - self.storage.user_id = user_id - def _guess_user_id(self): - if isinstance(self.storage, storage.vcs.base.VCS): - return self.storage.get_user_id() - def _set_user_id(self, old_user_id, new_user_id): - self._setup_user_id(new_user_id) - self._prop_save_settings(old_user_id, new_user_id) - - @_versioned_property(name="user_id", - doc= -"""The user's prefered name, e.g. 'John Doe '. Note -that the Arch VCS backend *enforces* ids with this format.""", - change_hook=_set_user_id, - generator=_guess_user_id) - def user_id(): return {} - - @_versioned_property(name="default_assignee", - doc= -"""The default assignee for new bugs e.g. 'John Doe '.""") - def default_assignee(): return {} - diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py new file mode 100644 index 0000000..de2138c --- /dev/null +++ b/libbe/ui/util/user.py @@ -0,0 +1,84 @@ +# Copyright + +""" +Tools for getting, setting, creating, and parsing the user's id. For +example, + 'John Doe ' +Note that the Arch VCS backend *enforces* ids with this format. +""" + +import libbe.storage.util.config + +def _get_fallback_username(self): + name = None + for env in ["LOGNAME", "USERNAME"]: + if os.environ.has_key(env): + name = os.environ[env] + break + assert name != None + return name + +def _get_fallback_email(self): + hostname = gethostname() + name = _get_fallback_username() + return "%s@%s" % (name, hostname) + +def create_user_id(self, name, email=None): + """ + >>> create_id("John Doe", "jdoe@example.com") + 'John Doe ' + >>> create_id("John Doe") + 'John Doe' + """ + assert len(name) > 0 + if email == None or len(email) == 0: + return name + else: + return "%s <%s>" % (name, email) + +def parse_user_id(self, value): + """ + >>> parse_id("John Doe ") + ('John Doe', 'jdoe@example.com') + >>> parse_id("John Doe") + ('John Doe', None) + >>> try: + ... parse_id("John Doe ") + ... except AssertionError: + ... print "Invalid match" + Invalid match + """ + emailexp = re.compile("(.*) <([^>]*)>(.*)") + match = emailexp.search(value) + if match == None: + email = None + name = value + else: + assert len(match.groups()) == 3 + assert match.groups()[2] == "", match.groups() + email = match.groups()[1] + name = match.groups()[0] + assert name != None + assert len(name) > 0 + return (name, email) + +def get_user_id(self, storage=None): + """ + Sometimes the storage will also keep track of the user id (e.g. most VCSs). + """ + user = libbe.storage.util.config.get_val('user') + if user != None: + return user + if storage != None and hasattr(storage, 'get_user_id'): + user = vcs.get_user_id() + if user != None: + return user + name = _get_fallback_username() + email = _get_fallback_email() + user = _create_user_id(name, email) + return user + +def set_user_id(self, user_id): + """ + """ + user = libbe.storage.util.config.set_val('user', user_id) -- cgit From dff6bd9bf89ca80e2265696a478e540476718c9c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 12 Dec 2009 20:57:59 -0500 Subject: Moved be to libbe.ui.command_line and transitioned to Command format. --- libbe/ui/base.py | 1 + libbe/ui/command_line.py | 270 +++++++++++++++++++++++++++++++++++++++++++++++ libbe/ui/util/cmdutil.py | 108 ------------------- 3 files changed, 271 insertions(+), 108 deletions(-) create mode 100755 libbe/ui/command_line.py (limited to 'libbe/ui') diff --git a/libbe/ui/base.py b/libbe/ui/base.py index e69de29..b98f164 100644 --- a/libbe/ui/base.py +++ b/libbe/ui/base.py @@ -0,0 +1 @@ +# Copyright diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py new file mode 100755 index 0000000..e84d32a --- /dev/null +++ b/libbe/ui/command_line.py @@ -0,0 +1,270 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Chris Ball +# Gianluca Montecchi +# Oleg Romanyshyn +# W. Trevor King +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" +A command line interface to Bugs Everywhere. +""" + +import optparse +import os +import sys + +import libbe +import libbe.command +import libbe.command.util +import libbe.version +import libbe.ui.util.pager + +if libbe.TESTING == True: + import doctest + +class CallbackExit (Exception): + pass + +class CmdOptionParser(optparse.OptionParser): + def __init__(self, command): + self.command = command + optparse.OptionParser.__init__(self) + self.disable_interspersed_args() + self.remove_option('-h') + self._option_by_name = {} + for option in self.command.options: + self._add_option(option) + + def _add_option(self, option): + option.validate() + self._option_by_name[option.name] = option + opt_strings = ['--'+option.name] + if option.short_name != None: + opt_strings.append('-'+option.short_name) + if option.arg == None: # a callback option + opt = optparse.Option( + *opt_strings, action='callback', + callback=self.callback, help=option.help) + else: + kwargs = {} + if option.arg.type == 'bool': + action = 'store_true' + else: + kwargs['type'] = option.arg.type + action = 'store' + opt = optparse.Option( + *opt_strings, metavar=option.arg.metavar, + default=option.arg.default, action=action, + help=option.help, **kwargs) + opt._option = option + self.add_option(opt) + + def parse_args(self, args=None, values=None): + args = self._get_args(args) + options,parsed_args = optparse.OptionParser.parse_args( + self, args=args, values=values) + for name,value in options.__dict__.items(): + if value == '--complete': + argument = None + option = self._option_by_name[name] + if option.arg != None: + argument = option.arg + fragment = None + indices = [i for i,arg in enumerate(args) + if arg == '--complete'] + for i in indices: + assert i > 0 # this --complete is an option value + if args[i-1] in ['--%s' % o.name + for o in self.command.options]: + name = args[i-1][2:] + if name == option.name: + break + elif option.short_name != None \ + and args[i-1].startswith('-') \ + and args[i-1].endswith(option.short_name): + break + if i+1 < len(args): + fragment = args[i+1] + self.complete(argument, fragment) + for i,arg in enumerate(parsed_args): + if arg == '--complete': + if i < len(self.command.args): + argument = self.command.args[i] + else: + argument = self.command.args[-1] + if argument.repeatable == False: + raise libbe.command.UserError('Too many arguments') + fragment = None + if i < len(args) - 1: + fragment = args[i+1] + self.complete(argument, fragment) + if len(parsed_args) > len(self.command.args) \ + and self.command.args[-1] == False: + raise libbe.command.UserError('Too many arguments') + for arg in self.command.args[len(parsed_args):]: + if arg.optional == False: + raise libbe.command.UserError( + 'Missing required argument %s' % arg.metavar) + return (options, parsed_args) + + def callback(self, option, opt, value, parser): + command_option = option._option + if command_option.name == 'complete': + argument = None + fragment = None + if len(parser.rargs) > 0: + fragment = parser.rargs[0] + self.complete(argument, fragment) + else: + print command_option.callback( + self.command, command_option, value) + raise CallbackExit + + def complete(self, argument=None, fragment=None): + print argument, fragment + comps = self.command.complete(argument, fragment) + if fragment != None: + comps = [c for c in comps if c.startswith(fragment)] + print '\n'.join(comps) + raise CallbackExit + + +class BE (libbe.command.Command): + """Class for parsing the command line arguments for `be`. + This class does not contain a useful _run() method. Call this + module's main() function instead. + + >>> be = BE() + >>> p = CmdOptionParser(be) + >>> p.exit_after_callback = False + >>> try: + ... options,args = p.parse_args(['--help']) # doctest: +ELLIPSIS + ... except CallbackExit: + ... pass + usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]] + + Options: + -h HELP, --help=HELP Print a help message. + + --complete=STRING Print a list of possible completions. + + --version=VERSION Print version string. + ... + >>> options,args = p.parse_args(['--complete']) # doctest: +ELLIPSIS + """ + name = 'be' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='version', + help='Print version string.', + callback=self.version), + libbe.command.Option(name='full-version', + help='Print full version information.', + callback=self.full_version), + libbe.command.Option(name='repo', short_name='r', + help='Select BE repository (see `be help repo`) rather ' + 'than the current directory.', + arg=libbe.command.Argument( + name='repo', metavar='REPO', default='.', + completion_callback=libbe.command.util.complete_path)), + libbe.command.Option(name='paginate', + help='Pipe all output into less (or if set, $PAGER).'), + libbe.command.Option(name='no-pager', + help='Do not pipe git output into a pager.'), + ]) + self.args.extend([ + libbe.command.Argument( + name='command', optional=False, + completion_callback=libbe.command.util.complete_command), + libbe.command.Argument( + name='args', optional=True, repeatable=True) + ]) + + def _usage(self): + return 'usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]]' + + def _long_help(self): + cmdlist = [] + for name in libbe.command.commands(): + module = libbe.command.get_command(name) + Class = getattr(module, name.capitalize()) + cmdlist.append((name, Class.__doc__.splitlines()[0])) + cmdlist.sort() + longest_cmd_len = max([len(name) for name,desc in cmdlist]) + ret = ['Bugs Everywhere - Distributed bug tracking', + '', 'Supported commands'] + for name, desc in cmdlist: + numExtraSpaces = longest_cmd_len-len(name) + ret.append('be %s%*s %s' % (name, numExtraSpaces, '', desc)) + ret.extend(['', 'Run', ' be help [command]', 'for more information.']) + return '\n'.join(ret) + + def version(self, *args): + return libbe.version.version(verbose=False) + + def full_version(self, *args): + return libbe.version.version(verbose=True) + +def main(): + parser = CmdOptionParser(BE()) + try: + options,args = parser.parse_args() + except CallbackExit: + return 0 + except libbe.command.UserError, e: + print 'ERROR:\n', e + return 1 + + paginate = 'auto' + if options.paginate == True: + paginate = 'always' + if options.no_pager== True: + paginate = 'never' + libbe.ui.util.pager.run_pager(paginate) + + command_name = args[0] + try: + module = libbe.command.get_command(command_name) + except libbe.command.UnknownCommand, e: + print e + return 1 + Class = getattr(module, command_name.capitalize()) + command = Class() + parser = CmdOptionParser(command) + if command.requires_bugdir == True: + storage = libbe.storage.get_storage(options['repo']) + storage.connect() + bugdir = BugDir(storage) + else: + storage = None + bugdir = None + try: + options,args = parser.parse_args(args[1:]) + command.run(bugdir, options, args) + except CallbackExit: + if storage != None: storage.disconnect() + return 0 + except libbe.command.UserError, e: + if storage != None: storage.disconnect() + print 'ERROR:\n', e + return 1 + if storage != None: storage.disconnect() + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/libbe/ui/util/cmdutil.py b/libbe/ui/util/cmdutil.py index b2d8a99..86ff9fc 100644 --- a/libbe/ui/util/cmdutil.py +++ b/libbe/ui/util/cmdutil.py @@ -55,115 +55,7 @@ def execute(cmd, args, ret = 0 return ret -class GetHelp(Exception): - pass - - -class GetCompletions(Exception): - def __init__(self, completions=[]): - msg = "Get allowed completions" - Exception.__init__(self, msg) - self.completions = completions - -def raise_get_help(option, opt, value, parser): - raise GetHelp - -def raise_get_completions(option, opt, value, parser): - if hasattr(parser, "command") and parser.command == "be": - comps = [] - for command, module in iter_commands(): - comps.append(command) - for opt in parser.option_list: - comps.append(opt.get_opt_string()) - raise GetCompletions(comps) - raise GetCompletions(completions(sys.argv[1])) - -def completions(cmd): - parser = get_command(cmd).get_parser() - longopts = [] - for opt in parser.option_list: - longopts.append(opt.get_opt_string()) - return longopts - - -class CmdOptionParser(optparse.OptionParser): - def __init__(self, usage): - optparse.OptionParser.__init__(self, usage) - self.disable_interspersed_args() - self.remove_option("-h") - self.add_option("-h", "--help", action="callback", - callback=raise_get_help, help="Print a help message") - self.add_option("--complete", action="callback", - callback=raise_get_completions, - help="Print a list of available completions") - - def error(self, message): - raise UsageError(message) - - def iter_options(self): - return iter_combine([self._short_opt.iterkeys(), - self._long_opt.iterkeys()]) - - def help_str(self): - f = StringIO() - self.print_help(f) - return f.getvalue() - -def option_value_pairs(options, parser): - """ - Iterate through OptionParser (option, value) pairs. - """ - for option in [o.dest for o in parser.option_list if o.dest != None]: - value = getattr(options, option) - yield (option, value) -def default_complete(options, args, parser, bugid_args={}): - """ - A dud complete implementation for becommands so that the - --complete argument doesn't cause any problems. Use this - until you've set up a command-specific complete function. - - bugid_args is an optional dict where the keys are positional - arguments taking bug shortnames and the values are functions for - filtering, since that's a common enough operation. - e.g. for "be open [options] BUGID" - bugid_args = {0: lambda bug : bug.active == False} - A positional argument of -1 specifies all remaining arguments - (e.g in the case of "be show BUGID BUGID ..."). - """ - for option,value in option_value_pairs(options, parser): - if value == "--complete": - raise GetCompletions() - if len(bugid_args.keys()) > 0: - max_pos_arg = max(bugid_args.keys()) - else: - max_pos_arg = -1 - for pos,value in enumerate(args): - if value == "--complete": - filter = None - if pos in bugid_args: - filter = bugid_args[pos] - if pos > max_pos_arg and -1 in bugid_args: - filter = bugid_args[-1] - if filter != None: - bugshortnames = [] - try: - bd = bugdir.BugDir(from_disk=True, - manipulate_encodings=False) - bd.load_all_bugs() - bugs = [bug for bug in bd if filter(bug) == True] - bugshortnames = [bd.bug_shortname(bug) for bug in bugs] - except bugdir.NoBugDir: - pass - raise GetCompletions(bugshortnames) - raise GetCompletions() - -def complete_path(path): - """List possible path completions for path.""" - comps = glob.glob(path+"*") + glob.glob(path+"/*") - if len(comps) == 1 and os.path.isdir(comps[0]): - comps.extend(glob.glob(comps[0]+"/*")) - return comps def restrict_file_access(bugdir, path): -- cgit From 4d057dab603f42ec40b911dbee6792dcf107bd14 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 06:19:23 -0500 Subject: Converted libbe.storage.vcs.base to new Storage format. --- libbe/ui/command_line.py | 20 ++++++++++++++------ libbe/ui/util/user.py | 18 +++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index e84d32a..b0b52af 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -27,6 +27,7 @@ import os import sys import libbe +import libbe.bugdir import libbe.command import libbe.command.util import libbe.version @@ -52,11 +53,14 @@ class CmdOptionParser(optparse.OptionParser): option.validate() self._option_by_name[option.name] = option opt_strings = ['--'+option.name] + dest = option.name.replace('-', '_') + assert '_' not in option.name, \ + 'Non-reconstructable option name %s' % option.name if option.short_name != None: opt_strings.append('-'+option.short_name) if option.arg == None: # a callback option opt = optparse.Option( - *opt_strings, action='callback', + *opt_strings, action='callback', dest=dest, callback=self.callback, help=option.help) else: kwargs = {} @@ -68,7 +72,7 @@ class CmdOptionParser(optparse.OptionParser): opt = optparse.Option( *opt_strings, metavar=option.arg.metavar, default=option.arg.default, action=action, - help=option.help, **kwargs) + dest=dest, help=option.help, **kwargs) opt._option = option self.add_option(opt) @@ -76,7 +80,11 @@ class CmdOptionParser(optparse.OptionParser): args = self._get_args(args) options,parsed_args = optparse.OptionParser.parse_args( self, args=args, values=values) - for name,value in options.__dict__.items(): + options = options.__dict__ + for name,value in options.items(): + if '_' in name: # reconstruct original option name + options[name.replace('_', '-')] = options.pop(name) + for name,value in options.items(): if value == '--complete': argument = None option = self._option_by_name[name] @@ -231,9 +239,9 @@ def main(): return 1 paginate = 'auto' - if options.paginate == True: + if options['paginate'] == True: paginate = 'always' - if options.no_pager== True: + if options['no-pager'] == True: paginate = 'never' libbe.ui.util.pager.run_pager(paginate) @@ -249,7 +257,7 @@ def main(): if command.requires_bugdir == True: storage = libbe.storage.get_storage(options['repo']) storage.connect() - bugdir = BugDir(storage) + bugdir = libbe.bugdir.BugDir(storage, from_storage=True) else: storage = None bugdir = None diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py index de2138c..64eb30c 100644 --- a/libbe/ui/util/user.py +++ b/libbe/ui/util/user.py @@ -7,6 +7,10 @@ example, Note that the Arch VCS backend *enforces* ids with this format. """ +import re +from socket import gethostname + +import libbe import libbe.storage.util.config def _get_fallback_username(self): @@ -23,11 +27,11 @@ def _get_fallback_email(self): name = _get_fallback_username() return "%s@%s" % (name, hostname) -def create_user_id(self, name, email=None): +def create_user_id(name, email=None): """ - >>> create_id("John Doe", "jdoe@example.com") + >>> create_user_id("John Doe", "jdoe@example.com") 'John Doe ' - >>> create_id("John Doe") + >>> create_user_id("John Doe") 'John Doe' """ assert len(name) > 0 @@ -36,14 +40,14 @@ def create_user_id(self, name, email=None): else: return "%s <%s>" % (name, email) -def parse_user_id(self, value): +def parse_user_id(value): """ - >>> parse_id("John Doe ") + >>> parse_user_id("John Doe ") ('John Doe', 'jdoe@example.com') - >>> parse_id("John Doe") + >>> parse_user_id("John Doe") ('John Doe', None) >>> try: - ... parse_id("John Doe ") + ... parse_user_id("John Doe ") ... except AssertionError: ... print "Invalid match" Invalid match -- cgit From c2a50865f1ea73f43f2d347b2e7595a484f43e78 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 06:33:50 -0500 Subject: Rearrange libbe.ui.command_line.CmdOptionParser._add_option() for Python 2.5 Python 2.6 doesn't mind, but 2.5 doesn't like kwargs after a * expansion: $ ./be list Traceback (most recent call last): File "./be", line 5, in import libbe.ui.command_line File "/home/wking/src/fun/be/be.restructure/libbe/ui/command_line.py", line 63 *opt_strings, action='callback', dest=dest, ^ SyntaxError: invalid syntax --- libbe/ui/command_line.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index b0b52af..60741f5 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -52,27 +52,28 @@ class CmdOptionParser(optparse.OptionParser): def _add_option(self, option): option.validate() self._option_by_name[option.name] = option - opt_strings = ['--'+option.name] - dest = option.name.replace('-', '_') + long_opt = '--%s' % option.name + if option.short_name != None: + short_opt = '-%s' % option.short_name assert '_' not in option.name, \ 'Non-reconstructable option name %s' % option.name - if option.short_name != None: - opt_strings.append('-'+option.short_name) + kwargs = {'dest':option.name.replace('-', '_'), + 'help':option.help} if option.arg == None: # a callback option - opt = optparse.Option( - *opt_strings, action='callback', dest=dest, - callback=self.callback, help=option.help) + kwargs['action'] = 'callback' + kwargs['callback'] = self.callback else: - kwargs = {} if option.arg.type == 'bool': - action = 'store_true' + kwargs['action'] = 'store_true' else: kwargs['type'] = option.arg.type - action = 'store' - opt = optparse.Option( - *opt_strings, metavar=option.arg.metavar, - default=option.arg.default, action=action, - dest=dest, help=option.help, **kwargs) + kwargs['action'] = 'store' + kwargs['metavar'] = option.arg.metavar + kwargs['default'] = option.arg.default + if option.short_name != None: + opt = optparse.Option(short_opt, long_opt, **kwargs) + else: + opt = optparse.Option(long_opt, **kwargs) opt._option = option self.add_option(opt) -- cgit From 55e0abfa27b693768f495044d10444d9d92e4fca Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 07:39:55 -0500 Subject: More fixes for libbe.storage.vcs.hg + .git transition. --- libbe/ui/util/user.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py index 64eb30c..a58f83a 100644 --- a/libbe/ui/util/user.py +++ b/libbe/ui/util/user.py @@ -13,7 +13,7 @@ from socket import gethostname import libbe import libbe.storage.util.config -def _get_fallback_username(self): +def get_fallback_username(self): name = None for env in ["LOGNAME", "USERNAME"]: if os.environ.has_key(env): @@ -22,9 +22,9 @@ def _get_fallback_username(self): assert name != None return name -def _get_fallback_email(self): +def get_fallback_email(self): hostname = gethostname() - name = _get_fallback_username() + name = get_fallback_username() return "%s@%s" % (name, hostname) def create_user_id(name, email=None): @@ -77,9 +77,9 @@ def get_user_id(self, storage=None): user = vcs.get_user_id() if user != None: return user - name = _get_fallback_username() - email = _get_fallback_email() - user = _create_user_id(name, email) + name = get_fallback_username() + email = get_fallback_email() + user = create_user_id(name, email) return user def set_user_id(self, user_id): -- cgit From 9b1f9db34189744ded87a28aaa395d9344593df2 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 01:12:08 -0500 Subject: Transitioned assign to Command format --- libbe/ui/util/user.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py index a58f83a..5b16b73 100644 --- a/libbe/ui/util/user.py +++ b/libbe/ui/util/user.py @@ -7,13 +7,14 @@ example, Note that the Arch VCS backend *enforces* ids with this format. """ +import os import re from socket import gethostname import libbe import libbe.storage.util.config -def get_fallback_username(self): +def get_fallback_username(): name = None for env in ["LOGNAME", "USERNAME"]: if os.environ.has_key(env): @@ -22,7 +23,7 @@ def get_fallback_username(self): assert name != None return name -def get_fallback_email(self): +def get_fallback_email(): hostname = gethostname() name = get_fallback_username() return "%s@%s" % (name, hostname) @@ -66,7 +67,7 @@ def parse_user_id(value): assert len(name) > 0 return (name, email) -def get_user_id(self, storage=None): +def get_user_id(storage=None): """ Sometimes the storage will also keep track of the user id (e.g. most VCSs). """ @@ -82,7 +83,7 @@ def get_user_id(self, storage=None): user = create_user_id(name, email) return user -def set_user_id(self, user_id): +def set_user_id(user_id): """ """ user = libbe.storage.util.config.set_val('user', user_id) -- cgit From 1bec5c0d3880a1cd848d765365104e221f390e71 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 02:01:06 -0500 Subject: Added parse_user() calls to Assign --- libbe/ui/util/repo.py | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 libbe/ui/util/repo.py (limited to 'libbe/ui') diff --git a/libbe/ui/util/repo.py b/libbe/ui/util/repo.py deleted file mode 100644 index 174c5b1..0000000 --- a/libbe/ui/util/repo.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright - -def complete(string): - pass -- cgit From 2f0ceedba5b6619faf476cd1aa67e826e91d5c7c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 03:29:20 -0500 Subject: Transitioned init to Command format --- libbe/ui/command_line.py | 10 ++++++---- libbe/ui/util/user.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 60741f5..c59a302 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -255,16 +255,18 @@ def main(): Class = getattr(module, command_name.capitalize()) command = Class() parser = CmdOptionParser(command) + storage = None + bugdir = None if command.requires_bugdir == True: + assert command.requires_unconnected_storage == False storage = libbe.storage.get_storage(options['repo']) storage.connect() bugdir = libbe.bugdir.BugDir(storage, from_storage=True) - else: - storage = None - bugdir = None + elif: command.requires_unconnected_storage == True: + storage = libbe.storage.get_storage(options['repo']) try: options,args = parser.parse_args(args[1:]) - command.run(bugdir, options, args) + command.run(storage, bugdir, options, args) except CallbackExit: if storage != None: storage.disconnect() return 0 diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py index 5b16b73..d6af89b 100644 --- a/libbe/ui/util/user.py +++ b/libbe/ui/util/user.py @@ -75,7 +75,7 @@ def get_user_id(storage=None): if user != None: return user if storage != None and hasattr(storage, 'get_user_id'): - user = vcs.get_user_id() + user = storage.get_user_id() if user != None: return user name = get_fallback_username() -- cgit From 19fe0817ba7c2cd04caea3adfa82d4490288a548 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 07:37:51 -0500 Subject: Transitioned comment to Command format --- libbe/ui/command_line.py | 38 ++++++++++++++++++++++++++------------ libbe/ui/util/editor.py | 11 +++++------ 2 files changed, 31 insertions(+), 18 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index c59a302..4042123 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -43,8 +43,8 @@ class CmdOptionParser(optparse.OptionParser): def __init__(self, command): self.command = command optparse.OptionParser.__init__(self) - self.disable_interspersed_args() self.remove_option('-h') + self.disable_interspersed_args() self._option_by_name = {} for option in self.command.options: self._add_option(option) @@ -62,12 +62,13 @@ class CmdOptionParser(optparse.OptionParser): if option.arg == None: # a callback option kwargs['action'] = 'callback' kwargs['callback'] = self.callback + elif option.arg.type == 'bool': + kwargs['action'] = 'store_true' + kwargs['metavar'] = None + kwargs['default'] = False else: - if option.arg.type == 'bool': - kwargs['action'] = 'store_true' - else: - kwargs['type'] = option.arg.type - kwargs['action'] = 'store' + kwargs['type'] = option.arg.type + kwargs['action'] = 'store' kwargs['metavar'] = option.arg.metavar kwargs['default'] = option.arg.default if option.short_name != None: @@ -143,7 +144,6 @@ class CmdOptionParser(optparse.OptionParser): raise CallbackExit def complete(self, argument=None, fragment=None): - print argument, fragment comps = self.command.complete(argument, fragment) if fragment != None: comps = [c for c in comps if c.startswith(fragment)] @@ -166,13 +166,24 @@ class BE (libbe.command.Command): usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]] Options: - -h HELP, --help=HELP Print a help message. + -h, --help Print a help message. - --complete=STRING Print a list of possible completions. + --complete Print a list of possible completions. - --version=VERSION Print version string. + --version Print version string. + ... + >>> try: + ... options,args = p.parse_args(['--complete']) # doctest: +ELLIPSIS + ... except CallbackExit: + ... print ' got callback' + --help + --complete + --version ... - >>> options,args = p.parse_args(['--complete']) # doctest: +ELLIPSIS + subscribe + tag + target + got callback """ name = 'be' @@ -262,8 +273,11 @@ def main(): storage = libbe.storage.get_storage(options['repo']) storage.connect() bugdir = libbe.bugdir.BugDir(storage, from_storage=True) - elif: command.requires_unconnected_storage == True: + elif command.requires_storage == True \ + or command.requires_unconnected_storage == True: storage = libbe.storage.get_storage(options['repo']) + if command.requires_unconnected_storage == False: + storage.connect() try: options,args = parser.parse_args(args[1:]) command.run(storage, bugdir, options, args) diff --git a/libbe/ui/util/editor.py b/libbe/ui/util/editor.py index 859cedc..83262e7 100644 --- a/libbe/ui/util/editor.py +++ b/libbe/ui/util/editor.py @@ -28,12 +28,12 @@ import sys import tempfile import libbe +import libbe.util.encoding + if libbe.TESTING == True: import doctest -default_encoding = sys.getfilesystemencoding() or locale.getpreferredencoding() - comment_marker = u"== Anything below this line will be ignored\n" class CantFindEditor(Exception): @@ -60,7 +60,7 @@ def editor_string(comment=None, encoding=None): >>> del os.environ["VISUAL"] """ if encoding == None: - encoding = default_encoding + encoding = libbe.util.encoding.get_filesystem_encoding() for name in ('VISUAL', 'EDITOR'): try: editor = os.environ[name] @@ -77,9 +77,8 @@ def editor_string(comment=None, encoding=None): os.close(fhandle) oldmtime = os.path.getmtime(fname) os.system("%s %s" % (editor, fname)) - f = codecs.open(fname, "r", encoding) - output = trimmed_string(f.read()) - f.close() + output = libbe.util.encoding.get_file_contents( + fname, encoding=encoding, decode=True) if output.rstrip('\n') == "": output = None finally: -- cgit From 3e5823d0985a54dec37f103dc72fda604d12a948 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 18:25:28 -0500 Subject: Transitioned import_xml to Command-format --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 4042123..feeccd4 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -222,7 +222,7 @@ class BE (libbe.command.Command): cmdlist = [] for name in libbe.command.commands(): module = libbe.command.get_command(name) - Class = getattr(module, name.capitalize()) + Class = libbe.command.get_command_class(module, name) cmdlist.append((name, Class.__doc__.splitlines()[0])) cmdlist.sort() longest_cmd_len = max([len(name) for name,desc in cmdlist]) -- cgit From f9ee7a537561be80b9c232dd4fc848ddb564f6b0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 20:33:35 -0500 Subject: Transitioned help to Command-format --- libbe/ui/command_line.py | 1 + 1 file changed, 1 insertion(+) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index feeccd4..84f9450 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -265,6 +265,7 @@ def main(): return 1 Class = getattr(module, command_name.capitalize()) command = Class() + command.ui = self parser = CmdOptionParser(command) storage = None bugdir = None -- cgit From 1b9c628529848af370adbc67b5ba298236a1b86d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 23:15:58 -0500 Subject: Transitioned severity to Command-format, also added Command._get_*() The old .requires_* thing was rediculous. The new ._get_*() callbacks allow the caller to provide a means for getting the expensive structures, which the command can use, or not, as required. This will also make it easier to implement the completion callbacks. The callbacks should probably have matching .set_*() methods, to avoid the current cache tweaking cmd._storage = ... etc. But that can wait for now... --- libbe/ui/command_line.py | 23 ++++++----------------- libbe/ui/util/cmdutil.py | 14 -------------- 2 files changed, 6 insertions(+), 31 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 84f9450..0aa34f7 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -264,29 +264,18 @@ def main(): print e return 1 Class = getattr(module, command_name.capitalize()) - command = Class() - command.ui = self + def gucs(): + return libbe.storage.get_storage(options['repo']) + command = Class(get_unconnected_storage=gucs, ui=ui) parser = CmdOptionParser(command) - storage = None - bugdir = None - if command.requires_bugdir == True: - assert command.requires_unconnected_storage == False - storage = libbe.storage.get_storage(options['repo']) - storage.connect() - bugdir = libbe.bugdir.BugDir(storage, from_storage=True) - elif command.requires_storage == True \ - or command.requires_unconnected_storage == True: - storage = libbe.storage.get_storage(options['repo']) - if command.requires_unconnected_storage == False: - storage.connect() try: options,args = parser.parse_args(args[1:]) - command.run(storage, bugdir, options, args) + command.run(options, args) except CallbackExit: - if storage != None: storage.disconnect() + command.cleanup() return 0 except libbe.command.UserError, e: - if storage != None: storage.disconnect() + command.cleanup() print 'ERROR:\n', e return 1 if storage != None: storage.disconnect() diff --git a/libbe/ui/util/cmdutil.py b/libbe/ui/util/cmdutil.py index 86ff9fc..f2eb5b9 100644 --- a/libbe/ui/util/cmdutil.py +++ b/libbe/ui/util/cmdutil.py @@ -59,20 +59,6 @@ def execute(cmd, args, def restrict_file_access(bugdir, path): - """ - Check that the file at path is inside bugdir.root. This is - important if you allow other users to execute becommands with your - username (e.g. if you're running be-handle-mail through your - ~/.procmailrc). If this check wasn't made, a user could e.g. - run - be commit -b ~/.ssh/id_rsa "Hack to expose ssh key" - which would expose your ssh key to anyone who could read the VCS - log. - """ - in_root = bugdir.vcs.path_in_root(path, bugdir.root) - if in_root == False: - raise UserError('file access restricted!\n %s not in %s' - % (path, bugdir.root)) def parse_id(id): """ -- cgit From 6e474b0dc04efa60aaeb82c09d4fd4f4b10678da Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 23:31:57 -0500 Subject: Transitioned status to Command-format --- libbe/ui/command_line.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 0aa34f7..bb38be8 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -241,7 +241,8 @@ class BE (libbe.command.Command): return libbe.version.version(verbose=True) def main(): - parser = CmdOptionParser(BE()) + be = BE() + parser = CmdOptionParser(be) try: options,args = parser.parse_args() except CallbackExit: @@ -264,9 +265,12 @@ def main(): print e return 1 Class = getattr(module, command_name.capitalize()) - def gucs(): - return libbe.storage.get_storage(options['repo']) - command = Class(get_unconnected_storage=gucs, ui=ui) + class GUCS (object): + def __init__(self, repo): + self.repo = repo + def __call__(self): + return libbe.storage.get_storage(self.repo) + command = Class(get_unconnected_storage=GUCS(options['repo']), ui=be) parser = CmdOptionParser(command) try: options,args = parser.parse_args(args[1:]) @@ -278,7 +282,7 @@ def main(): command.cleanup() print 'ERROR:\n', e return 1 - if storage != None: storage.disconnect() + command.cleanup() return 0 if __name__ == '__main__': -- cgit From a1bd5432ffbf28bf2fadfed8a5b2db917f243344 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 15 Dec 2009 01:52:17 -0500 Subject: Transitioned tag to Command-format --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index bb38be8..2dff930 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -122,7 +122,7 @@ class CmdOptionParser(optparse.OptionParser): fragment = args[i+1] self.complete(argument, fragment) if len(parsed_args) > len(self.command.args) \ - and self.command.args[-1] == False: + and self.command.args[-1].repeatable == False: raise libbe.command.UserError('Too many arguments') for arg in self.command.args[len(parsed_args):]: if arg.optional == False: -- cgit From 4370270929db62a32d168ae221ecc70a2d80269e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 15 Dec 2009 02:38:51 -0500 Subject: Transitioned target to Command-format --- libbe/ui/command_line.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 2dff930..ce0e55e 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -223,6 +223,8 @@ class BE (libbe.command.Command): for name in libbe.command.commands(): module = libbe.command.get_command(name) Class = libbe.command.get_command_class(module, name) + assert hasattr(Class, '__doc__') and Class.__doc__ != None, \ + 'Command class %s missing docstring' % Class cmdlist.append((name, Class.__doc__.splitlines()[0])) cmdlist.sort() longest_cmd_len = max([len(name) for name,desc in cmdlist]) -- cgit From 89b7a1411e4658e831f5d635534b24355dbb941d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 15 Dec 2009 06:44:20 -0500 Subject: Fixed libbe.command.diff + ugly BugDir.duplicate_bugdir implementation duplicate_bugdir() works, but for the vcs backends, it could require shelling out for _every_ file read. This could, and probably will, be horribly slow. Still it works ;). I'm not sure what a better implementation would be. The old implementation checked out the entire earlier state into a temporary directory pros: single shell out, simple upgrade implementation cons: wouldn't work well for HTTP backens I think a good solution would run along the lines of the currently commented out code in duplicate_bugdir(), where a VersionedStorage.changed_since(revision) call would give you a list of changed files. diff could work off of that directly, without the need to generate a whole duplicate bugdir. I'm stuck on how to handle upgrades though... Also removed trailing whitespace from all python files. --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index ce0e55e..6eead67 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -259,7 +259,7 @@ def main(): if options['no-pager'] == True: paginate = 'never' libbe.ui.util.pager.run_pager(paginate) - + command_name = args[0] try: module = libbe.command.get_command(command_name) -- cgit From cfebc238cbda9b6338ec57d5c215c4cbf0246f8b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 27 Dec 2009 16:50:36 -0500 Subject: Moved InvalidStorageVersion from libbe.command to libbe.storage Also added ConnectionError pretty-print to ui.command_line, storage version checking to BugDir.duplicate_bugdir(), and optional revision argument to Storage.storage_version(). --- libbe/ui/command_line.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 6eead67..3812789 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -284,6 +284,10 @@ def main(): command.cleanup() print 'ERROR:\n', e return 1 + except libbe.storage.ConnectionError, e: + command.cleanup() + print 'Connection Error:\n', e + return 1 command.cleanup() return 0 -- cgit From 83d7624d1deeb73b7f0baddef88069ff27a128ab Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Dec 2009 07:09:18 -0500 Subject: Don't run pager for the 'comment' command. It may need access to the tty for the spawned editor. --- libbe/ui/command_line.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 3812789..856f810 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -253,13 +253,6 @@ def main(): print 'ERROR:\n', e return 1 - paginate = 'auto' - if options['paginate'] == True: - paginate = 'always' - if options['no-pager'] == True: - paginate = 'never' - libbe.ui.util.pager.run_pager(paginate) - command_name = args[0] try: module = libbe.command.get_command(command_name) @@ -274,6 +267,17 @@ def main(): return libbe.storage.get_storage(self.repo) command = Class(get_unconnected_storage=GUCS(options['repo']), ui=be) parser = CmdOptionParser(command) + + if command.name in ['comment']: + paginate = 'never' + else: + paginate = 'auto' + if options['paginate'] == True: + paginate = 'always' + if options['no-pager'] == True: + paginate = 'never' + libbe.ui.util.pager.run_pager(paginate) + try: options,args = parser.parse_args(args[1:]) command.run(options, args) -- cgit From 4fbf5d1d222610b0775f95472fe1a60aaedea29f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Dec 2009 10:40:48 -0500 Subject: Restore comment stripping to libbe.ui.util.editor.editor_string() --- libbe/ui/util/editor.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'libbe/ui') diff --git a/libbe/ui/util/editor.py b/libbe/ui/util/editor.py index 83262e7..1a10fa4 100644 --- a/libbe/ui/util/editor.py +++ b/libbe/ui/util/editor.py @@ -56,6 +56,9 @@ def editor_string(comment=None, encoding=None): >>> os.environ["VISUAL"] = "echo baz > " >>> editor_string() u'baz\\n' + >>> os.environ["VISUAL"] = "echo 'baz\\n== Anything below this line will be ignored\\nHi' > " + >>> editor_string() + u'baz\\n' >>> del os.environ["EDITOR"] >>> del os.environ["VISUAL"] """ @@ -79,6 +82,7 @@ def editor_string(comment=None, encoding=None): os.system("%s %s" % (editor, fname)) output = libbe.util.encoding.get_file_contents( fname, encoding=encoding, decode=True) + output = trimmed_string(output) if output.rstrip('\n') == "": output = None finally: -- cgit From e2b648f2148d7b6550fb3a4bcfde4eff714a7ec6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Dec 2009 10:56:04 -0500 Subject: Allow external use of Command.usage() and use CmdOptionParser.set_usage() This fixes $ python be diff -2 Usage: be [options] be: error: no such option: -2 and we now get the correct output $ python be diff -2 Usage: be diff [options] [REVISION] be: error: no such option: -2 --- libbe/ui/command_line.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 856f810..b5a3991 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -48,6 +48,8 @@ class CmdOptionParser(optparse.OptionParser): self._option_by_name = {} for option in self.command.options: self._add_option(option) + self.set_usage(command.usage()) + def _add_option(self, option): option.validate() @@ -215,7 +217,7 @@ class BE (libbe.command.Command): name='args', optional=True, repeatable=True) ]) - def _usage(self): + def usage(self): return 'usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]]' def _long_help(self): -- cgit From 4372a17b4215df25b3da0b68daf4d6b490a8955c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 19:00:40 -0500 Subject: Fixed up the completion helpers in libbe.command.util This entailed a fairly thorough cleanup of libbe.util.id. Remaining unimplemented completion helpers: * complete_assigned() * complete_extra_strings() Since these would require scanning all (active?) bugs to compile lists, and I was feeling lazy... --- libbe/ui/command_line.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index b5a3991..b99f812 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -113,15 +113,19 @@ class CmdOptionParser(optparse.OptionParser): self.complete(argument, fragment) for i,arg in enumerate(parsed_args): if arg == '--complete': - if i < len(self.command.args): + if i > 0 and self.command.name == 'be': + break # let this pass through for the command parser to handle + elif i < len(self.command.args): argument = self.command.args[i] + elif len(self.command.args) == 0: + break # command doesn't take arguments else: argument = self.command.args[-1] if argument.repeatable == False: raise libbe.command.UserError('Too many arguments') fragment = None - if i < len(args) - 1: - fragment = args[i+1] + if i < len(parsed_args) - 1: + fragment = parsed_args[i+1] self.complete(argument, fragment) if len(parsed_args) > len(self.command.args) \ and self.command.args[-1].repeatable == False: @@ -149,7 +153,8 @@ class CmdOptionParser(optparse.OptionParser): comps = self.command.complete(argument, fragment) if fragment != None: comps = [c for c in comps if c.startswith(fragment)] - print '\n'.join(comps) + if len(comps) > 0: + print '\n'.join(comps) raise CallbackExit -- cgit From 1d764100cbe15c8fe26b836ed0e9d494ab14500d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 19:49:50 -0500 Subject: Remove libbe.ui.util.cmdutil All of its functionality has moved off into more focused modules. --- libbe/ui/util/cmdutil.py | 129 ----------------------------------------------- 1 file changed, 129 deletions(-) delete mode 100644 libbe/ui/util/cmdutil.py (limited to 'libbe/ui') diff --git a/libbe/ui/util/cmdutil.py b/libbe/ui/util/cmdutil.py deleted file mode 100644 index f2eb5b9..0000000 --- a/libbe/ui/util/cmdutil.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. -# Gianluca Montecchi -# Oleg Romanyshyn -# W. Trevor King -# -# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. - -""" -Define assorted utilities to make command-line handling easier. -""" - -import glob -import optparse -import os -from textwrap import TextWrapper -from StringIO import StringIO -import sys - -import libbe -import bugdir -import comment -import plugin -import encoding -if libbe.TESTING == True: - import doctest - - - -def iter_commands(): - for name, module in plugin.iter_plugins("becommands"): - yield name.replace("_", "-"), module - -def execute(cmd, args, - manipulate_encodings=True, restrict_file_access=False, - dir="."): - enc = encoding.get_encoding() - cmd = get_command(cmd) - ret = cmd.execute([a.decode(enc) for a in args], - manipulate_encodings=manipulate_encodings, - restrict_file_access=restrict_file_access, - dir=dir) - if ret == None: - ret = 0 - return ret - - - - -def restrict_file_access(bugdir, path): - -def parse_id(id): - """ - Return (bug_id, comment_id) tuple. - Basically inverts Comment.comment_shortnames() - >>> parse_id('XYZ') - ('XYZ', None) - >>> parse_id('XYZ:123') - ('XYZ', ':123') - >>> parse_id('') - Traceback (most recent call last): - ... - UserError: invalid id ''. - >>> parse_id('::') - Traceback (most recent call last): - ... - UserError: invalid id '::'. - """ - if len(id) == 0: - raise UserError("invalid id '%s'." % id) - if id.count(':') > 1: - raise UserError("invalid id '%s'." % id) - elif id.count(':') == 1: - # Split shortname generated by Comment.comment_shortnames() - bug_id,comment_id = id.split(':') - comment_id = ':'+comment_id - else: - bug_id = id - comment_id = None - return (bug_id, comment_id) - -def bug_from_id(bdir, id): - """ - Exception translation for the command-line interface. - id can be either the bug shortname or the full uuid. - """ - try: - bug = bdir.bug_from_shortname(id) - except (bugdir.MultipleBugMatches, bugdir.NoBugMatches), e: - raise UserError(e.message) - return bug - -def bug_comment_from_id(bdir, id): - """ - Return (bug,comment) tuple matching shortname. id can be either - the bug/comment shortname or the full uuid. If there is no - comment part to the id, the returned comment is the bug's - .comment_root. - """ - bug_id,comment_id = parse_id(id) - try: - bug = bdir.bug_from_shortname(bug_id) - except (bugdir.MultipleBugMatches, bugdir.NoBugMatches), e: - raise UserError(e.message) - if comment_id == None: - comm = bug.comment_root - else: - #bug.load_comments(load_full=False) - try: - comm = bug.comment_root.comment_from_shortname(comment_id) - except comment.InvalidShortname, e: - raise UserError(e.message) - return (bug, comm) - - - -if libbe.TESTING == True: - suite = doctest.DocTestSuite() -- cgit From 182a44a4a284e118da03c1182f2b9a8b76dd0d93 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 19:53:13 -0500 Subject: Don't worry about whitespace in `be --help` --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index b99f812..9df474e 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -167,7 +167,7 @@ class BE (libbe.command.Command): >>> p = CmdOptionParser(be) >>> p.exit_after_callback = False >>> try: - ... options,args = p.parse_args(['--help']) # doctest: +ELLIPSIS + ... options,args = p.parse_args(['--help']) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE ... except CallbackExit: ... pass usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]] -- cgit From 7452e54d3e08c8c40a52209a19e1436622987d36 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 31 Dec 2009 06:51:27 -0500 Subject: Removed libbe/ui/base.py. Basic UI definitions are in libbe/command/base.py. --- libbe/ui/base.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 libbe/ui/base.py (limited to 'libbe/ui') diff --git a/libbe/ui/base.py b/libbe/ui/base.py deleted file mode 100644 index b98f164..0000000 --- a/libbe/ui/base.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright -- cgit From cfae8a8302f06a84196700138d7ddbb25e91ea31 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 31 Dec 2009 14:32:39 -0500 Subject: Added UserInterface and other improved abstractions for command handling --- libbe/ui/command_line.py | 78 ++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 35 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 9df474e..b36d251 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -145,7 +145,7 @@ class CmdOptionParser(optparse.OptionParser): fragment = parser.rargs[0] self.complete(argument, fragment) else: - print command_option.callback( + print >> self.command.stdout, command_option.callback( self.command, command_option, value) raise CallbackExit @@ -154,16 +154,18 @@ class CmdOptionParser(optparse.OptionParser): if fragment != None: comps = [c for c in comps if c.startswith(fragment)] if len(comps) > 0: - print '\n'.join(comps) + print >> self.command.stdout, '\n'.join(comps) raise CallbackExit - class BE (libbe.command.Command): """Class for parsing the command line arguments for `be`. This class does not contain a useful _run() method. Call this module's main() function instead. - >>> be = BE() + >>> ui = libbe.command.UserInterface() + >>> ui.io.stdout = sys.stdout + >>> be = BE(ui=ui) + >>> ui.io.setup_command(be) >>> p = CmdOptionParser(be) >>> p.exit_after_callback = False >>> try: @@ -228,8 +230,7 @@ class BE (libbe.command.Command): def _long_help(self): cmdlist = [] for name in libbe.command.commands(): - module = libbe.command.get_command(name) - Class = libbe.command.get_command_class(module, name) + Class = libbe.command.get_command_class(command_name=name) assert hasattr(Class, '__doc__') and Class.__doc__ != None, \ 'Command class %s missing docstring' % Class cmdlist.append((name, Class.__doc__.splitlines()[0])) @@ -249,31 +250,51 @@ class BE (libbe.command.Command): def full_version(self, *args): return libbe.version.version(verbose=True) +def dispatch(ui, command, args): + parser = CmdOptionParser(command) + try: + options,args = parser.parse_args(args) + ret = ui.run(command, options, args) + except CallbackExit: + return 0 + except libbe.command.UserError, e: + print >> ui.io.stdout, 'ERROR:\n', e + return 1 + except libbe.storage.ConnectionError, e: + print >> ui.io.stdout, 'Connection Error:\n', e + return 1 + except (libbe.util.id.MultipleIDMatches, libbe.util.id.NoIDMatches, + libbe.util.id.InvalidIDStructure), e: + print >> ui.io.stdout, 'Invalid id:\n', e + return 1 + finally: + command.cleanup() + return ret + def main(): - be = BE() + io = libbe.command.StdInputOutput() + ui = libbe.command.UserInterface(io) + ui.restrict_file_access = False + ui.storage_callbacks = None + be = BE(ui=ui) parser = CmdOptionParser(be) try: options,args = parser.parse_args() except CallbackExit: return 0 except libbe.command.UserError, e: - print 'ERROR:\n', e + print >> be.stdout, 'ERROR:\n', e return 1 - command_name = args[0] + command_name = args.pop(0) try: - module = libbe.command.get_command(command_name) + Class = libbe.command.get_command_class(command_name=command_name) except libbe.command.UnknownCommand, e: - print e + print >> be.stdout, e return 1 - Class = getattr(module, command_name.capitalize()) - class GUCS (object): - def __init__(self, repo): - self.repo = repo - def __call__(self): - return libbe.storage.get_storage(self.repo) - command = Class(get_unconnected_storage=GUCS(options['repo']), ui=be) - parser = CmdOptionParser(command) + + ui.storage_callbacks = libbe.command.StorageCallbacks(options['repo']) + command = Class(ui=ui) if command.name in ['comment']: paginate = 'never' @@ -285,22 +306,9 @@ def main(): paginate = 'never' libbe.ui.util.pager.run_pager(paginate) - try: - options,args = parser.parse_args(args[1:]) - command.run(options, args) - except CallbackExit: - command.cleanup() - return 0 - except libbe.command.UserError, e: - command.cleanup() - print 'ERROR:\n', e - return 1 - except libbe.storage.ConnectionError, e: - command.cleanup() - print 'Connection Error:\n', e - return 1 - command.cleanup() - return 0 + ret = dispatch(ui, command, args) + ui.cleanup() + return ret if __name__ == '__main__': sys.exit(main()) -- cgit From 2d1562d951e763fed71fe60e77cc9921be9abdc9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 31 Dec 2009 15:33:39 -0500 Subject: Use fragment in base command completion + command io fixups. --- libbe/ui/command_line.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index b36d251..1c7399d 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -277,24 +277,27 @@ def main(): ui.restrict_file_access = False ui.storage_callbacks = None be = BE(ui=ui) + ui.setup_command(be) + parser = CmdOptionParser(be) try: options,args = parser.parse_args() except CallbackExit: return 0 except libbe.command.UserError, e: - print >> be.stdout, 'ERROR:\n', e + print >> ui.io.stdout, 'ERROR:\n', e return 1 command_name = args.pop(0) try: Class = libbe.command.get_command_class(command_name=command_name) except libbe.command.UnknownCommand, e: - print >> be.stdout, e + print >> ui.io.stdout, e return 1 ui.storage_callbacks = libbe.command.StorageCallbacks(options['repo']) command = Class(ui=ui) + ui.setup_command(command) if command.name in ['comment']: paginate = 'never' -- cgit From 4d4283ecd654f1efb058cd7f7dba6be88b70ee92 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 1 Jan 2010 08:11:08 -0500 Subject: Updated copyright information --- libbe/ui/__init__.py | 16 +++++++++++++++- libbe/ui/command_line.py | 2 +- libbe/ui/util/__init__.py | 16 +++++++++++++++- libbe/ui/util/editor.py | 2 +- libbe/ui/util/pager.py | 2 +- libbe/ui/util/user.py | 16 +++++++++++++++- 6 files changed, 48 insertions(+), 6 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/__init__.py b/libbe/ui/__init__.py index b98f164..3b461a5 100644 --- a/libbe/ui/__init__.py +++ b/libbe/ui/__init__.py @@ -1 +1,15 @@ -# Copyright +# Copyright (C) 2009-2010 W. Trevor King +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 7f74782..f57983f 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -1,4 +1,4 @@ -# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. # Chris Ball # Gianluca Montecchi # Oleg Romanyshyn diff --git a/libbe/ui/util/__init__.py b/libbe/ui/util/__init__.py index b98f164..3b461a5 100644 --- a/libbe/ui/util/__init__.py +++ b/libbe/ui/util/__init__.py @@ -1 +1,15 @@ -# Copyright +# Copyright (C) 2009-2010 W. Trevor King +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/libbe/ui/util/editor.py b/libbe/ui/util/editor.py index 1a10fa4..ce14365 100644 --- a/libbe/ui/util/editor.py +++ b/libbe/ui/util/editor.py @@ -1,5 +1,5 @@ # Bugs Everywhere, a distributed bugtracker -# Copyright (C) 2008-2009 Gianluca Montecchi +# Copyright (C) 2008-2010 Gianluca Montecchi # W. Trevor King # # This program is free software; you can redistribute it and/or modify diff --git a/libbe/ui/util/pager.py b/libbe/ui/util/pager.py index 1ddc3fa..88b58af 100644 --- a/libbe/ui/util/pager.py +++ b/libbe/ui/util/pager.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 W. Trevor King +# Copyright (C) 2009-2010 W. Trevor King # # 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 diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py index d6af89b..98f16a6 100644 --- a/libbe/ui/util/user.py +++ b/libbe/ui/util/user.py @@ -1,4 +1,18 @@ -# Copyright +# Copyright (C) 2009-2010 W. Trevor King +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. """ Tools for getting, setting, creating, and parsing the user's id. For -- cgit From 5f26c407789a2f8fc51d89b6d0c590253b50c754 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 1 Jan 2010 14:43:03 -0500 Subject: Added libbe.command.serve and libbe.storage.http for HTTP backend. Now the following works: some-BE-dir$ ./be serve $ ./be --repo http://localhost:8000 list I haven't come up with a clean idea for testing this yet, so other commands may be broken, but once we get the testing working, it shouldn't be too hard to get everything working over HTTP :). --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index f57983f..7ba6cf5 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -299,7 +299,7 @@ def main(): command = Class(ui=ui) ui.setup_command(command) - if command.name in ['comment', 'commit']: + if command.name in ['comment', 'commit', 'serve']: paginate = 'never' else: paginate = 'auto' -- cgit From 34b5bbd98e420672ecce64ba085bbf1a4205267e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 20 Jan 2010 07:19:56 -0500 Subject: Add better help message on COMMAND-less be call --- libbe/ui/command_line.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 7ba6cf5..17f7b35 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -285,7 +285,14 @@ def main(): except CallbackExit: return 0 except libbe.command.UserError, e: - print >> ui.io.stdout, 'ERROR:\n', e + if str(e).endswith('COMMAND'): + # no command given, print usage string + print >> ui.io.stdout, 'ERROR:' + print >> ui.io.stdout, be.usage(), '\n', e + print >> ui.io.stdout, 'For example, try' + print >> ui.io.stdout, ' be help' + else: + print >> ui.io.stdout, 'ERROR:\n', e return 1 command_name = args.pop(0) -- cgit From 116c6eb9e54dd223d715923dc6fb32e87a89aad6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 20 Jan 2010 07:32:56 -0500 Subject: Fixed `be help` (used to raise NotImplementedError) --- libbe/ui/command_line.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 17f7b35..9c97eec 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -250,6 +250,16 @@ class BE (libbe.command.Command): def full_version(self, *args): return libbe.version.version(verbose=True) +class CommandLine (libbe.command.UserInterface): + def __init__(self, *args, **kwargs): + libbe.command.UserInterface.__init__(self, *args, **kwargs) + self.restrict_file_access = False + self.storage_callbacks = None + def help(self): + be = BE(ui=self) + self.setup_command(be) + return be.help() + def dispatch(ui, command, args): parser = CmdOptionParser(command) try: @@ -273,9 +283,7 @@ def dispatch(ui, command, args): def main(): io = libbe.command.StdInputOutput() - ui = libbe.command.UserInterface(io) - ui.restrict_file_access = False - ui.storage_callbacks = None + ui = CommandLine(io) be = BE(ui=ui) ui.setup_command(be) -- cgit From 55f1e8588edc35410dd16b883e7cddd06ebc4ed6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 20 Jan 2010 15:44:39 -0500 Subject: Strip footers (signatures) in be-mail-to-xml --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 9c97eec..89d791d 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -314,7 +314,7 @@ def main(): command = Class(ui=ui) ui.setup_command(command) - if command.name in ['comment', 'commit', 'serve']: + if command.name in ['comment', 'commit', 'import-xml', 'serve']: paginate = 'never' else: paginate = 'auto' -- cgit From 12a948ddf009fb2510eda5be4f576dc3e7de401f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 29 Jan 2010 07:32:41 -0500 Subject: Fix editor spawning on null-string EDITOR and VISUAL. $ EDITOR= VISUAL= python -c 'import os; import sys; print os.environ' {..., 'EDITOR': '', ..., 'VISUAL': '', ...} --- libbe/ui/util/editor.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/util/editor.py b/libbe/ui/util/editor.py index ce14365..1a430c7 100644 --- a/libbe/ui/util/editor.py +++ b/libbe/ui/util/editor.py @@ -64,13 +64,12 @@ def editor_string(comment=None, encoding=None): """ if encoding == None: encoding = libbe.util.encoding.get_filesystem_encoding() + editor = None for name in ('VISUAL', 'EDITOR'): - try: + if name in os.environ and os.environ[name] != '': editor = os.environ[name] break - except KeyError: - pass - else: + if editor == None: raise CantFindEditor() fhandle, fname = tempfile.mkstemp() try: -- cgit From 148379492d314c0eb98ee8559aea7a2fd0baaeb2 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 1 Feb 2010 10:59:52 -0500 Subject: Clearer UnicodeDecodeError message in command_line.dispatch(). See #bea/e30# ("Where should the vcs-name and encoding configuration options live?") for details. --- libbe/ui/command_line.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'libbe/ui') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 89d791d..dd10954 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -267,6 +267,14 @@ def dispatch(ui, command, args): ret = ui.run(command, options, args) except CallbackExit: return 0 + except UnicodeDecodeError, e: + print >> ui.io.stdout, '\n'.join([ + 'ERROR:', str(e), + 'You should set a locale that supports unicode, e.g.', + ' export LANG=en_US.utf8', + 'See http://docs.python.org/library/locale.html for details', + ]) + return 1 except libbe.command.UserError, e: print >> ui.io.stdout, 'ERROR:\n', e return 1 -- cgit From 8ccb71280c24480eb661fc668c31ff06bc7a3148 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 7 Feb 2010 13:37:37 -0500 Subject: Use email.utils.formataddr/parseaddr to generate/parse user IDs --- libbe/ui/util/user.py | 89 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 29 deletions(-) (limited to 'libbe/ui') diff --git a/libbe/ui/util/user.py b/libbe/ui/util/user.py index 98f16a6..460a1dd 100644 --- a/libbe/ui/util/user.py +++ b/libbe/ui/util/user.py @@ -14,13 +14,21 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" -Tools for getting, setting, creating, and parsing the user's id. For -example, - 'John Doe ' -Note that the Arch VCS backend *enforces* ids with this format. +"""Tools for getting, setting, creating, and parsing the user's ID. + +IDs will look like 'John Doe '. Note that the +:mod:`libbe.storage.vcs.arch ` *enforces* IDs with +this format. + +Do not confuse the user IDs discussed in this module, which refer to +humans, with the "user IDs" discussed in :mod:`libbe.util.id`, which +are human-readable tags refering to objects. """ +try: + from email.utils import formataddr, parseaddr +except ImportErrror: # adjust to old python < 2.5 + from email.Utils import formataddr, parseaddr import os import re from socket import gethostname @@ -29,6 +37,8 @@ import libbe import libbe.storage.util.config def get_fallback_username(): + """Return a username extracted from environmental variables. + """ name = None for env in ["LOGNAME", "USERNAME"]: if os.environ.has_key(env): @@ -38,52 +48,69 @@ def get_fallback_username(): return name def get_fallback_email(): + """Return an email address extracted from environmental variables. + """ hostname = gethostname() name = get_fallback_username() return "%s@%s" % (name, hostname) def create_user_id(name, email=None): - """ + """Create a user ID string from given `name` and `email` strings. + + Examples + -------- + >>> create_user_id("John Doe", "jdoe@example.com") 'John Doe ' >>> create_user_id("John Doe") 'John Doe' + + See Also + -------- + parse_user_id : inverse """ assert len(name) > 0 if email == None or len(email) == 0: return name else: - return "%s <%s>" % (name, email) + return formataddr((name, email)) def parse_user_id(value): - """ + """Parse a user ID string into `name` and `email` strings. + + Examples + -------- + >>> parse_user_id("John Doe ") ('John Doe', 'jdoe@example.com') >>> parse_user_id("John Doe") ('John Doe', None) - >>> try: - ... parse_user_id("John Doe ") - ... except AssertionError: - ... print "Invalid match" - Invalid match + >>> parse_user_id("John Doe ") + ('John Doe', 'jdoe@example.com') + + See Also + -------- + create_user_id : inverse """ - emailexp = re.compile("(.*) <([^>]*)>(.*)") - match = emailexp.search(value) - if match == None: - email = None - name = value - else: - assert len(match.groups()) == 3 - assert match.groups()[2] == "", match.groups() - email = match.groups()[1] - name = match.groups()[0] - assert name != None - assert len(name) > 0 - return (name, email) + if '<' not in value: + return (value, None) + return parseaddr(value) def get_user_id(storage=None): - """ - Sometimes the storage will also keep track of the user id (e.g. most VCSs). + """Return a user ID, checking a list of possible sources. + + The source order is: + + 1. Global BE configuration. + 2. `storage.get_user_id`, if that function is defined. + 3. :func:`get_fallback_username` and :func:`get_fallback_email`. + + Notes + ----- + Sometimes the storage will keep track of the user ID (e.g. most + VCSs, see :meth:`libbe.storage.vcs.base.VCS.get_user_id`). If so, + we prefer that ID to the fallback, since the user has likely + configured it directly. """ user = libbe.storage.util.config.get_val('user') if user != None: @@ -98,6 +125,10 @@ def get_user_id(storage=None): return user def set_user_id(user_id): - """ + """Set the user ID in a user's BE configuration. + + See Also + -------- + libbe.storage.util.config.set_val """ user = libbe.storage.util.config.set_val('user', user_id) -- cgit