From 5699aef2a5741c5ffc24d9cb12d6bc9b085d484a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 25 Nov 2008 15:47:19 -0500 Subject: [PATCH] Added libbe/encoding.py to wrap input/output/file access appropriately. 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. --- .../028d2e8d-5b0f-4c43-a913-35a1709b2276/body | 5 + .../values | 21 +++ .../values | 2 +- be | 5 +- becommands/assign.py | 14 +- becommands/close.py | 11 +- becommands/comment.py | 27 ++-- becommands/diff.py | 12 +- becommands/help.py | 15 ++- becommands/list.py | 11 +- becommands/merge.py | 50 +++---- becommands/new.py | 8 +- becommands/open.py | 11 +- becommands/remove.py | 8 +- becommands/set.py | 17 ++- becommands/set_root.py | 19 ++- becommands/severity.py | 17 ++- becommands/show.py | 10 +- becommands/status.py | 17 ++- becommands/target.py | 23 ++-- libbe/arch.py | 26 ++-- libbe/bug.py | 5 +- libbe/bugdir.py | 123 ++++++++++++++---- libbe/bzr.py | 2 +- libbe/cmdutil.py | 26 ++-- libbe/comment.py | 14 +- libbe/config.py | 28 +++- libbe/diff.py | 26 ++-- libbe/editor.py | 103 +++++++++++++++ libbe/encoding.py | 53 ++++++++ libbe/git.py | 2 +- libbe/hg.py | 4 +- libbe/rcs.py | 37 +++--- libbe/utility.py | 73 +---------- 34 files changed, 525 insertions(+), 300 deletions(-) create mode 100644 .be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/body create mode 100644 .be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values create mode 100644 libbe/editor.py create mode 100644 libbe/encoding.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 index 0000000..02bbe3a --- /dev/null +++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/body @@ -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 index 0000000..eb56317 --- /dev/null +++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/028d2e8d-5b0f-4c43-a913-35a1709b2276/values @@ -0,0 +1,21 @@ + + + +Content-type=text/plain + + + + + + +Date=Tue, 25 Nov 2008 19:41:02 +0000 + + + + + + +From=W. Trevor King + + + diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/values b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/values index 40ac06d..e710d29 100644 --- a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/values +++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/values @@ -15,7 +15,7 @@ severity=minor -status=open +status=fixed diff --git a/be b/be index 1ef7b3a..6be236b 100755 --- 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) diff --git a/becommands/assign.py b/becommands/assign.py index cb732b3..b00c5c2 100644 --- a/becommands/assign.py +++ b/becommands/assign.py @@ -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 diff --git a/becommands/close.py b/becommands/close.py index 8d2ccdb..d125397 100644 --- a/becommands/close.py +++ b/becommands/close.py @@ -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() diff --git a/becommands/comment.py b/becommands/comment.py index 172f818..c9c6b41 100644 --- a/becommands/comment.py +++ b/becommands/comment.py @@ -15,16 +15,16 @@ # 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') diff --git a/becommands/diff.py b/becommands/diff.py index 862afc5..8714d77 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): +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(): diff --git a/becommands/help.py b/becommands/help.py index bf0b4fc..1c99af5 100644 --- a/becommands/help.py +++ b/becommands/help.py @@ -21,20 +21,25 @@ __desc__ = __doc__ def execute(args): """ Print help of specified command. + >>> execute(["help"]) + Usage: be help [COMMAND] + + Options: + -h, --help Print a help message + + Print help for specified command or list of all commands. + """ 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]") diff --git a/becommands/list.py b/becommands/list.py index 63e1cd6..c63039d 100644 --- a/becommands/list.py +++ b/becommands/list.py @@ -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: diff --git a/becommands/merge.py b/becommands/merge.py index b079f2c..d1829d6 100644 --- a/becommands/merge.py +++ b/becommands/merge.py @@ -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 Testing - --------- Comment --------- - Name: a:2 - From: wking - Date: Thu, 01 Jan 1970 00:00:02 +0000 + --------- Comment --------- + Name: a:2 + From: wking + Date: Thu, 01 Jan 1970 00:00:02 +0000 - Testing... + Testing... --------- Comment --------- Name: a:3 From: wking Date: Thu, 01 Jan 1970 00:00:03 +0000 Merged from bug b - --------- Comment --------- - Name: a:4 - From: wking - Date: Thu, 01 Jan 1970 00:00:01 +0000 + --------- Comment --------- + Name: a:4 + From: wking + Date: Thu, 01 Jan 1970 00:00:01 +0000 - 1 2 - --------- Comment --------- - Name: a:5 - From: wking - Date: Thu, 01 Jan 1970 00:00:02 +0000 + 1 2 + --------- Comment --------- + Name: a:5 + From: wking + Date: Thu, 01 Jan 1970 00:00:02 +0000 - 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 1 2 - --------- Comment --------- - Name: b:2 - From: wking - Date: Thu, 01 Jan 1970 00:00:02 +0000 + --------- Comment --------- + Name: b:2 + From: wking + Date: Thu, 01 Jan 1970 00:00:02 +0000 - 1 2 3 4 + 1 2 3 4 --------- Comment --------- Name: b:3 From: wking @@ -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]) diff --git a/becommands/new.py b/becommands/new.py index caa1549..bc17f83 100644 --- a/becommands/new.py +++ b/becommands/new.py @@ -18,14 +18,14 @@ 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) diff --git a/becommands/open.py b/becommands/open.py index 788a183..9a9667d 100644 --- a/becommands/open.py +++ b/becommands/open.py @@ -18,25 +18,24 @@ 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() diff --git a/becommands/remove.py b/becommands/remove.py index 8f7c2c6..386d9d4 100644 --- a/becommands/remove.py +++ b/becommands/remove.py @@ -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() diff --git a/becommands/set.py b/becommands/set.py index 24011f0..3904262 100644 --- a/becommands/set.py +++ b/becommands/set.py @@ -18,25 +18,24 @@ 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() diff --git a/becommands/set_root.py b/becommands/set_root.py index e17bd87..11f38b9 100644 --- a/becommands/set_root.py +++ b/becommands/set_root.py @@ -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: diff --git a/becommands/severity.py b/becommands/severity.py index 3adefaa..3c856de 100644 --- a/becommands/severity.py +++ b/becommands/severity.py @@ -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() diff --git a/becommands/show.py b/becommands/show.py index 45cd6ad..3bd7e63 100644 --- a/becommands/show.py +++ b/becommands/show.py @@ -18,12 +18,12 @@ 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 ...]") diff --git a/becommands/status.py b/becommands/status.py index a30b3d6..73d43f8 100644 --- a/becommands/status.py +++ b/becommands/status.py @@ -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: diff --git a/becommands/target.py b/becommands/target.py index dce100f..4371ef0 100644 --- a/becommands/target.py +++ b/becommands/target.py @@ -18,33 +18,32 @@ 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: diff --git a/libbe/arch.py b/libbe/arch.py index fd953a4..1173535 100644 --- a/libbe/arch.py +++ b/libbe/arch.py @@ -14,10 +14,11 @@ # 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) diff --git a/libbe/bug.py b/libbe/bug.py index 68cff7b..67051f2 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -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 : diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 596922f..6bb6a43 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -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 '. 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 " diff --git a/libbe/bzr.py b/libbe/bzr.py index a0ae715..38af6bb 100644 --- a/libbe/bzr.py +++ b/libbe/bzr.py @@ -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) diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index 6d7ab01..1a321e9 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -16,14 +16,14 @@ # 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): diff --git a/libbe/comment.py b/libbe/comment.py index 579e294..bd085fa 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -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] diff --git a/libbe/config.py b/libbe/config.py index 79c0d6f..94c700e 100644 --- a/libbe/config.py +++ b/libbe/config.py @@ -15,30 +15,40 @@ # 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: diff --git a/libbe/diff.py b/libbe/diff.py index 86a91ca..5fc0166 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -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 index 0000000..4a63e5c --- /dev/null +++ b/libbe/editor.py @@ -0,0 +1,103 @@ +# Bugs Everywhere, a distributed bugtracker +# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc. +# +# +# 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 index 0000000..8b0ef73 --- /dev/null +++ b/libbe/encoding.py @@ -0,0 +1,53 @@ +# Bugs Everywhere, a distributed bugtracker +# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc. +# +# +# 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() diff --git a/libbe/git.py b/libbe/git.py index 046e72e..98fda2b 100644 --- a/libbe/git.py +++ b/libbe/git.py @@ -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) diff --git a/libbe/hg.py b/libbe/hg.py index 27cbb79..c00d7e2 100644 --- a/libbe/hg.py +++ b/libbe/hg.py @@ -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): diff --git a/libbe/rcs.py b/libbe/rcs.py index 3519c3d..786f9dd 100644 --- a/libbe/rcs.py +++ b/libbe/rcs.py @@ -15,13 +15,14 @@ # 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) diff --git a/libbe/utility.py b/libbe/utility.py index 2c77fcf..30240a9 100644 --- a/libbe/utility.py +++ b/libbe/utility.py @@ -15,10 +15,11 @@ # 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() -- 2.26.2