aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--becommands/assign.py11
-rw-r--r--becommands/close.py7
-rw-r--r--becommands/comment.py19
-rw-r--r--becommands/depend.py13
-rw-r--r--becommands/diff.py7
-rw-r--r--becommands/help.py6
-rw-r--r--becommands/init.py12
-rw-r--r--becommands/list.py14
-rw-r--r--becommands/merge.py7
-rw-r--r--becommands/new.py7
-rw-r--r--becommands/open.py7
-rw-r--r--becommands/remove.py7
-rw-r--r--becommands/set.py15
-rw-r--r--becommands/severity.py13
-rw-r--r--becommands/show.py9
-rw-r--r--becommands/status.py16
-rw-r--r--becommands/tag.py21
-rw-r--r--becommands/target.py17
-rw-r--r--interfaces/email/interactive/_procmailrc22
-rwxr-xr-xinterfaces/email/interactive/be-handle-mail283
l---------interfaces/email/interactive/becommands1
-rw-r--r--interfaces/email/interactive/examples/blank0
-rw-r--r--interfaces/email/interactive/examples/comment9
-rw-r--r--interfaces/email/interactive/examples/help9
-rw-r--r--interfaces/email/interactive/examples/invalid_command9
-rw-r--r--interfaces/email/interactive/examples/invalid_subject9
-rw-r--r--interfaces/email/interactive/examples/list9
-rw-r--r--interfaces/email/interactive/examples/missing_command9
-rw-r--r--interfaces/email/interactive/examples/new9
-rw-r--r--interfaces/email/interactive/examples/show9
-rw-r--r--interfaces/email/interactive/examples/unicode9
l---------interfaces/email/interactive/libbe1
-rw-r--r--interfaces/email/interactive/send_pgp_mime.py608
-rwxr-xr-xinterfaces/xml/be-mbox-to-xml3
-rw-r--r--libbe/cmdutil.py5
-rw-r--r--libbe/encoding.py4
37 files changed, 1125 insertions, 99 deletions
diff --git a/Makefile b/Makefile
index b1207c2..cc2f91c 100644
--- a/Makefile
+++ b/Makefile
@@ -38,9 +38,9 @@ MODULES += ${DOC_DIR}
RM = rm
-#PREFIX = /usr/local
+PREFIX = /usr/local
PREFIX = ${HOME}
-INSTALL_OPTIONS = "--prefix=${PREFIX}"
+#INSTALL_OPTIONS = "--prefix=${PREFIX}"
.PHONY: all
@@ -57,8 +57,8 @@ build: libbe/_version.py
.PHONY: install
install: doc build
python setup.py install ${INSTALL_OPTIONS}
- cp -v interfaces/xml/* ${PREFIX}/bin
- cp -v interfaces/email/catmutt ${PREFIX}/bin
+#cp -v interfaces/xml/* ${PREFIX}/bin
+#cp -v interfaces/email/catmutt ${PREFIX}/bin
.PHONY: clean
diff --git a/becommands/assign.py b/becommands/assign.py
index 536bca6..ba79aac 100644
--- a/becommands/assign.py
+++ b/becommands/assign.py
@@ -20,7 +20,7 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
>>> bd = bugdir.simple_bug_dir()
@@ -28,17 +28,17 @@ def execute(args, test=False):
>>> bd.bug_from_shortname("a").assigned is None
True
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> bd.bug_from_shortname("a").assigned == bd.user_id
True
- >>> execute(["a", "someone"], test=True)
+ >>> execute(["a", "someone"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> print bd.bug_from_shortname("a").assigned
someone
- >>> execute(["a","none"], test=True)
+ >>> execute(["a","none"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> bd.bug_from_shortname("a").assigned is None
True
@@ -53,7 +53,8 @@ def execute(args, test=False):
if len(args) > 2:
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bug = bd.bug_from_shortname(args[0])
if len(args) == 1:
bug.assigned = bd.user_id
diff --git a/becommands/close.py b/becommands/close.py
index 0ba8f50..05bdc10 100644
--- a/becommands/close.py
+++ b/becommands/close.py
@@ -20,7 +20,7 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import bugdir
>>> import os
@@ -28,7 +28,7 @@ def execute(args, test=False):
>>> os.chdir(bd.root)
>>> print bd.bug_from_shortname("a").status
open
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> print bd.bug_from_shortname("a").status
closed
@@ -41,7 +41,8 @@ def execute(args, test=False):
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=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bug = bd.bug_from_shortname(args[0])
bug.status = "closed"
bd.save()
diff --git a/becommands/comment.py b/becommands/comment.py
index 66f8da1..918f922 100644
--- a/becommands/comment.py
+++ b/becommands/comment.py
@@ -25,12 +25,12 @@ except ImportError: # look for non-core module
from elementtree import ElementTree
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import time
>>> bd = bugdir.simple_bug_dir()
>>> os.chdir(bd.root)
- >>> execute(["a", "This is a comment about a"], test=True)
+ >>> execute(["a", "This is a comment about a"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> bug = bd.bug_from_shortname("a")
>>> bug.load_comments(load_full=False)
@@ -47,12 +47,12 @@ def execute(args, test=False):
>>> if 'EDITOR' in os.environ:
... del os.environ["EDITOR"]
- >>> execute(["b"], test=True)
+ >>> execute(["b"], manipulate_encodings=False)
Traceback (most recent call last):
UserError: No comment supplied, and EDITOR not specified.
>>> os.environ["EDITOR"] = "echo 'I like cheese' > "
- >>> execute(["b"], test=True)
+ >>> execute(["b"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> bug = bd.bug_from_shortname("b")
>>> bug.load_comments(load_full=False)
@@ -80,7 +80,8 @@ def execute(args, test=False):
bugname = shortname
is_reply = False
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bug = bd.bug_from_shortname(bugname)
bug.load_comments(load_full=False)
if is_reply:
@@ -113,6 +114,10 @@ def execute(args, test=False):
if options.XML == False:
new = parent.new_reply(body=body)
+ if options.author != None:
+ new.From = options.author
+ if options.alt_id != None:
+ new.alt_id = options.alt_id
if options.content_type != None:
new.content_type = options.content_type
else: # import XML comment [list]
@@ -157,6 +162,10 @@ def execute(args, test=False):
def get_parser():
parser = cmdutil.CmdOptionParser("be comment ID [COMMENT]")
+ parser.add_option("-a", "--author", metavar="AUTHOR", dest="author",
+ help="Set the comment author", default=None)
+ parser.add_option("--alt-id", metavar="ID", dest="alt_id",
+ help="Set an alternate comment ID", default=None)
parser.add_option("-c", "--content-type", metavar="MIME", dest="content_type",
help="Set comment content-type (e.g. text/plain)", default=None)
parser.add_option("-x", "--xml", action="store_true", default=False,
diff --git a/becommands/depend.py b/becommands/depend.py
index 48e1527..977edae 100644
--- a/becommands/depend.py
+++ b/becommands/depend.py
@@ -18,22 +18,22 @@ from libbe import cmdutil, bugdir
import os, copy
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility
>>> bd = bugdir.simple_bug_dir()
>>> bd.save()
>>> os.chdir(bd.root)
- >>> execute(["a", "b"], test=True)
+ >>> execute(["a", "b"], manipulate_encodings=False)
Blocks on a:
b
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
Blocks on a:
b
- >>> execute(["--show-status", "a"], test=True) # doctest: +NORMALIZE_WHITESPACE
+ >>> execute(["--show-status", "a"], manipulate_encodings=False) # doctest: +NORMALIZE_WHITESPACE
Blocks on a:
b closed
- >>> execute(["-r", "a", "b"], test=True)
+ >>> execute(["-r", "a", "b"], manipulate_encodings=False)
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -47,7 +47,8 @@ def execute(args, test=False):
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bugA = bd.bug_from_shortname(args[0])
if len(args) == 2:
bugB = bd.bug_from_shortname(args[1])
diff --git a/becommands/diff.py b/becommands/diff.py
index f3474b3..20fcb4c 100644
--- a/becommands/diff.py
+++ b/becommands/diff.py
@@ -20,7 +20,7 @@ from libbe import cmdutil, bugdir, diff
import os
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
>>> bd = bugdir.simple_bug_dir()
@@ -31,7 +31,7 @@ def execute(args, test=False):
>>> changed = bd.rcs.commit("Closed bug a")
>>> os.chdir(bd.root)
>>> if bd.rcs.versioned == True:
- ... execute([original], test=True)
+ ... execute([original], manipulate_encodings=False)
... else:
... print "a:cm: Bug A\\nstatus: open -> closed\\n"
Modified bug reports:
@@ -48,7 +48,8 @@ def execute(args, test=False):
revision = args[0]
if len(args) > 1:
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if bd.rcs.versioned == False:
print "This directory is not revision-controlled."
else:
diff --git a/becommands/help.py b/becommands/help.py
index a8ae338..a8f346a 100644
--- a/becommands/help.py
+++ b/becommands/help.py
@@ -19,9 +19,11 @@
from libbe import cmdutil, utility
__desc__ = __doc__
-def execute(args):
+def execute(args, manipulate_encodings=False):
"""
- Print help of specified command.
+ Print help of specified command (the manipulate_encodings argument
+ is ignored).
+
>>> execute(["help"])
Usage: be help [COMMAND]
<BLANKLINE>
diff --git a/becommands/init.py b/becommands/init.py
index 5b2a416..4156a26 100644
--- a/becommands/init.py
+++ b/becommands/init.py
@@ -19,7 +19,7 @@ import os.path
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility, rcs
>>> import os
@@ -29,7 +29,7 @@ def execute(args, test=False):
... except bugdir.NoBugDir, e:
... True
True
- >>> execute(['--root', dir.path], test=True)
+ >>> execute(['--root', dir.path], manipulate_encodings=False)
No revision control detected.
Directory initialized.
>>> del(dir)
@@ -40,17 +40,17 @@ def execute(args, test=False):
>>> rcs.init('.')
>>> print rcs.name
Arch
- >>> execute([], test=True)
+ >>> execute([], manipulate_encodings=False)
Using Arch for revision control.
Directory initialized.
>>> rcs.cleanup()
>>> try:
- ... execute(['--root', '.'], test=True)
+ ... execute(['--root', '.'], manipulate_encodings=False)
... except cmdutil.UserError, e:
... str(e).startswith("Directory already initialized: ")
True
- >>> execute(['--root', '/highly-unlikely-to-exist'], test=True)
+ >>> execute(['--root', '/highly-unlikely-to-exist'], manipulate_encodings=False)
Traceback (most recent call last):
UserError: No such directory: /highly-unlikely-to-exist
>>> os.chdir('/')
@@ -64,7 +64,7 @@ def execute(args, test=False):
bd = bugdir.BugDir(options.root_dir, from_disk=False,
sink_to_existing_root=False,
assert_new_BugDir=True,
- manipulate_encodings=not test)
+ manipulate_encodings=manipulate_encodings)
except bugdir.NoRootEntry:
raise cmdutil.UserError("No such directory: %s" % options.root_dir)
except bugdir.AlreadyInitialized:
diff --git a/becommands/list.py b/becommands/list.py
index 5ba1821..50038e6 100644
--- a/becommands/list.py
+++ b/becommands/list.py
@@ -26,14 +26,14 @@ __desc__ = __doc__
AVAILABLE_CMPS = [fn[4:] for fn in dir(bug) if fn[:4] == 'cmp_']
AVAILABLE_CMPS.remove("attr") # a cmp_* template.
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
>>> bd = bugdir.simple_bug_dir()
>>> os.chdir(bd.root)
- >>> execute([], test=True)
+ >>> execute([], manipulate_encodings=False)
a:om: Bug A
- >>> execute(["--status", "all"], test=True)
+ >>> execute(["--status", "all"], manipulate_encodings=False)
a:om: Bug A
b:cm: Bug B
"""
@@ -46,11 +46,13 @@ def execute(args, test=False):
if options.sort_by != None:
for cmp in options.sort_by.split(','):
if cmp not in AVAILABLE_CMPS:
- raise cmdutil.UserError("Invalid sort on '%s'.\nValid sorts:\n %s"
- % (cmp, '\n '.join(AVAILABLE_CMPS)))
+ raise cmdutil.UserError(
+ "Invalid sort on '%s'.\nValid sorts:\n %s"
+ % (cmp, '\n '.join(AVAILABLE_CMPS)))
cmp_list.append(eval('bug.cmp_%s' % cmp))
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bd.load_all_bugs()
# select status
if options.status != None:
diff --git a/becommands/merge.py b/becommands/merge.py
index c030dd0..c7cae2b 100644
--- a/becommands/merge.py
+++ b/becommands/merge.py
@@ -18,7 +18,7 @@ from libbe import cmdutil, bugdir
import os, copy
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility
>>> bd = bugdir.simple_bug_dir()
@@ -37,7 +37,7 @@ def execute(args, test=False):
>>> dummy.time = 2
>>> bd.save()
>>> os.chdir(bd.root)
- >>> execute(["a", "b"], test=True)
+ >>> execute(["a", "b"], manipulate_encodings=False)
Merging bugs a and b
>>> bd._clear_bugs()
>>> a = bd.bug_from_shortname("a")
@@ -133,7 +133,8 @@ def execute(args, test=False):
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bugA = bd.bug_from_shortname(args[0])
bugA.load_comments()
bugB = bd.bug_from_shortname(args[1])
diff --git a/becommands/new.py b/becommands/new.py
index 5325ccc..8512e22 100644
--- a/becommands/new.py
+++ b/becommands/new.py
@@ -19,14 +19,14 @@ from libbe import cmdutil, bugdir
import sys
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os, time
>>> from libbe import bug
>>> bd = bugdir.simple_bug_dir()
>>> os.chdir(bd.root)
>>> bug.uuid_gen = lambda: "X"
- >>> execute (["this is a test",], test=True)
+ >>> execute (["this is a test",], manipulate_encodings=False)
Created bug with ID X
>>> bd.load()
>>> bug = bd.bug_from_uuid("X")
@@ -44,7 +44,8 @@ def execute(args, test=False):
cmdutil.default_complete(options, args, parser)
if len(args) != 1:
raise cmdutil.UsageError("Please supply a summary message")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if args[0] == '-': # read summary from stdin
summary = sys.stdin.readline()
else:
diff --git a/becommands/open.py b/becommands/open.py
index b4b1025..ee81422 100644
--- a/becommands/open.py
+++ b/becommands/open.py
@@ -20,14 +20,14 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
>>> bd = bugdir.simple_bug_dir()
>>> os.chdir(bd.root)
>>> print bd.bug_from_shortname("b").status
closed
- >>> execute(["b"], test=True)
+ >>> execute(["b"], manipulate_encodings=False)
>>> bd._clear_bugs()
>>> print bd.bug_from_shortname("b").status
open
@@ -40,7 +40,8 @@ def execute(args, test=False):
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=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bug = bd.bug_from_shortname(args[0])
bug.status = "open"
bd.save()
diff --git a/becommands/remove.py b/becommands/remove.py
index d441bfe..d6ba999 100644
--- a/becommands/remove.py
+++ b/becommands/remove.py
@@ -17,7 +17,7 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import mapfile
>>> import os
@@ -25,7 +25,7 @@ def execute(args, test=False):
>>> os.chdir(bd.root)
>>> print bd.bug_from_shortname("b").status
closed
- >>> execute (["b"], test=True)
+ >>> execute (["b"], manipulate_encodings=False)
Removed bug b
>>> bd._clear_bugs()
>>> try:
@@ -40,7 +40,8 @@ def execute(args, test=False):
bugid_args={0: lambda bug : bug.active==True})
if len(args) != 1:
raise cmdutil.UsageError, "Please specify a bug id."
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bug = bd.bug_from_shortname(args[0])
bd.remove_bug(bug)
bd.save()
diff --git a/becommands/set.py b/becommands/set.py
index 510eca7..7bef644 100644
--- a/becommands/set.py
+++ b/becommands/set.py
@@ -32,18 +32,18 @@ def _value_string(bd, setting):
val = None
return str(val)
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
>>> bd = bugdir.simple_bug_dir()
>>> os.chdir(bd.root)
- >>> execute(["target"], test=True)
+ >>> execute(["target"], manipulate_encodings=False)
None
- >>> execute(["target", "tomorrow"], test=True)
- >>> execute(["target"], test=True)
+ >>> execute(["target", "tomorrow"], manipulate_encodings=False)
+ >>> execute(["target"], manipulate_encodings=False)
tomorrow
- >>> execute(["target", "none"], test=True)
- >>> execute(["target"], test=True)
+ >>> execute(["target", "none"], manipulate_encodings=False)
+ >>> execute(["target"], manipulate_encodings=False)
None
"""
parser = get_parser()
@@ -51,7 +51,8 @@ def execute(args, test=False):
complete(options, args, parser)
if len(args) > 2:
raise cmdutil.UsageError, "Too many arguments"
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if len(args) == 0:
keys = bd.settings_properties
keys.sort()
diff --git a/becommands/severity.py b/becommands/severity.py
index fde9fba..4e95638 100644
--- a/becommands/severity.py
+++ b/becommands/severity.py
@@ -20,17 +20,17 @@
from libbe import cmdutil, bugdir, bug
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
>>> bd = bugdir.simple_bug_dir()
>>> os.chdir(bd.root)
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
minor
- >>> execute(["a", "wishlist"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "wishlist"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
wishlist
- >>> execute(["a", "none"], test=True)
+ >>> execute(["a", "none"], manipulate_encodings=False)
Traceback (most recent call last):
UserError: Invalid severity level: none
"""
@@ -39,7 +39,8 @@ def execute(args, test=False):
complete(options, args, parser)
if len(args) not in (1,2):
raise cmdutil.UsageError
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bug = bd.bug_from_shortname(args[0])
if len(args) == 1:
print bug.severity
diff --git a/becommands/show.py b/becommands/show.py
index d053cc3..ae1c7f3 100644
--- a/becommands/show.py
+++ b/becommands/show.py
@@ -22,12 +22,12 @@ import sys
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
>>> bd = bugdir.simple_bug_dir()
>>> os.chdir(bd.root)
- >>> execute (["a",], test=True) # doctest: +ELLIPSIS
+ >>> execute (["a",], manipulate_encodings=False) # doctest: +ELLIPSIS
ID : a
Short name : a
Severity : minor
@@ -39,7 +39,7 @@ def execute(args, test=False):
Created : ...
Bug A
<BLANKLINE>
- >>> execute (["--xml", "a"], test=True) # doctest: +ELLIPSIS
+ >>> execute (["--xml", "a"], manipulate_encodings=False) # doctest: +ELLIPSIS
<?xml version="1.0" encoding="..." ?>
<bug>
<uuid>a</uuid>
@@ -57,7 +57,8 @@ def execute(args, test=False):
bugid_args={-1: lambda bug : bug.active==True})
if len(args) == 0:
raise cmdutil.UsageError
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
for shortname in args:
if shortname.count(':') > 1:
raise cmdutil.UserError("Invalid id '%s'." % shortname)
diff --git a/becommands/status.py b/becommands/status.py
index 89ae49a..a122aec 100644
--- a/becommands/status.py
+++ b/becommands/status.py
@@ -17,17 +17,17 @@
from libbe import cmdutil, bugdir, bug
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
>>> bd = bugdir.simple_bug_dir()
>>> os.chdir(bd.root)
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
open
- >>> execute(["a", "closed"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "closed"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
closed
- >>> execute(["a", "none"], test=True)
+ >>> execute(["a", "none"], manipulate_encodings=False)
Traceback (most recent call last):
UserError: Invalid status: none
"""
@@ -36,7 +36,8 @@ def execute(args, test=False):
complete(options, args, parser)
if len(args) not in (1,2):
raise cmdutil.UsageError
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
bug = bd.bug_from_shortname(args[0])
if len(args) == 1:
print bug.status
@@ -56,7 +57,8 @@ def get_parser():
def help():
try: # See if there are any per-tree status configurations
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=False)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=False)
except bugdir.NoBugDir, e:
pass # No tree, just show the defaults
longest_status_len = max([len(s) for s in bug.status_values])
diff --git a/becommands/tag.py b/becommands/tag.py
index a139528..2932589 100644
--- a/becommands/tag.py
+++ b/becommands/tag.py
@@ -18,7 +18,7 @@ from libbe import cmdutil, bugdir
import os, copy
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> from libbe import utility
>>> bd = bugdir.simple_bug_dir()
@@ -26,25 +26,25 @@ def execute(args, test=False):
>>> a = bd.bug_from_shortname("a")
>>> print a.extra_strings
[]
- >>> execute(["a", "GUI"], test=True)
+ >>> execute(["a", "GUI"], manipulate_encodings=False)
Tags for a:
GUI
>>> bd._clear_bugs() # resync our copy of bug
>>> a = bd.bug_from_shortname("a")
>>> print a.extra_strings
['TAG:GUI']
- >>> execute(["a", "later"], test=True)
+ >>> execute(["a", "later"], manipulate_encodings=False)
Tags for a:
GUI
later
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
Tags for a:
GUI
later
- >>> execute(["--list"], test=True)
+ >>> execute(["--list"], manipulate_encodings=False)
GUI
later
- >>> execute(["a", "Alphabetically first"], test=True)
+ >>> execute(["a", "Alphabetically first"], manipulate_encodings=False)
Tags for a:
Alphabetically first
GUI
@@ -57,15 +57,15 @@ def execute(args, test=False):
>>> print a.extra_strings
[]
>>> a.save()
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
>>> bd._clear_bugs() # resync our copy of bug
>>> a = bd.bug_from_shortname("a")
>>> print a.extra_strings
[]
- >>> execute(["a", "Alphabetically first"], test=True)
+ >>> execute(["a", "Alphabetically first"], manipulate_encodings=False)
Tags for a:
Alphabetically first
- >>> execute(["--remove", "a", "Alphabetically first"], test=True)
+ >>> execute(["--remove", "a", "Alphabetically first"], manipulate_encodings=False)
"""
parser = get_parser()
options, args = parser.parse_args(args)
@@ -78,7 +78,8 @@ def execute(args, test=False):
help()
raise cmdutil.UsageError("Too many arguments.")
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if options.list:
bd.load_all_bugs()
tags = []
diff --git a/becommands/target.py b/becommands/target.py
index 905c639..66bacb8 100644
--- a/becommands/target.py
+++ b/becommands/target.py
@@ -22,20 +22,20 @@
from libbe import cmdutil, bugdir
__desc__ = __doc__
-def execute(args, test=False):
+def execute(args, manipulate_encodings=True):
"""
>>> import os
>>> bd = bugdir.simple_bug_dir()
>>> os.chdir(bd.root)
- >>> execute(["a"], test=True)
+ >>> execute(["a"], manipulate_encodings=False)
No target assigned.
- >>> execute(["a", "tomorrow"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "tomorrow"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
tomorrow
- >>> execute(["--list"], test=True)
+ >>> execute(["--list"], manipulate_encodings=False)
tomorrow
- >>> execute(["a", "none"], test=True)
- >>> execute(["a"], test=True)
+ >>> execute(["a", "none"], manipulate_encodings=False)
+ >>> execute(["a"], manipulate_encodings=False)
No target assigned.
"""
parser = get_parser()
@@ -46,7 +46,8 @@ def execute(args, test=False):
if len(args) not in (1, 2):
if not (options.list == True and len(args) == 0):
raise cmdutil.UsageError
- bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
+ bd = bugdir.BugDir(from_disk=True,
+ manipulate_encodings=manipulate_encodings)
if options.list:
ts = set([bd.bug_from_uuid(bug).target for bug in bd.list_uuids()])
for target in sorted(ts):
diff --git a/interfaces/email/interactive/_procmailrc b/interfaces/email/interactive/_procmailrc
new file mode 100644
index 0000000..56f11e5
--- /dev/null
+++ b/interfaces/email/interactive/_procmailrc
@@ -0,0 +1,22 @@
+# .procmailrc
+#
+# see man procmail, procmailrc, and procmailex
+#
+# If you already have a ~/.procmailrc file, you probably only need to
+# insert the bug-email grabbing stanza in your ~/.procmailrc.
+#
+# This file is released to the Public Domain.
+
+MAILDIR=$HOME/be-mail
+LOGFILE=$MAILDIR/procmail.log
+
+# Grab all incoming bug emails (but not replies). This rule eats
+# matching emails (i.e. no further procmail processing).
+:0
+* ^Subject: [be-mail]
+* !^Subject:.*[be-bug].*Re:
+| be-handle-mail
+
+# Drop everything else
+:0
+/dev/null
diff --git a/interfaces/email/interactive/be-handle-mail b/interfaces/email/interactive/be-handle-mail
new file mode 100755
index 0000000..c3769be
--- /dev/null
+++ b/interfaces/email/interactive/be-handle-mail
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+#
+# 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.
+"""Provide and email interface to the distributed bugtracker Bugs
+Everywhere. Recieves incoming email via procmail and allows users to
+select actions with their subject lines. Subject lines follow the
+format
+ [be-bug] command (options) (args)
+With the body of the email being used as the final argument for the
+commands "new" and "comment", and ignored otherwise. The options and
+arguments are split on whitespace, so don't use whitespace inside a
+single argument.
+
+Eventually we'll commit after every message.
+"""
+
+import codecs
+import cStringIO as StringIO
+import email
+import email.utils
+import libbe.cmdutil, libbe.encoding, libbe.utility
+import os
+import os.path
+import send_pgp_mime
+import sys
+import time
+import traceback
+
+SUBJECT_COMMENT = "[be-bug]"
+HANDLER_ADDRESS = "BE Bugs <wking@thor.physics.drexel.edu>"
+_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
+BE_DIR = _THIS_DIR
+LOGPATH = os.path.join(_THIS_DIR, "be-handle-mail.log")
+LOGFILE = None
+
+libbe.encoding.ENCODING = "utf-8" # force default encoding
+ENCODING = libbe.encoding.get_encoding()
+
+ALLOWED_COMMANDS = ["new", "comment", "list", "show", "help"]
+
+class InvalidEmail (ValueError):
+ def __init__(self, msg, info, message):
+ ValueError.__init__(self, message)
+ self.msg = msg
+ self.info = info
+ def response(self):
+ ret = 1
+ out_text = None
+ return (ret, out_text, self.stderr_msg(), self.info)
+ def stderr_msg(self):
+ err_text = [u"Invalid email (particular type unknown):\n",
+ unicode(self), u"",
+ send_pgp_mime.flatten(self.msg, to_unicode=True)]
+ return u"\n".join(err_text)
+
+class InvalidSubject (InvalidEmail):
+ def stderr_msg(self):
+ err_text = u"\n".join([u"InvalidSubject:\n",
+ unicode(self), u"",
+ u"full subject was:",
+ self.msg["subject"]])
+ return err_text
+
+class InvalidCommand (InvalidEmail):
+ def __init__(self, msg, info, command):
+ message = "Invalid command '%s'" % command
+ InvalidEmail.__init__(self, msg, info, message)
+ self.command = command
+ def stderr_msg(self):
+ err_text = u"\n".join([u"InvalidCommand:\n",
+ unicode(self), u"",
+ u"full subject was:",
+ self.msg["subject"]])
+ return err_text
+
+def get_body_type(msg):
+ for part in msg.walk():
+ if part.is_multipart():
+ continue
+ return (part.get_payload(decode=1), part.get_content_type())
+
+def run_message(msg_text):
+ """
+ Attempt to execute the email given in the email string msg_text.
+ Raises assorted subclasses of InvalidEmail in the case of invalid
+ messages, otherwise return the exit code, stdout, and stderr
+ produced by the command, as well as a dictionary of information
+ gleaned from the email.
+ """
+ p=email.Parser.Parser()
+ msg=p.parsestr(msg_text)
+
+ info = {}
+ author = send_pgp_mime.source_email(msg, return_realname=True)
+ info["author_name"] = author[0]
+ info["author_email"] = author[1]
+ info["author_addr"] = email.utils.formataddr(
+ (info["author_name"], info["author_email"]))
+ info["message-id"] = msg["message-id"]
+ if LOGFILE != None:
+ LOGFILE.write("handling %s\n" % (info["author_addr"]))
+ LOGFILE.write("\n%s\n\n" % msg_text)
+ if "subject" not in msg:
+ raise InvalidSubject(msg, info, "Email must contain a subject")
+ args = msg["subject"].split()
+ if len(args) < 1 or args[0] != SUBJECT_COMMENT:
+ raise InvalidSubject(
+ msg, info, "Subject must start with '%s '" % SUBJECT_COMMENT)
+ elif len(args) < 2:
+ raise InvalidCommand(msg, info, "") # don't accept blank commands
+ command = args[1]
+ info["command"] = command
+ if command not in ALLOWED_COMMANDS:
+ raise InvalidCommand(msg, info, command)
+ if len(args) > 2:
+ command_args = args[2:]
+ else:
+ command_args = []
+ stdin = None
+ if command in ["new", "comment"]:
+ body,mime_type = get_body_type(msg)
+ if command == "new":
+ if "--reporter" not in args and "-r" not in args:
+ command_args = ["--reporter", info["author_addr"]]+command_args
+ body = body.strip().split("\n", 1)[0] # only take first line
+ elif command == "comment":
+ if "--author" not in args and "-a" not in args:
+ command_args = ["--author", info["author_addr"]] + command_args
+ if "--content-type" not in args and "-c" not in args:
+ command_args = ["--content-type", mime_type] + command_args
+ if "--alt-id" not in args:
+ command_args = ["--alt-id", msg["message-id"]] + command_args
+ command_args.append("-")
+ stdin = body
+ info["command-args"] = command_args
+ # set stdin and catch stdout and stderr
+ new_stdin = StringIO.StringIO(stdin)
+ new_stdout = codecs.getwriter(ENCODING)(StringIO.StringIO())
+ new_stderr = codecs.getwriter(ENCODING)(StringIO.StringIO())
+ orig_stdin = sys.stdin
+ orig_stdout = sys.stdout
+ orig_stderr = sys.stderr
+ sys.stdin = new_stdin
+ sys.stdout = new_stdout
+ sys.stderr = new_stderr
+ # run the command
+ err = None
+ os.chdir(BE_DIR)
+ try:
+ ret = libbe.cmdutil.execute(command, command_args,
+ manipulate_encodings=False)
+ except libbe.cmdutil.GetHelp:
+ print libbe.cmdutil.help(command)
+ except libbe.cmdutil.GetCompletions:
+ err = InvalidCommand(msg, info, "invalid option '--complete'")
+ except libbe.cmdutil.UsageError, e:
+ err = InvalidCommand(msg, info, e)
+ except libbe.cmdutil.UserError, e:
+ err = InvalidCommand(msg, info, e)
+ # restore stdin, stdout, and stderr
+ sys.stdout.flush()
+ sys.stderr.flush()
+ sys.stdin = orig_stdin
+ sys.stdout = orig_stdout
+ sys.stderr = orig_stderr
+ out_text = codecs.decode(new_stdout.getvalue(), ENCODING)
+ err_text = codecs.decode(new_stderr.getvalue(), ENCODING)
+ if err != None:
+ raise err
+ if LOGFILE != None:
+ LOGFILE.write(u"stdout? " + str(type(out_text)))
+ LOGFILE.write(u"\n%s\n\n" % out_text)
+ return (ret, out_text, err_text, info)
+
+def compose_response(ret, out_text, err_text, info):
+ if "author_addr" not in info:
+ return None
+ if "command" not in info:
+ info["command"] = u"-BLANK-"
+ if "command_args" not in info:
+ info["command_args"] = []
+ response_header = [u"From: %s" % HANDLER_ADDRESS,
+ u"To: %s" % info["author_addr"],
+ u"Date: %s" % libbe.utility.time_to_str(time.time()),
+ u"Subject: %s Re: %s"%(SUBJECT_COMMENT,info["command"]),
+ ]
+ if "message-id" in info:
+ response_header.append(u"In-reply-to: %s" % info["message-id"])
+ response_body = [u"Results of running: (exit code %d)" % ret,
+ u" %s %s" % (info["command"],
+ u" ".join(info["command_args"]))]
+ if out_text != None and len(out_text) > 0:
+ response_body.extend([u"", u"stdout:", u"", out_text])
+ if err_text != None and len(err_text) > 0:
+ response_body.extend([u"", u"stderr:", u"", err_text])
+ response_body.append(u"") # trailing endline
+ response_email = send_pgp_mime.Mail(u"\n".join(response_header),
+ u"\n".join(response_body))
+ if LOGFILE != None:
+ LOGFILE.write("responding to %s: %s\n"
+ % (info["author_addr"], info["command"]))
+ LOGFILE.write("\n%s\n\n"
+ % send_pgp_mime.flatten(response_email.plain(),
+ to_unicode=True))
+ return response_email
+
+def open_logfile(logpath=None):
+ """
+ If logpath=None, default to global LOGPATH.
+ Special logpath strings:
+ "-" set LOGFILE to sys.stderr
+ "none" disable logging
+ Relative logpaths are expanded relative to _THIS_DIR
+ """
+ global LOGPATH, LOGFILE
+ if logpath != None:
+ if logpath == "-":
+ LOGPATH = "stderr"
+ LOGFILE = sys.stderr
+ elif logpath == "none":
+ LOGPATH = "none"
+ LOGFILE = None
+ elif os.path.isabs(logpath):
+ LOGPATH = logpath
+ else:
+ LOGPATH = os.path.join(_THIS_DIR, logpath)
+ if LOGFILE == None and LOGPATH != "none":
+ LOGFILE = codecs.open(LOGPATH, "a+", ENCODING)
+ LOGFILE.write("Default encoding: %s\n" % ENCODING)
+
+def close_logfile():
+ if LOGFILE != None and LOGPATH not in ["stderr", "none"]:
+ LOGFILE.close()
+
+
+def main():
+ from optparse import OptionParser
+
+ usage="be-handle-mail [options]\n\n%s" % (__doc__)
+ parser = OptionParser(usage=usage)
+ 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 be-handle-mail functionality without the whole mail transfer agent and procmail setup.")
+ parser.add_option('-l', '--logfile', dest='logfile', metavar='LOGFILE',
+ help='Set the logfile to LOGFILE. Relative paths are relative to the location of this be-handle-mail file (%s). The special value of "-" directs the log output to stderr, and "none" disables logging.' % _THIS_DIR)
+
+ options,args = parser.parse_args()
+
+ msg_text = sys.stdin.read()
+ libbe.encoding.set_IO_stream_encodings(ENCODING) # _after_ reading message
+ open_logfile(options.logfile)
+ try:
+ ret,out_text,err_text,info = run_message(msg_text)
+ except InvalidEmail, e:
+ ret,out_text,err_text,info = e.response()
+ except Exception, e:
+ if LOGFILE != None:
+ LOGFILE.write("Uncaught exception:\n%s\n" % (e,))
+ traceback.print_tb(sys.exc_traceback, file=LOGFILE)
+ close_logfile()
+ sys.exit(1)
+ response_email = compose_response(ret, out_text, err_text, info).plain()
+ if options.output == True:
+ print send_pgp_mime.flatten(response_email, to_unicode=True)
+ else:
+ send_pgp_mime.mail(response_email, send_pgp_mime.sendmail)
+ close_logfile()
+
+if __name__ == "__main__":
+ main()
diff --git a/interfaces/email/interactive/becommands b/interfaces/email/interactive/becommands
new file mode 120000
index 0000000..8af773c
--- /dev/null
+++ b/interfaces/email/interactive/becommands
@@ -0,0 +1 @@
+../../../becommands \ No newline at end of file
diff --git a/interfaces/email/interactive/examples/blank b/interfaces/email/interactive/examples/blank
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/interfaces/email/interactive/examples/blank
diff --git a/interfaces/email/interactive/examples/comment b/interfaces/email/interactive/examples/comment
new file mode 100644
index 0000000..1d60748
--- /dev/null
+++ b/interfaces/email/interactive/examples/comment
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <xyz@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] comment a1d
+
+We sure do.
diff --git a/interfaces/email/interactive/examples/help b/interfaces/email/interactive/examples/help
new file mode 100644
index 0000000..14e887c
--- /dev/null
+++ b/interfaces/email/interactive/examples/help
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] help
+
+Dummy content
diff --git a/interfaces/email/interactive/examples/invalid_command b/interfaces/email/interactive/examples/invalid_command
new file mode 100644
index 0000000..4d18f09
--- /dev/null
+++ b/interfaces/email/interactive/examples/invalid_command
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] close
+
+Dummy content
diff --git a/interfaces/email/interactive/examples/invalid_subject b/interfaces/email/interactive/examples/invalid_subject
new file mode 100644
index 0000000..e148d0b
--- /dev/null
+++ b/interfaces/email/interactive/examples/invalid_subject
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: Spam!
+
+Dummy content
diff --git a/interfaces/email/interactive/examples/list b/interfaces/email/interactive/examples/list
new file mode 100644
index 0000000..333315f
--- /dev/null
+++ b/interfaces/email/interactive/examples/list
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] list --status all
+
+Dummy content
diff --git a/interfaces/email/interactive/examples/missing_command b/interfaces/email/interactive/examples/missing_command
new file mode 100644
index 0000000..fefe41b
--- /dev/null
+++ b/interfaces/email/interactive/examples/missing_command
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] abcde
+
+Dummy content
diff --git a/interfaces/email/interactive/examples/new b/interfaces/email/interactive/examples/new
new file mode 100644
index 0000000..7ac6dce
--- /dev/null
+++ b/interfaces/email/interactive/examples/new
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] new
+
+Need tests for the email interface.
diff --git a/interfaces/email/interactive/examples/show b/interfaces/email/interactive/examples/show
new file mode 100644
index 0000000..3ff56f4
--- /dev/null
+++ b/interfaces/email/interactive/examples/show
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] show --xml 361
+
+Dummy content
diff --git a/interfaces/email/interactive/examples/unicode b/interfaces/email/interactive/examples/unicode
new file mode 100644
index 0000000..e5b0775
--- /dev/null
+++ b/interfaces/email/interactive/examples/unicode
@@ -0,0 +1,9 @@
+From jdoe@example.com Fri Apr 18 11:18:58 2008
+Message-ID: <abcd@example.com>
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+From: John Doe <jdoe@example.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+Subject: [be-bug] show --xml f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a
+
+Dummy content
diff --git a/interfaces/email/interactive/libbe b/interfaces/email/interactive/libbe
new file mode 120000
index 0000000..7d18612
--- /dev/null
+++ b/interfaces/email/interactive/libbe
@@ -0,0 +1 @@
+../../../libbe \ No newline at end of file
diff --git a/interfaces/email/interactive/send_pgp_mime.py b/interfaces/email/interactive/send_pgp_mime.py
new file mode 100644
index 0000000..e0451c9
--- /dev/null
+++ b/interfaces/email/interactive/send_pgp_mime.py
@@ -0,0 +1,608 @@
+#!/usr/bin/python
+#
+# 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.
+"""
+Python module and command line tool for sending pgp/mime email.
+
+Mostly uses subprocess to call gpg and a sendmail-compatible mailer.
+If you lack gpg, either don't use the encryption functions or adjust
+the pgp_* commands. You may need to adjust the sendmail command to
+point to whichever sendmail-compatible mailer you have on your system.
+"""
+
+from cStringIO import StringIO
+import os
+import re
+#import GnuPGInterface # Maybe should use this instead of subprocess
+import smtplib
+import subprocess
+import sys
+import tempfile
+import types
+
+try:
+ from email.mime.text import MIMEText
+ from email.mime.multipart import MIMEMultipart
+ from email.mime.application import MIMEApplication
+ from email.encoders import encode_7or8bit
+ from email.generator import Generator
+ from email.parser import Parser
+ from email.utils import getaddress
+except ImportError:
+ # adjust to old python 2.4
+ from email.MIMEText import MIMEText
+ from email.MIMEMultipart import MIMEMultipart
+ from email.MIMENonMultipart import MIMENonMultipart
+ from email.Encoders import encode_7or8bit
+ from email.Generator import Generator
+ from email.parser import Parser
+ from email.Utils import getaddresses
+
+ getaddress = getaddresses
+ class MIMEApplication (MIMENonMultipart):
+ def __init__(self, _data, _subtype, _encoder, **params):
+ MIMENonMultipart.__init__(self, 'application', _subtype, **params)
+ self.set_payload(_data)
+ _encoder(self)
+
+usage="""usage: %prog [options]
+
+Scriptable PGP MIME email using gpg.
+
+You can use gpg-agent for passphrase caching if your key requires a
+passphrase (it better!). Example usage would be to install gpg-agent,
+and then run
+ export GPG_TTY=`tty`
+ eval $(gpg-agent --daemon)
+in your shell before invoking this script. See gpg-agent(1) for more
+details. Alternatively, you can send your passphrase in on stdin
+ echo 'passphrase' | %prog [options]
+or use the --passphrase-file option
+ %prog [options] --passphrase-file FILE [more options]
+Both of these alternatives are much less secure than gpg-agent. You
+have been warned.
+"""
+
+verboseInvoke = False
+PGP_SIGN_AS = None
+PASSPHRASE = None
+
+# The following commands are adapted from my .mutt/pgp configuration
+#
+# Printf-like sequences:
+# %a The value of PGP_SIGN_AS.
+# %f Expands to the name of a file with text to be signed/encrypted.
+# %p Expands to the passphrase argument.
+# %R A string with some number (0 on up) of pgp_reciepient_arg
+# strings.
+# %r One key ID (e.g. recipient email address) to build a
+# pgp_reciepient_arg string.
+#
+# The above sequences can be used to optionally print a string if
+# their length is nonzero. For example, you may only want to pass the
+# -u/--local-user argument to gpg if PGP_SIGN_AS is defined. To
+# optionally print a string based upon one of the above sequences, the
+# following construct is used
+# %?<sequence_char>?<optional_string>?
+# where sequence_char is a character from the table above, and
+# optional_string is the string you would like printed if status_char
+# is nonzero. optional_string may contain other sequence as well as
+# normal text, but it may not contain any question marks.
+#
+# see http://codesorcery.net/old/mutt/mutt-gnupg-howto
+# http://www.mutt.org/doc/manual/manual-6.html#pgp_autosign
+# http://tldp.org/HOWTO/Mutt-GnuPG-PGP-HOWTO-8.html
+# for more details
+
+pgp_recipient_arg='-r "%r"'
+pgp_stdin_passphrase_arg='--passphrase-fd 0'
+pgp_sign_command='/usr/bin/gpg --no-verbose --quiet --batch %p --output - --detach-sign --armor --textmode %?a?-u "%a"? %f'
+pgp_encrypt_only_command='/usr/bin/gpg --no-verbose --quiet --batch --output - --encrypt --armor --textmode --always-trust --encrypt-to "%a" %R -- %f'
+pgp_encrypt_sign_command='/usr/bin/gpg --no-verbose --quiet --batch %p --output - --encrypt --sign %?a?-u "%a"? --armor --textmode --always-trust --encrypt-to "%a" %R -- %f'
+sendmail='/usr/sbin/sendmail -t'
+
+def execute(args, stdin=None, expect=(0,)):
+ """
+ Execute a command (allows us to drive gpg).
+ """
+ if verboseInvoke == True:
+ print >> sys.stderr, '$ '+args
+ try:
+ p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, close_fds=True)
+ except OSError, e:
+ strerror = '%s\nwhile executing %s' % (e.args[1], args)
+ raise Exception, strerror
+ output, error = p.communicate(input=stdin)
+ status = p.wait()
+ if verboseInvoke == True:
+ print >> sys.stderr, '(status: %d)\n%s%s' % (status, output, error)
+ if status not in expect:
+ strerror = '%s\nwhile executing %s\n%s\n%d' % (args[1], args, error, status)
+ raise Exception, strerror
+ return status, output, error
+
+def replace(template, format_char, replacement_text):
+ """
+ >>> replace('--textmode %?a?-u %a? %f', 'f', 'file.in')
+ '--textmode %?a?-u %a? file.in'
+ >>> replace('--textmode %?a?-u %a? %f', 'a', '0xHEXKEY')
+ '--textmode -u 0xHEXKEY %f'
+ >>> replace('--textmode %?a?-u %a? %f', 'a', '')
+ '--textmode %f'
+ """
+ if replacement_text == None:
+ replacement_text = ""
+ regexp = re.compile('%[?]'+format_char+'[?]([^?]*)[?]')
+ if len(replacement_text) > 0:
+ str = regexp.sub('\g<1>', template)
+ else:
+ str = regexp.sub('', template)
+ regexp = re.compile('%'+format_char)
+ str = regexp.sub(replacement_text, str)
+ return str
+
+def flatten(msg, to_unicode=False):
+ """
+ Produce flat text output from an email Message instance.
+ """
+ assert msg != None
+ fp = StringIO()
+ g = Generator(fp, mangle_from_=False)
+ g.flatten(msg)
+ text = fp.getvalue()
+ if to_unicode == True:
+ encoding = msg.get_content_charset()
+ text = unicode(text, encoding=encoding)
+ return text
+
+def source_email(msg, return_realname=False):
+ """
+ Search the header of an email Message instance to find the
+ sender's email address.
+ """
+ froms = msg.get_all('from', [])
+ from_tuples = getaddresses(froms) # [(realname, email_address), ...]
+ assert len(from_tuples) == 1
+ if return_realname == True:
+ return from_tuples[0] # (realname, email_address)
+ return from_tuples[0][1] # email_address
+
+def target_emails(msg):
+ """
+ Search the header of an email Message instance to find a
+ list of recipient's email addresses.
+ """
+ tos = msg.get_all('to', [])
+ ccs = msg.get_all('cc', [])
+ bccs = msg.get_all('bcc', [])
+ resent_tos = msg.get_all('resent-to', [])
+ resent_ccs = msg.get_all('resent-cc', [])
+ resent_bccs = msg.get_all('resent-bcc', [])
+ all_recipients = getaddresses(tos + ccs + bccs + resent_tos + resent_ccs + resent_bccs)
+ return [addr[1] for addr in all_recipients]
+
+def mail(msg, sendmail=None):
+ """
+ Send an email Message instance on its merry way.
+
+ We can shell out to the user specified sendmail in case
+ the local host doesn't have an SMTP server set up
+ for easy smtplib usage.
+ """
+ if sendmail != None:
+ execute(sendmail, stdin=flatten(msg))
+ return None
+ s = smtplib.SMTP()
+ s.connect()
+ s.sendmail(from_addr=source_email(msg),
+ to_addrs=target_emails(msg),
+ msg=flatten(msg))
+ s.close()
+
+class Mail (object):
+ """
+ See http://www.ietf.org/rfc/rfc3156.txt for specification details.
+ >>> m = Mail('\\n'.join(['From: me@big.edu','To: you@big.edu','Subject: testing']), 'check 1 2\\ncheck 1 2\\n')
+ >>> print m.sourceEmail()
+ me@big.edu
+ >>> print m.targetEmails()
+ ['you@big.edu']
+ >>> print flatten(m.clearBodyPart())
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Disposition: inline
+ <BLANKLINE>
+ check 1 2
+ check 1 2
+ <BLANKLINE>
+ >>> print flatten(m.plain())
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ From: me@big.edu
+ To: you@big.edu
+ Subject: testing
+ <BLANKLINE>
+ check 1 2
+ check 1 2
+ <BLANKLINE>
+ >>> m.sign()
+ >>> signed.set_boundary('boundsep')
+ >>> print m.stripSig(flatten(signed)).replace('\\t', ' '*4)
+ Content-Type: multipart/signed;
+ protocol="application/pgp-signature";
+ micalg="pgp-sha1"; boundary="boundsep"
+ MIME-Version: 1.0
+ From: me@big.edu
+ To: you@big.edu
+ Subject: testing
+ Content-Disposition: inline
+ <BLANKLINE>
+ --boundsep
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Type: text/plain
+ Content-Disposition: inline
+ <BLANKLINE>
+ check 1 2
+ check 1 2
+ <BLANKLINE>
+ --boundsep
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Description: signature
+ Content-Type: application/pgp-signature; name="signature.asc";
+ charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP SIGNATURE-----
+ SIGNATURE STRIPPED (depends on current time)
+ -----END PGP SIGNATURE-----
+ <BLANKLINE>
+ --boundsep--
+ >>> encrypted = m.encrypt()
+ >>> encrypted.set_boundary('boundsep')
+ >>> print m.stripPGP(flatten(encrypted)).replace('\\t', ' '*4)
+ Content-Type: multipart/encrypted;
+ protocol="application/pgp-encrypted";
+ micalg="pgp-sha1"; boundary="boundsep"
+ MIME-Version: 1.0
+ From: me@big.edu
+ To: you@big.edu
+ Subject: testing
+ Content-Disposition: inline
+ <BLANKLINE>
+ --boundsep
+ Content-Type: application/pgp-encrypted
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ <BLANKLINE>
+ Version: 1
+ <BLANKLINE>
+ --boundsep
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Type: application/octet-stream; charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP MESSAGE-----
+ MESSAGE STRIPPED (depends on current time)
+ -----END PGP MESSAGE-----
+ <BLANKLINE>
+ --boundsep--
+ >>> signedAndEncrypted = m.signAndEncrypt()
+ >>> signedAndEncrypted.set_boundary('boundsep')
+ >>> print m.stripPGP(flatten(signedAndEncrypted)).replace('\\t', ' '*4)
+ Content-Type: multipart/encrypted;
+ protocol="application/pgp-encrypted";
+ micalg="pgp-sha1"; boundary="boundsep"
+ MIME-Version: 1.0
+ From: me@big.edu
+ To: you@big.edu
+ Subject: testing
+ Content-Disposition: inline
+ <BLANKLINE>
+ --boundsep
+ Content-Type: application/pgp-encrypted
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ <BLANKLINE>
+ Version: 1
+ <BLANKLINE>
+ --boundsep
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Type: application/octet-stream; charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP MESSAGE-----
+ MESSAGE STRIPPED (depends on current time)
+ -----END PGP MESSAGE-----
+ <BLANKLINE>
+ --boundsep--
+ """
+ def __init__(self, header, body):
+ self.header = header.strip()
+ self.body = body
+ if type(self.header) == types.UnicodeType:
+ self.header = self.header.encode("ascii")
+ p = Parser()
+ self.headermsg = p.parsestr(self.header, headersonly=True)
+ def sourceEmail(self):
+ return source_email(self.headermsg)
+ def targetEmails(self):
+ return target_emails(self.headermsg)
+ def encodedMIMEText(self, body, encoding=None):
+ if encoding == None:
+ if type(body) == types.StringType:
+ encoding = "US-ASCII"
+ elif type(body) == types.UnicodeType:
+ for encoding in ["US-ASCII", "ISO-8859-1", "UTF-8"]:
+ try:
+ body.encode(encoding)
+ except UnicodeError:
+ pass
+ else:
+ break
+ assert encoding != None
+ # Create the message ('plain' stands for Content-Type: text/plain)
+ if encoding == "US-ASCII":
+ return MIMEText(body)
+ else:
+ return MIMEText(body.encode(encoding), 'plain', encoding)
+ def clearBodyPart(self):
+ body = self.encodedMIMEText(self.body)
+ body.add_header('Content-Disposition', 'inline')
+ return body
+ def passphrase_arg(self, passphrase=None):
+ if passphrase == None and PASSPHRASE != None:
+ passphrase = PASSPHRASE
+ if passphrase == None:
+ return (None,'')
+ return (passphrase, pgp_stdin_passphrase_arg)
+ def plain(self):
+ """
+ text/plain
+ """
+ msg = self.encodedMIMEText(self.body)
+ for k,v in self.headermsg.items():
+ msg[k] = v
+ return msg
+ def sign(self, passphrase=None):
+ """
+ multipart/signed
+ +-> text/plain (body)
+ +-> application/pgp-signature (signature)
+ """
+ passphrase,pass_arg = self.passphrase_arg(passphrase)
+ body = self.clearBodyPart()
+ bfile = tempfile.NamedTemporaryFile()
+ bfile.write(flatten(body))
+ bfile.flush()
+
+ args = replace(pgp_sign_command, 'f', bfile.name)
+ if PGP_SIGN_AS == None:
+ pgp_sign_as = '<%s>' % self.sourceEmail()
+ else:
+ pgp_sign_as = PGP_SIGN_AS
+ args = replace(args, 'a', pgp_sign_as)
+ args = replace(args, 'p', pass_arg)
+ status,output,error = execute(args, stdin=passphrase)
+ signature = output
+
+ sig = MIMEApplication(_data=signature, _subtype='pgp-signature; name="signature.asc"', _encoder=encode_7or8bit)
+ sig['Content-Description'] = 'signature'
+ sig.set_charset('us-ascii')
+
+ msg = MIMEMultipart('signed', micalg='pgp-sha1', protocol='application/pgp-signature')
+ msg.attach(body)
+ msg.attach(sig)
+
+ for k,v in self.headermsg.items():
+ msg[k] = v
+ msg['Content-Disposition'] = 'inline'
+ return msg
+ def encrypt(self, passphrase=None):
+ """
+ multipart/encrypted
+ +-> application/pgp-encrypted (control information)
+ +-> application/octet-stream (body)
+ """
+ body = self.clearBodyPart()
+ bfile = tempfile.NamedTemporaryFile()
+ bfile.write(flatten(body))
+ bfile.flush()
+
+ recipient_string = ' '.join([replace(pgp_recipient_arg, 'r', recipient) for recipient in self.targetEmails()])
+ args = replace(pgp_encrypt_only_command, 'R', recipient_string)
+ args = replace(args, 'f', bfile.name)
+ if PGP_SIGN_AS == None:
+ pgp_sign_as = '<%s>' % self.sourceEmail()
+ else:
+ pgp_sign_as = PGP_SIGN_AS
+ args = replace(args, 'a', pgp_sign_as)
+ status,output,error = execute(args)
+ encrypted = output
+
+ enc = MIMEApplication(_data=encrypted, _subtype='octet-stream', _encoder=encode_7or8bit)
+ enc.set_charset('us-ascii')
+
+ control = MIMEApplication(_data='Version: 1\n', _subtype='pgp-encrypted', _encoder=encode_7or8bit)
+
+ msg = MIMEMultipart('encrypted', micalg='pgp-sha1', protocol='application/pgp-encrypted')
+ msg.attach(control)
+ msg.attach(enc)
+
+ for k,v in self.headermsg.items():
+ msg[k] = v
+ msg['Content-Disposition'] = 'inline'
+ return msg
+ def signAndEncrypt(self, passphrase=None):
+ """
+ multipart/encrypted
+ +-> application/pgp-encrypted (control information)
+ +-> application/octet-stream (body)
+ """
+ passphrase,pass_arg = self.passphrase_arg(passphrase)
+ body = self.sign()
+ body.__delitem__('Bcc')
+ bfile = tempfile.NamedTemporaryFile()
+ bfile.write(flatten(body))
+ bfile.flush()
+
+ recipient_string = ' '.join([replace(pgp_recipient_arg, 'r', recipient) for recipient in self.targetEmails()])
+ args = replace(pgp_encrypt_only_command, 'R', recipient_string)
+ args = replace(args, 'f', bfile.name)
+ if PGP_SIGN_AS == None:
+ pgp_sign_as = '<%s>' % self.sourceEmail()
+ else:
+ pgp_sign_as = PGP_SIGN_AS
+ args = replace(args, 'a', pgp_sign_as)
+ args = replace(args, 'p', pass_arg)
+ status,output,error = execute(args, stdin=passphrase)
+ encrypted = output
+
+ enc = MIMEApplication(_data=encrypted, _subtype='octet-stream', _encoder=encode_7or8bit)
+ enc.set_charset('us-ascii')
+
+ control = MIMEApplication(_data='Version: 1\n', _subtype='pgp-encrypted', _encoder=encode_7or8bit)
+
+ msg = MIMEMultipart('encrypted', micalg='pgp-sha1', protocol='application/pgp-encrypted')
+ msg.attach(control)
+ msg.attach(enc)
+
+ for k,v in self.headermsg.items():
+ msg[k] = v
+ msg['Content-Disposition'] = 'inline'
+ return msg
+ def stripChanging(self, text, start, stop, replacement):
+ stripping = False
+ lines = []
+ for line in text.splitlines():
+ line.strip()
+ if stripping == False:
+ lines.append(line)
+ if line == start:
+ stripping = True
+ lines.append(replacement)
+ else:
+ if line == stop:
+ stripping = False
+ lines.append(line)
+ return '\n'.join(lines)
+ def stripSig(self, text):
+ return self.stripChanging(text,
+ '-----BEGIN PGP SIGNATURE-----',
+ '-----END PGP SIGNATURE-----',
+ 'SIGNATURE STRIPPED (depends on current time)')
+ def stripPGP(self, text):
+ return self.stripChanging(text,
+ '-----BEGIN PGP MESSAGE-----',
+ '-----END PGP MESSAGE-----',
+ 'MESSAGE STRIPPED (depends on current time)')
+
+def test():
+ import doctest
+ doctest.testmod()
+
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser(usage=usage)
+ parser.add_option('-t', '--test', dest='test', action='store_true',
+ help='Run doctests and exit')
+
+ parser.add_option('-H', '--header-file', dest='header_filename',
+ help='file containing email header', metavar='FILE')
+ parser.add_option('-B', '--body-file', dest='body_filename',
+ help='file containing email body', metavar='FILE')
+
+ parser.add_option('-P', '--passphrase-file', dest='passphrase_file',
+ help='file containing gpg passphrase', metavar='FILE')
+ parser.add_option('-p', '--passphrase-fd', dest='passphrase_fd',
+ help='file descriptor from which to read gpg passphrase (0 for stdin)',
+ type="int", metavar='DESCRIPTOR')
+
+ parser.add_option('--mode', dest='mode', default='sign',
+ help="One of 'sign', 'encrypt', 'sign-encrypt', or 'plain'. Defaults to %default.",
+ metavar='MODE')
+
+ parser.add_option('-a', '--sign-as', dest='sign_as',
+ help="The gpg key to sign with (gpg's -u/--local-user)",
+ metavar='KEY')
+
+ parser.add_option('--output', dest='output', action='store_true',
+ help="Don't mail the generated message, print it to stdout instead.")
+
+ (options, args) = parser.parse_args()
+
+ stdin_used = False
+
+ if options.passphrase_file != None:
+ PASSPHRASE = file(options.passphrase_file, 'r').read()
+ elif options.passphrase_fd != None:
+ if options.passphrase_fd == 0:
+ stdin_used = True
+ PASSPHRASE = sys.stdin.read()
+ else:
+ PASSPHRASE = os.read(options.passphrase_fd)
+
+ if options.sign_as:
+ PGP_SIGN_AS = options.sign_as
+
+ if options.test == True:
+ test()
+ sys.exit(0)
+
+ header = None
+ if options.header_filename != None:
+ if options.header_filename == '-':
+ assert stdin_used == False
+ stdin_used = True
+ header = sys.stdin.read()
+ else:
+ header = file(options.header_filename, 'r').read()
+ if header == None:
+ raise Exception, "missing header"
+ body = None
+ if options.body_filename != None:
+ if options.body_filename == '-':
+ assert stdin_used == False
+ stdin_used = True
+ body = sys.stdin.read()
+ else:
+ body = file(options.body_filename, 'r').read()
+ if body == None:
+ raise Exception, "missing body"
+
+ m = Mail(header, body)
+ if options.mode == "sign":
+ message = m.sign()
+ elif options.mode == "encrypt":
+ message = m.encrypt()
+ elif options.mode == "sign-encrypt":
+ message = m.signAndEncrypt()
+ elif options.mode == "plain":
+ message = m.plain()
+ else:
+ print "Unrecognized mode '%s'" % options.mode
+
+ if options.output == True:
+ message = flatten(message)
+ print message
+ else:
+ mail(message, sendmail)
diff --git a/interfaces/xml/be-mbox-to-xml b/interfaces/xml/be-mbox-to-xml
index 840a2a6..a5bf13e 100755
--- a/interfaces/xml/be-mbox-to-xml
+++ b/interfaces/xml/be-mbox-to-xml
@@ -28,9 +28,6 @@ from libbe.encoding import get_encoding, set_IO_stream_encodings
from mailbox import mbox, Message # the mailbox people really want an on-disk copy
from time import asctime, gmtime
import types
-from xml.sax import make_parser
-from xml.sax.handler import ContentHandler
-from xml.sax.saxutils import escape
DEFAULT_ENCODING = get_encoding()
set_IO_stream_encodings(DEFAULT_ENCODING)
diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py
index 36d5d96..e9c16ed 100644
--- a/libbe/cmdutil.py
+++ b/libbe/cmdutil.py
@@ -70,10 +70,11 @@ def get_command(command_name):
return cmd
-def execute(cmd, args):
+def execute(cmd, args, manipulate_encodings=True):
enc = encoding.get_encoding()
cmd = get_command(cmd)
- cmd.execute([a.decode(enc) for a in args])
+ cmd.execute([a.decode(enc) for a in args],
+ manipulate_encodings=manipulate_encodings)
return 0
def help(cmd=None, parser=None):
diff --git a/libbe/encoding.py b/libbe/encoding.py
index d603602..4af864e 100644
--- a/libbe/encoding.py
+++ b/libbe/encoding.py
@@ -19,11 +19,15 @@ import locale
import sys
import doctest
+ENCODING = None # override get_encoding() output by setting this
+
def get_encoding():
"""
Guess a useful input/output/filesystem encoding... Maybe we need
seperate encodings for input/output and filesystem? Hmm...
"""
+ if ENCODING != None:
+ return ENCODING
encoding = locale.getpreferredencoding() or sys.getdefaultencoding()
if sys.platform != 'win32' or sys.version_info[:2] > (2, 3):
encoding = locale.getlocale(locale.LC_TIME)[1] or encoding