aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/README.dev104
-rw-r--r--libbe/command/close.py63
-rw-r--r--libbe/command/email_bugs.py239
-rw-r--r--libbe/command/open.py61
-rw-r--r--[-rwxr-xr-x]libbe/ui/command_line.py2
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 &lt;jdoe@example.com&gt;</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 &lt;jdoe@example.com&gt;</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'