Added libbe/encoding.py to wrap input/output/file access appropriately.
authorW. Trevor King <wking@drexel.edu>
Tue, 25 Nov 2008 20:47:19 +0000 (15:47 -0500)
committerW. Trevor King <wking@drexel.edu>
Tue, 25 Nov 2008 20:47:19 +0000 (15:47 -0500)
I borrowed most of the code for this.

get_encoding() is from Trac
  http://trac.edgewall.org/browser/trunk/trac/util/datefmt.py
  format_datetime()
Trac has a BSD license
  http://trac.edgewall.org/wiki/TracLicense
I don't know if such a small snippet requires us to "reproduce the
above copyright" or where we need to reproduce it if it is needed.

The stdout/stdin replacement code follows
  http://wiki.python.org/moin/ShellRedirectionFails

Because of the stdout replacement, the doctests executes now need an
optional 'test' argument to turn off replacement during the doctests,
otherwise doctest flips out (since it had set up stdout to catch
output, and then we clobbered it's setup).

References:
  http://wiki.python.org/moin/Unicode
  http://www.amk.ca/python/howto/unicode
  http://www.python.org/dev/peps/pep-0100/

I also split libbe/editor.py off from libbe.utility.py and started
explaining the motivation for the BugDir init flags in it's docstring.

34 files changed:
.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/body [new file with mode: 0644]
.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values [new file with mode: 0644]
.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/values
be
becommands/assign.py
becommands/close.py
becommands/comment.py
becommands/diff.py
becommands/help.py
becommands/list.py
becommands/merge.py
becommands/new.py
becommands/open.py
becommands/remove.py
becommands/set.py
becommands/set_root.py
becommands/severity.py
becommands/show.py
becommands/status.py
becommands/target.py
libbe/arch.py
libbe/bug.py
libbe/bugdir.py
libbe/bzr.py
libbe/cmdutil.py
libbe/comment.py
libbe/config.py
libbe/diff.py
libbe/editor.py [new file with mode: 0644]
libbe/encoding.py [new file with mode: 0644]
libbe/git.py
libbe/hg.py
libbe/rcs.py
libbe/utility.py

diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/body b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/body
new file mode 100644 (file)
index 0000000..02bbe3a
--- /dev/null
@@ -0,0 +1,5 @@
+Wrote/borrowed libbe/encoding.py.
+Now the following works:
+
+python -c 'import libbe.encoding as e; print e.get_encoding(); e.set_IO_stream_encodings(e.get_encoding()) ;print u"\u2019"' | cat
+
diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values
new file mode 100644 (file)
index 0000000..eb56317
--- /dev/null
@@ -0,0 +1,21 @@
+
+
+
+Content-type=text/plain
+
+
+
+
+
+
+Date=Tue, 25 Nov 2008 19:41:02 +0000
+
+
+
+
+
+
+From=W. Trevor King <wking@drexel.edu>
+
+
+
index 40ac06ddf29619b5986049a027c85fb5569763dc..e710d29d28416e0a5f2a79f6feab4163fc39a78b 100644 (file)
@@ -15,7 +15,7 @@ severity=minor
 
 
 
-status=open
+status=fixed
 
 
 
diff --git a/be b/be
index 1ef7b3a2e47989459d7984d979c5a2d7ff282741..6be236ba1cd2752dcc6623e3da0f2f6969fc3b5e 100755 (executable)
--- a/be
+++ b/be
@@ -33,9 +33,12 @@ else:
         except cmdutil.GetHelp:
             print cmdutil.help(sys.argv[1])
             sys.exit(0)
-        except cmdutil.UsageError:
+        except cmdutil.UsageError, e:
+            print "Invalid usage:", e
+            print "\nArgs:", sys.argv[1:]
             print cmdutil.help(sys.argv[1])
             sys.exit(1)
     except cmdutil.UserError, e:
+        print "ERROR:"
         print e
         sys.exit(1)
index cb732b32bbe5e1d51de6e5f84deae472a38cdcc3..b00c5c23b4ea48e0bc6b0c45007781711a950bd8 100644 (file)
@@ -18,7 +18,7 @@
 from libbe import cmdutil, bugdir
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import os
     >>> bd = bugdir.simple_bug_dir()
@@ -26,17 +26,17 @@ def execute(args):
     >>> bd.bug_from_shortname("a").assigned is None
     True
 
-    >>> execute(["a"])
+    >>> execute(["a"], test=True)
     >>> bd._clear_bugs()
     >>> bd.bug_from_shortname("a").assigned == bd.user_id
     True
 
-    >>> execute(["a", "someone"])
+    >>> execute(["a", "someone"], test=True)
     >>> bd._clear_bugs()
     >>> print bd.bug_from_shortname("a").assigned
     someone
 
-    >>> execute(["a","none"])
+    >>> execute(["a","none"], test=True)
     >>> bd._clear_bugs()
     >>> bd.bug_from_shortname("a").assigned is None
     True
@@ -44,11 +44,11 @@ def execute(args):
     options, args = get_parser().parse_args(args)
     assert(len(args) in (0, 1, 2))
     if len(args) == 0:
-        raise cmdutil.UserError("Please specify a bug id.")
+        raise cmdutil.UsageError("Please specify a bug id.")
     if len(args) > 2:
         help()
-        raise cmdutil.UserError("Too many arguments.")
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError("Too many arguments.")
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bug = bd.bug_from_shortname(args[0])
     if len(args) == 1:
         bug.assigned = bd.user_id
index 8d2ccdb94cc01aae4f7f542e88932e1412b603b9..d1253973d972258c0bac1b8ade08bc55a9b5fbd8 100644 (file)
@@ -18,7 +18,7 @@
 from libbe import cmdutil, bugdir
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> from libbe import bugdir
     >>> import os
@@ -26,18 +26,17 @@ def execute(args):
     >>> os.chdir(bd.root)
     >>> print bd.bug_from_shortname("a").status
     open
-    >>> execute(["a"])
+    >>> execute(["a"], test=True)
     >>> bd._clear_bugs()
     >>> print bd.bug_from_shortname("a").status
     closed
     """
     options, args = get_parser().parse_args(args)
     if len(args) == 0:
-        raise cmdutil.UserError("Please specify a bug id.")
+        raise cmdutil.UsageError("Please specify a bug id.")
     if len(args) > 1:
-        help()
-        raise cmdutil.UserError("Too many arguments.")
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError("Too many arguments.")
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bug = bd.bug_from_shortname(args[0])
     bug.status = "closed"
     bd.save()
index 172f81896464f1e931a57b16e459cdfc6f70931f..c9c6b41f5d151523004a2986c09213e6e192bc63 100644 (file)
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 """Add a comment to a bug"""
-from libbe import cmdutil, bugdir, utility
+from libbe import cmdutil, bugdir, editor
 import os
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import time
     >>> bd = bugdir.simple_bug_dir()
     >>> os.chdir(bd.root)
-    >>> execute(["a", "This is a comment about a"])
+    >>> execute(["a", "This is a comment about a"], test=True)
     >>> bd._clear_bugs()
     >>> bug = bd.bug_from_shortname("a")
     >>> bug.load_comments()
@@ -41,12 +41,12 @@ def execute(args):
 
     >>> if 'EDITOR' in os.environ:
     ...     del os.environ["EDITOR"]
-    >>> execute(["b"])
+    >>> execute(["b"], test=True)
     Traceback (most recent call last):
     UserError: No comment supplied, and EDITOR not specified.
 
     >>> os.environ["EDITOR"] = "echo 'I like cheese' > "
-    >>> execute(["b"])
+    >>> execute(["b"], test=True)
     >>> bd._clear_bugs()
     >>> bug = bd.bug_from_shortname("b")
     >>> bug.load_comments()
@@ -57,10 +57,9 @@ def execute(args):
     """
     options, args = get_parser().parse_args(args)
     if len(args) == 0:
-        raise cmdutil.UserError("Please specify a bug or comment id.")
+        raise cmdutil.UsageError("Please specify a bug or comment id.")
     if len(args) > 2:
-        help()
-        raise cmdutil.UserError("Too many arguments.")
+        raise cmdutil.UsageError("Too many arguments.")
     
     shortname = args[0]
     if shortname.count(':') > 1:
@@ -73,20 +72,20 @@ def execute(args):
         bugname = shortname
         is_reply = False
     
-    bd = bugdir.BugDir(from_disk=True)
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bug = bd.bug_from_shortname(bugname)
     bug.load_comments()
     if is_reply:
-        parent = bug.comment_root.comment_from_shortname(shortname, bug_shortname=bugname)
+        parent = bug.comment_root.comment_from_shortname(shortname,
+                                                         bug_shortname=bugname)
     else:
         parent = bug.comment_root
     
     if len(args) == 1:
         try:
-            body = utility.editor_string("Please enter your comment above")
-        except utility.CantFindEditor:
-            raise cmdutil.UserError(
-                "No comment supplied, and EDITOR not specified.")
+            body = editor.editor_string("Please enter your comment above")
+        except editor.CantFindEditor, e:
+            raise cmdutil.UserError, "No comment supplied, and EDITOR not specified."
         if body is None:
             raise cmdutil.UserError("No comment entered.")
         body = body.decode('utf-8')
index 862afc5b2bb2a7226aca580f1a66a09cab09946f..8714d775ac3aeecfe4a3051bac1ea8a0ffc77257 100644 (file)
@@ -20,7 +20,7 @@ from libbe import cmdutil, bugdir, diff
 import os
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import os
     >>> bd = bugdir.simple_bug_dir()
@@ -31,7 +31,7 @@ def execute(args):
     >>> changed = bd.rcs.commit("Closed bug a")
     >>> os.chdir(bd.root)
     >>> if bd.rcs.versioned == True:
-    ...     execute([original])
+    ...     execute([original], test=True)
     ... else:
     ...     print "a:cm: Bug A\\nstatus: open -> closed\\n"
     Modified bug reports:
@@ -45,16 +45,14 @@ def execute(args):
     if len(args) == 1:
         revision = args[0]
     if len(args) > 1:
-        help()
-        raise cmdutil.UserError("Too many arguments.")
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError("Too many arguments.")
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     if bd.rcs.versioned == False:
         print "This directory is not revision-controlled."
     else:
         old_bd = bd.duplicate_bugdir(revision)
         r,m,a = diff.diff(old_bd, bd)
-        diff.diff_report((r,m,a), bd)
-        # TODO, string return from diff report
+        print diff.diff_report((r,m,a), bd).encode(bd.encoding)
         bd.remove_duplicate_bugdir()
 
 def get_parser():
index bf0b4fc921d30b8a4a16050f98c92df8cb4f860e..1c99af57737faf3366528fb4b4127abd132c7373 100644 (file)
@@ -21,20 +21,25 @@ __desc__ = __doc__
 def execute(args):
     """
     Print help of specified command.
+    >>> execute(["help"])
+    Usage: be help [COMMAND]
+    <BLANKLINE>
+    Options:
+      -h, --help  Print a help message
+    <BLANKLINE>
+    Print help for specified command or list of all commands.
+    <BLANKLINE>
     """
     options, args = get_parser().parse_args(args)
     if len(args) > 1:
-        raise cmdutil.UserError("Too many arguments.")
+        raise cmdutil.UsageError("Too many arguments.")
     if len(args) == 0:
         print cmdutil.help()
     else:
         try:
             print cmdutil.help(args[0])
         except AttributeError:
-            print "No help available"
-    
-    return
-
+            print "No help available"    
 
 def get_parser():
     parser = cmdutil.CmdOptionParser("be help [COMMAND]")
index 63e1cd6750d94241eaaf55af9dee0678a00b3421..c63039d6e63aac0861c0e6f31a367c203969f439 100644 (file)
@@ -21,22 +21,21 @@ from libbe.bug import cmp_full, severity_values, status_values, \
 import os
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import os
     >>> bd = bugdir.simple_bug_dir()
     >>> os.chdir(bd.root)
-    >>> execute([])
+    >>> execute([], test=True)
     a:om: Bug A
-    >>> execute(["--status", "all"])
+    >>> execute(["--status", "all"], test=True)
     a:om: Bug A
     b:cm: Bug B
     """
     options, args = get_parser().parse_args(args)
     if len(args) > 0:
-        help()
-        raise cmdutil.UserError("Too many arguments.")
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError("Too many arguments.")
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bd.load_all_bugs()
     # select status
     if options.status != None:
index b079f2cb49b82524063abe5bfac460be85581483..d1829d66c67fa17dc142b63b0b561c8aac4d7d32 100644 (file)
@@ -19,7 +19,7 @@ from libbe import cmdutil, bugdir
 import os, copy
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> from libbe import utility
     >>> bd = bugdir.simple_bug_dir()
@@ -38,7 +38,7 @@ def execute(args):
     >>> dummy.time = 2
     >>> bd.save()
     >>> os.chdir(bd.root)
-    >>> execute(["a", "b"])
+    >>> execute(["a", "b"], test=True)
     Merging bugs a and b
     >>> bd._clear_bugs()
     >>> a = bd.bug_from_shortname("a")
@@ -61,30 +61,30 @@ def execute(args):
     Date: Thu, 01 Jan 1970 00:00:01 +0000
     <BLANKLINE>
     Testing
-    --------- Comment ---------
-    Name: a:2
-    From: wking <wking@thor.yang.physics.drexel.edu>
-    Date: Thu, 01 Jan 1970 00:00:02 +0000
+      --------- Comment ---------
+      Name: a:2
+      From: wking <wking@thor.yang.physics.drexel.edu>
+      Date: Thu, 01 Jan 1970 00:00:02 +0000
     <BLANKLINE>
-    Testing...
+      Testing...
     --------- Comment ---------
     Name: a:3
     From: wking <wking@thor.yang.physics.drexel.edu>
     Date: Thu, 01 Jan 1970 00:00:03 +0000
     <BLANKLINE>
     Merged from bug b
-    --------- Comment ---------
-    Name: a:4
-    From: wking <wking@thor.yang.physics.drexel.edu>
-    Date: Thu, 01 Jan 1970 00:00:01 +0000
+      --------- Comment ---------
+      Name: a:4
+      From: wking <wking@thor.yang.physics.drexel.edu>
+      Date: Thu, 01 Jan 1970 00:00:01 +0000
     <BLANKLINE>
-    1 2
-    --------- Comment ---------
-    Name: a:5
-    From: wking <wking@thor.yang.physics.drexel.edu>
-    Date: Thu, 01 Jan 1970 00:00:02 +0000
+      1 2
+        --------- Comment ---------
+        Name: a:5
+        From: wking <wking@thor.yang.physics.drexel.edu>
+        Date: Thu, 01 Jan 1970 00:00:02 +0000
     <BLANKLINE>
-    1 2 3 4
+        1 2 3 4
     >>> b = bd.bug_from_shortname("b")
     >>> b.load_comments()
     >>> mergeB = b.comment_from_shortname(":3")
@@ -105,12 +105,12 @@ def execute(args):
     Date: Thu, 01 Jan 1970 00:00:01 +0000
     <BLANKLINE>
     1 2
-    --------- Comment ---------
-    Name: b:2
-    From: wking <wking@thor.yang.physics.drexel.edu>
-    Date: Thu, 01 Jan 1970 00:00:02 +0000
+      --------- Comment ---------
+      Name: b:2
+      From: wking <wking@thor.yang.physics.drexel.edu>
+      Date: Thu, 01 Jan 1970 00:00:02 +0000
     <BLANKLINE>
-    1 2 3 4
+      1 2 3 4
     --------- Comment ---------
     Name: b:3
     From: wking <wking@thor.yang.physics.drexel.edu>
@@ -122,12 +122,12 @@ def execute(args):
     """
     options, args = get_parser().parse_args(args)
     if len(args) < 2:
-        raise cmdutil.UserError("Please two bug ids.")
+        raise cmdutil.UsageError("Please specify two bug ids.")
     if len(args) > 2:
         help()
-        raise cmdutil.UserError("Too many arguments.")
+        raise cmdutil.UsageError("Too many arguments.")
     
-    bd = bugdir.BugDir(from_disk=True)
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bugA = bd.bug_from_shortname(args[0])
     bugA.load_comments()
     bugB = bd.bug_from_shortname(args[1])
index caa1549e13e46c3a65ffb6564d0a5c16d730d1e5..bc17f83b24a9154ebd06a3286d964fc0d8f7acf9 100644 (file)
 from libbe import cmdutil, bugdir
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> 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",])
+    >>> execute (["this is a test",], test=True)
     Created bug with ID X
     >>> bd.load()
     >>> bug = bd.bug_from_uuid("X")
@@ -40,8 +40,8 @@ def execute(args):
     """
     options, args = get_parser().parse_args(args)
     if len(args) != 1:
-        raise cmdutil.UserError("Please supply a summary message")
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError("Please supply a summary message")
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bug = bd.new_bug(summary=args[0])
     bd.save()
     print "Created bug with ID %s" % bd.bug_shortname(bug)
index 788a183ed2a69a04122f5b42cb41fc515253f1a5..9a9667d6f6dda4144237041f244e32eca9703ab1 100644 (file)
 from libbe import cmdutil, bugdir
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import os
     >>> bd = bugdir.simple_bug_dir()
     >>> os.chdir(bd.root)
     >>> print bd.bug_from_shortname("b").status
     closed
-    >>> execute(["b"])
+    >>> execute(["b"], test=True)
     >>> bd._clear_bugs()
     >>> print bd.bug_from_shortname("b").status
     open
     """
     options, args = get_parser().parse_args(args)
     if len(args) == 0:
-        raise cmdutil.UserError("Please specify a bug id.")
+        raise cmdutil.UsageError, "Please specify a bug id."
     if len(args) > 1:
-        help()
-        raise cmdutil.UserError("Too many arguments.")
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError, "Too many arguments."
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bug = bd.bug_from_shortname(args[0])
     bug.status = "open"
     bd.save()
index 8f7c2c63f49f9159699ece4fdb35f9a1ce2abe24..386d9d41320970c023a32abc516ce2a7e2aa6968 100644 (file)
@@ -18,7 +18,7 @@
 from libbe import cmdutil, bugdir
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> from libbe import mapfile
     >>> import os
@@ -26,7 +26,7 @@ def execute(args):
     >>> os.chdir(bd.root)
     >>> print bd.bug_from_shortname("b").status
     closed
-    >>> execute (["b"])
+    >>> execute (["b"], test=True)
     Removed bug b
     >>> bd._clear_bugs()
     >>> try:
@@ -37,8 +37,8 @@ def execute(args):
     """
     options, args = get_parser().parse_args(args)
     if len(args) != 1:
-        raise cmdutil.UserError("Please specify a bug id.")
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError, "Please specify a bug id."
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bug = bd.bug_from_shortname(args[0])
     bd.remove_bug(bug)
     bd.save()
index 24011f0801eb3d3dd538bb23b66f290f6d9f0b7a..39042624ef3a119b826898e4b98f230902cb0490 100644 (file)
 from libbe import cmdutil, bugdir
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import os
     >>> bd = bugdir.simple_bug_dir()
     >>> os.chdir(bd.root)
-    >>> execute(["target"])
+    >>> execute(["target"], test=True)
     None
-    >>> execute(["target", "tomorrow"])
-    >>> execute(["target"])
+    >>> execute(["target", "tomorrow"], test=True)
+    >>> execute(["target"], test=True)
     tomorrow
-    >>> execute(["target", "none"])
-    >>> execute(["target"])
+    >>> execute(["target", "none"], test=True)
+    >>> execute(["target"], test=True)
     None
     """
     options, args = get_parser().parse_args(args)
     if len(args) > 2:
-        help()
-        raise cmdutil.UserError("Too many arguments.")
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError, "Too many arguments"
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     if len(args) == 0:
         keys = bd.settings.keys()
         keys.sort()
index e17bd87bd270713952413d7a7a903a6d829f9e47..11f38b9e97b32b49f20e58bd457755ddbc6a3a9e 100644 (file)
@@ -19,7 +19,7 @@ import os.path
 from libbe import cmdutil, bugdir
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> from libbe import utility, rcs
     >>> import os
@@ -29,7 +29,7 @@ def execute(args):
     ... except bugdir.NoBugDir, e:
     ...     True
     True
-    >>> execute([dir.path])
+    >>> execute([dir.path], test=True)
     No revision control detected.
     Directory initialized.
     >>> del(dir)
@@ -40,34 +40,31 @@ def execute(args):
     >>> rcs.init('.')
     >>> print rcs.name
     Arch
-    >>> execute([])
+    >>> execute([], test=True)
     Using Arch for revision control.
     Directory initialized.
     >>> rcs.cleanup()
 
     >>> try:
-    ...     execute(['.'])
+    ...     execute(['.'], test=True)
     ... except cmdutil.UserError, e:
     ...     str(e).startswith("Directory already initialized: ")
     True
-    >>> execute(['/highly-unlikely-to-exist'])
+    >>> execute(['/highly-unlikely-to-exist'], test=True)
     Traceback (most recent call last):
     UserError: No such directory: /highly-unlikely-to-exist
     >>> os.chdir('/')
     """
     options, args = get_parser().parse_args(args)
     if len(args) > 1:
-        print help()
-        raise cmdutil.UserError, "Too many arguments"
+        raise cmdutil.UsageError
     if len(args) == 1:
         basedir = args[0]
     else:
         basedir = "."
-    if os.path.exists(basedir) == False:
-        pass
-        #raise cmdutil.UserError, "No such directory: %s" % basedir
     try:
-        bd = bugdir.BugDir(basedir, from_disk=False, sink_to_existing_root=False, assert_new_BugDir=True)
+        bd = bugdir.BugDir(basedir, from_disk=False, sink_to_existing_root=False, assert_new_BugDir=True,
+                           manipulate_encodings=not test)
     except bugdir.NoRootEntry:
         raise cmdutil.UserError("No such directory: %s" % basedir)
     except bugdir.AlreadyInitialized:
index 3adefaac12548b89cb135fb921ec973f42593472..3c856de54d96825b5b987af42923483025f3946f 100644 (file)
@@ -19,25 +19,24 @@ from libbe import cmdutil, bugdir
 from libbe.bug import severity_values, severity_description
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import os
     >>> bd = bugdir.simple_bug_dir()
     >>> os.chdir(bd.root)
-    >>> execute(["a"])
+    >>> execute(["a"], test=True)
     minor
-    >>> execute(["a", "wishlist"])
-    >>> execute(["a"])
+    >>> execute(["a", "wishlist"], test=True)
+    >>> execute(["a"], test=True)
     wishlist
-    >>> execute(["a", "none"])
+    >>> execute(["a", "none"], test=True)
     Traceback (most recent call last):
     UserError: Invalid severity level: none
     """
     options, args = get_parser().parse_args(args)
     if len(args) not in (1,2):
-        print help()
-        return
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bug = bd.bug_from_shortname(args[0])
     if len(args) == 1:
         print bug.severity
@@ -46,7 +45,7 @@ def execute(args):
             bug.severity = args[1]
         except ValueError, e:
             if e.name != "severity":
-                raise
+                raise e
             raise cmdutil.UserError ("Invalid severity level: %s" % e.value)
         bd.save()
 
index 45cd6ad00a30fd829e3e4170f2c9dfa05599e08a..3bd7e6364d406e7be2391cee350dfc51125b5de8 100644 (file)
 from libbe import cmdutil, bugdir
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import os
     >>> bd = bugdir.simple_bug_dir()
     >>> os.chdir(bd.root)
-    >>> execute (["a",])
+    >>> execute (["a",], test=True)
               ID : a
       Short name : a
         Severity : minor
@@ -37,11 +37,11 @@ def execute(args):
     """
     options, args = get_parser().parse_args(args)
     if len(args) == 0:
-        raise cmdutil.UserError("Please specify a bug id.")
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     for bugid in args:
         bug = bd.bug_from_shortname(bugid)
-        print bug.string(show_comments=True).encode('utf-8')
+        print bug.string(show_comments=True)
 
 def get_parser():
     parser = cmdutil.CmdOptionParser("be show BUG-ID [BUG-ID ...]")
index a30b3d6d8bb55a68f0a63608a352719bd43dfd0c..73d43f85dacc2fd4a0918108eca336da9bbc227f 100644 (file)
@@ -19,29 +19,28 @@ from libbe import cmdutil, bugdir
 from libbe.bug import status_values, status_description
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import os
     >>> bd = bugdir.simple_bug_dir()
     >>> os.chdir(bd.root)
-    >>> execute(["a"])
+    >>> execute(["a"], test=True)
     open
-    >>> execute(["a", "closed"])
-    >>> execute(["a"])
+    >>> execute(["a", "closed"], test=True)
+    >>> execute(["a"], test=True)
     closed
-    >>> execute(["a", "none"])
+    >>> execute(["a", "none"], test=True)
     Traceback (most recent call last):
     UserError: Invalid status: none
     """
     options, args = get_parser().parse_args(args)
     if len(args) not in (1,2):
-        print help()
-        return
-    bd = bugdir.BugDir(from_disk=True)
+        raise cmdutil.UsageError
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bug = bd.bug_from_shortname(args[0])
     if len(args) == 1:
         print bug.status
-    elif len(args) == 2:
+    else:
         try:
             bug.status = args[1]
         except ValueError, e:
index dce100fda1239a676835d09a4a0d7f1d7b787e8a..4371ef0d6bf6d5f7519012fc8335450bc2103410 100644 (file)
 from libbe import cmdutil, bugdir
 __desc__ = __doc__
 
-def execute(args):
+def execute(args, test=False):
     """
     >>> import os
     >>> bd = bugdir.simple_bug_dir()
     >>> os.chdir(bd.root)
-    >>> execute(["a"])
+    >>> execute(["a"], test=True)
     No target assigned.
-    >>> execute(["a", "tomorrow"])
-    >>> execute(["a"])
+    >>> execute(["a", "tomorrow"], test=True)
+    >>> execute(["a"], test=True)
     tomorrow
-    >>> execute(["a", "none"])
-    >>> execute(["a"])
+    >>> execute(["a", "none"], test=True)
+    >>> execute(["a"], test=True)
     No target assigned.
     """
     options, args = get_parser().parse_args(args)
-    assert(len(args) in (0, 1, 2))
-    if len(args) == 0:
-        print help()
-        return
-    bd = bugdir.BugDir(from_disk=True)
+    if len(args) not in (1, 2):
+        raise cmdutil.UsageError
+    bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test)
     bug = bd.bug_from_shortname(args[0])
     if len(args) == 1:
         if bug.target is None:
             print "No target assigned."
         else:
             print bug.target
-    elif len(args) == 2:
+    else:
+        assert len(args) == 2
         if args[1] == "none":
             bug.target = None
         else:
index fd953a45238d403a50b7ce5771ebf1413ceab0a5..11735359d378e5401f971da8f20f38f226119344 100644 (file)
 #    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+import codecs
 import os
+import re
 import shutil
 import time
-import re
 import unittest
 import doctest
 
@@ -133,13 +134,16 @@ class Arch(RCS):
         """
         tagpath = os.path.join(path, "{arch}", "=tagging-method")
         lines_out = []
-        for line in file(tagpath, "rb"):
-            line.decode("utf-8")
+        f = codecs.open(tagpath, "r", self.encoding)
+        for line in f:
             if line.startswith("source "):
                 lines_out.append("source ^[._=a-zA-X0-9].*$\n")
             else:
                 lines_out.append(line)
-        file(tagpath, "wb").write("".join(lines_out).encode("utf-8"))
+        f.close()
+        f = codecs.open(tagpath, "w", self.encoding)
+        f.write("".join(lines_out))
+        f.close()
 
     def _add_project_code(self, path):
         # http://mwolson.org/projects/GettingStartedWithArch.html
@@ -215,7 +219,9 @@ class Arch(RCS):
         return [os.path.join(root, p) for p in inv_str.split('\n')]
     def _add_dir_rule(self, rule, dirname, root):
         inv_path = os.path.join(dirname, '.arch-inventory')
-        file(inv_path, "ab").write(rule)
+        f = codecs.open(inv_path, "a", self.encoding)
+        f.write(rule)
+        f.close()
         if os.path.realpath(inv_path) not in self._list_added(root):
             paranoid = self.paranoid
             self.paranoid = False
@@ -233,12 +239,16 @@ class Arch(RCS):
         pass
     def _rcs_get_file_contents(self, path, revision=None):
         if revision == None:
-            return file(self._u_abspath(path), "rb").read()
+            return RCS._rcs_get_file_contents(self, path, revision)
         else:
             status,output,error = \
                 self._invoke_client("file-find", path, revision)
-            path = output.rstrip('\n')
-            return file(self._u_abspath(path), "rb").read()
+            relpath = output.rstrip('\n')
+            abspath = os.path.join(self.rootdir, relpath)
+            f = codecs.open(abspath, "r", self.encoding)
+            contents = f.read()
+            f.close()
+            return contents
     def _rcs_duplicate_repo(self, directory, revision=None):
         if revision == None:
             RCS._rcs_duplicate_repo(self, directory, revision)
index 68cff7b7eb9085081f9ca992cb5dab7997784d4d..67051f2e8ab7ed672723a4b2d077bffe8e70a621 100644 (file)
@@ -182,7 +182,10 @@ class Bug(object):
         if show_comments == True:
             if self._comments_loaded == False:
                 self.load_comments()
-            comout = self.comment_root.string_thread(auto_name_map=True,
+            # take advantage of the string_thread(auto_name_map=True)
+            # SIDE-EFFECT of sorting by bug time.
+            comout = self.comment_root.string_thread(flatten=False,
+                                                     auto_name_map=True,
                                                      bug_shortname=shortname)
             output = bugout + '\n' + comout.rstrip('\n')
         else :
index 596922f99e0a6711051d4b152983ec0590f99acb..6bb6a433486624fc79c19089a8972e9753c65beb 100644 (file)
@@ -24,8 +24,9 @@ import doctest
 
 import mapfile
 import bug
-import utility
 import rcs
+import encoding
+import utility
 
 
 class NoBugDir(Exception):
@@ -64,7 +65,9 @@ class MultipleBugMatches(ValueError):
 TREE_VERSION_STRING = "Bugs Everywhere Tree 1 0\n"
 
 
-def setting_property(name, valid=None, doc=None):
+def setting_property(name, valid=None, default=None, doc=None):
+    if default != None:
+        raise NotImplementedError
     def getter(self):
         value = self.settings.get(name) 
         if valid is not None:
@@ -88,7 +91,27 @@ def setting_property(name, valid=None, doc=None):
 
 class BugDir (list):
     """
-    File-system access:
+    Sink to existing root
+    ======================
+    
+    Consider the following usage case:
+    You have a bug directory rooted in
+      /path/to/source
+    by which I mean the '.be' directory is at
+      /path/to/source/.be
+    However, you're of in some subdirectory like
+      /path/to/source/GUI/testing
+    and you want to comment on a bug.  Setting sink_to_root=True wen
+    you initialize your BugDir will cause it to search for the '.be'
+    file in the ancestors of the path you passed in as 'root'.
+      /path/to/source/GUI/testing/.be     miss
+      /path/to/source/GUI/.be             miss
+      /path/to/source/.be                 hit!
+    So it still roots itself appropriately without much work for you.
+        
+    File-system access
+    ==================
+    
     When rooted in non-bugdir directory, BugDirs live completely in
     memory until the first call to .save().  This creates a '.be'
     sub-directory containing configurations options, bugs, comments,
@@ -98,12 +121,35 @@ class BugDir (list):
     will only load information from the file system when it loads new
     bugs/comments that it doesn't already have in memory, or when it
     explicitly asked to do so (e.g. .load() or __init__(from_disk=True)).
+    
+    Allow RCS initialization
+    ========================
+    
+    This one is for testing purposes.  Setting it to True allows the
+    BugDir to search for an installed RCS backend and initialize it in
+    the root directory.  This is a convenience option for supporting
+    tests of versioning functionality (e.g. .duplicate_bugdir).
+
+    Disable encoding manipulation
+    =============================
+    
+    This one is for testing purposed.  You might have non-ASCII
+    Unicode in your bugs, comments, files, etc.  BugDir instances try
+    and support your preferred encoding scheme (e.g. "utf-8") when
+    dealing with stream and file input/output.  For stream output,
+    this involves replacing sys.stdout and sys.stderr
+    (libbe.encode.set_IO_stream_encodings).  However this messes up
+    doctest's output catching.  In order to support doctest tests
+    using BugDirs, set manipulate_encodings=False, and stick to ASCII
+    in your tests.
     """
     def __init__(self, root=None, sink_to_existing_root=True,
                  assert_new_BugDir=False, allow_rcs_init=False,
+                 manipulate_encodings=True,
                  from_disk=False, rcs=None):
         list.__init__(self)
         self._save_user_id = False
+        self._manipulate_encodings = manipulate_encodings
         self.settings = {}
         if root == None:
             root = os.getcwd()
@@ -131,7 +177,8 @@ class BugDir (list):
         """
         if not os.path.exists(path):
             raise NoRootEntry(path)
-        versionfile = utility.search_parent_directories(path, os.path.join(".be", "version"))
+        versionfile=utility.search_parent_directories(path,
+                                                      os.path.join(".be", "version"))
         if versionfile != None:
             beroot = os.path.dirname(versionfile)
             root = os.path.dirname(beroot)
@@ -142,11 +189,11 @@ class BugDir (list):
                 raise NoBugDir(path)
             return beroot
         
-    def get_version(self, path=None):
-        if self.rcs_name == None:
-            # Use a temporary RCS to check the version for the first time
+    def get_version(self, path=None, use_none_rcs=False):
+        if use_none_rcs == True:
             RCS = rcs.rcs_by_name("None")
             RCS.root(self.root)
+            RCS.encoding = encoding.get_encoding()
         else:
             RCS = self.rcs
 
@@ -159,47 +206,65 @@ class BugDir (list):
         self.rcs.set_file_contents(self.get_path("version"),
                                    TREE_VERSION_STRING)
 
-    rcs_name = setting_property("rcs_name",
-                                ("None", "bzr", "git", "Arch", "hg"),
-                                doc=
-"""The name of the current RCS.  Kept seperate to make saving/loading
-settings easy.  Don't set this attribute.  Set .rcs instead, and
-.rcs_name will be automatically adjusted.""")
-
-    _rcs = None
+    def _get_encoding(self):
+        if self._encoding == None:
+            return encoding.get_encoding()
+        else:
+            return self._encoding
+    def _set_encoding(self, new_encoding):
+        if new_encoding != None:
+            if encoding.known_encoding(new_encoding) == False:
+                raise InvalidValue("encoding", new_encoding)
+        self._encoding = new_encoding
+        if self._manipulate_encodings == True:
+            encoding.set_IO_stream_encodings(self.encoding)
+        if hasattr(self, "rcs"):
+            if self.rcs != None:
+                self.rcs.encoding = self.encoding
+    _encoding = setting_property("encoding",
+                                 doc=
+"""The default input/output encoding to use (e.g. "utf-8").
+Dont' set this attribute, set .encoding instead.""")
+    encoding = property(_get_encoding, _set_encoding, doc=
+"""The default input/output encoding to use (e.g. "utf-8").""")
 
     def _get_rcs(self):
         return self._rcs
-
     def _set_rcs(self, new_rcs):
         if new_rcs == None:
             new_rcs = rcs.rcs_by_name("None")
+        new_rcs.encoding = self.encoding
         self._rcs = new_rcs
         new_rcs.root(self.root)
         self.rcs_name = new_rcs.name
-
+    _rcs = None
     rcs = property(_get_rcs, _set_rcs,
                    doc="A revision control system (RCS) instance")
+    rcs_name = setting_property("rcs_name",
+                                ("None", "bzr", "git", "Arch", "hg"),
+                                doc=
+"""The name of the current RCS.  Kept seperate to make saving/loading
+settings easy.  Don't set this attribute.  Set .rcs instead, and
+.rcs_name will be automatically adjusted.""")
 
-    _user_id = setting_property("user_id", doc=
-"""The user's prefered name.  Kept seperate to make saving/loading
-settings easy.  Don't set this attribute.  Set .user_id instead,
-and ._user_id will be automatically adjusted.  This setting is
-only saved if ._save_user_id == True""")
 
     def _get_user_id(self):
         if self._user_id == None and self.rcs != None:
             self._user_id = self.rcs.get_user_id()
         return self._user_id
-
     def _set_user_id(self, user_id):
         if self.rcs != None:
             self.rcs.user_id = user_id
         self._user_id = user_id
-
     user_id = property(_get_user_id, _set_user_id, doc=
 """The user's prefered name, e.g 'John Doe <jdoe@example.com>'.  Note
 that the Arch RCS backend *enforces* ids with this format.""")
+    _user_id = setting_property("user_id", doc=
+"""The user's prefered name.  Kept seperate to make saving/loading
+settings easy.  Don't set this attribute.  Set .user_id instead,
+and ._user_id will be automatically adjusted.  This setting is
+only saved if ._save_user_id == True""")
+
 
     target = setting_property("target",
                               doc="The current project development target")
@@ -231,7 +296,7 @@ that the Arch RCS backend *enforces* ids with this format.""")
         return new_rcs
 
     def load(self):
-        version = self.get_version()
+        version = self.get_version(use_none_rcs=True)
         if version != TREE_VERSION_STRING:
             raise NotImplementedError, \
                 "BugDir cannot handle version '%s' yet." % version
@@ -241,6 +306,7 @@ that the Arch RCS backend *enforces* ids with this format.""")
             self.settings = self._get_settings(self.get_path("settings"))
             
             self.rcs = rcs.rcs_by_name(self.rcs_name)
+            self.encoding = self.encoding # setup encoding, IO_stream_encoding...
             if self.settings.get("user_id") != None:
                 self.save_user_id()  # was a user name in the settings file
 
@@ -292,6 +358,8 @@ that the Arch RCS backend *enforces* ids with this format.""")
                 if "user_id" in settings:
                     settings = copy.copy(settings)
                     del settings["user_id"]
+            if settings.get("encoding") == encoding.get_encoding():
+                del settings["encoding"] # don't duplicate system default
         allow_no_rcs = not self.rcs.path_in_root(settings_path)
         # allow_no_rcs=True should only be for the special case of
         # configuring duplicate bugdir settings
@@ -310,7 +378,7 @@ that the Arch RCS backend *enforces* ids with this format.""")
             duplicate_settings["user_id"] = self.user_id
             self._save_settings(duplicate_settings_path, duplicate_settings)
 
-        return BugDir(duplicate_path, from_disk=True)
+        return BugDir(duplicate_path, from_disk=True, manipulate_encodings=self._manipulate_encodings)
 
     def remove_duplicate_bugdir(self):
         self.rcs.remove_duplicate_repo()
@@ -423,7 +491,8 @@ def simple_bug_dir():
     """
     dir = utility.Dir()
     assert os.path.exists(dir.path)
-    bugdir = BugDir(dir.path, sink_to_existing_root=False, allow_rcs_init=True)
+    bugdir = BugDir(dir.path, sink_to_existing_root=False, allow_rcs_init=True,
+                    manipulate_encodings=False)
     bugdir._dir_ref = dir # postpone cleanup since dir.__del__() removes dir.
     bug_a = bugdir.new_bug("a", summary="Bug A")
     bug_a.creator = "John Doe <jdoe@example.com>"
index a0ae71539a587b419437cb5e5d71e6148384855c..38af6bb397887b2af38dd68e41cb64ac2d9cf964 100644 (file)
@@ -55,7 +55,7 @@ class Bzr(RCS):
         pass
     def _rcs_get_file_contents(self, path, revision=None):
         if revision == None:
-            return file(os.path.join(self.rootdir, path), "rb").read()
+            return RCS._rcs_get_file_contents(self, path, revision)
         else:
             status,output,error = \
                 self._u_invoke_client("cat","-r",revision,path)
index 6d7ab0122de7ee01aa858af649e1931fdc9e67d0..1a321e9970b40eca201beee1dc56351e6189be40 100644 (file)
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 import optparse
 import os
-import locale
 from textwrap import TextWrapper
 from StringIO import StringIO
 import doctest
 
 import bugdir
 import plugin
-import utility
+import encoding
+
 
 class UserError(Exception):
     def __init__(self, msg):
@@ -34,6 +34,13 @@ class UserErrorWrap(UserError):
         UserError.__init__(self, str(exception))
         self.exception = exception
 
+class GetHelp(Exception):
+    pass
+
+class UsageError(Exception):
+    pass
+
+
 def iter_commands():
     for name, module in plugin.iter_plugins("becommands"):
         yield name.replace("_", "-"), module
@@ -52,9 +59,11 @@ def get_command(command_name):
         raise UserError("Unknown command %s" % command_name)
     return cmd
 
+
 def execute(cmd, args):
-    encoding = locale.getpreferredencoding() or 'ascii'
-    return get_command(cmd).execute([a.decode(encoding) for a in args])
+    enc = encoding.get_encoding()
+    get_command(cmd).execute([a.decode(enc) for a in args])
+    return 0
 
 def help(cmd=None):
     if cmd != None:
@@ -71,17 +80,8 @@ def help(cmd=None):
             ret.append("be %s%*s    %s" % (name, numExtraSpaces, "", desc))
         return "\n".join(ret)
 
-class GetHelp(Exception):
-    pass
-
-
-class UsageError(Exception):
-    pass
-
-
 def raise_get_help(option, opt, value, parser):
     raise GetHelp
-
         
 class CmdOptionParser(optparse.OptionParser):
     def __init__(self, usage):
index 579e294909c48c2567c6bc968574406af7a8675d..bd085fae63af21fc0287d5879af6df75bd4c32e1 100644 (file)
@@ -243,11 +243,19 @@ class Comment(Tree):
         self.add_reply(reply)
         return reply
 
-    def string_thread(self, name_map={}, indent=0,
+    def string_thread(self, name_map={}, indent=0, flatten=True,
                       auto_name_map=False, bug_shortname=None):
         """
         Return a sting displaying a thread of comments.
         bug_shortname is only used if auto_name_map == True.
+        
+        SIDE-EFFECT: if auto_name_map==True, calls comment_shornames()
+        which will sort the tree by comment.time.  Avoid by calling
+          name_map = {}
+          for shortname,comment in comm.comment_shortnames(bug_shortname):
+              name_map[comment.uuid] = shortname
+          comm.sort(key=lambda c : c.From) # your sort
+          comm.string_thread(name_map=name_map)
 
         >>> a = Comment(bug=None, uuid="a", body="Insightful remarks")
         >>> a.time = utility.str_to_time("Thu, 20 Nov 2008 01:00:00 +0000")
@@ -261,7 +269,7 @@ class Comment(Tree):
         >>> d.uuid = "d"
         >>> d.time = utility.str_to_time("Thu, 20 Nov 2008 04:00:00 +0000")
         >>> a.sort(key=lambda comm : comm.time)
-        >>> print a.string_thread()
+        >>> print a.string_thread(flatten=True)
         --------- Comment ---------
         Name: a
         From: 
@@ -317,7 +325,7 @@ class Comment(Tree):
             for shortname,comment in self.comment_shortnames(bug_shortname):
                 name_map[comment.uuid] = shortname
         stringlist = []
-        for depth,comment in self.thread(flatten=True):
+        for depth,comment in self.thread(flatten=flatten):
             ind = 2*depth+indent
             if comment.uuid in name_map:
                 sname = name_map[comment.uuid]
index 79c0d6f6dc927ec219b2e82c9a1a9c8cce061afb..94c700ec7dcd67b2e900d5b630ab3ca6d2b0f4d8 100644 (file)
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 import ConfigParser
+import codecs
+import locale
 import os.path
+import sys
 import doctest
 
+default_encoding = sys.getfilesystemencoding() or locale.getpreferredencoding()
+
 def path():
     """Return the path to the per-user config file"""
     return os.path.expanduser("~/.bugs_everywhere")
 
-def set_val(name, value, section="DEFAULT"):
+def set_val(name, value, section="DEFAULT", encoding=None):
     """Set a value in the per-user config file
 
     :param name: The name of the value to set
     :param value: The new value to set (or None to delete the value)
     :param section: The section to store the name/value in
     """
+    if encoding == None:
+        encoding = default_encoding
     config = ConfigParser.ConfigParser()
-    config.read(path())
+    f = codecs.open(path(), "r", encoding)
+    config.readfp(f, path())
+    f.close()
     if value is not None:
         config.set(section, name, value)
     else:
         config.remove_option(section, name)
-    config.write(file(path(), "wb"))
-    pass
+    f = codecs.open(path(), "w", encoding)
+    config.write(f)
+    f.close()
 
-def get_val(name, section="DEFAULT"):
+def get_val(name, section="DEFAULT", encoding=None):
     """
     Get a value from the per-user config file
 
@@ -49,13 +59,17 @@ def get_val(name, section="DEFAULT"):
     True
     >>> set_val("junk", "random")
     >>> get_val("junk")
-    'random'
+    u'random'
     >>> set_val("junk", None)
     >>> get_val("junk") is None
     True
     """
+    if encoding == None:
+        encoding = default_encoding
     config = ConfigParser.ConfigParser()
-    config.read(path())
+    f = codecs.open(path(), "r", encoding)
+    config.readfp(f, path())
+    f.close()
     try:
         return config.get(section, name)
     except ConfigParser.NoOptionError:
index 86a91ca4330fbdf9af421ac38b4cee437ac05b03..5fc0166ac0fd0cb6e45aa0276d0a11ecb133dd5f 100644 (file)
@@ -46,12 +46,13 @@ def diff_report(diff_data, bug_dir):
     added.sort(cmp_severity)
     removed.sort(cmp_severity)
     modified.sort(modified_cmp)
-
+    lines = []
+    
     if len(added) > 0:
-        print "New bug reports:"
+        lines.append("New bug reports:")
         for bug in added:
-            print bug.string(shortlist=True)
-        print ""
+            lines.extend(bug.string(shortlist=True).splitlines())
+        lines.append("")
 
     if len(modified) > 0:
         printed = False
@@ -61,15 +62,18 @@ def diff_report(diff_data, bug_dir):
                 continue
             if not printed:
                 printed = True
-                print "Modified bug reports:"
-            print change_str
-        print ""
+                lines.append("Modified bug reports:")
+            lines.extend(change_str.splitlines())
+        if printed == True:
+            lines.append("")
 
-    if len(removed) > 0: 
-        print "Removed bug reports:"
+    if len(removed) > 0:
+        lines.append("Removed bug reports:")
         for bug in removed:
-            print bug.string(shortlist=True)
-        print ""
+            lines.extend(bug.string(shortlist=True).splitlines())
+        lines.append("")
+    
+    return '\n'.join(lines)
 
 def change_lines(old, new, attributes):
     change_list = []    
diff --git a/libbe/editor.py b/libbe/editor.py
new file mode 100644 (file)
index 0000000..4a63e5c
--- /dev/null
@@ -0,0 +1,103 @@
+# Bugs Everywhere, a distributed bugtracker
+# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc.
+# <abentley@panoramicfeedback.com>
+#
+#    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
+
+import codecs
+import locale
+import os
+import sys
+import tempfile
+import doctest
+
+default_encoding = sys.getfilesystemencoding() or locale.getpreferredencoding()
+
+comment_marker = u"== Anything below this line will be ignored\n"
+
+class CantFindEditor(Exception):
+    def __init__(self):
+        Exception.__init__(self, "Can't find editor to get string from")
+
+def editor_string(comment=None, encoding=None):
+    """Invokes the editor, and returns the user_produced text as a string
+
+    >>> if "EDITOR" in os.environ:
+    ...     del os.environ["EDITOR"]
+    >>> if "VISUAL" in os.environ:
+    ...     del os.environ["VISUAL"]
+    >>> editor_string()
+    Traceback (most recent call last):
+    CantFindEditor: Can't find editor to get string from
+    >>> os.environ["EDITOR"] = "echo bar > "
+    >>> editor_string()
+    u'bar\\n'
+    >>> os.environ["VISUAL"] = "echo baz > "
+    >>> editor_string()
+    u'baz\\n'
+    >>> del os.environ["EDITOR"]
+    >>> del os.environ["VISUAL"]
+    """
+    if encoding == None:
+        encoding = default_encoding
+    for name in ('VISUAL', 'EDITOR'):
+        try:
+            editor = os.environ[name]
+            break
+        except KeyError:
+            pass
+    else:
+        raise CantFindEditor()
+    fhandle, fname = tempfile.mkstemp()
+    try:
+        if comment is not None:
+            os.write(fhandle, '\n'+comment_string(comment))
+        os.close(fhandle)
+        oldmtime = os.path.getmtime(fname)
+        os.system("%s %s" % (editor, fname))
+        f = codecs.open(fname, "r", encoding)
+        output = trimmed_string(f.read())
+        f.close()
+        if output.rstrip('\n') == "":
+            output = None
+    finally:
+        os.unlink(fname)
+    return output
+
+
+def comment_string(comment):
+    """
+    >>> comment_string('hello') == comment_marker+"hello"
+    True
+    """
+    return comment_marker + comment
+
+
+def trimmed_string(instring):
+    """
+    >>> trimmed_string("hello\\n"+comment_marker)
+    u'hello\\n'
+    >>> trimmed_string("hi!\\n" + comment_string('Booga'))
+    u'hi!\\n'
+    """
+    out = []
+    for line in instring.splitlines(True):
+        if line.startswith(comment_marker):
+            break
+        out.append(line)
+    return ''.join(out)
+
+suite = doctest.DocTestSuite()
diff --git a/libbe/encoding.py b/libbe/encoding.py
new file mode 100644 (file)
index 0000000..8b0ef73
--- /dev/null
@@ -0,0 +1,53 @@
+# Bugs Everywhere, a distributed bugtracker
+# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc.
+# <abentley@panoramicfeedback.com>
+#
+#    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
+import codecs
+import locale
+import sys
+import doctest
+
+def get_encoding():
+    """
+    Guess a useful input/output/filesystem encoding...  Maybe we nees
+    seperate encodings for input/output and filessytem?  Hmm...
+    """
+    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
+        # Python 2.3 on windows doesn't know about 'XYZ' alias for 'cpXYZ'
+    return encoding
+
+def known_encoding(encoding):
+    """
+    >>> known_encoding("highly-unlikely-encoding")
+    False
+    >>> known_encoding(get_encoding())
+    True
+    """
+    try:
+        codecs.lookup(encoding)
+        return True
+    except LookupError:
+        return False
+
+def set_IO_stream_encodings(encoding):
+    sys.stdin = codecs.getreader(encoding)(sys.__stdin__)
+    sys.stdout = codecs.getwriter(encoding)(sys.__stdout__)
+    sys.stderr = codecs.getwriter(encoding)(sys.__stderr__)
+
+suite = doctest.DocTestSuite()
index 046e72e2bb4a4e77f18e0c21879e98d08df29cf1..98fda2bb536b8fe82e4e6d8a52fe60405619dcb3 100644 (file)
@@ -69,7 +69,7 @@ class Git(RCS):
         self._rcs_add(path)
     def _rcs_get_file_contents(self, path, revision=None):
         if revision == None:
-            return file(self._u_abspath(path), "rb").read()
+            return RCS._rcs_get_file_contents(self, path, revision)
         else:
             arg = "%s:%s" % (revision,path)
             status,output,error = self._u_invoke_client("show", arg)
index 27cbb79582d0d781de36f21a649d7e75861356b7..c00d7e2c16d8f4c1d87715c9ff4b5d518e3e7e9a 100644 (file)
@@ -58,14 +58,14 @@ class Hg(RCS):
         pass
     def _rcs_get_file_contents(self, path, revision=None):
         if revision == None:
-            return file(os.path.join(self.rootdir, path), "rb").read()
+            return RCS._rcs_get_file_contents(self, path, revision)
         else:
             status,output,error = \
                 self._u_invoke_client("cat","-r",revision,path)
             return output
     def _rcs_duplicate_repo(self, directory, revision=None):
         if revision == None:
-            RCS._rcs_duplicate_repo(self, directory, revision)
+            return RCS._rcs_duplicate_repo(self, directory, revision)
         else:
             self._u_invoke_client("archive", "--rev", revision, directory)
     def _rcs_commit(self, commitfile):
index 3519c3daddb46bd736b7ad7913796034aea309cd..786f9dd591928c7ce045a146e9490abb076876ef 100644 (file)
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 from subprocess import Popen, PIPE
+import codecs
 import os
 import os.path
-from socket import gethostname
 import re
+from socket import gethostname
+import shutil
 import sys
 import tempfile
-import shutil
 import unittest
 import doctest
 
@@ -77,8 +78,9 @@ class PathNotInRoot(Exception):
         self.root = root
 
 class NoSuchFile(Exception):
-    def __init__(self, pathname):
-        Exception.__init__(self, "No such file: %s" % pathname)
+    def __init__(self, pathname, root="."):
+        path = os.path.abspath(os.path.join(root, pathname))
+        Exception.__init__(self, "No such file: %s" % path)
 
 
 def new():
@@ -97,12 +99,13 @@ class RCS(object):
     name = "None"
     client = "" # command-line tool for _u_invoke_client
     versioned = False
-    def __init__(self, paranoid=False):
+    def __init__(self, paranoid=False, encoding=sys.getdefaultencoding()):
         self.paranoid = paranoid
         self.verboseInvoke = False
         self.rootdir = None
         self._duplicateBasedir = None
         self._duplicateDirname = None
+        self.encoding = encoding
     def __del__(self):
         self.cleanup()
 
@@ -171,15 +174,15 @@ class RCS(object):
         pass
     def _rcs_get_file_contents(self, path, revision=None):
         """
-        Get the file contents as they were in a given revision.  Don't
-        worry about decoding the contents, the RCS.get_file_contents()
-        method will handle that.
-        
+        Get the file contents as they were in a given revision.
         Revision==None specifies the current revision.
         """
         assert revision == None, \
             "The %s RCS does not support revision specifiers" % self.name
-        return file(os.path.join(self.rootdir, path), "rb").read()
+        f = codecs.open(os.path.join(self.rootdir, path), "r", self.encoding)
+        contents = f.read()
+        f.close()
+        return contents
     def _rcs_duplicate_repo(self, directory, revision=None):
         """
         Get the repository as it was in a given revision.
@@ -297,14 +300,18 @@ class RCS(object):
             relpath = self._u_rel_path(path)
             contents = self._rcs_get_file_contents(relpath,revision)
         else:
-            contents = file(path, "rb").read()
-        return contents.decode("utf-8")
+            f = codecs.open(path, "r", self.encoding)
+            contents = f.read()
+            f.close()
+        return contents
     def set_file_contents(self, path, contents, allow_no_rcs=False):
         """
         Set the file contents under version control.
         """
         add = not os.path.exists(path)
-        file(path, "wb").write(contents.encode("utf-8"))
+        f = codecs.open(path, "w", self.encoding)
+        f.write(contents)
+        f.close()
         
         if self._use_rcs(path, allow_no_rcs):
             if add:
@@ -537,13 +544,13 @@ class RCS(object):
         Split the commitfile created in self.commit() back into
         summary and header lines.
         """
-        f = file(commitfile, "rb")
+        f = codecs.open(commitfile, "r", self.encoding)
         summary = f.readline()
         body = f.read()
         body.lstrip('\n')
         if len(body) == 0:
             body = None
-        f.close
+        f.close()
         return (summary, body)
         
 
index 2c77fcf66e5804c228c9b574d61ac0d635867e97..30240a903bcdd62be7665f03c85db99f7f4e6fb7 100644 (file)
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 import calendar
-import time
+import codecs
 import os
-import tempfile
 import shutil
+import tempfile
+import time
 import doctest
 
 
@@ -87,73 +88,5 @@ def str_to_time(str_time):
 def handy_time(time_val):
     return time.strftime("%a, %d %b %Y %H:%M", time.localtime(time_val))
 
-class CantFindEditor(Exception):
-    def __init__(self):
-        Exception.__init__(self, "Can't find editor to get string from")
-
-def editor_string(comment=None):
-
-    """Invokes the editor, and returns the user_produced text as a string
-
-    >>> if "EDITOR" in os.environ:
-    ...     del os.environ["EDITOR"]
-    >>> if "VISUAL" in os.environ:
-    ...     del os.environ["VISUAL"]
-    >>> editor_string()
-    Traceback (most recent call last):
-    CantFindEditor: Can't find editor to get string from
-    >>> os.environ["EDITOR"] = "echo bar > "
-    >>> editor_string()
-    u'bar\\n'
-    >>> os.environ["VISUAL"] = "echo baz > "
-    >>> editor_string()
-    u'baz\\n'
-    >>> del os.environ["EDITOR"]
-    >>> del os.environ["VISUAL"]
-    """
-    for name in ('VISUAL', 'EDITOR'):
-        try:
-            editor = os.environ[name]
-            break
-        except KeyError:
-            pass
-    else:
-        raise CantFindEditor()
-    fhandle, fname = tempfile.mkstemp()
-    try:
-        if comment is not None:
-            os.write(fhandle, '\n'+comment_string(comment))
-        os.close(fhandle)
-        oldmtime = os.path.getmtime(fname)
-        os.system("%s %s" % (editor, fname))
-        output = trimmed_string(file(fname, "rb").read().decode("utf-8"))
-        if output.rstrip('\n') == "":
-            output = None
-    finally:
-        os.unlink(fname)
-    return output
-
-
-def comment_string(comment):
-    """
-    >>> comment_string('hello')
-    '== Anything below this line will be ignored ==\\nhello'
-    """
-    return '== Anything below this line will be ignored ==\n' + comment
-
-
-def trimmed_string(instring):
-    """
-    >>> trimmed_string("hello\\n== Anything below this line will be ignored")
-    'hello\\n'
-    >>> trimmed_string("hi!\\n" + comment_string('Booga'))
-    'hi!\\n'
-    """
-    out = []
-    for line in instring.splitlines(True):
-        if line.startswith('== Anything below this line will be ignored'):
-            break
-        out.append(line)
-    return ''.join(out)
 
 suite = doctest.DocTestSuite()