diff options
-rw-r--r-- | doc/README.dev | 104 | ||||
-rw-r--r-- | libbe/command/close.py | 63 | ||||
-rw-r--r-- | libbe/command/email_bugs.py | 239 | ||||
-rw-r--r-- | libbe/command/open.py | 61 | ||||
-rw-r--r--[-rwxr-xr-x] | libbe/ui/command_line.py | 2 |
5 files changed, 400 insertions, 69 deletions
diff --git a/doc/README.dev b/doc/README.dev index 2a09463..a2b8d30 100644 --- a/doc/README.dev +++ b/doc/README.dev @@ -1,84 +1,52 @@ Extending BE ============ -To write a plugin, you simply create a new file in the becommands -directory. Take a look at one of the simpler plugins (e.g. open.py) +Adding commands +--------------- + +To write a plugin, you simply create a new file in the libbe/commands/ +directory. Take a look at one of the simpler plugins (e.g. remove.py) for an example of how that looks, and to start getting a feel for the libbe interface. -To fit into the current framework, your extension module should -provide the following elements: - __desc__ - A short string describing the purpose of your plugin - execute(args, manipulate_encodings=True, restrict_file_access=False, - dir=".") - The entry function for your plugin. args is everything from - sys.argv after the name of your plugin (e.g. for the command - `be open abc', args=['abc']). - - manipulate_encodings should be passed through to any calls to - bugdir.BugDir(). See the BugDir documentation for details. - - If restrict_file_access==True, you should call - cmdutil.restrict_file_access(bugdir, path) - before attempting to read or write a file. See the - restrict_file_access documentation for details. - - dir is a directory inside the repository of interest. - - Note: be supports command-completion. To avoid raising errors you - need to deal with possible '--complete' options and arguments. - See the 'Command completion' section below for more information. - help() - Return the string to be output by `be help <yourplugin>', - `be <yourplugin> --help', etc. - -While that's all that's strictly necessary, many plugins (all the -current ones) use libbe.cmdutil.CmdOptionParser to provide a -consistent interface - get_parser() - Return an instance of CmdOptionParser("<usage string>"). You can - alter the parser (e.g. add some more options) before returning it. - -Again, you can just browse around in becommands to get a feel for things. +See libbe/commands/base.py for the definition of the important classes +Option, Argument, Command, InputOutput, StorageCallbacks, and +UserInterface classes. You'll be subclassing Command for your +command, but all those classes will be important. + + +Command completion + +BE implements a general framework to make it easy to support command +completion for arbitrary plugins. In order to support this system, +any of your completable Argument() instances (in your commands +.options or .args) should be initialized with some valid +completion_callback function. Some common cases are defined in +libbe.command.util. If you need more flexibility, see +libbe.command.list's "--sort" option for an example of extensions via +libbe.command.util.Completer, or write a custom completion function +from scratch. + + +Adding user interfaces +---------------------- + +Take a look at libbe/ui/command_line.py for an example. Basically +you'll need to setup a UserInterface instance for running commands. +More details to come after I write an HTML ui... Testing -------- +======= -Run any doctests in your plugin with - be$ python test.py <yourplugin> +Run any tests in your module with + be$ python test.py <python.module.name> for example - be$ python test.py merge + be$ python test.py libbe.command.merge +For a definition of "any tests", see test.py's add_module_tests() +function. -Command completion ------------------- - -BE implements a general framework to make it easy to support command -completion for arbitrary plugins. In order to support this system, -all becommands should properly handle the '--complete' commandline -argument, returning a list of possible completions. For example - $ be --commands - lists options accepted by be and the names of all available becommands. - $ be list --commands - lists options accepted by becommand/list - $ be list --status --commands - lists arguments accepted by the becommand/list --status option - $ be show -- --commands - lists possible vals for the first positional argument of becommand/show -This is a lot of information, but command-line completion is really -convenient for the user. See becommand/list.py and becommand/show.py -for example implementations. The basic idea is to raise - cmdutil.GetCompletions(['list','of','possible','completions']) -once you've determined what that list should be. - -However, command completion is not critical. The first priority is to -implement the target functionality, with fancy shell sugar coming -later. In recognition of this, cmdutil provides the default_complete -function which ensures that if '--complete' is any one of the -arguments, options, or option-arguments, GetCompletions will be raised -with and empty list. Profiling ========= diff --git a/libbe/command/close.py b/libbe/command/close.py new file mode 100644 index 0000000..026c605 --- /dev/null +++ b/libbe/command/close.py @@ -0,0 +1,63 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# Marien Zwart <marienz@gentoo.org> +# Thomas Gerigk <tgerigk@gmx.de> +# W. Trevor King <wking@drexel.edu> +# +# 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. +"""Close a bug""" +from libbe import cmdutil, bugdir +__desc__ = __doc__ + +def execute(args, manipulate_encodings=True, restrict_file_access=False, + dir="."): + """ + >>> from libbe import bugdir + >>> import os + >>> bd = bugdir.SimpleBugDir() + >>> os.chdir(bd.root) + >>> print bd.bug_from_shortname("a").status + open + >>> execute(["a"], manipulate_encodings=False) + >>> bd._clear_bugs() + >>> print bd.bug_from_shortname("a").status + closed + >>> bd.cleanup() + """ + parser = get_parser() + options, args = parser.parse_args(args) + cmdutil.default_complete(options, args, parser, + bugid_args={0: lambda bug : bug.active==True}) + if len(args) == 0: + raise cmdutil.UsageError("Please specify a bug id.") + if len(args) > 1: + raise cmdutil.UsageError("Too many arguments.") + bd = bugdir.BugDir(from_disk=True, + manipulate_encodings=manipulate_encodings, + root=dir) + bug = cmdutil.bug_from_id(bd, args[0]) + bug.status = "closed" + bd.save() + +def get_parser(): + parser = cmdutil.CmdOptionParser("be close BUG-ID") + return parser + +longhelp=""" +Close the bug identified by BUG-ID. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/libbe/command/email_bugs.py b/libbe/command/email_bugs.py new file mode 100644 index 0000000..f6641e3 --- /dev/null +++ b/libbe/command/email_bugs.py @@ -0,0 +1,239 @@ +# Copyright (C) 2009 W. Trevor King <wking@drexel.edu> +# +# 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. +"""Email specified bugs in a be-handle-mail compatible format.""" + +import copy +from cStringIO import StringIO +from email import Message +from email.mime.text import MIMEText +from email.generator import Generator +import sys +import time + +from libbe import cmdutil, bugdir +from libbe.subproc import invoke +from libbe.utility import time_to_str +from libbe.vcs import detect_vcs, installed_vcs +import show + +__desc__ = __doc__ + +sendmail='/usr/sbin/sendmail -t' + +def execute(args, manipulate_encodings=True, restrict_file_access=False, + dir="."): + """ + >>> import os + >>> from libbe import bug + >>> bd = bugdir.SimpleBugDir() + >>> bd.encoding = 'utf-8' + >>> os.chdir(bd.root) + >>> import email.charset as c + >>> c.add_charset('utf-8', c.SHORTEST, c.QP, 'utf-8') + >>> execute(["-o", "--to", "a@b.com", "--from", "b@c.edu", "a", "b"], + ... manipulate_encodings=False) # doctest: +ELLIPSIS + Content-Type: text/xml; charset="utf-8" + MIME-Version: 1.0 + Content-Transfer-Encoding: quoted-printable + From: b@c.edu + To: a@b.com + Date: ... + Subject: [be-bug:xml] Updates to a, b + <BLANKLINE> + <?xml version=3D"1.0" encoding=3D"utf-8" ?> + <be-xml> + <version> + <tag>...</tag> + <branch-nick>...</branch-nick> + <revno>...</revno> + <revision-id>... + </version> + <bug> + <uuid>a</uuid> + <short-name>a</short-name> + <severity>minor</severity> + <status>open</status> + <creator>John Doe <jdoe@example.com></creator> + <created>Thu, 01 Jan 1970 00:00:00 +0000</created> + <summary>Bug A</summary> + </bug> + <bug> + <uuid>b</uuid> + <short-name>b</short-name> + <severity>minor</severity> + <status>closed</status> + <creator>Jane Doe <jdoe@example.com></creator> + <created>Thu, 01 Jan 1970 00:00:00 +0000</created> + <summary>Bug B</summary> + </bug> + </be-xml> + >>> bd.cleanup() + + Note that the '=3D' bits in + <?xml version=3D"1.0" encoding=3D"utf-8" ?> + are the way quoted-printable escapes '='. + + The unclosed <revision-id>... is because revision ids can be long + enough to cause line wraps, and we want to ensure we match even if + the closing </revision-id> is split by the wrapping. + """ + parser = get_parser() + options, args = parser.parse_args(args) + cmdutil.default_complete(options, args, parser, + bugid_args={-1: lambda bug : bug.active==True}) + if len(args) == 0: + raise cmdutil.UsageError + bd = bugdir.BugDir(from_disk=True, + manipulate_encodings=manipulate_encodings, + root=dir) + xml = show.output(args, bd, as_xml=True, with_comments=True) + subject = options.subject + if subject == None: + subject = '[be-bug:xml] Updates to %s' % ', '.join(args) + submit_email = TextEmail(to_address=options.to_address, + from_address=options.from_address, + subject=subject, + body=xml, + encoding=bd.encoding, + subtype='xml') + if options.output == True: + print submit_email + else: + submit_email.send() + +def get_parser(): + parser = cmdutil.CmdOptionParser("be email-bugs [options] ID [ID ...]") + parser.add_option("-t", "--to", metavar="EMAIL", dest="to_address", + help="Submission email address (%default)", + default="be-devel@bugseverywhere.org") + parser.add_option("-f", "--from", metavar="EMAIL", dest="from_address", + help="Senders email address, overriding auto-generated default", + default=None) + parser.add_option("-s", "--subject", metavar="STRING", dest="subject", + help="Subject line, overriding auto-generated default. If you use this option, remember that be-handle-mail probably want something like '[be-bug:xml] ...'", + default=None) + parser.add_option('-o', '--output', dest='output', action='store_true', + help="Don't mail the generated message, print it to stdout instead. Useful for testing functionality.") + return parser + +longhelp=""" +Email specified bugs in a be-handle-mail compatible format. This is +the prefered method for reporting bugs if you did not install bzr by +branching a bzr repository. + +If you _did_ install bzr by branching a bzr repository, we suggest you +commit any new bug information with + bzr commit --message "Reported bug in demuxulizer" +and then email a bzr merge directive with + bzr send --mail-to "be-devel@bugseverywhere.org" +rather than using this command. +""" + +def help(): + return get_parser().help_str() + longhelp + +class TextEmail (object): + """ + Make it very easy to compose and send single-part text emails. + >>> msg = TextEmail(to_address='Monty <monty@a.com>', + ... from_address='Python <python@b.edu>', + ... subject='Parrots', + ... header={'x-special-header':'your info here'}, + ... body="Remarkable bird, id'nit, squire?\\nLovely plumage!") + >>> print msg # doctest: +ELLIPSIS + Content-Type: text/plain; charset="utf-8" + MIME-Version: 1.0 + Content-Transfer-Encoding: base64 + From: Python <python@b.edu> + To: Monty <monty@a.com> + Date: ... + Subject: Parrots + x-special-header: your info here + <BLANKLINE> + UmVtYXJrYWJsZSBiaXJkLCBpZCduaXQsIHNxdWlyZT8KTG92ZWx5IHBsdW1hZ2Uh + <BLANKLINE> + >>> import email.charset as c + >>> c.add_charset('utf-8', c.SHORTEST, c.QP, 'utf-8') + >>> print msg # doctest: +ELLIPSIS + Content-Type: text/plain; charset="utf-8" + MIME-Version: 1.0 + Content-Transfer-Encoding: quoted-printable + From: Python <python@b.edu> + To: Monty <monty@a.com> + Date: ... + Subject: Parrots + x-special-header: your info here + <BLANKLINE> + Remarkable bird, id'nit, squire? + Lovely plumage! + """ + def __init__(self, to_address, from_address=None, subject=None, + header=None, body=None, encoding='utf-8', subtype='plain'): + self.to_address = to_address + self.from_address = from_address + if self.from_address == None: + self.from_address = self._guess_from_address() + self.subject = subject + self.header = header + if self.header == None: + self.header = {} + self.body = body + self.encoding = encoding + self.subtype = subtype + def _guess_from_address(self): + vcs = detect_vcs('.') + if vcs.name == "None": + vcs = installed_vcs() + return vcs.get_user_id() + def encoded_MIME_body(self): + return MIMEText(self.body.encode(self.encoding), + self.subtype, + self.encoding) + def message(self): + response = self.encoded_MIME_body() + response['From'] = self.from_address + response['To'] = self.to_address + response['Date'] = time_to_str(time.time()) + response['Subject'] = self.subject + for k,v in self.header.items(): + response[k] = v + return response + def flatten(self, to_unicode=False): + """ + This is a simplified version of send_pgp_mime.flatten(). + """ + fp = StringIO() + g = Generator(fp, mangle_from_=False) + g.flatten(self.message()) + text = fp.getvalue() + if to_unicode == True: + encoding = msg.get_content_charset() or "utf-8" + text = unicode(text, encoding=encoding) + return text + def __str__(self): + return self.flatten() + def __unicode__(self): + return self.flatten(to_unicode=True) + def send(self, sendmail=None): + """ + This is a simplified version of send_pgp_mime.mail(). + + Send an email Message instance on its merry way by shelling + out to the user specified sendmail. + """ + if sendmail == None: + sendmail = SENDMAIL + invoke(sendmail, stdin=self.flatten()) diff --git a/libbe/command/open.py b/libbe/command/open.py new file mode 100644 index 0000000..a6fe48d --- /dev/null +++ b/libbe/command/open.py @@ -0,0 +1,61 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Gianluca Montecchi <gian@grys.it> +# Marien Zwart <marienz@gentoo.org> +# Thomas Gerigk <tgerigk@gmx.de> +# W. Trevor King <wking@drexel.edu> +# +# 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. +"""Re-open a bug""" +from libbe import cmdutil, bugdir +__desc__ = __doc__ + +def execute(args, manipulate_encodings=True, restrict_file_access=False, + dir="."): + """ + >>> import os + >>> bd = bugdir.SimpleBugDir() + >>> os.chdir(bd.root) + >>> print bd.bug_from_shortname("b").status + closed + >>> execute(["b"], manipulate_encodings=False) + >>> bd._clear_bugs() + >>> print bd.bug_from_shortname("b").status + open + >>> bd.cleanup() + """ + parser = get_parser() + options, args = parser.parse_args(args) + cmdutil.default_complete(options, args, parser, + bugid_args={0: lambda bug : bug.active==False}) + if len(args) == 0: + raise cmdutil.UsageError, "Please specify a bug id." + if len(args) > 1: + raise cmdutil.UsageError, "Too many arguments." + bd = bugdir.BugDir(from_disk=True, + manipulate_encodings=manipulate_encodings, + root=dir) + bug = cmdutil.bug_from_id(bd, args[0]) + bug.status = "open" + +def get_parser(): + parser = cmdutil.CmdOptionParser("be open BUG-ID") + return parser + +longhelp=""" +Mark a bug as 'open'. +""" + +def help(): + return get_parser().help_str() + longhelp diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 1c7399d..7f74782 100755..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']: + if command.name in ['comment', 'commit']: paginate = 'never' else: paginate = 'auto' |