diff options
65 files changed, 777 insertions, 397 deletions
diff --git a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values index a68a21d..4b8a017 100644 --- a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values +++ b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/4be73baf-e46b-4acb-a58e-4719e57c550b/values @@ -1,21 +1,8 @@ +Content-type: text/plain - -Content-type=text/plain - - - - - - -Date=Fri, 18 Apr 2008 11:21:03 +0000 - - - - - - -From=benf +Date: Fri, 18 Apr 2008 11:21:03 +0000 +From: benf diff --git a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body new file mode 100644 index 0000000..19b1cf5 --- /dev/null +++ b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/body @@ -0,0 +1 @@ +We could add this functionality to update_copyright.sh diff --git a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values new file mode 100644 index 0000000..0b164a1 --- /dev/null +++ b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/comments/d5ed4f87-f1a1-4138-b0ad-190e4a49d820/values @@ -0,0 +1,11 @@ +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 14:08:45 +0000 + + +From: W. Trevor King <wking@drexel.edu> + + +In-reply-to: 4be73baf-e46b-4acb-a58e-4719e57c550b + diff --git a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values index a2d65ff..420d967 100644 --- a/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values +++ b/.be/bugs/00f26f04-9202-4288-8744-b29abc2342d6/values @@ -7,7 +7,7 @@ creator: benf severity: minor -status: closed +status: open summary: Address is outdated for FSF offices diff --git a/.be/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values b/.be/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values index 9da9004..70ec5f5 100644 --- a/.be/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values +++ b/.be/bugs/3613e6e9-db9e-4775-8914-f31f0b4b81ac/values @@ -4,7 +4,7 @@ creator: abentley severity: minor -status: closed +status: fixed summary: auto-add files to revision control diff --git a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values index c8719a0..62b29d5 100644 --- a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values +++ b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/1ba36272-7ae1-4f95-8002-7b45e62e6790/values @@ -1,28 +1,11 @@ +Content-type: text/plain +Date: Mon, 16 Jul 2007 15:23:47 +0000 -Content-type=text/plain +From: abentley - - - -Date=Mon, 16 Jul 2007 15:23:47 +0000 - - - - - - -From=abentley - - - - - - -In-reply-to=e173c09a-1b3e-4d8a-a86a-6b8c94a76247 - - +In-reply-to: e173c09a-1b3e-4d8a-a86a-6b8c94a76247 diff --git a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values index 96cc18c..e31b44b 100644 --- a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values +++ b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/comments/e173c09a-1b3e-4d8a-a86a-6b8c94a76247/values @@ -1,21 +1,8 @@ +Content-type: text/plain - -Content-type=text/plain - - - - - - -Date=Sun, 15 Jul 2007 13:34:52 +0000 - - - - - - -From=jelmer +Date: Sun, 15 Jul 2007 13:34:52 +0000 +From: jelmer diff --git a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values index 9b17373..c471b0f 100644 --- a/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values +++ b/.be/bugs/508ea95e-7bc6-4b9b-9e36-a3a87014423d/values @@ -4,7 +4,7 @@ creator: jelmer severity: minor -status: closed +status: fixed summary: should check not just EDITOR but also VISUAL. diff --git a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values index 4cb1f35..59a3828 100644 --- a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values +++ b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/db2c18d9-9573-4d68-88a5-ee47ed24b813/values @@ -1,21 +1,8 @@ +Content-type: text/plain - -Content-type=text/plain - - - - - - -Date=Thu, 24 Mar 2005 17:04:47 +0000 - - - - - - -From=abentley +Date: Thu, 24 Mar 2005 17:04:47 +0000 +From: abentley diff --git a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values index 51af41d..ddbdc15 100644 --- a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values +++ b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/comments/ec16300f-529a-4492-8327-f9a72e4447c2/values @@ -1,21 +1,8 @@ +Content-type: text/plain - -Content-type=text/plain - - - - - - -Date=Thu, 24 Mar 2005 13:05:13 +0000 - - - - - - -From=abentley +Date: Thu, 24 Mar 2005 13:05:13 +0000 +From: abentley diff --git a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values index 94a1f9f..e2a930c 100644 --- a/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values +++ b/.be/bugs/7ba4bc51-b251-483a-a67a-f1b89c83f6af/values @@ -4,7 +4,7 @@ creator: abentley severity: serious -status: closed +status: fixed summary: Add test cases diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body new file mode 100644 index 0000000..0598d70 --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/body @@ -0,0 +1,8 @@ +<type 'unicode'> <body>�</body> +Traceback (most recent call last): + File "<string>", line 1, in <module> + File "/usr/lib/python2.5/xml/etree/ElementTree.py", line 963, in XML + parser.feed(text) + File "/usr/lib/python2.5/xml/etree/ElementTree.py", line 1245, in feed + self._parser.Parse(data, 0) +UnicodeEncodeError: 'ascii' codec can't encode character u'\u1234' in position 6: ordinal not in range(128) diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values new file mode 100644 index 0000000..cd8d8b9 --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/07fc448f-c42e-4846-929a-8924de485766/values @@ -0,0 +1,11 @@ +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:34:22 +0000 + + +From: W. Trevor King <wking@drexel.edu> + + +In-reply-to: faa686bf-c0eb-48bf-8a0b-d9a2e02bd132 + diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body new file mode 100644 index 0000000..397d4b6 --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/body @@ -0,0 +1 @@ +It looks like etree wants a byte string, not unicode input diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values new file mode 100644 index 0000000..8bdaf52 --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/520a9829-8d90-43ce-be64-868b8321e5b0/values @@ -0,0 +1,11 @@ +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:42:16 +0000 + + +From: W. Trevor King <wking@drexel.edu> + + +In-reply-to: faa686bf-c0eb-48bf-8a0b-d9a2e02bd132 + diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body new file mode 100644 index 0000000..ce2bb8d --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/body @@ -0,0 +1,5 @@ +For example, this works: + +python -c 'from xml.etree import ElementTree; a=u"<body>\u1234</body>"; print type(a), a; b=ElementTree.XML(a.encode("unicode_escape")); print type(b.text), unicode(b.text).decode("unicode_escape");' + +Ugly though :p. Ah well. diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values new file mode 100644 index 0000000..1784e0e --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/8b54e56e-c693-4594-998f-5bd6c1f385d7/values @@ -0,0 +1,11 @@ +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:46:57 +0000 + + +From: W. Trevor King <wking@drexel.edu> + + +In-reply-to: 520a9829-8d90-43ce-be64-868b8321e5b0 + diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body new file mode 100644 index 0000000..89a8f8d --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/body @@ -0,0 +1 @@ +That's with Python 2.5.2 and ElementTree "2326 2005-03-17 07:45:21Z fredrik" diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values new file mode 100644 index 0000000..cca07c3 --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/bb124fd9-08f5-4f82-a035-6355e8403075/values @@ -0,0 +1,11 @@ +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:37:55 +0000 + + +From: W. Trevor King <wking@drexel.edu> + + +In-reply-to: 07fc448f-c42e-4846-929a-8924de485766 + diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body new file mode 100644 index 0000000..57e050d --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/body @@ -0,0 +1,5 @@ +Isolated problem to: + +python -c 'from xml.etree import ElementTree; a=u"<body>\u1234</body>"; print type(a), a; b=ElementTree.XML(a);' + +Output attached below diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values new file mode 100644 index 0000000..e430ea0 --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/comments/faa686bf-c0eb-48bf-8a0b-d9a2e02bd132/values @@ -0,0 +1,8 @@ +Content-type: text/plain + + +Date: Sun, 12 Jul 2009 11:31:13 +0000 + + +From: W. Trevor King <wking@drexel.edu> + diff --git a/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values new file mode 100644 index 0000000..4bc81f5 --- /dev/null +++ b/.be/bugs/e4ed63f6-9000-4d0b-98c3-487269140141/values @@ -0,0 +1,17 @@ +creator: W. Trevor King <wking@drexel.edu> + + +reporter: W. Trevor King <wking@drexel.edu> + + +severity: minor + + +status: fixed + + +summary: utf8 problems in xml parsing + + +time: Sat, 11 Jul 2009 15:48:32 +0000 + diff --git a/.be/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values b/.be/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values index df99653..a82beb8 100644 --- a/.be/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values +++ b/.be/bugs/ecc91b94-7f3f-44a7-af58-03191d327a7f/values @@ -4,7 +4,7 @@ creator: abentley severity: minor -status: closed +status: fixed summary: no tests for missing $EDITOR 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 index eb56317..d39c4a1 100644 --- 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 @@ -1,21 +1,8 @@ +Content-type: text/plain - -Content-type=text/plain - - - - - - -Date=Tue, 25 Nov 2008 19:41:02 +0000 - - - - - - -From=W. Trevor King <wking@drexel.edu> +Date: Tue, 25 Nov 2008 19:41:02 +0000 +From: W. Trevor King <wking@drexel.edu> diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values index f976972..639fd4a 100644 --- a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values +++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/15602c0c-25e4-4c2c-9e24-79bdb90721b1/values @@ -1,21 +1,8 @@ +Content-type: text/plain - -Content-type=text/plain - - - - - - -Date=Tue, 25 Nov 2008 02:36:16 +0000 - - - - - - -From=W. Trevor King <wking@drexel.edu> +Date: Tue, 25 Nov 2008 02:36:16 +0000 +From: W. Trevor King <wking@drexel.edu> diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values index bf5085b..2821b2f 100644 --- a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values +++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/3f556a48-c538-4569-8609-3e829b561d78/values @@ -1,21 +1,8 @@ +Content-type: text/plain - -Content-type=text/plain - - - - - - -Date=Tue, 25 Nov 2008 03:02:59 +0000 - - - - - - -From=W. Trevor King <wking@drexel.edu> +Date: Tue, 25 Nov 2008 03:02:59 +0000 +From: W. Trevor King <wking@drexel.edu> diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body new file mode 100644 index 0000000..b441da9 --- /dev/null +++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/body @@ -0,0 +1 @@ +Test unicode �quotes� diff --git a/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values new file mode 100644 index 0000000..a67680d --- /dev/null +++ b/.be/bugs/f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a/comments/f376debf-9f7e-4347-807f-00e7263487c7/values @@ -0,0 +1,8 @@ +Content-type: text/plain + + +Date: Sat, 11 Jul 2009 18:28:57 +0000 + + +From: W. Trevor King <wking@drexel.edu> + @@ -29,6 +29,7 @@ MODULES += ${DOC_DIR} RM = rm +#PREFIX = /usr/local PREFIX = ${HOME} INSTALL_OPTIONS = "--prefix=${PREFIX}" @@ -19,38 +19,66 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - +import os import sys + from libbe import cmdutil, _version -__doc__ == cmdutil.help() +__doc__ = cmdutil.help() + +usage = "be [options] [command] [command_options ...] [command_args ...]" -if len(sys.argv) == 1 or sys.argv[1] in ('--help', '-h'): - print cmdutil.help() -elif sys.argv[1] == '--complete': - for command, module in cmdutil.iter_commands(): - print command - print '\n'.join(["--help","--complete","--options","--version"]) -elif sys.argv[1] == '--version': +parser = cmdutil.CmdOptionParser(usage) +parser.command = "be" +parser.add_option("--version", action="store_true", dest="version", + help="Print version string and exit.") +parser.add_option("-d", "--dir", dest="dir", metavar="DIR", + help="Run this command from DIR instead of the current directory.") + +try: + options,args = parser.parse_args() + for option,value in cmdutil.option_value_pairs(options, parser): + if value == "--complete": + if option == "dir": + if len(args) == 0: + args = ["."] + paths = cmdutil.complete_path(args[0]) + raise cmdutil.GetCompletions(paths) +except cmdutil.GetHelp: + print cmdutil.help(parser=parser) + sys.exit(0) +except cmdutil.GetCompletions, e: + print '\n'.join(e.completions) + sys.exit(0) + +if options.version == True: print _version.version_info["revision_id"] -else: - try: - try: - sys.exit(cmdutil.execute(sys.argv[1], sys.argv[2:])) - except KeyError, e: - raise cmdutil.UserError("Unknown command \"%s\"" % e.args[0]) - except cmdutil.GetHelp: - print cmdutil.help(sys.argv[1]) - sys.exit(0) - except cmdutil.GetCompletions, e: - print '\n'.join(e.completions) - sys.exit(0) - 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) + sys.exit(0) +if options.dir != None: + os.chdir(options.dir) + +try: + if len(args) == 0: + raise cmdutil.UsageError, "must supply a command" + sys.exit(cmdutil.execute(args[0], args[1:])) +except cmdutil.GetHelp: + print cmdutil.help(sys.argv[1]) + sys.exit(0) +except cmdutil.GetCompletions, e: + print '\n'.join(e.completions) + sys.exit(0) +except cmdutil.UnknownCommand, e: + print e + sys.exit(1) +except cmdutil.UsageError, e: + print "Invalid usage:", e + if len(args) == 0: + print cmdutil.help(parser=parser) + else: + print "\nArgs:", args + 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 985cfdd..e6126b8 100644 --- a/becommands/assign.py +++ b/becommands/assign.py @@ -2,7 +2,6 @@ # Marien Zwart <marienz@gentoo.org> # Thomas Gerigk <tgerigk@gmx.de> # W. Trevor King <wking@drexel.edu> -# <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 @@ -18,7 +17,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Assign an individual or group to fix a bug""" -from libbe import cmdutil, bugdir, settings_object +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args, test=False): @@ -26,7 +25,7 @@ def execute(args, test=False): >>> import os >>> bd = bugdir.simple_bug_dir() >>> os.chdir(bd.root) - >>> bd.bug_from_shortname("a").assigned is settings_object.EMPTY + >>> bd.bug_from_shortname("a").assigned is None True >>> execute(["a"], test=True) @@ -41,7 +40,7 @@ def execute(args, test=False): >>> execute(["a","none"], test=True) >>> bd._clear_bugs() - >>> bd.bug_from_shortname("a").assigned is settings_object.EMPTY + >>> bd.bug_from_shortname("a").assigned is None True """ parser = get_parser() diff --git a/becommands/close.py b/becommands/close.py index deaccce..ddffaa5 100644 --- a/becommands/close.py +++ b/becommands/close.py @@ -2,7 +2,6 @@ # Marien Zwart <marienz@gentoo.org> # Thomas Gerigk <tgerigk@gmx.de> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/becommands/comment.py b/becommands/comment.py index 0b3a576..da82854 100644 --- a/becommands/comment.py +++ b/becommands/comment.py @@ -1,7 +1,6 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # Chris Ball <cjb@laptop.org> # W. Trevor King <wking@drexel.edu> -# <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 @@ -17,9 +16,13 @@ # 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, settings_object, editor +from libbe import cmdutil, bugdir, comment, editor import os import sys +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree __desc__ = __doc__ def execute(args, test=False): @@ -39,7 +42,7 @@ def execute(args, test=False): True >>> comment.time <= int(time.time()) True - >>> comment.in_reply_to is settings_object.EMPTY + >>> comment.in_reply_to is None True >>> if 'EDITOR' in os.environ: @@ -108,15 +111,59 @@ def execute(args, test=False): if not body.endswith('\n'): body+='\n' - comment = parent.new_reply(body=body) - if options.content_type != None: - comment.content_type = options.content_type + if options.XML == False: + new = parent.new_reply(body=body) + if options.content_type != None: + new.content_type = options.content_type + else: # import XML comment [list] + # read in the comments + str_body = body.encode("unicode_escape").replace(r'\n', '\n') + comment_list = ElementTree.XML(str_body) + if comment_list.tag not in ["bug", "comment-list"]: + raise comment.InvalidXML( + comment_list, "root element must be <bug> or <comment-list>") + new_comments = [] + ids = [] + for c in bug.comment_root.traverse(): + ids.append(c.uuid) + if c.alt_id != None: + ids.append(c.alt_id) + for child in comment_list.getchildren(): + if child.tag == "comment": + new = comment.Comment(bug) + new.from_xml(unicode(ElementTree.tostring(child)).decode("unicode_escape")) + if new.alt_id in ids: + raise cmdutil.UserError( + "Clashing comment alt_id: %s" % new.alt_id) + ids.append(new.uuid) + if new.alt_id != None: + ids.append(new.alt_id) + if new.in_reply_to == None: + new.in_reply_to = parent.uuid + new_comments.append(new) + else: + print >> sys.stderr, "Ignoring unknown tag %s in %s" \ + % (child.tag, comment_list.tag) + try: + comment.list_to_root(new_comments,bug,root=parent, # link new comments + ignore_missing_references=options.ignore_missing_references) + except comment.MissingReference, e: + raise cmdutil.UserError(e) + # Protect against programmer error causing data loss: + kids = [c.uuid for c in parent.traverse()] + for nc in new_comments: + assert nc.uuid in kids, "%s wasn't added to %s" % (nc.uuid, parent.uuid) bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be comment ID [COMMENT]") parser.add_option("-c", "--content-type", metavar="MIME", dest="content_type", help="Set comment content-type (e.g. text/plain)", default=None) + parser.add_option("-x", "--xml", action="store_true", default=False, + dest='XML', help="Use COMMENT to specify an XML comment description rather than the comment body. The root XML element should be either <bug> or <comment-list> with one or more <comment> children. The syntax for the <comment> elements should match that generated by 'be show --xml COMMENT-ID'. Unrecognized tags are ignored. Missing tags are left at the default value. The comment UUIDs are always auto-generated, so if you set a <uuid> field, but no <alt-id> field, your <uuid> will be used as the comment's <alt-id>. An exception is raised if <alt-id> conflicts with an existing comment.") + parser.add_option("-i", "--ignore-missing-references", action="store_true", + dest="ignore_missing_references", + help="For XML import, if any comment's <in-reply-to> refers to a non-existent comment, ignore it (instead of raising an exception).") return parser longhelp=""" diff --git a/becommands/diff.py b/becommands/diff.py index 2bdea93..aa782b4 100644 --- a/becommands/diff.py +++ b/becommands/diff.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/becommands/init.py b/becommands/init.py index 390dd15..5d9ccda 100644 --- a/becommands/init.py +++ b/becommands/init.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/becommands/list.py b/becommands/list.py index 443704b..76614a0 100644 --- a/becommands/list.py +++ b/becommands/list.py @@ -2,7 +2,6 @@ # Chris Ball <cjb@laptop.org> # Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com> # W. Trevor King <wking@drexel.edu> -# <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 @@ -135,11 +134,12 @@ def execute(args, test=False): return True bugs = [b for b in bd if filter(b) ] - if len(bugs) == 0: + if len(bugs) == 0 and options.xml == False: print "No matching bugs found" def list_bugs(cur_bugs, title=None, just_uuids=False, xml=False): if xml == True: + print '<?xml version="1.0" encoding="%s" ?>' % bd.encoding print "<bugs>" if len(cur_bugs) > 0: if title != None and xml == False: diff --git a/becommands/merge.py b/becommands/merge.py index 4bec6bf..3839774 100644 --- a/becommands/merge.py +++ b/becommands/merge.py @@ -1,5 +1,4 @@ # Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu> -# <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 diff --git a/becommands/new.py b/becommands/new.py index 32e070a..ceb4949 100644 --- a/becommands/new.py +++ b/becommands/new.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 @@ -16,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Create a new bug""" -from libbe import cmdutil, bugdir, settings_object +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args, test=False): @@ -36,7 +35,7 @@ def execute(args, test=False): True >>> print bug.severity minor - >>> bug.target == settings_object.EMPTY + >>> bug.target == None True """ parser = get_parser() @@ -45,14 +44,18 @@ def execute(args, test=False): if len(args) != 1: 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]) + if args[0] == '-': # read summary from stdin + summary = sys.stdin.readline() + else: + summary = args[0] + bug = bd.new_bug(summary=summary.strip()) if options.reporter != None: bug.reporter = options.reporter else: bug.reporter = bug.creator if options.assigned != None: bug.assigned = options.assigned - elif bd.default_assignee != settings_object.EMPTY: + elif bd.default_assignee != None: bug.assigned = bd.default_assignee bd.save() print "Created bug with ID %s" % bd.bug_shortname(bug) @@ -66,8 +69,9 @@ def get_parser(): return parser longhelp=""" -Create a new bug, with a new ID. The summary specified on the commandline -is a string that describes the bug briefly. +Create a new bug, with a new ID. The summary specified on the +commandline is a string (only one line) that describes the bug briefly +or "-", in which case the string will be read from stdin. """ def help(): diff --git a/becommands/open.py b/becommands/open.py index f9abcbb..6d13af0 100644 --- a/becommands/open.py +++ b/becommands/open.py @@ -2,7 +2,6 @@ # Marien Zwart <marienz@gentoo.org> # Thomas Gerigk <tgerigk@gmx.de> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/becommands/remove.py b/becommands/remove.py index 213a8d9..e20440b 100644 --- a/becommands/remove.py +++ b/becommands/remove.py @@ -1,5 +1,4 @@ # Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu> -# <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 diff --git a/becommands/set.py b/becommands/set.py index e771018..fa431e9 100644 --- a/becommands/set.py +++ b/becommands/set.py @@ -3,7 +3,6 @@ # Marien Zwart <marienz@gentoo.org> # Thomas Gerigk <tgerigk@gmx.de> # W. Trevor King <wking@drexel.edu> -# <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 @@ -19,14 +18,15 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Change tree settings""" -from libbe import cmdutil, bugdir, settings_object +import textwrap +from libbe import cmdutil, bugdir, rcs, settings_object __desc__ = __doc__ def _value_string(bd, setting): val = bd.settings.get(setting, settings_object.EMPTY) if val == settings_object.EMPTY: default = getattr(bd, bd._setting_name_to_attr_name(setting)) - if default != settings_object.EMPTY: + if default not in [None, settings_object.EMPTY]: val = "None (%s)" % default else: val = None @@ -60,7 +60,9 @@ def execute(args, test=False): elif len(args) == 1: print _value_string(bd, args[0]) else: - if args[1] != "none": + if args[1] == "none": + del bd.settings[args[0]] + else: if args[0] not in bd.settings_properties: msg = "Invalid setting %s\n" % args[0] msg += 'Allowed settings:\n ' @@ -68,14 +70,36 @@ def execute(args, test=False): raise cmdutil.UserError(msg) old_setting = bd.settings.get(args[0]) setattr(bd, args[0], args[1]) - else: - del bd.settings[args[0]] bd.save() def get_parser(): parser = cmdutil.CmdOptionParser("be set [NAME] [VALUE]") return parser +def get_bugdir_settings(): + settings = [] + for s in bugdir.BugDir.settings_properties: + settings.append(s) + settings.sort() + documented_settings = [] + for s in settings: + set = getattr(bugdir.BugDir, s) + dstr = set.__doc__.strip() + # per-setting comment adjustments + if s == "rcs_name": + lines = dstr.split('\n') + while lines[0].startswith("This property defaults to") == False: + lines.pop(0) + assert len(lines) != None, \ + "Unexpected rcs_name docstring:\n '%s'" % dstr + lines.insert( + 0, "The name of the revision control system to use.\n") + dstr = '\n'.join(lines) + doc = textwrap.wrap(dstr, width=70, initial_indent=' ', + subsequent_indent=' ') + documented_settings.append("%s\n%s" % (s, '\n'.join(doc))) + return documented_settings + longhelp=""" Show or change per-tree settings. @@ -83,14 +107,11 @@ If name and value are supplied, the name is set to a new value. If no value is specified, the current value is printed. If no arguments are provided, all names and values are listed. -Interesting settings are: -rcs_name - The name of the revision control system. "Arch" and "None" are supported. -target - The current development goal - To unset a setting, set it to "none". -""" + +Allowed settings are: + +%s""" % ('\n'.join(get_bugdir_settings()),) def help(): return get_parser().help_str() + longhelp diff --git a/becommands/severity.py b/becommands/severity.py index f8a0c02..36dea6e 100644 --- a/becommands/severity.py +++ b/becommands/severity.py @@ -2,7 +2,6 @@ # Marien Zwart <marienz@gentoo.org> # Thomas Gerigk <tgerigk@gmx.de> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/becommands/show.py b/becommands/show.py index ff434ab..f700caa 100644 --- a/becommands/show.py +++ b/becommands/show.py @@ -3,7 +3,6 @@ # Thomas Gerigk <tgerigk@gmx.de> # Thomas Habets <thomas@habets.pp.se> # W. Trevor King <wking@drexel.edu> -# <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 @@ -19,6 +18,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Show a particular bug""" +import sys from libbe import cmdutil, bugdir __desc__ = __doc__ @@ -40,6 +40,7 @@ def execute(args, test=False): Bug A <BLANKLINE> >>> execute (["--xml", "a"], test=True) # doctest: +ELLIPSIS + <?xml version="1.0" encoding="..." ?> <bug> <uuid>a</uuid> <short-name>a</short-name> @@ -53,27 +54,53 @@ def execute(args, test=False): parser = get_parser() options, args = parser.parse_args(args) cmdutil.default_complete(options, args, parser, - bugid_args={0: lambda bug : bug.active==True}) + bugid_args={-1: lambda bug : bug.active==True}) if len(args) == 0: raise cmdutil.UsageError bd = bugdir.BugDir(from_disk=True, manipulate_encodings=not test) - for bugid in args: - bug = bd.bug_from_shortname(bugid) - if options.dumpXML: - print bug.xml(show_comments=True) + for shortname in args: + if shortname.count(':') > 1: + raise cmdutil.UserError("Invalid id '%s'." % shortname) + elif shortname.count(':') == 1: + # Split shortname generated by Comment.comment_shortnames() + bugname = shortname.split(':')[0] + is_comment = True else: - print bug.string(show_comments=True) - if bugid != args[-1]: - print "" # add a blank line between bugs + bugname = shortname + is_comment = False + bug = bd.bug_from_shortname(bugname) + if is_comment == False: + if options.dumpXML: + print '<?xml version="1.0" encoding="%s" ?>' % bd.encoding + print bug.xml(show_comments=True) + else: + print bug.string(show_comments=True) + else: + comment = bug.comment_root.comment_from_shortname( + shortname, bug_shortname=bugname) + if options.dumpXML: + print comment.xml(shortname=shortname) + else: + if len(args) == 1 and options.only_raw_body == True: + sys.__stdout__.write(comment.body) + else: + print comment.string(shortname=shortname) + if shortname != args[-1] and options.dumpXML == False: + print "" # add a blank line between bugs/comments def get_parser(): - parser = cmdutil.CmdOptionParser("be show [options] BUG-ID [BUG-ID ...]") + parser = cmdutil.CmdOptionParser("be show [options] ID [ID ...]") parser.add_option("-x", "--xml", action="store_true", dest='dumpXML', help="Dump as XML") + parser.add_option("--only-raw-body", action="store_true", + dest='only_raw_body', help="When printing only a single comment, just print it's body. This allows extraction of non-text content types.") return parser longhelp=""" -Show all information about a bug. +Show all information about the bugs or comments whose IDs are given. + +It's probably not a good idea to mix bug and comment IDs in a single +call, but you're free to do so if you like. """ def help(): diff --git a/becommands/status.py b/becommands/status.py index d8bd4c4..882f7b3 100644 --- a/becommands/status.py +++ b/becommands/status.py @@ -1,5 +1,4 @@ # Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu> -# <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 @@ -56,24 +55,39 @@ def get_parser(): def help(): - longhelp=[""" -Show or change a bug's status. - -If no status is specified, the current value is printed. If a status -is specified, it will be assigned to the bug. - -Status levels are: -"""] try: # See if there are any per-tree status configurations bd = bugdir.BugDir(from_disk=True, manipulate_encodings=False) except bugdir.NoBugDir, e: pass # No tree, just show the defaults longest_status_len = max([len(s) for s in bug.status_values]) - for status in bug.status_values : + active_statuses = [] + for status in bug.active_status_values : + description = bug.status_description[status] + s = "%*s : %s" % (longest_status_len, status, description) + active_statuses.append(s) + inactive_statuses = [] + for status in bug.inactive_status_values : description = bug.status_description[status] - s = "%*s : %s\n" % (longest_status_len, status, description) - longhelp.append(s) - longhelp = ''.join(longhelp) + s = "%*s : %s" % (longest_status_len, status, description) + inactive_statuses.append(s) + longhelp=""" +Show or change a bug's status. + +If no status is specified, the current value is printed. If a status +is specified, it will be assigned to the bug. + +There are two classes of statuses, active and inactive, which are only +important for commands like "be list" that show only active bugs by +default. + +Active status levels are: + %s +Inactive status levels are: + %s + +You can overide the list of allowed statuses on a per-repository basis. +See "be set --help" for more details. +""" % ('\n '.join(active_statuses), '\n '.join(inactive_statuses)) return get_parser().help_str() + longhelp def complete(options, args, parser): diff --git a/becommands/tag.py b/becommands/tag.py index ab0324e..1b20ddb 100644 --- a/becommands/tag.py +++ b/becommands/tag.py @@ -102,7 +102,7 @@ def execute(args, test=False): else: # add the tag estrs.append(tag_string) bug.extra_strings = estrs # reassign to notice change - bug.save() + bd.save() tags = [] for estr in bug.extra_strings: diff --git a/becommands/target.py b/becommands/target.py index 283998a..e2283f4 100644 --- a/becommands/target.py +++ b/becommands/target.py @@ -4,7 +4,6 @@ # Marien Zwart <marienz@gentoo.org> # Thomas Gerigk <tgerigk@gmx.de> # W. Trevor King <wking@drexel.edu> -# <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 @@ -20,7 +19,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Show or change a bug's target for fixing""" -from libbe import cmdutil, bugdir, settings_object +from libbe import cmdutil, bugdir __desc__ = __doc__ def execute(args, test=False): @@ -56,7 +55,7 @@ def execute(args, test=False): return bug = bd.bug_from_shortname(args[0]) if len(args) == 1: - if bug.target is None or bug.target is settings_object.EMPTY: + if bug.target is None: print "No target assigned." else: print bug.target diff --git a/libbe/arch.py b/libbe/arch.py index 3051b34..8f7603b 100644 --- a/libbe/arch.py +++ b/libbe/arch.py @@ -2,7 +2,6 @@ # Ben Finney <ben+python@benfinney.id.au> # James Rowe <jnrowe@ukfsn.org> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/beuuid.py b/libbe/beuuid.py index de67cb7..020ea9f 100644 --- a/libbe/beuuid.py +++ b/libbe/beuuid.py @@ -1,5 +1,4 @@ # Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/bug.py b/libbe/bug.py index dfa49f2..8cb8a0a 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -1,7 +1,6 @@ # Copyright (C) 2008-2009 Chris Ball <cjb@laptop.org> # Thomas Habets <thomas@habets.pp.se> # W. Trevor King <wking@drexel.edu> -# <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 @@ -68,7 +67,7 @@ def load_severities(severity_def): global severity_values global severity_description global severity_index - if severity_def == settings_object.EMPTY: + if severity_def == None: return severity_values = tuple([val for val,description in severity_def]) severity_description = dict(severity_def) @@ -88,9 +87,9 @@ def load_status(active_status_def, inactive_status_def): global status_values global status_description global status_index - if active_status_def == settings_object.EMPTY: + if active_status_def == None: active_status_def = globals()["active_status_def"] - if inactive_status_def == settings_object.EMPTY: + if inactive_status_def == None: inactive_status_def = globals()["inactive_status_def"] active_status_values = tuple([val for val,description in active_status_def]) inactive_status_values = tuple([val for val,description in inactive_status_def]) @@ -178,7 +177,7 @@ class Bug(settings_object.SavedSettingsObject): def time_string(): return {} def _get_time(self): - if self.time_string == None or self.time_string == settings_object.EMPTY: + if self.time_string in [None, settings_object.EMPTY]: return None return utility.str_to_time(self.time_string) def _set_time(self, value): @@ -255,7 +254,7 @@ class Bug(settings_object.SavedSettingsObject): def _setting_attr_string(self, setting): value = getattr(self, setting) - if value == settings_object.EMPTY: + if value in [None, settings_object.EMPTY]: return "" else: return str(value) @@ -283,7 +282,7 @@ class Bug(settings_object.SavedSettingsObject): ("summary", self.summary)] ret = '<bug>\n' for (k,v) in info: - if v is not settings_object.EMPTY: + if v is not None: ret += ' <%s>%s</%s>\n' % (k,xml.sax.saxutils.escape(v),k) for estr in self.extra_strings: ret += ' <extra-string>%s</extra-string>\n' % estr @@ -477,8 +476,8 @@ def cmp_attr(bug_1, bug_2, attr, invert=False): return 1 val_1 = getattr(bug_1, attr) val_2 = getattr(bug_2, attr) - if val_1 == settings_object.EMPTY: val_1 = None - if val_2 == settings_object.EMPTY: val_2 = None + if val_1 == None: val_1 = None + if val_2 == None: val_2 = None if invert == True : return -cmp(val_1, val_2) diff --git a/libbe/bugdir.py b/libbe/bugdir.py index d4a39cb..fed9aa3 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -3,7 +3,6 @@ # Chris Ball <cjb@laptop.org> # Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com> # W. Trevor King <wking@drexel.edu> -# <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 @@ -136,7 +135,7 @@ class BugDir (list, settings_object.SavedSettingsObject): return settings_object.versioned_property(**kwargs) @_versioned_property(name="target", - doc="The current project development target") + doc="The current project development target.") def target(): return {} def _guess_encoding(self): diff --git a/libbe/bzr.py b/libbe/bzr.py index d73392a..56a1648 100644 --- a/libbe/bzr.py +++ b/libbe/bzr.py @@ -2,7 +2,6 @@ # Ben Finney <ben+python@benfinney.id.au> # Marien Zwart <marienz@gentoo.org> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index edc6442..7589241 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -1,7 +1,6 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com> # W. Trevor King <wking@drexel.edu> -# <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 @@ -16,6 +15,7 @@ # 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 glob import optparse import os from textwrap import TextWrapper @@ -32,10 +32,10 @@ class UserError(Exception): def __init__(self, msg): Exception.__init__(self, msg) -class UserErrorWrap(UserError): - def __init__(self, exception): - UserError.__init__(self, str(exception)) - self.exception = exception +class UnknownCommand(UserError): + def __init__(self, cmd): + Exception.__init__(self, "Unknown command '%s'" % cmd) + self.cmd = cmd class UsageError(Exception): pass @@ -56,24 +56,27 @@ def iter_commands(): def get_command(command_name): """Retrieves the module for a user command - >>> get_command("asdf") - Traceback (most recent call last): - UserError: Unknown command asdf + >>> try: + ... get_command("asdf") + ... except UnknownCommand, e: + ... print e + Unknown command 'asdf' >>> repr(get_command("list")).startswith("<module 'becommands.list' from ") True """ cmd = plugin.get_plugin("becommands", command_name.replace("-", "_")) if cmd is None: - raise UserError("Unknown command %s" % command_name) + raise UnknownCommand(command_name) return cmd def execute(cmd, args): enc = encoding.get_encoding() - get_command(cmd).execute([a.decode(enc) for a in args]) + cmd = get_command(cmd) + cmd.execute([a.decode(enc) for a in args]) return 0 -def help(cmd=None): +def help(cmd=None, parser=None): if cmd != None: return get_command(cmd).help() else: @@ -82,17 +85,15 @@ def help(cmd=None): cmdlist.append((name, module.__desc__)) longest_cmd_len = max([len(name) for name,desc in cmdlist]) ret = ["Bugs Everywhere - Distributed bug tracking", - "", - "usage: be [command] [command_options ...] [command_args ...]", - "or: be help", - "or: be help [command]", - "", - "Supported commands"] + "", "Supported commands"] for name, desc in cmdlist: numExtraSpaces = longest_cmd_len-len(name) ret.append("be %s%*s %s" % (name, numExtraSpaces, "", desc)) - - return "\n".join(ret) + ret.extend(["", "Run", " be help [command]", "for more information."]) + longhelp = "\n".join(ret) + if parser == None: + return longhelp + return parser.help_str() + "\n" + longhelp def completions(cmd): parser = get_command(cmd).get_parser() @@ -106,6 +107,13 @@ def raise_get_help(option, opt, value, parser): def raise_get_completions(option, opt, value, parser): print "got completion arg" + if hasattr(parser, "command") and parser.command == "be": + comps = [] + for command, module in iter_commands(): + comps.append(command) + for opt in parser.option_list: + comps.append(opt.get_opt_string()) + raise GetCompletions(comps) raise GetCompletions(completions(sys.argv[1])) class CmdOptionParser(optparse.OptionParser): @@ -141,7 +149,7 @@ def option_value_pairs(options, parser): def default_complete(options, args, parser, bugid_args={}): """ - A dud complete implementation for becommands to that the + A dud complete implementation for becommands so that the --complete argument doesn't cause any problems. Use this until you've set up a command-specific complete function. @@ -149,15 +157,25 @@ def default_complete(options, args, parser, bugid_args={}): arguments taking bug shortnames and the values are functions for filtering, since that's a common enough operation. e.g. for "be open [options] BUGID" - bugid_args = {0: lambda bug : bug.active == False} + bugid_args = {0: lambda bug : bug.active == False} + A positional argument of -1 specifies all remaining arguments + (e.g in the case of "be show BUGID BUGID ..."). """ for option,value in option_value_pairs(options, parser): if value == "--complete": raise cmdutil.GetCompletions() + if len(bugid_args.keys()) > 0: + max_pos_arg = max(bugid_args.keys()) + else: + max_pos_arg = -1 for pos,value in enumerate(args): if value == "--complete": + filter = None if pos in bugid_args: filter = bugid_args[pos] + if pos > max_pos_arg and -1 in bugid_args: + filter = bugid_args[-1] + if filter != None: bugshortnames = [] try: bd = bugdir.BugDir(from_disk=True, @@ -170,6 +188,13 @@ def default_complete(options, args, parser, bugid_args={}): raise GetCompletions(bugshortnames) raise GetCompletions() +def complete_path(path): + """List possible path completions for path.""" + comps = glob.glob(path+"*") + glob.glob(path+"/*") + if len(comps) == 1 and os.path.isdir(comps[0]): + comps.extend(glob.glob(comps[0]+"/*")) + return comps + def underlined(instring): """Produces a version of a string that is underlined with '=' diff --git a/libbe/comment.py b/libbe/comment.py index 9d4f744..68deaf3 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -2,7 +2,6 @@ # Copyright (C) 2008-2009 Chris Ball <cjb@laptop.org> # Thomas Habets <thomas@habets.pp.se> # W. Trevor King <wking@drexel.edu> -# <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 @@ -18,12 +17,17 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA -import email.mime.base, email.encoders +import base64 import os import os.path +import sys import time +import types +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree import xml.sax.saxutils -import textwrap import doctest from beuuid import uuid_gen @@ -43,16 +47,33 @@ class InvalidShortname(KeyError): self.shortname = shortname self.shortnames = shortnames +class InvalidXML(ValueError): + def __init__(self, element, comment): + msg = "Invalid comment xml: %s\n %s\n" \ + % (comment, ElementTree.tostring(element)) + ValueError.__init__(self, msg) + self.element = element + self.comment = comment + +class MissingReference(ValueError): + def __init__(self, comment): + msg = "Missing reference to %s" % (comment.in_reply_to) + ValueError.__init__(self, msg) + self.reference = comment.in_reply_to + self.comment = comment INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!" -def _list_to_root(comments, bug): +def list_to_root(comments, bug, root=None, + ignore_missing_references=False): """ - Convert a raw list of comments to single (dummy) root comment. We - use a dummy root comment, because there can be several comment - threads rooted on the same parent bug. To simplify comment - interaction, we condense these threads into a single thread with a - Comment dummy root. + Convert a raw list of comments to single root comment. We use a + dummy root comment by default, because there can be several + comment threads rooted on the same parent bug. To simplify + comment interaction, we condense these threads into a single + thread with a Comment dummy root. Can also be used to append + a list of subcomments to a non-dummy root comment, so long as + all the new comments are descendants of the root comment. No Comment method should use the dummy comment. """ @@ -61,17 +82,34 @@ def _list_to_root(comments, bug): for comment in comments: assert comment.uuid != None uuid_map[comment.uuid] = comment + for comment in comments: + if comment.alt_id != None and comment.alt_id not in uuid_map: + uuid_map[comment.alt_id] = comment + if root == None: + root = Comment(bug, uuid=INVALID_UUID) + else: + uuid_map[root.uuid] = root for comm in comments: + if comm.in_reply_to == INVALID_UUID: + comm.in_reply_to = None rep = comm.in_reply_to - if rep == None or rep == settings_object.EMPTY or rep == bug.uuid: + if rep == None or rep == bug.uuid: root_comments.append(comm) else: parentUUID = comm.in_reply_to - parent = uuid_map[parentUUID] - parent.add_reply(comm) - dummy_root = Comment(bug, uuid=INVALID_UUID) - dummy_root.extend(root_comments) - return dummy_root + try: + parent = uuid_map[parentUUID] + parent.add_reply(comm) + except KeyError, e: + if ignore_missing_references == True: + print >> sys.stderr, \ + "Ignoring missing reference to %s" % parentUUID + comm.in_reply_to = None + root_comments.append(comm) + else: + raise MissingReference(comm) + root.extend(root_comments) + return root def loadComments(bug, load_full=False): """ @@ -90,7 +128,7 @@ def loadComments(bug, load_full=False): comm.load_settings() dummy = comm.body # force the body to load comments.append(comm) - return _list_to_root(comments, bug) + return list_to_root(comments, bug) def saveComments(bug): path = bug.get_path("comments") @@ -122,6 +160,10 @@ class Comment(Tree, settings_object.SavedSettingsObject): kwargs["required_saved_properties"]=required_saved_properties return settings_object.versioned_property(**kwargs) + @_versioned_property(name="Alt-id", + doc="Alternate ID for linking imported comments. Internally comments are linked (via In-reply-to) to the parent's UUID. However, these UUIDs are generated internally, so Alt-id is provided as a user-controlled linking target.") + def alt_id(): return {} + @_versioned_property(name="From", doc="The author of the comment") def From(): return {} @@ -217,7 +259,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): def _setting_attr_string(self, setting): value = getattr(self, setting) - if value == settings_object.EMPTY: + if value in [None, settings_object.EMPTY]: return "" else: return str(value) @@ -248,8 +290,9 @@ class Comment(Tree, settings_object.SavedSettingsObject): msg = email.mime.base.MIMEBase(maintype, subtype) msg.set_payload(self.body or "") email.encoders.encode_base64(msg) - body = msg.as_string() + body = base64.encodestring(self.body or "") info = [("uuid", self.uuid), + ("alt-id", self.alt_id), ("short-name", shortname), ("in-reply-to", self.in_reply_to), ("from", self._setting_attr_string("From")), @@ -258,13 +301,77 @@ class Comment(Tree, settings_object.SavedSettingsObject): ("body", body)] lines = ["<comment>"] for (k,v) in info: - if v not in [settings_object.EMPTY, None]: + if v != None: lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k)) lines.append("</comment>") istring = ' '*indent sep = '\n' + istring return istring + sep.join(lines).rstrip('\n') + def from_xml(self, xml_string, verbose=True): + """ + Note: If alt-id is not given, translates any <uuid> fields to + <alt-id> fields. + >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") + >>> commA.uuid = "0123" + >>> commA.time_string = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> xml = commA.xml(shortname="com-1") + >>> commB = Comment() + >>> commB.from_xml(xml) + >>> attrs=['uuid','alt_id','in_reply_to','From','time_string','content_type','body'] + >>> for attr in attrs: # doctest: +ELLIPSIS + ... if getattr(commB, attr) != getattr(commA, attr): + ... estr = "Mismatch on %s: '%s' should be '%s'" + ... args = (attr, getattr(commB, attr), getattr(commA, attr)) + ... print estr % args + Mismatch on uuid: '...' should be '0123' + Mismatch on alt_id: '0123' should be 'None' + >>> print commB.alt_id + 0123 + >>> commA.From + >>> commB.From + """ + if type(xml_string) == types.UnicodeType: + xml_string = xml_string.strip().encode("unicode_escape") + comment = ElementTree.XML(xml_string) + if comment.tag != "comment": + raise InvalidXML(comment, "root element must be <comment>") + tags=['uuid','alt-id','in-reply-to','from','date','content-type','body'] + uuid = None + body = None + for child in comment.getchildren(): + if child.tag == "short-name": + pass + elif child.tag in tags: + if child.text == None or len(child.text) == 0: + text = settings_object.EMPTY + else: + text = xml.sax.saxutils.unescape(child.text) + text = unicode(text).decode("unicode_escape").strip() + if child.tag == "uuid": + uuid = text + continue # don't set the bug's uuid tag. + if child.tag == "body": + body = text + continue # don't set the bug's body yet. + elif child.tag == 'from': + attr_name = "From" + elif child.tag == 'date': + attr_name = 'time_string' + else: + attr_name = child.tag.replace('-','_') + setattr(self, attr_name, text) + elif verbose == True: + print >> sys.stderr, "Ignoring unknown tag %s in %s" \ + % (child.tag, comment.tag) + if self.alt_id == None and uuid not in [None, self.uuid]: + self.alt_id = uuid + if body != None: + if self.content_type.startswith("text/"): + self.body = body+"\n" # restore trailing newline + else: + self.body = base64.decodestring(body) + def string(self, indent=0, shortname=None): """ >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") @@ -287,13 +394,10 @@ class Comment(Tree, settings_object.SavedSettingsObject): lines.append("From: %s" % (self._setting_attr_string("From"))) lines.append("Date: %s" % self.time_string) lines.append("") - #lines.append(textwrap.fill(self.body or "", - # width=(79-indent))) if self.content_type.startswith("text/"): lines.extend((self.body or "").splitlines()) else: lines.append("Content type %s not printable. Try XML output instead" % self.content_type) - # some comments shouldn't be wrapped... istring = ' '*indent sep = '\n' + istring @@ -335,9 +439,6 @@ class Comment(Tree, settings_object.SavedSettingsObject): def save(self): assert self.body != None, "Can't save blank comment" - #if self.in_reply_to == None: - # raise Exception, str(self)+'\n'+str(self.settings)+'\n'+str(self._settings_loaded) - #assert self.in_reply_to != None, "Comment must be a reply to something" self.save_settings() self._set_comment_body(new=self.body, force=True) @@ -362,7 +463,6 @@ class Comment(Tree, settings_object.SavedSettingsObject): """ reply = Comment(self.bug, body=body) self.add_reply(reply) - #raise Exception, "new reply added (%s),\n%s\n%s\n\t--%s--" % (body, self, reply, reply.in_reply_to) return reply def string_thread(self, string_method_name="string", name_map={}, diff --git a/libbe/config.py b/libbe/config.py index 7f600a5..fafb4f0 100644 --- a/libbe/config.py +++ b/libbe/config.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/diff.py b/libbe/diff.py index a349e14..13efc9f 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/editor.py b/libbe/editor.py index 6638ed9..4bab0fa 100644 --- a/libbe/editor.py +++ b/libbe/editor.py @@ -1,6 +1,5 @@ # Bugs Everywhere, a distributed bugtracker # Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/encoding.py b/libbe/encoding.py index bdac98e..84c360a 100644 --- a/libbe/encoding.py +++ b/libbe/encoding.py @@ -1,6 +1,5 @@ # Bugs Everywhere, a distributed bugtracker # Copyright (C) 2008-2009 W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/mapfile.py b/libbe/mapfile.py index 9ff2215..b183bfe 100644 --- a/libbe/mapfile.py +++ b/libbe/mapfile.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 @@ -68,9 +67,9 @@ def generate(map): assert(':' not in key) assert(len(key) > 0) except AssertionError: - raise IllegalKey(key.encode('string_escape')) + raise IllegalKey(unicode(key).encode('unicode_escape')) if "\n" in map[key]: - raise IllegalValue(map[key].encode('string_escape')) + raise IllegalValue(unicode(map[key]).encode('unicode_escape')) lines = [] for key in keys: diff --git a/libbe/plugin.py b/libbe/plugin.py index 3c7c753..a21ba91 100644 --- a/libbe/plugin.py +++ b/libbe/plugin.py @@ -1,7 +1,6 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # Marien Zwart <marienz@gentoo.org> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/rcs.py b/libbe/rcs.py index 2c416f4..844920a 100644 --- a/libbe/rcs.py +++ b/libbe/rcs.py @@ -3,7 +3,6 @@ # Ben Finney <ben+python@benfinney.id.au> # Chris Ball <cjb@laptop.org> # W. Trevor King <wking@drexel.edu> -# <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 diff --git a/libbe/settings_object.py b/libbe/settings_object.py index 7326d1b..1dadd0a 100644 --- a/libbe/settings_object.py +++ b/libbe/settings_object.py @@ -125,17 +125,17 @@ def versioned_property(name, doc, required_saved_properties.append(name) def decorator(funcs): fulldoc = doc - if default != None: + if default != None or generator == None: defaulting = defaulting_property(default=default, null=EMPTY, default_mutable=mutable) - fulldoc += "\n\nThis property defaults to %s" % default + fulldoc += "\n\nThis property defaults to %s." % default if generator != None: cached = cached_property(generator=generator, initVal=EMPTY, mutable=mutable) - fulldoc += "\n\nThis property is generated with %s" % generator + fulldoc += "\n\nThis property is generated with %s." % generator if check_fn != None: fn_checked = fn_checked_property(value_allowed_fn=check_fn) - fulldoc += "\n\nThis property is checked with %s" % check_fn + fulldoc += "\n\nThis property is checked with %s." % check_fn if allowed != None: checked = checked_property(allowed=allowed) fulldoc += "\n\nThe allowed values for this property are: %s." \ @@ -145,7 +145,7 @@ def versioned_property(name, doc, settings = settings_property(name=name, null=UNPRIMED) docp = doc_property(doc=fulldoc) deco = hooked(primed(settings(docp(funcs)))) - if default != None: + if default != None or generator == None: deco = defaulting(deco) if generator != None: deco = cached(deco) @@ -235,7 +235,7 @@ class SavedSettingsObjectTests(unittest.TestCase): # access missing setting self.failUnless(t._settings_loaded == False, t._settings_loaded) self.failUnless(len(t.settings) == 0, len(t.settings)) - self.failUnless(t.content_type == EMPTY, t.content_type) + self.failUnless(t.content_type == None, t.content_type) # accessing t.content_type triggers the priming, which runs # t._setup_saved_settings, which fills out t.settings with # EMPTY data. t._settings_loaded is still false though, since @@ -250,16 +250,16 @@ class SavedSettingsObjectTests(unittest.TestCase): self.failUnless(t._settings_loaded == True, t._settings_loaded) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) - self.failUnless(t.content_type == EMPTY, t.content_type) + self.failUnless(t.content_type == None, t.content_type) self.failUnless(len(t.settings) == 1, len(t.settings)) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) # now we set a value - t.content_type = None - self.failUnless(t.settings["Content-type"] == None, + t.content_type = 5 + self.failUnless(t.settings["Content-type"] == 5, t.settings["Content-type"]) - self.failUnless(t.content_type == None, t.content_type) - self.failUnless(t.settings["Content-type"] == None, + self.failUnless(t.content_type == 5, t.content_type) + self.failUnless(t.settings["Content-type"] == 5, t.settings["Content-type"]) # now we set another value t.content_type = "text/plain" @@ -273,7 +273,7 @@ class SavedSettingsObjectTests(unittest.TestCase): self.failUnless(t._settings_loaded == True, t._settings_loaded) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) - self.failUnless(t.content_type == EMPTY, t.content_type) + self.failUnless(t.content_type == None, t.content_type) self.failUnless(len(t.settings) == 1, len(t.settings)) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) @@ -376,7 +376,7 @@ class SavedSettingsObjectTests(unittest.TestCase): t.load_settings() self.failUnless(SAVES == [], SAVES) self.failUnless(t._settings_loaded == True, t._settings_loaded) - self.failUnless(t.list_type == EMPTY, t.list_type) + self.failUnless(t.list_type == None, t.list_type) self.failUnless(SAVES == [ "'None' -> '<class 'libbe.settings_object.EMPTY'>'" ], SAVES) diff --git a/libbe/utility.py b/libbe/utility.py index 8a0f318..e16b94a 100644 --- a/libbe/utility.py +++ b/libbe/utility.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King <wking@drexel.edu> -# <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 @@ -21,6 +20,7 @@ import os import shutil import tempfile import time +import types import doctest @@ -77,17 +77,34 @@ def time_to_str(time_val): return time.strftime(RFC_2822_TIME_FMT, time.gmtime(time_val)) def str_to_time(str_time): - """Convert an RFC 2822-fomatted string into a time falue. + """Convert an RFC 2822-fomatted string into a time value. >>> str_to_time("Thu, 01 Jan 1970 00:00:00 +0000") 0 >>> q = time.time() >>> str_to_time(time_to_str(q)) == int(q) True + >>> str_to_time("Thu, 01 Jan 1970 00:00:00 -1000") + 36000 """ - return calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT)) + timezone_str = str_time[-5:] + if timezone_str != "+0000": + str_time = str_time.replace(timezone_str, "+0000") + time_val = calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT)) + timesign = -int(timezone_str[0]+"1") # "+" -> time_val ahead of GMT + timezone_tuple = time.strptime(timezone_str[1:], "%H%M") + timezone = timezone_tuple.tm_hour*3600 + timezone_tuple.tm_min*60 + return time_val + timesign*timezone def handy_time(time_val): return time.strftime("%a, %d %b %Y %H:%M", time.localtime(time_val)) +def time_to_gmtime(str_time): + """Convert an RFC 2822-fomatted string to a GMT string. + >>> time_to_gmtime("Thu, 01 Jan 1970 00:00:00 -1000") + 'Thu, 01 Jan 1970 10:00:00 +0000' + """ + time_val = str_to_time(str_time) + return time_to_str(time_val) + suite = doctest.DocTestSuite() diff --git a/test_usage.sh b/test_usage.sh index b2e2cab..05832d3 100755 --- a/test_usage.sh +++ b/test_usage.sh @@ -124,7 +124,13 @@ BUGB=`echo "$OUT" | sed -n 's/Created bug with ID //p'` be comment $BUGB "Blissfully unaware of a similar bug" be merge $BUG $BUGB # join BUGB to BUG be show $BUG # show bug details & comments +# you can also export/import XML bugs/comments +OUT=`be new 'yet more fun'` +BUGC=`echo "$OUT" | sed -n 's/Created bug with ID //p'` +be comment $BUGC "The ants go marching..." +be show --xml $BUGC | be comment --xml ${BUG}:2 - be remove $BUG # decide that you don't like that bug after all + cd / rm -rf $TESTDIR diff --git a/xml/be-mbox-to-xml b/xml/be-mbox-to-xml new file mode 100755 index 0000000..9054cfd --- /dev/null +++ b/xml/be-mbox-to-xml @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# Copyright (C) 2009 W. Trevor King <wking@drexel.edu> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" +Convert an mbox into xml suitable for imput into be. + $ cat mbox | be-mbox-to-xml | be comment --xml <ID> - +mbox is a flat-file format, consisting of a series of messages. +Messages begin with a a From_ line, followed by RFC 822 email, +followed by a blank line. +""" + +import base64 +import email.utils +from libbe.encoding import get_encoding, set_IO_stream_encodings +from mailbox import mbox, Message # the mailbox people really want an on-disk copy +from time import asctime, gmtime +import types +from xml.sax import make_parser +from xml.sax.handler import ContentHandler +from xml.sax.saxutils import escape + +DEFAULT_ENCODING = get_encoding() +set_IO_stream_encodings(DEFAULT_ENCODING) + +def comment_message_to_xml(message, fields=None): + if fields == None: + fields = {} + new_fields = {} + new_fields[u'alt-id'] = message[u'message-id'] + new_fields[u'in-reply-to'] = message[u'in-reply-to'] + new_fields[u'from'] = message[u'from'] + new_fields[u'date'] = message[u'date'] + new_fields[u'content-type'] = message.get_content_type() + for k,v in new_fields.items(): + if v != None and type(v) != types.UnicodeType: + fields[k] = unicode(v, encoding=DEFAULT_ENCODING) + elif v == None and k in fields: + new_fields[k] = fields[k] + for k,v in fields.items(): + if k not in new_fields: + new_fields.k = fields[k] + fields = new_fields + + if message.is_multipart(): + ret = [] + alt_id = fields[u'alt-id'] + from_str = fields[u'from'] + date = fields[u'date'] + for m in message.walk(): + if m == message: + continue + fields[u'from'] = from_str + fields[u'date'] = date + if len(ret) >= 0: + fields.pop(u'alt-id') + fields[u'in-reply-to'] = alt_id + ret.append(comment_message_to_xml(m, fields)) + return u'\n'.join(ret) + + charset = message.get_content_charset(DEFAULT_ENCODING).lower() + #assert charset == DEFAULT_ENCODING.lower(), \ + # u"Unknown charset: %s" % charset + + encoding = message[u'content-transfer-encoding'].lower() + body = message.get_payload(decode=True) # attempt to decode + assert body != None, "Unable to decode?" + if fields[u'content-type'].startswith(u"text/"): + body = unicode(body, encoding=charset).rstrip(u'\n') + else: + body = base64.encode(body) + fields[u'body'] = body + lines = [u"<comment>"] + for tag,body in fields.items(): + if body != None: + ebody = escape(body) + lines.append(u" <%s>%s</%s>" % (tag, ebody, tag)) + lines.append(u"</comment>") + return u'\n'.join(lines) + +def main(mbox_filename): + mb = mbox(mbox_filename) + print u'<?xml version="1.0" encoding="%s" ?>' % DEFAULT_ENCODING + print u"<comment-list>" + for message in mb: + print comment_message_to_xml(message) + print u"</comment-list>" + + +if __name__ == "__main__": + import sys + main(sys.argv[1]) diff --git a/xml/be-xml-to-mbox b/xml/be-xml-to-mbox index 80db634..b74a33d 100755 --- a/xml/be-xml-to-mbox +++ b/xml/be-xml-to-mbox @@ -26,14 +26,16 @@ followed by a blank line. """ #from mailbox import mbox, Message # the mailbox people really want an on-disk copy +import codecs import email.utils -import types - from libbe.encoding import get_encoding, set_IO_stream_encodings from libbe.utility import str_to_time as rfc2822_to_gmtime_integer from time import asctime, gmtime -from xml.sax import make_parser -from xml.sax.handler import ContentHandler +import types +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree from xml.sax.saxutils import unescape @@ -83,7 +85,7 @@ class Bug (LimitedAttrDict): u"created", u"summary", u"comments", - u"extra_strings"] + u"extra-strings"] def print_to_mbox(self): name,addr = email.utils.parseaddr(self["creator"]) print "From %s %s" % (addr, rfc2822_to_asctime(self["created"])) @@ -96,28 +98,62 @@ class Bug (LimitedAttrDict): print "" print self["summary"] print "" - if len(self["extra_strings"]) > 0: + if "extra-strings" in self: print "extra strings:\n ", print '\n '.join(self["extra_strings"]) print "" - for comment in self["comments"]: - comment.print_to_mbox(self) + if "comments" in self: + for comment in self["comments"]: + comment.print_to_mbox(self) + def init_from_etree(self, element): + assert element.tag == "bug", element.tag + for field in element.getchildren(): + text = unescape(unicode(field.text).decode("unicode_escape").strip()) + if field.tag == "comment": + comm = Comment() + comm.init_from_etree(field) + if "comments" in self: + self["comments"].append(comm) + else: + self["comments"] = [comm] + elif field.tag == "extra-string": + if "extra-strings" in self: + self["extra-strings"].append(text) + else: + self["extra-strings"] = [text] + else: + self[field.tag] = text class Comment (LimitedAttrDict): _attrs = [u"uuid", + u"alt-id", u"short-name", u"in-reply-to", u"from", u"date", u"content-type", u"body"] - def print_to_mbox(self, bug): + def print_to_mbox(self, bug=None): + if bug == None: + bug = Bug() + bug[u"uuid"] = u"no-uuid" name,addr = email.utils.parseaddr(self["from"]) print "From %s %s" % (addr, rfc2822_to_asctime(self["date"])) - print "Message-ID: <%s@%s>" % (self["uuid"], DEFAULT_DOMAIN) + if "uuid" in self: id = self["uuid"] + elif "alt-id" in self: id = self["alt-id"] + else: id = None + if id != None: + print "Message-ID: <%s@%s>" % (id, DEFAULT_DOMAIN) print "Date: %s" % self["date"] print "From: %s" % self["from"] - print "Subject: %s: %s" % (self["short-name"], bug["summary"]) + subject = "" + if "short-name" in self: + subject += self["short-name"]+u": " + if "summary" in bug: + subject += bug["summary"] + else: + subject += u"no-subject" + print "Subject: %s" % subject if "in-reply-to" not in self.keys(): self["in-reply-to"] = bug["uuid"] print "In-Reply-To: <%s@%s>" % (self["in-reply-to"], DEFAULT_DOMAIN) @@ -129,72 +165,41 @@ class Comment (LimitedAttrDict): else: # content type and transfer encoding already in XML MIME output print self["body"] print "" + def init_from_etree(self, element): + assert element.tag == "comment", element.tag + for field in element.getchildren(): + text = unescape(unicode(field.text).decode("unicode_escape").strip()) + if field.tag == "body": + text+="\n" + self[field.tag] = text -class BE_list_handler (ContentHandler): - def __init__(self): - self.reset() - - def reset(self): - self.bug = None - self.comment = None - self.extra_strings = None - self.text_field = None - - def startElement(self, name, attributes): - if name == "bug": - assert self.bug == None, "Nested bugs?!" - assert self.comment == None - assert self.text_field == None - self.bug = Bug(comments=[], extra_strings=[]) - elif name == "comment": - assert self.bug != None, "<comment> not in <bug>?" - assert self.comment == None, "Nested comments?!" - assert self.text_field == None, "<comment> in text field %s?" % self.text_field - self.comment = Comment() - elif self.bug != None and self.comment == None: - # parse bug text field - self.text_field = name - self.text_data = "" - elif self.bug != None and self.comment != None: - # parse comment text field - self.text_field = name - self.text_data = "" - - def endElement(self, name): - if name == "bug": - assert self.bug != None, "Invalid XML?" - assert self.comment == None, "Invalid XML?" - assert self.text_field == None, "Invalid XML?" - self.bug.print_to_mbox() - self.bug = None - elif name == "comment": - assert self.bug != None, "<comment> not in <bug>?" - assert self.comment != None, "Invalid XML?" - assert self.text_field == None, "<comment> in text field %s?" % self.text_field - self.bug["comments"].append(self.comment) - # comments printed by bug.print_to_mbox() - self.comment = None - elif self.bug != None and self.comment == None: - # parse bug text field - if self.text_field == "extra-string": - self.bug["extra_strings"].append(unescape(self.text_data.strip())) - else: - self.bug[self.text_field] = unescape(self.text_data.strip()) - self.text_field = None - self.text_data = None - elif self.bug != None and self.comment != None: - # parse comment text field - self.comment[self.text_field] = unescape(self.text_data.strip()) - self.text_field = None - self.text_data = None - - def characters(self, data): - if self.text_field != None: - self.text_data += data +def print_to_mbox(element): + if element.tag == "bug": + b = Bug() + b.init_from_etree(element) + b.print_to_mbox() + elif element.tag == "comment": + c = Comment() + c.init_from_etree(element) + c.print_to_mbox() + elif element.tag in ["bugs", "bug-list"]: + for b_elt in element.getchildren(): + b = Bug() + b.init_from_etree(b_elt) + b.print_to_mbox() + elif element.tag in ["comments", "comment-list"]: + for c_elt in element.getchildren(): + c = Comment() + c.init_from_etree(c_elt) + c.print_to_mbox() if __name__ == "__main__": import sys - - parser = make_parser() - parser.setContentHandler(BE_list_handler()) - parser.parse(sys.stdin) + + if len(sys.argv) == 1: # no filename given, use stdin + xml_unicode = sys.stdin.read() + else: + xml_unicode = codecs.open(sys.argv[1], "r", DEFAULT_ENCODING).read() + xml_str = xml_unicode.encode("unicode_escape").replace(r"\n", "\n") + elist = ElementTree.XML(xml_str) + print_to_mbox(elist) |