Merge lp:~kfogel/launchpad/cc-script-new-world into lp:launchpad

Proposed by Karl Fogel
Status: Merged
Approved by: Jonathan Lange
Approved revision: not available
Merge reported by: Karl Fogel
Merged at revision: not available
Proposed branch: lp:~kfogel/launchpad/cc-script-new-world
Merge into: lp:launchpad
Diff against target: 513 lines (+274/-131)
1 file modified
utilities/community-contributions.py (+274/-131)
To merge this branch: bzr merge lp:~kfogel/launchpad/cc-script-new-world
Reviewer Review Type Date Requested Status
Jonathan Lange (community) Approve
Karl Fogel (community) Needs Resubmitting
Review via email: mp+19060@code.launchpad.net

Commit message

[r=jml][ui=none][bug=432742] Improve the community-contributions.py script: show non-Launchpad Canonical developers in the output, elide large commit blocks, clean up various utf-8 encoding issues (bug #432742), and add a '--draft-run' option.

To post a comment you must log in.
Revision history for this message
Karl Fogel (kfogel) wrote :

Various improvements to utilities/community-contributions.py. Namely:

  1. Show Canonical developers too (those not on the Launchpad team)
  2. Don't show huge commit blocks; just elide and link to the top rev.
  3. Finally clean up various utf-8 encoding issues (bug #432742)
  4. Add a '--draft-run' option for testing.

You can see the results here:

  https://dev.launchpad.net/Contributions/Draft

(Compare with https://dev.launchpad.net/Contributions for old output.)

Revision history for this message
Jonathan Lange (jml) wrote :
Download full text (18.4 KiB)

On Wed, Feb 10, 2010 at 10:49 PM, Karl Fogel <email address hidden> wrote:
> Karl Fogel has proposed merging lp:~kfogel/launchpad/cc-script-new-world into lp:launchpad/devel.
>
>    Requested reviews:
>    Jonathan Lange (jml)
>

Hey Karl,

The change looks great. Most of my comments on the code are related to
Python idioms.

If this were application code, I might push a bit harder on whether
the ExCon data structure is the best way of thinking about the
problem. As it is, it's not application code and I've run out of
neurons for the morning :)

>
> Various improvements to utilities/community-contributions.py.  Namely:
>
>  1. Show Canonical developers too (those not on the Launchpad team)
>  2. Don't show huge commit blocks; just elide and link to the top rev.
>  3. Finally clean up various utf-8 encoding issues (bug #432742)
>  4. Add a '--draft-run' option for testing.
>
> You can see the results here:
>
>  https://dev.launchpad.net/Contributions/Draft
>
> (Compare with https://dev.launchpad.net/Contributions for old output.)
>

The new output is much better.

> === modified file 'utilities/community-contributions.py'
> --- utilities/community-contributions.py        2010-02-08 14:38:31 +0000
> +++ utilities/community-contributions.py        2010-02-10 22:49:18 +0000
...
> @@ -56,85 +57,133 @@
>     sys.exit(1)
>
>
> -# While anyone with "@canonical.com" in their email address will be
> -# counted as a Canonical contributor, sometimes Canonical people
> -# submit from personal addresses, so we still need a list.
> +# The output contains two classes of contributors: people who don't
> +# work for Canonical at all, and people who do work for Canonical but
> +# not on the Launchpad team.

As wgrant hinted on IRC, it's worth being explicit about what we do
with Launchpad team alumnists (e.g. Carlos & Daniel Silverstone).

>  #
>  # XXX: Karl Fogel 2009-09-10 bug=513608: We should use launchpadlib
> -# to consult Launchpad itself to find out who's a Canonical developer.
> -known_canonical_devs = (
> -    u'Aaron Bentley',
> -    u'Abel Deuring',
> -    u'Adam Conrad',
> -    u'Andrew Bennetts',
> -    u'Anthony Lenton',
> -    u'Barry Warsaw',
> -    u'Brad Crittenden',
> -    u'Carlos Perello Marin',
> -    u'Carlos Perelló Marín',
> -    u'Celso Providelo',
> -    u'Christian Robottom Reis',
> -    u'Cody Somerville',
> -    u'Curtis Hovey',
> -    u'Dafydd Harries',
> -    u'Daniel Silverstone',
> -    u'Danilo Šegan',
> -    u'Данило Шеган',
> -    u'данило шеган',
> -    u'David Allouche',
> -    u'Deryck Hodge',
> -    u'Diogo Matsubara',
> -    u'Elliot Murphy',
> -    u'Francis J. Lacoste',
> -    u'Gabriel Neuman <email address hidden>',
> -    u'Gary Poster',
> -    u'Guilherme Salgado',
> -    u'Gustavo Niemeyer',
> -    u'Henning Eggers',
> -    u'Herb McNew',
> -    u'James Henstridge',
> -    u'Jelmer Vernooij',
> -    u'Jeroen Vermeulen',
> -    u'Jonathan Knowles',
> -    u'Jonathan Lange',
> -    u'Julian Edwards',
> -    u'Karl Fogel',
> -    u'Kees Cook',
> -    u'Launch Pad',
> -    u'Launchpad Developers',
> -    u'LaMont Jones',
> -    u'Leonard Richardson',
> -    u'Malcolm Cleaton',
> -    u'Maris Fogels',
> -    u'Martin...

Revision history for this message
Jonathan Lange (jml) :
review: Needs Fixing
Revision history for this message
Karl Fogel (kfogel) wrote :

Thanks for the thorough review, Jono. All comments addressed; please see the latest.

review: Needs Resubmitting
Revision history for this message
Jonathan Lange (jml) wrote :

Thanks Karl. Looks good to me.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'utilities/community-contributions.py'
2--- utilities/community-contributions.py 2010-02-08 14:38:31 +0000
3+++ utilities/community-contributions.py 2010-02-22 15:15:33 +0000
4@@ -18,9 +18,10 @@
5 have it, the error message will tell you where to get it).
6
7 Options:
8- -q Print no non-essential messages.
9- -h, --help Print this help.
10- --dry-run Don't update the wiki, just print the new wiki page to stdout.
11+ -q Print no non-essential messages.
12+ -h, --help Print this help.
13+ --dry-run Don't update the wiki, just print the new wiki page to stdout.
14+ --draft-run Update the wiki "/Draft" page instead of the real page.
15 """
16
17 # General notes:
18@@ -56,85 +57,160 @@
19 sys.exit(1)
20
21
22-# While anyone with "@canonical.com" in their email address will be
23-# counted as a Canonical contributor, sometimes Canonical people
24-# submit from personal addresses, so we still need a list.
25+def wiki_encode(x):
26+ """Encode a Unicode string for display on the wiki."""
27+ return x.encode('utf-8', 'xmlcharrefreplace')
28+
29+
30+
31+# The output contains two classes of contributors: people who don't
32+# work for Canonical at all, and people who do work for Canonical but
33+# not on the Launchpad team.
34+#
35+# People who used to work for Canonical on the Launchpad team are not
36+# shown in the output, since they don't help us from a "contributions
37+# from outside the team" perspective, so they are listed as known
38+# Canonical Launchpad developers even though they aren't actually on
39+# the team anymore. There may be a few former Canonicalites who
40+# didn't work on the Launchpad team but who still contributed to
41+# Launchpad; most of them would have done so before Launchpad was open
42+# sourced in July 2009, though, and since this script is really about
43+# showing things that have happened since Launchpad was open sourced,
44+# they may be listed as Launchpad team members anyway just to ensure
45+# they don't appear in the output.
46+#
47+# (As time goes on, that assumption will be less and less correct, of
48+# course, and eventually we may wish to do something about it. Also,
49+# there are some people, e.g. Jelmer Vernooij, who made contributions
50+# to Launchpad before working at Canonical, but who now work on the
51+# Launchpad team at Canonical. Ideally, each potentially listable
52+# contributor could have a set of roles, and a date range associated
53+# with each role... but that would be overkill for this script. That
54+# last 2% of correctness would cost way too much to achieve.)
55 #
56 # XXX: Karl Fogel 2009-09-10 bug=513608: We should use launchpadlib
57-# to consult Launchpad itself to find out who's a Canonical developer.
58-known_canonical_devs = (
59- u'Aaron Bentley',
60- u'Abel Deuring',
61- u'Adam Conrad',
62- u'Andrew Bennetts',
63- u'Anthony Lenton',
64- u'Barry Warsaw',
65- u'Brad Crittenden',
66- u'Carlos Perello Marin',
67- u'Carlos Perelló Marín',
68- u'Celso Providelo',
69- u'Christian Robottom Reis',
70- u'Cody Somerville',
71- u'Curtis Hovey',
72- u'Dafydd Harries',
73- u'Daniel Silverstone',
74- u'Danilo Šegan',
75- u'Данило Шеган',
76- u'данило шеган',
77- u'David Allouche',
78- u'Deryck Hodge',
79- u'Diogo Matsubara',
80- u'Elliot Murphy',
81- u'Francis J. Lacoste',
82- u'Gabriel Neuman gneuman@async.com',
83- u'Gary Poster',
84- u'Guilherme Salgado',
85- u'Gustavo Niemeyer',
86- u'Henning Eggers',
87- u'Herb McNew',
88- u'James Henstridge',
89- u'Jelmer Vernooij',
90- u'Jeroen Vermeulen',
91- u'Jonathan Knowles',
92- u'Jonathan Lange',
93- u'Julian Edwards',
94- u'Karl Fogel',
95- u'Kees Cook',
96- u'Launch Pad',
97- u'Launchpad Developers',
98- u'LaMont Jones',
99- u'Leonard Richardson',
100- u'Malcolm Cleaton',
101- u'Maris Fogels',
102- u'Martin Albisetti',
103- u'Martin Pool',
104- u'Matt Zimmerman',
105- u'Matthew Revell',
106- u'Michael Hudson',
107- u'Michael Nelson',
108- u'Muharem Hrnjadovic',
109- u'Patch Queue Manager',
110- u'Paul Hummer',
111- u'Robert Collins',
112- u'Sidnei',
113- u'Sidnei da Silva',
114- u'Steve Kowalik',
115- u'Steve McInerney',
116- u'Stuart Bishop',
117- u'Tom Berger',
118- u'david',
119- u'jml@mumak.net',
120- u'kiko@beetle',
121- )
122+# to consult Launchpad itself to find out who's a Canonical developer,
123+# and within that who's a Launchpad developer.
124+
125+
126+# If a contributor's address contains this, then they are or were a
127+# Canonical developer -- maybe on the Launchpad team, maybe not.
128+CANONICAL_ADDR = wiki_encode(u" {_AT_} canonical.com")
129+
130+# People on the Canonical Launchpad team.
131+known_canonical_lp_devs = \
132+ [wiki_encode(x) for x in (u'Aaron Bentley',
133+ u'Abel Deuring',
134+ u'Andrew Bennetts',
135+ u'Barry Warsaw',
136+ u'Bjorn Tillenius',
137+ u'Björn Tillenius',
138+ u'Brad Crittenden',
139+ u'Brian Fromme',
140+ u'Carlos Perello Marin',
141+ u'Carlos Perelló Marín',
142+ u'Celso Providelo',
143+ u'Christian Reis',
144+ u'kiko {_AT_} beetle',
145+ u'Curtis Hovey',
146+ u'Dafydd Harries',
147+ u'Danilo Šegan',
148+ u'Данило Шеган',
149+ u'данило шеган',
150+ u'Daniel Silverstone',
151+ u'David Allouche',
152+ u'Deryck Hodge',
153+ u'Diogo Matsubara',
154+ u'Edwin Grubbs',
155+ u'Francis Lacoste',
156+ u'Francis J. Lacoste',
157+ u'Gary Poster',
158+ u'Gavin Panella',
159+ u'Graham Binns',
160+ u'Guilherme Salgado',
161+ u'Henning Eggers',
162+ u'James Henstridge',
163+ u'Jelmer Vernooij',
164+ u'Jeroen Vermeulen',
165+ u'Jeroen T. Vermeulen',
166+ u'Joey Stanford',
167+ u'Jonathan Lange',
168+ u'jml {_AT_} canonical.com',
169+ u'jml {_AT_} mumak.net',
170+ u'Jonathan Knowles',
171+ u'Julian Edwards',
172+ u'Karl Fogel',
173+ u'Launchpad APA',
174+ u'Launchpad Patch Queue Manager',
175+ u'Launchpad PQM Bot',
176+ u'Leonard Richardson',
177+ u'Malcolm Cleaton',
178+ u'Maris Fogels',
179+ u'Mark Shuttleworth',
180+ u'Martin Albisetti',
181+ u'Martin Pool',
182+ u'Matt Zimmerman',
183+ u'Matthew Paul Thomas',
184+ u'Matthew Revell',
185+ u'matthew.revell {_AT_} canonical.com',
186+ u'Michael Hudson',
187+ u'Michael Nelson',
188+ u'Muharem Hrnjadovic',
189+ u'muharem {_AT_} canonical.com',
190+ u'Paul Hummer',
191+ u'Robert Collins',
192+ u'Stuart Bishop',
193+ u'Steve McInerney',
194+ u'<steve {_AT_} stedee.id.au>',
195+ u'Tom Haddon',
196+ u'Tim Penhey',
197+ u'Tom Berger',
198+ u'Ursula Junque',
199+ )]
200+
201+# People known to work for Canonical but not on the Launchpad team.
202+# Anyone with "@canonical.com" in their email address is considered to
203+# work for Canonical, but some people occasionally submit changes from
204+# their personal email addresses; this list contains people known to
205+# do that, so we can treat them appropriately in the output.
206+known_canonical_non_lp_devs = \
207+ [wiki_encode(x) for x in (u'Adam Conrad',
208+ u'Andrew Bennetts',
209+ u'Anthony Lenton',
210+ u'Cody Somerville',
211+ u'Cody A.W. Somerville',
212+ u'David Murphy',
213+ u'Elliot Murphy',
214+ u'Gabriel Neuman gneuman {_AT_} async.com',
215+ u'Gustavo Niemeyer',
216+ u'James Henstridge',
217+ u'John Lenton',
218+ u'Kees Cook',
219+ u'LaMont Jones',
220+ u'Martin Pool',
221+ u'Matt Zimmerman',
222+ u'Michael Casadevall',
223+ u'Michael Vogt',
224+ u'Sidnei da Silva',
225+ u'Steve Kowalik',
226+ )]
227
228 # Some people have made commits using various names and/or email
229 # addresses, so this map will be used to merge them accordingly.
230-merge_names_map = {
231- u'Jamal Fanaian <jfanaian@gmail.com>':
232- u'Jamal Fanaian <jamal.fanaian@gmail.com>',
233- u'Jamal Fanaian <jamal@jfvm1>':
234- u'Jamal Fanaian <jamal.fanaian@gmail.com>',
235- }
236+# The map is initialized from this list of pairs, where each pair is
237+# of the form (CONTRIBUTOR_AS_SEEN, UNIFYING_IDENTITY_FOR_CONTRIBUTOR).
238+merge_names_pairs = (
239+ (u'Jamal Fanaian <jfanaian {_AT_} gmail.com>',
240+ u'Jamal Fanaian <jamal.fanaian {_AT_} gmail.com>'),
241+ (u'Jamal Fanaian <jamal {_AT_} jfvm1>',
242+ u'Jamal Fanaian <jamal.fanaian {_AT_} gmail.com>'),
243+ (u'LaMont Jones <lamont {_AT_} rover3>',
244+ u'LaMont Jones <lamont {_AT_} debian.org>'),
245+ )
246+# Then put it in dictionary form with the correct encodings.
247+merge_names_map = dict((wiki_encode(a), wiki_encode(b))
248+ for a, b in merge_names_pairs)
249+
250
251 class ContainerRevision():
252 """A wrapper for a top-level LogRevision containing child LogRevisions."""
253@@ -187,14 +263,22 @@
254 # will give you some information about it before you click
255 # (because a rev id often identifies the committer).
256 rev_id_url = rev_url_base + rev_id
257+
258+ if len(self.contained_revs) <= 10:
259+ commits_block = "\n ".join(
260+ ["[[%s|%s]]" % (rev_url_base + lr.rev.revision_id, lr.revno)
261+ for lr in self.contained_revs])
262+ else:
263+ commits_block = ("''see the [[%s|full revision]] for details "
264+ "(it contains %d commits)''"
265+ % (rev_id_url, len(self.contained_revs)))
266+
267 text = [
268 " * [[%s|r%s]] -- %s\n" % (rev_id_url, self.top_rev.revno,
269 date_str),
270 " {{{\n%s\n}}}\n" % message,
271 " '''Commits:'''\n ",
272- "\n ".join(["[[%s|%s]]" % (rev_url_base + lr.rev.revision_id,
273- lr.revno)
274- for lr in self.contained_revs]),
275+ commits_block,
276 "\n",
277 ]
278 return ''.join(text)
279@@ -202,14 +286,17 @@
280
281 # "ExternalContributor" is too much to type, so I guess we'll just use this.
282 class ExCon():
283- """A contributor to Launchpad from outside Canonical."""
284-
285- def __init__(self, name):
286- """Create a new external contributor named NAME. NAME is usually
287- e.g. "Veronica Random <veronica@example.com>", but any "@"-sign
288- will be disguised in the new object."""
289-
290- self.name = name.replace("@", " {_AT_} ")
291+ """A contributor to Launchpad from outside Canonical's Launchpad team."""
292+
293+ def __init__(self, name, is_canonical=False):
294+ """Create a new external contributor named 'name'.
295+
296+ If 'is_canonical' is True, then this is a contributor from
297+ within Canonical, but not on the Launchpad team at Canonical.
298+ 'name' is something like "Veronica Random <vr {_AT_} example.com>".
299+ """
300+ self.name = name
301+ self.is_canonical = is_canonical
302 # If name is "Veronica Random <veronica {_AT_} example.com>",
303 # then name_as_anchor will be "veronica_random".
304 self.name_as_anchor = \
305@@ -231,10 +318,13 @@
306 def show_contributions(self):
307 "Return a wikified string showing this contributor's contributions."
308 plural = "s"
309+ name = self.name
310+ if self.is_canonical:
311+ name = name + " (Canonical developer)"
312 if self.num_landings() == 1:
313 plural = ""
314 text = [
315- "== %s ==\n\n" % self.name,
316+ "=== %s ===\n\n" % name,
317 "''%d top-level landing%s:''\n\n" % (self.num_landings(), plural),
318 ''.join(map(str, sorted(self._landings,
319 key=lambda x: x.top_rev.revno,
320@@ -253,29 +343,47 @@
321 the bzr logs, i.e., with email address undisguised) to ExCon objects.
322 """
323 ex_cons_this_rev = []
324- for a in authors:
325- known = False
326- for name_fragment in known_canonical_devs:
327- if u"@canonical.com" in a or name_fragment in a:
328- known = True
329+ for author in authors:
330+ known_canonical_lp_dev = False
331+ known_canonical_non_lp_dev = False
332+ # The authors we list in the source code have their addresses
333+ # disguised (since this source code is public). We must
334+ # disguise the ones coming from the Bazaar logs in the same way,
335+ # so string matches will work.
336+ author = wiki_encode(author)
337+ author = author.replace("@", " {_AT_} ")
338+
339+ # If someone works/worked for Canonical on the Launchpad team,
340+ # then skip them -- we don't want to show them in the output.
341+ for name_fragment in known_canonical_lp_devs:
342+ if name_fragment in author:
343+ known_canonical_lp_dev = True
344 break
345- if not known:
346- # Use the merge names map to merge contributions from the same
347- # person using alternate names and/or emails.
348- if a in merge_names_map:
349- a = merge_names_map[a]
350-
351- ### There's a variant of the Singleton pattern that could be
352- ### used for this, whereby instantiating an ExCon object would
353- ### just get back an existing object if such has already been
354- ### instantiated for this name. But that would make this code
355- ### non-reentrant, and that's just not cool.
356- if a in all_ex_cons:
357- ec = all_ex_cons[a]
358- else:
359- ec = ExCon(a)
360- all_ex_cons[a] = ec
361- ex_cons_this_rev.append(ec)
362+ if known_canonical_lp_dev:
363+ continue
364+
365+ # Use the merge names map to merge contributions from the same
366+ # person using alternate names and/or emails.
367+ author = merge_names_map.get(author, author)
368+
369+ if CANONICAL_ADDR in author:
370+ known_canonical_non_lp_dev = True
371+ else:
372+ for name_fragment in known_canonical_non_lp_devs:
373+ if name_fragment in author:
374+ known_canonical_non_lp_dev = True
375+ break
376+
377+ # There's a variant of the Singleton pattern that could be
378+ # used for this, whereby instantiating an ExCon object would
379+ # just get back an existing object if such has already been
380+ # instantiated for this name. But that would make this code
381+ # non-reentrant, and that's just not cool.
382+ ec = all_ex_cons.get(author, None)
383+ if ec is None:
384+ ec = ExCon(author, is_canonical=known_canonical_non_lp_dev)
385+ all_ex_cons[author] = ec
386+ ex_cons_this_rev.append(ec)
387 return ex_cons_this_rev
388
389
390@@ -296,26 +404,55 @@
391 # top-level rev.
392 current_top_level_rev = None
393
394+ def _toc(self, contributors):
395+ toc_text = []
396+ for val in contributors:
397+ plural = "s"
398+ if val.num_landings() == 1:
399+ plural = ""
400+ toc_text.extend(" 1. [[#%s|%s]] ''(%d top-level landing%s)''\n"
401+ % (val.name_as_anchor, val.name,
402+ val.num_landings(), plural))
403+ return toc_text
404+
405 def result(self):
406 "Return a moin-wiki-syntax string with TOC followed by contributions."
407+
408+ # Divide contributors into non-Canonical and Canonical.
409+ non_canonical_contributors = [x for x in self.all_ex_cons.values()
410+ if not x.is_canonical]
411+ canonical_contributors = [x for x in self.all_ex_cons.values()
412+ if x.is_canonical]
413+ # Sort them.
414+ non_canonical_contributors = sorted(non_canonical_contributors,
415+ key=lambda x: x.num_landings(),
416+ reverse=True)
417+ canonical_contributors = sorted(canonical_contributors,
418+ key=lambda x: x.num_landings(),
419+ reverse=True)
420+
421 text = [
422 "-----\n\n",
423- "= Who =\n\n",
424+ "= Who =\n\n"
425+ "== Contributors (from outside Canonical) ==\n\n",
426 ]
427- sorted_contributors = sorted(self.all_ex_cons.values(),
428- key=lambda x: x.num_landings(),
429- reverse=True)
430- for val in sorted_contributors:
431- plural = "s"
432- if val.num_landings() == 1:
433- plural = ""
434- text.extend(" 1. [[#%s|%s]] ''(%d top-level landing%s)''\n"
435- % (val.name_as_anchor, val.name,
436- val.num_landings(), plural))
437+ text.extend(self._toc(non_canonical_contributors))
438+ text.extend([
439+ "== Contributors (from Canonical, but outside "
440+ "the Launchpad team) ==\n\n",
441+ ])
442+ text.extend(self._toc(canonical_contributors))
443 text.extend(["\n-----\n\n",
444 "= What =\n\n",
445- ])
446- for val in sorted_contributors:
447+ "== Contributions (from outside Canonical) ==\n\n",
448+ ])
449+ for val in non_canonical_contributors:
450+ text.extend("<<Anchor(%s)>>\n" % val.name_as_anchor)
451+ text.extend(val.show_contributions())
452+ text.extend(["== Contributions (from Canonical, but outside "
453+ "the Launchpad team) ==\n\n",
454+ ])
455+ for val in canonical_contributors:
456 text.extend("<<Anchor(%s)>>\n" % val.name_as_anchor)
457 text.extend(val.show_contributions())
458 return ''.join(text)
459@@ -343,11 +480,14 @@
460 print __doc__
461
462
463+# Use backslashes to suppress newlines because this is wiki syntax,
464+# not HTML, so newlines would be rendered as line breaks.
465 page_intro = """This page shows contributions to Launchpad from \
466-outside Canonical. It only lists changes that have landed in the \
467-Launchpad ''devel'' tree, so changes that land in ''db-devel'' first \
468-may take a while to show up (see the [[Trunk|trunk explanation]] for \
469-more).
470+developers not on the Launchpad team at Canonical.
471+
472+It only lists changes that have landed in the Launchpad ''devel'' \
473+tree, so changes that land in ''db-devel'' first may take a while to \
474+show up (see the [[Trunk|trunk explanation]] for more).
475
476 ~-''Note for maintainers: this page is updated every 10 minutes by a \
477 cron job running as kfogel on devpad (though if there are no new \
478@@ -363,13 +503,15 @@
479 target = None
480 dry_run = False
481
482+ wiki_dest = "https://dev.launchpad.net/Contributions"
483+
484 if len(sys.argv) < 2:
485 usage()
486 sys.exit(1)
487
488 try:
489 opts, args = getopt.getopt(sys.argv[1:], '?hq',
490- ['help', 'usage', 'dry-run'])
491+ ['help', 'usage', 'dry-run', 'draft-run'])
492 except getopt.GetoptError, e:
493 sys.stderr.write("ERROR: " + str(e) + '\n\n')
494 usage()
495@@ -383,6 +525,8 @@
496 quiet = True
497 elif opt == '--dry-run':
498 dry_run = True
499+ elif opt == '--draft-run':
500+ wiki_dest += "/Draft"
501
502 # Ensure we have the arguments we need.
503 if len(args) < 1:
504@@ -419,8 +563,7 @@
505 if not quiet:
506 print "Updating wiki..."
507 # Not sure how to get editmoin to obey our quiet flag.
508- editshortcut("https://dev.launchpad.net/Contributions",
509- editfile_func=update_if_modified)
510+ editshortcut(wiki_dest, editfile_func=update_if_modified)
511 if not quiet:
512 print "Done updating wiki."
513 else: