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