Merge lp:~stgraber/upstart/upstart-initctl2dot-python3 into lp:upstart

Proposed by Stéphane Graber
Status: Merged
Merged at revision: 1392
Proposed branch: lp:~stgraber/upstart/upstart-initctl2dot-python3
Merge into: lp:upstart
Diff against target: 917 lines (+386/-436)
1 file modified
scripts/initctl2dot.py (+386/-436)
To merge this branch: bzr merge lp:~stgraber/upstart/upstart-initctl2dot-python3
Reviewer Review Type Date Requested Status
James Hunt Approve
Barry Warsaw (community) Approve
Review via email: mp+136721@code.launchpad.net

Description of the change

This branch updates initctl2dot to work with both python2 and python3.
The tested versions were python2.7 and python3.3.

The main changes are:
 - Fixing a bunch of errors spotted by pyflakes.
 - Added universal_newlines=True to the subproces call
 - Stop using string.split, instead split use <str>.split
 - Re-indent the code, split lines, ... to please pep8

I also fixed a small bug where if a job name contained a dot, the .dot file
would be invalid. I'm simply replacing dots by underscores which appears to do
the trick.

The code should now be PEP-8 clean and the output appears to work as expected
when fed to graphviz.

To post a comment you must log in.
Revision history for this message
Steve Langasek (vorlon) wrote :

On Wed, Nov 28, 2012 at 05:10:25PM -0000, Stéphane Graber wrote:
> === modified file 'scripts/initctl2dot.py'
> --- scripts/initctl2dot.py 2011-06-06 12:52:08 +0000
> +++ scripts/initctl2dot.py 2012-11-28 17:09:22 +0000
> @@ -50,522 +50,530 @@
> import re
> import fnmatch
> import os
> -from string import split
> import datetime
> from subprocess import (Popen, PIPE)
> from optparse import OptionParser

Shouldn't this also change the interpreter to /usr/bin/python3? I don't see
any reason why we would need this script to be bilingual, we ought to just
change it and go. If people want to use newer versions of upstart on OSes
that don't have python3, then they just won't be able to use this script...
but this is hardly a requirement anyway.

(Also, looking at this I notice we're shipping initctl2dot in /bin in the
Ubuntu package... that doesn't make much sense when the interpreter is in
/usr/bin.)

1396. By Stéphane Graber

Switch interpreter to python3 (code still works under python2)

Revision history for this message
Stéphane Graber (stgraber) wrote :

> On Wed, Nov 28, 2012 at 05:10:25PM -0000, Stéphane Graber wrote:
> > === modified file 'scripts/initctl2dot.py'
> > --- scripts/initctl2dot.py 2011-06-06 12:52:08 +0000
> > +++ scripts/initctl2dot.py 2012-11-28 17:09:22 +0000
> > @@ -50,522 +50,530 @@
> > import re
> > import fnmatch
> > import os
> > -from string import split
> > import datetime
> > from subprocess import (Popen, PIPE)
> > from optparse import OptionParser
>
> Shouldn't this also change the interpreter to /usr/bin/python3? I don't see
> any reason why we would need this script to be bilingual, we ought to just
> change it and go. If people want to use newer versions of upstart on OSes
> that don't have python3, then they just won't be able to use this script...
> but this is hardly a requirement anyway.

Oops, even though I named the branch -python3 I completely forgot to change the default interpreter to python3 :)
Done now.

> (Also, looking at this I notice we're shipping initctl2dot in /bin in the
> Ubuntu package... that doesn't make much sense when the interpreter is in
> /usr/bin.)

Good point, we should change the packaging to put the script in /usr/bin.

Revision history for this message
Barry Warsaw (barry) wrote :

Just a couple more suggestions, since why not.

* No parens are needed in `from subprocess import Popen, PIPE`
* How about switching to argparse instead of optparse?
* In header(), the global statement isn't necessary because you're not assigning to options
* I'd probably rewrite the concats in header() into a triple-quoted multiline string, with {options.foo} replacements
* Remove the global statement from footer()
* There should be some better way to get rid of the multiple concats in footer() too
* sanitize() seems pretty inefficient. maybe that doesn't matter for this script, but it might be better written with a re.sub() where `repl` is a function that knows the mappings
* Why does mk_node_name() even exist? ;)
* show_events(): remove global and rewrite the concats
* Remove the globals from show_events()
* Remove the global in show_jobs()
* Remove the globals in show_jobs()
* In show_jobs(), the `if not restrictions_list: return` is kind of unnecessary.
* None of the show_*() methods need their globals either (just keep at it for the rest of this file ;) - you only need globals if you're *assigning* to a global variable (not if just referencing it, or calling methods like .append() on it)
* read_data(): `for line in ifh:` is probably good enough
* Might want to use a context manager to handle ifh in read_data(). For Python 3.3, see http://docs.python.org/3/library/contextlib.html#contextlib.ExitStack
* line 430: extra parens

Probably other stuff to be cleaned up, but those are the major things that jump out at me. I can take a crack at it if you want.

review: Needs Fixing
1397. By Stéphane Graber

Cleanup subprocess import and use argparse instead of optparse

1398. By Stéphane Graber

Drop all unneeded global statements and do some extra code simplification.

1399. By Stéphane Graber

Remove some more globals and unused variable

1400. By Stéphane Graber

Simplify header and footer functions

1401. By Stéphane Graber

Fix indent

1402. By Stéphane Graber

Move the code replacing dots by underscores to the sanitise function

Revision history for this message
Stéphane Graber (stgraber) wrote :

> Just a couple more suggestions, since why not.
>
> * No parens are needed in `from subprocess import Popen, PIPE`

Done

> * How about switching to argparse instead of optparse?

Done

> * In header(), the global statement isn't necessary because you're not
> assigning to options

Done

> * I'd probably rewrite the concats in header() into a triple-quoted multiline
> string, with {options.foo} replacements

Done

> * Remove the global statement from footer()

Done

> * There should be some better way to get rid of the multiple concats in
> footer() too

Done. Pre-processed the ifs, then used a giant string with format, similar to header()

> * sanitize() seems pretty inefficient. maybe that doesn't matter for this
> script, but it might be better written with a re.sub() where `repl` is a
> function that knows the mappings

Kept it as-is for now as I'm not a fan of using regexps when not absolutely necessary :)

> * Why does mk_node_name() even exist? ;)

No good reason apparently, dropped.

> * show_events(): remove global and rewrite the concats

Done.

> * Remove the globals from show_events()

Done

> * Remove the global in show_jobs()

Done

> * Remove the globals in show_jobs()

Done

> * In show_jobs(), the `if not restrictions_list: return` is kind of
> unnecessary.

Right, at first read, I assumed that restrictions_list could be None, but rechecking the code, that's not actually the case, so iterating will always work and that if statement is unnecessary. Dropped.

> * None of the show_*() methods need their globals either (just keep at it for
> the rest of this file ;) - you only need globals if you're *assigning* to a
> global variable (not if just referencing it, or calling methods like .append()
> on it)

Done

> * read_data(): `for line in ifh:` is probably good enough

Agreed, done.

> * Might want to use a context manager to handle ifh in read_data(). For Python
> 3.3, see http://docs.python.org/3/library/contextlib.html#contextlib.ExitStack

I'd rather not depend on python 3.3. Some people may still want to use the fixed script on something slightly older, but that's good to know nevertheless.

> * line 430: extra parens

Fixed that one and another one a few lines further down.

> Probably other stuff to be cleaned up, but those are the major things that
> jump out at me. I can take a crack at it if you want.

As far as I can tell, the script still works with those changes and the output is similar.

Revision history for this message
Barry Warsaw (barry) wrote :

Much improved, thanks! Just a few more to go:

* Remove global jobs, events from read_data()
* Remove global jobs, cmd, default_color_* from main(), but keep global
  options and restrictions_list.

I'll go ahead and approve the merge on the above conditions.

Revision history for this message
Barry Warsaw (barry) :
review: Approve
1403. By Stéphane Graber

Remove last useless globals, fix .dot file syntax in footer, fix indentation of output file.

Revision history for this message
Stéphane Graber (stgraber) wrote :

Thanks for the comments. I removed those last few globals and fixed a mistake I did in the output file so that it's now valid. While I was at it, I also tweaked the code a bit so that the output file's indentation is correct.

Revision history for this message
James Hunt (jamesodhunt) wrote :

Thanks Stéphane. I found a new bug in testing (bug 1084985), but that is unrelated to the changes you've made, so merged.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'scripts/initctl2dot.py'
2--- scripts/initctl2dot.py 2011-06-06 12:52:08 +0000
3+++ scripts/initctl2dot.py 2012-11-29 16:35:44 +0000
4@@ -1,4 +1,4 @@
5-#!/usr/bin/python
6+#!/usr/bin/python3
7 # -*- coding: utf-8 -*-
8 #---------------------------------------------------------------------
9 #
10@@ -50,522 +50,472 @@
11 import re
12 import fnmatch
13 import os
14-from string import split
15 import datetime
16-from subprocess import (Popen, PIPE)
17-from optparse import OptionParser
18+from subprocess import Popen, PIPE
19+from argparse import ArgumentParser
20
21-jobs = {}
22+options = None
23+jobs = {}
24 events = {}
25 cmd = "initctl --system show-config -e"
26-script_name = os.path.basename(sys.argv[0])
27-
28-job_events = [ 'starting', 'started', 'stopping', 'stopped' ]
29+script_name = os.path.basename(sys.argv[0])
30
31 # list of jobs to restict output to
32 restrictions_list = []
33
34-default_color_emits = 'green'
35+default_color_emits = 'green'
36 default_color_start_on = 'blue'
37-default_color_stop_on = 'red'
38-default_color_event = 'thistle'
39-default_color_job = '#DCDCDC' # "Gainsboro"
40-default_color_text = 'black'
41-default_color_bg = 'white'
42+default_color_stop_on = 'red'
43+default_color_event = 'thistle'
44+default_color_job = '#DCDCDC' # "Gainsboro"
45+default_color_text = 'black'
46+default_color_bg = 'white'
47
48-default_outfile = 'upstart.dot'
49+default_outfile = 'upstart.dot'
50
51
52 def header(ofh):
53- global options
54-
55- str = "digraph upstart {\n"
56-
57- # make the default node an event to simplify glob code
58- str += " node [shape=\"diamond\", fontcolor=\"%s\", fillcolor=\"%s\", style=\"filled\"];\n" \
59- % (options.color_event_text, options.color_event)
60- str += " rankdir=LR;\n"
61- str += " overlap=false;\n"
62- str += " bgcolor=\"%s\";\n" % options.color_bg
63- str += " fontcolor=\"%s\";\n" % options.color_text
64-
65- ofh.write(str)
66+ ofh.write("""digraph upstart {{
67+ node [shape=\"diamond\", fontcolor=\"{options.color_event_text}\", """
68+ """fillcolor=\"{options.color_event}\", style=\"filled\"];
69+ rankdir=LR;
70+ overlap=false;
71+ bgcolor=\"{options.color_bg}\";
72+ fontcolor=\"{options.color_text}\";
73+""".format(options=options))
74
75
76 def footer(ofh):
77- global options
78-
79- epilog = "overlap=false;\n"
80- epilog += "label=\"Generated on %s by %s\\n" % \
81- (str(datetime.datetime.now()), script_name)
82-
83- if options.restrictions:
84- epilog += "(subset, "
85- else:
86- epilog += "("
87-
88- if options.infile:
89- epilog += "from file data).\\n"
90- else:
91- epilog += "from '%s' on host %s).\\n" % \
92- (cmd, os.uname()[1])
93-
94- epilog += "Boxes of color %s denote jobs.\\n" % options.color_job
95- epilog += "Solid diamonds of color %s denote events.\\n" % options.color_event
96- epilog += "Dotted diamonds denote 'glob' events.\\n"
97- epilog += "Emits denoted by %s lines.\\n" % options.color_emits
98- epilog += "Start on denoted by %s lines.\\n" % options.color_start_on
99- epilog += "Stop on denoted by %s lines.\\n" % options.color_stop_on
100- epilog += "\";\n"
101- epilog += "}\n"
102- ofh.write(epilog)
103+ if options.restrictions:
104+ details = "(subset, "
105+ else:
106+ details = "("
107+
108+ if options.infile:
109+ details += "from file data)."
110+ else:
111+ details += "from '%s' on host %s)." % (cmd, os.uname()[1])
112+
113+ ofh.write(" overlap=false;\n"
114+ " label=\"Generated on {datenow} by {script_name} {details}\\n"
115+ "Boxes of color {options.color_job} denote jobs.\\n"
116+ "Solid diamonds of color {options.color_event} denote events.\\n"
117+ "Dotted diamons denote 'glob' events.\\n"
118+ "Emits denoted by {options.color_emits} lines.\\n"
119+ "Start on denoted by {options.color_start_on} lines.\\n"
120+ "Stop on denoted by {options.color_stop_on} lines.\\n"
121+ "\";\n"
122+ "}}\n".format(options=options, datenow=datetime.datetime.now(),
123+ script_name=script_name, details=details))
124
125
126 # Map dash to underscore since graphviz node names cannot
127 # contain dashes. Also remove dollars and colons
128 def sanitise(s):
129- return s.replace('-', '_').replace('$', 'dollar_').replace('[', \
130- 'lbracket').replace(']', 'rbracket').replace('!', \
131- 'bang').replace(':', '_').replace('*', 'star').replace('?', 'question')
132+ return s.replace('-', '_').replace('$', 'dollar_') \
133+ .replace('[', 'lbracket').replace(']', 'rbracket') \
134+ .replace('!', 'bang').replace(':', 'colon').replace('*', 'star') \
135+ .replace('?', 'question').replace('.', '_')
136
137
138 # Convert a dollar in @name to a unique-ish new name, based on @job and
139 # return it. Used for very rudimentary instance handling.
140 def encode_dollar(job, name):
141- if name[0] == '$':
142- name = job + ':' + name
143- return name
144-
145-
146-def mk_node_name(name):
147- return sanitise(name)
148+ if name[0] == '$':
149+ name = job + ':' + name
150+ return name
151
152
153 # Jobs and events can have identical names, so prefix them to namespace
154 # them off.
155 def mk_job_node_name(name):
156- return mk_node_name('job_' + name)
157+ return sanitise('job_' + name)
158
159
160 def mk_event_node_name(name):
161- return mk_node_name('event_' + name)
162+ return sanitise('event_' + name)
163
164
165 def show_event(ofh, name):
166- global options
167- str = "%s [label=\"%s\", shape=diamond, fontcolor=\"%s\", fillcolor=\"%s\"," % \
168- (mk_event_node_name(name), name, options.color_event_text, options.color_event)
169+ str = " %s [label=\"%s\", shape=diamond, fontcolor=\"%s\", " \
170+ "fillcolor=\"%s\"," % (mk_event_node_name(name), name,
171+ options.color_event_text, options.color_event)
172
173 if '*' in name:
174- str += " style=\"dotted\""
175+ str += " style=\"dotted\""
176 else:
177- str += " style=\"filled\""
178+ str += " style=\"filled\""
179
180 str += "];\n"
181
182 ofh.write(str)
183
184+
185 def show_events(ofh):
186- global events
187- global options
188- global restrictions_list
189-
190- events_to_show = []
191-
192- if restrictions_list:
193- for job in restrictions_list:
194-
195- # We want all events emitted by the jobs in the restrictions_list.
196- events_to_show += jobs[job]['emits']
197-
198- # We also want all events that jobs in restrictions_list start/stop
199- # on.
200- events_to_show += jobs[job]['start on']['event']
201- events_to_show += jobs[job]['stop on']['event']
202-
203- # We also want all events emitted by all jobs that jobs in the
204- # restrictions_list start/stop on. Finally, we want all events
205- # emmitted by those jobs in the restrictions_list that we
206- # start/stop on.
207- for j in jobs[job]['start on']['job']:
208- if jobs.has_key(j) and jobs[j].has_key('emits'):
209- events_to_show += jobs[j]['emits']
210-
211- for j in jobs[job]['stop on']['job']:
212- if jobs.has_key(j) and jobs[j].has_key('emits'):
213- events_to_show += jobs[j]['emits']
214- else:
215- events_to_show = events
216-
217- for e in events_to_show:
218- show_event(ofh, e)
219+ events_to_show = []
220+
221+ if restrictions_list:
222+ for job in restrictions_list:
223+
224+ # We want all events emitted by the jobs in the restrictions_list.
225+ events_to_show += jobs[job]['emits']
226+
227+ # We also want all events that jobs in restrictions_list start/stop
228+ # on.
229+ events_to_show += jobs[job]['start on']['event']
230+ events_to_show += jobs[job]['stop on']['event']
231+
232+ # We also want all events emitted by all jobs that jobs in the
233+ # restrictions_list start/stop on. Finally, we want all events
234+ # emmitted by those jobs in the restrictions_list that we
235+ # start/stop on.
236+ for j in jobs[job]['start on']['job']:
237+ if j in jobs and 'emits' in jobs[j]:
238+ events_to_show += jobs[j]['emits']
239+
240+ for j in jobs[job]['stop on']['job']:
241+ if j in jobs and 'emits' in jobs[j]:
242+ events_to_show += jobs[j]['emits']
243+ else:
244+ events_to_show = events
245+
246+ for e in events_to_show:
247+ show_event(ofh, e)
248
249
250 def show_job(ofh, name):
251- global options
252-
253- ofh.write("""
254- %s [shape=\"record\", label=\"<job> %s | { <start> start on | <stop> stop on }\", fontcolor=\"%s\", style=\"filled\", fillcolor=\"%s\"];
255- """ % (mk_job_node_name(name), name, options.color_job_text, options.color_job))
256+ ofh.write(" %s [shape=\"record\", label=\"<job> %s | { <start> start on |"
257+ " <stop> stop on }\", fontcolor=\"%s\", style=\"filled\", "
258+ " fillcolor=\"%s\"];\n" % (mk_job_node_name(name), name,
259+ options.color_job_text,
260+ options.color_job))
261
262
263 def show_jobs(ofh):
264- global jobs
265- global options
266- global restrictions_list
267-
268- if restrictions_list:
269- jobs_to_show = restrictions_list
270- else:
271- jobs_to_show = jobs
272-
273- for j in jobs_to_show:
274- show_job(ofh, j)
275- # add those jobs which are referenced by existing jobs, but which
276- # might not be available as .conf files. For example, plymouth.conf
277- # references gdm *or* kdm, but you are unlikely to have both
278- # installed.
279- for s in jobs[j]['start on']['job']:
280- if s not in jobs_to_show:
281- show_job(ofh, s)
282-
283- for s in jobs[j]['stop on']['job']:
284- if s not in jobs_to_show:
285- show_job(ofh, s)
286-
287- if not restrictions_list:
288- return
289-
290- # Having displayed the jobs in restrictions_list,
291- # we now need to display all jobs that *those* jobs
292- # start on/stop on.
293- for j in restrictions_list:
294- for job in jobs[j]['start on']['job']:
295- show_job(ofh, job)
296- for job in jobs[j]['stop on']['job']:
297- show_job(ofh, job)
298-
299- # Finally, show all jobs which emit events that jobs in the
300- # restrictions_list care about.
301- for j in restrictions_list:
302-
303- for e in jobs[j]['start on']['event']:
304- for k in jobs:
305- if e in jobs[k]['emits']:
306- show_job(ofh, k)
307-
308- for e in jobs[j]['stop on']['event']:
309- for k in jobs:
310- if e in jobs[k]['emits']:
311- show_job(ofh, k)
312+ if restrictions_list:
313+ jobs_to_show = restrictions_list
314+ else:
315+ jobs_to_show = jobs
316+
317+ for j in jobs_to_show:
318+ show_job(ofh, j)
319+ # add those jobs which are referenced by existing jobs, but which
320+ # might not be available as .conf files. For example, plymouth.conf
321+ # references gdm *or* kdm, but you are unlikely to have both
322+ # installed.
323+ for s in jobs[j]['start on']['job']:
324+ if s not in jobs_to_show:
325+ show_job(ofh, s)
326+
327+ for s in jobs[j]['stop on']['job']:
328+ if s not in jobs_to_show:
329+ show_job(ofh, s)
330+
331+ # Having displayed the jobs in restrictions_list,
332+ # we now need to display all jobs that *those* jobs
333+ # start on/stop on.
334+ for j in restrictions_list:
335+ for job in jobs[j]['start on']['job']:
336+ show_job(ofh, job)
337+ for job in jobs[j]['stop on']['job']:
338+ show_job(ofh, job)
339+
340+ # Finally, show all jobs which emit events that jobs in the
341+ # restrictions_list care about.
342+ for j in restrictions_list:
343+ for e in jobs[j]['start on']['event']:
344+ for k in jobs:
345+ if e in jobs[k]['emits']:
346+ show_job(ofh, k)
347+
348+ for e in jobs[j]['stop on']['event']:
349+ for k in jobs:
350+ if e in jobs[k]['emits']:
351+ show_job(ofh, k)
352
353
354 def show_edge(ofh, from_node, to_node, color):
355- ofh.write("%s -> %s [color=\"%s\"];\n" % (from_node, to_node, color))
356+ ofh.write(" %s -> %s [color=\"%s\"];\n" % (from_node, to_node, color))
357
358
359 def show_start_on_job_edge(ofh, from_job, to_job):
360- global options
361- show_edge(ofh, "%s:start" % mk_job_node_name(from_job),
362- "%s:job" % mk_job_node_name(to_job), options.color_start_on)
363+ show_edge(ofh, "%s:start" % mk_job_node_name(from_job),
364+ "%s:job" % mk_job_node_name(to_job), options.color_start_on)
365
366
367 def show_start_on_event_edge(ofh, from_job, to_event):
368- global options
369- show_edge(ofh, "%s:start" % mk_job_node_name(from_job),
370- mk_event_node_name(to_event), options.color_start_on)
371+ show_edge(ofh, "%s:start" % mk_job_node_name(from_job),
372+ mk_event_node_name(to_event), options.color_start_on)
373
374
375 def show_stop_on_job_edge(ofh, from_job, to_job):
376- global options
377- show_edge(ofh, "%s:stop" % mk_job_node_name(from_job),
378- "%s:job" % mk_job_node_name(to_job), options.color_stop_on)
379+ show_edge(ofh, "%s:stop" % mk_job_node_name(from_job),
380+ "%s:job" % mk_job_node_name(to_job), options.color_stop_on)
381
382
383 def show_stop_on_event_edge(ofh, from_job, to_event):
384- global options
385- show_edge(ofh, "%s:stop" % mk_job_node_name(from_job),
386- mk_event_node_name(to_event), options.color_stop_on)
387+ show_edge(ofh, "%s:stop" % mk_job_node_name(from_job),
388+ mk_event_node_name(to_event), options.color_stop_on)
389
390
391 def show_job_emits_edge(ofh, from_job, to_event):
392- global options
393- show_edge(ofh, "%s:job" % mk_job_node_name(from_job),
394- mk_event_node_name(to_event), options.color_emits)
395+ show_edge(ofh, "%s:job" % mk_job_node_name(from_job),
396+ mk_event_node_name(to_event), options.color_emits)
397
398
399 def show_edges(ofh):
400- global events
401- global jobs
402- global options
403- global restrictions_list
404-
405- glob_jobs = {}
406-
407- if restrictions_list:
408- jobs_list = restrictions_list
409- else:
410- jobs_list = jobs
411-
412- for job in jobs_list:
413-
414- for s in jobs[job]['start on']['job']:
415- show_start_on_job_edge(ofh, job, s)
416-
417- for s in jobs[job]['start on']['event']:
418- show_start_on_event_edge(ofh, job, s)
419-
420- for s in jobs[job]['stop on']['job']:
421- show_stop_on_job_edge(ofh, job, s)
422-
423- for s in jobs[job]['stop on']['event']:
424- show_stop_on_event_edge(ofh, job, s)
425-
426- for e in jobs[job]['emits']:
427- if '*' in e:
428- # handle glob patterns in 'emits'
429- glob_events = []
430- for _e in events:
431- if e != _e and fnmatch.fnmatch(_e, e):
432- glob_events.append(_e)
433- glob_jobs[job] = glob_events
434-
435- show_job_emits_edge(ofh, job, e)
436+ glob_jobs = {}
437+
438+ if restrictions_list:
439+ jobs_list = restrictions_list
440+ else:
441+ jobs_list = jobs
442+
443+ for job in jobs_list:
444+ for s in jobs[job]['start on']['job']:
445+ show_start_on_job_edge(ofh, job, s)
446+
447+ for s in jobs[job]['start on']['event']:
448+ show_start_on_event_edge(ofh, job, s)
449+
450+ for s in jobs[job]['stop on']['job']:
451+ show_stop_on_job_edge(ofh, job, s)
452+
453+ for s in jobs[job]['stop on']['event']:
454+ show_stop_on_event_edge(ofh, job, s)
455+
456+ for e in jobs[job]['emits']:
457+ if '*' in e:
458+ # handle glob patterns in 'emits'
459+ glob_events = []
460+ for _e in events:
461+ if e != _e and fnmatch.fnmatch(_e, e):
462+ glob_events.append(_e)
463+ glob_jobs[job] = glob_events
464+
465+ show_job_emits_edge(ofh, job, e)
466+
467+ if not restrictions_list:
468+ continue
469+
470+ # Add links to events emitted by all jobs which current job
471+ # start/stops on
472+ for j in jobs[job]['start on']['job']:
473+ if j not in jobs:
474+ continue
475+ for e in jobs[j]['emits']:
476+ show_job_emits_edge(ofh, j, e)
477+
478+ for j in jobs[job]['stop on']['job']:
479+ for e in jobs[j]['emits']:
480+ show_job_emits_edge(ofh, j, e)
481+
482+ # Create links from jobs (which advertise they emits a class of
483+ # events, via the glob syntax) to all the events they create.
484+ for g in glob_jobs:
485+ for ge in glob_jobs[g]:
486+ show_job_emits_edge(ofh, g, ge)
487
488 if not restrictions_list:
489- continue
490-
491- # Add links to events emitted by all jobs which current job
492- # start/stops on
493- for j in jobs[job]['start on']['job']:
494- if not jobs.has_key(j):
495- continue
496- for e in jobs[j]['emits']:
497- show_job_emits_edge(ofh, j, e)
498-
499- for j in jobs[job]['stop on']['job']:
500- for e in jobs[j]['emits']:
501- show_job_emits_edge(ofh, j, e)
502-
503- # Create links from jobs (which advertise they emits a class of
504- # events, via the glob syntax) to all the events they create.
505- for g in glob_jobs:
506- for ge in glob_jobs[g]:
507- show_job_emits_edge(ofh, g, ge)
508-
509- if not restrictions_list:
510- return
511-
512- # Add jobs->event links to jobs which emit events that current job
513- # start/stops on.
514- for j in restrictions_list:
515-
516- for e in jobs[j]['start on']['event']:
517- for k in jobs:
518- if e in jobs[k]['emits'] and e not in restrictions_list:
519- show_job_emits_edge(ofh, k, e)
520-
521- for e in jobs[j]['stop on']['event']:
522- for k in jobs:
523- if e in jobs[k]['emits'] and e not in restrictions_list:
524- show_job_emits_edge(ofh, k, e)
525+ return
526+
527+ # Add jobs->event links to jobs which emit events that current job
528+ # start/stops on.
529+ for j in restrictions_list:
530+ for e in jobs[j]['start on']['event']:
531+ for k in jobs:
532+ if e in jobs[k]['emits'] and e not in restrictions_list:
533+ show_job_emits_edge(ofh, k, e)
534+
535+ for e in jobs[j]['stop on']['event']:
536+ for k in jobs:
537+ if e in jobs[k]['emits'] and e not in restrictions_list:
538+ show_job_emits_edge(ofh, k, e)
539
540
541 def read_data():
542- global jobs
543- global events
544- global options
545- global cmd
546- global job_events
547-
548- if options.infile:
549- try:
550- ifh = open(options.infile, 'r')
551- except:
552- sys.exit("ERROR: cannot read file '%s'" % options.infile)
553- else:
554- try:
555- ifh = Popen(split(cmd), stdout=PIPE).stdout
556- except:
557- sys.exit("ERROR: cannot run '%s'" % cmd)
558-
559- for line in ifh.readlines():
560- record = {}
561- line = line.rstrip()
562-
563- result = re.match('^\s+start on ([^,]+) \(job:\s*([^,]*), env:', line)
564- if result:
565- _event = encode_dollar(job, result.group(1))
566- _job = result.group(2)
567- if _job:
568- jobs[job]['start on']['job'][_job] = 1
569- else:
570- jobs[job]['start on']['event'][_event] = 1
571- events[_event] = 1
572- continue
573-
574- result = re.match('^\s+stop on ([^,]+) \(job:\s*([^,]*), env:', line)
575- if result:
576- _event = encode_dollar(job, result.group(1))
577- _job = result.group(2)
578- if _job:
579- jobs[job]['stop on']['job'][_job] = 1
580- else:
581- jobs[job]['stop on']['event'][_event] = 1
582- events[_event] = 1
583- continue
584-
585- if re.match('^\s+emits', line):
586- event = (line.lstrip().split())[1]
587- event = encode_dollar(job, event)
588- events[event] = 1
589- jobs[job]['emits'][event] = 1
590- else:
591- tokens = (line.lstrip().split())
592-
593- if len(tokens) != 1:
594- sys.exit("ERROR: invalid line: %s" % line.lstrip())
595-
596- job_record = {}
597-
598- start_on = {}
599- start_on_jobs = {}
600- start_on_events = {}
601-
602- stop_on = {}
603- stop_on_jobs = {}
604- stop_on_events = {}
605-
606- emits = {}
607-
608- start_on['job'] = start_on_jobs
609- start_on['event'] = start_on_events
610-
611- stop_on['job'] = stop_on_jobs
612- stop_on['event'] = stop_on_events
613-
614- job_record['start on'] = start_on
615- job_record['stop on'] = stop_on
616- job_record['emits'] = emits
617-
618- job = (tokens)[0]
619- jobs[job] = job_record
620+ if options.infile:
621+ try:
622+ ifh = open(options.infile, 'r')
623+ except:
624+ sys.exit("ERROR: cannot read file '%s'" % options.infile)
625+ else:
626+ try:
627+ ifh = Popen(cmd.split(), stdout=PIPE,
628+ universal_newlines=True).stdout
629+ except:
630+ sys.exit("ERROR: cannot run '%s'" % cmd)
631+
632+ job = None
633+ for line in ifh:
634+ line = line.rstrip()
635+
636+ result = re.match('^\s+start on ([^,]+) \(job:\s*([^,]*), env:', line)
637+ if result:
638+ _event = encode_dollar(job, result.group(1))
639+ _job = result.group(2)
640+ if _job:
641+ jobs[job]['start on']['job'][_job] = 1
642+ else:
643+ jobs[job]['start on']['event'][_event] = 1
644+ events[_event] = 1
645+ continue
646+
647+ result = re.match('^\s+stop on ([^,]+) \(job:\s*([^,]*), env:', line)
648+ if result:
649+ _event = encode_dollar(job, result.group(1))
650+ _job = result.group(2)
651+ if _job:
652+ jobs[job]['stop on']['job'][_job] = 1
653+ else:
654+ jobs[job]['stop on']['event'][_event] = 1
655+ events[_event] = 1
656+ continue
657+
658+ if re.match('^\s+emits', line):
659+ event = line.lstrip().split()[1]
660+ event = encode_dollar(job, event)
661+ events[event] = 1
662+ jobs[job]['emits'][event] = 1
663+ else:
664+ tokens = line.lstrip().split()
665+
666+ if len(tokens) != 1:
667+ sys.exit("ERROR: invalid line: %s" % line.lstrip())
668+
669+ job_record = {}
670+
671+ start_on = {}
672+ start_on_jobs = {}
673+ start_on_events = {}
674+
675+ stop_on = {}
676+ stop_on_jobs = {}
677+ stop_on_events = {}
678+
679+ emits = {}
680+
681+ start_on['job'] = start_on_jobs
682+ start_on['event'] = start_on_events
683+
684+ stop_on['job'] = stop_on_jobs
685+ stop_on['event'] = stop_on_events
686+
687+ job_record['start on'] = start_on
688+ job_record['stop on'] = stop_on
689+ job_record['emits'] = emits
690+
691+ job = (tokens)[0]
692+ jobs[job] = job_record
693
694
695 def main():
696- global jobs
697- global options
698- global cmd
699- global default_color_emits
700- global default_color_start_on
701- global default_color_stop_on
702- global default_color_event
703- global default_color_job
704- global default_color_text
705- global default_color_bg
706- global restrictions_list
707-
708- description = "Convert initctl(8) output to GraphViz dot(1) format."
709- epilog = \
710- "See http://www.graphviz.org/doc/info/colors.html for available colours."
711-
712- parser = OptionParser(description=description, epilog=epilog)
713-
714- parser.add_option("-r", "--restrict-to-jobs",
715- dest="restrictions",
716- help="Limit display of 'start on' and 'stop on' conditions to " +
717- "specified jobs (comma-separated list).")
718-
719- parser.add_option("-f", "--infile",
720- dest="infile",
721- help="File to read '%s' output from. If not specified, " \
722- "initctl will be run automatically." % cmd)
723-
724- parser.add_option("-o", "--outfile",
725- dest="outfile",
726- help="File to write output to (default=%s)" % default_outfile)
727-
728- parser.add_option("--color-emits",
729- dest="color_emits",
730- help="Specify color for 'emits' lines (default=%s)." %
731- default_color_emits)
732-
733- parser.add_option("--color-start-on",
734- dest="color_start_on",
735- help="Specify color for 'start on' lines (default=%s)." %
736- default_color_start_on)
737-
738- parser.add_option("--color-stop-on",
739- dest="color_stop_on",
740- help="Specify color for 'stop on' lines (default=%s)." %
741- default_color_stop_on)
742-
743- parser.add_option("--color-event",
744- dest="color_event",
745- help="Specify color for event boxes (default=%s)." %
746- default_color_event)
747-
748- parser.add_option("--color-text",
749- dest="color_text",
750- help="Specify color for summary text (default=%s)." %
751- default_color_text)
752-
753- parser.add_option("--color-bg",
754- dest="color_bg",
755- help="Specify background color for diagram (default=%s)." %
756- default_color_bg)
757-
758- parser.add_option("--color-event-text",
759- dest="color_event_text",
760- help="Specify color for text in event boxes (default=%s)." %
761- default_color_text)
762-
763- parser.add_option("--color-job-text",
764- dest="color_job_text",
765- help="Specify color for text in job boxes (default=%s)." %
766- default_color_text)
767-
768- parser.add_option("--color-job",
769- dest="color_job",
770- help="Specify color for job boxes (default=%s)." %
771- default_color_job)
772-
773- parser.set_defaults(color_emits=default_color_emits,
774- color_start_on=default_color_start_on,
775- color_stop_on=default_color_stop_on,
776- color_event=default_color_event,
777- color_job=default_color_job,
778- color_job_text=default_color_text,
779- color_event_text=default_color_text,
780- color_text=default_color_text,
781- color_bg=default_color_bg,
782- outfile=default_outfile)
783-
784- (options, args) = parser.parse_args()
785-
786- if options.outfile == '-':
787- ofh = sys.stdout
788- else:
789- try:
790- ofh = open(options.outfile, "w")
791- except:
792- sys.exit("ERROR: cannot open file %s for writing" % options.outfile)
793-
794- if options.restrictions:
795- restrictions_list = options.restrictions.split(",")
796-
797- read_data()
798-
799- for job in restrictions_list:
800- if not job in jobs:
801- sys.exit("ERROR: unknown job %s" % job)
802-
803- header(ofh)
804- show_events(ofh)
805- show_jobs(ofh)
806- show_edges(ofh)
807- footer(ofh)
808+ global options
809+ global restrictions_list
810+
811+ description = "Convert initctl(8) output to GraphViz dot(1) format."
812+ epilog = "See http://www.graphviz.org/doc/info/colors.html " \
813+ "for available colours."
814+
815+ parser = ArgumentParser(description=description, epilog=epilog)
816+
817+ parser.add_argument("-r", "--restrict-to-jobs",
818+ dest="restrictions",
819+ help="Limit display of 'start on' and 'stop on' "
820+ "conditions to specified jobs (comma-separated list).")
821+
822+ parser.add_argument("-f", "--infile",
823+ dest="infile",
824+ help="File to read '%s' output from. If not specified"
825+ ", initctl will be run automatically." % cmd)
826+
827+ parser.add_argument("-o", "--outfile",
828+ dest="outfile",
829+ help="File to write output to (default=%s)" %
830+ default_outfile)
831+
832+ parser.add_argument("--color-emits",
833+ dest="color_emits",
834+ help="Specify color for 'emits' lines (default=%s)." %
835+ default_color_emits)
836+
837+ parser.add_argument("--color-start-on",
838+ dest="color_start_on",
839+ help="Specify color for 'start on' lines "
840+ "(default=%s)." % default_color_start_on)
841+
842+ parser.add_argument("--color-stop-on",
843+ dest="color_stop_on",
844+ help="Specify color for 'stop on' lines "
845+ "(default=%s)." % default_color_stop_on)
846+
847+ parser.add_argument("--color-event",
848+ dest="color_event",
849+ help="Specify color for event boxes (default=%s)." %
850+ default_color_event)
851+
852+ parser.add_argument("--color-text",
853+ dest="color_text",
854+ help="Specify color for summary text (default=%s)." %
855+ default_color_text)
856+
857+ parser.add_argument("--color-bg",
858+ dest="color_bg",
859+ help="Specify background color for diagram "
860+ "(default=%s)." % default_color_bg)
861+
862+ parser.add_argument("--color-event-text",
863+ dest="color_event_text",
864+ help="Specify color for text in event boxes "
865+ "(default=%s)." % default_color_text)
866+
867+ parser.add_argument("--color-job-text",
868+ dest="color_job_text",
869+ help="Specify color for text in job boxes "
870+ "(default=%s)." % default_color_text)
871+
872+ parser.add_argument("--color-job",
873+ dest="color_job",
874+ help="Specify color for job boxes (default=%s)." %
875+ default_color_job)
876+
877+ parser.set_defaults(color_emits=default_color_emits,
878+ color_start_on=default_color_start_on,
879+ color_stop_on=default_color_stop_on,
880+ color_event=default_color_event,
881+ color_job=default_color_job,
882+ color_job_text=default_color_text,
883+ color_event_text=default_color_text,
884+ color_text=default_color_text,
885+ color_bg=default_color_bg,
886+ outfile=default_outfile)
887+
888+ options = parser.parse_args()
889+
890+ if options.outfile == '-':
891+ ofh = sys.stdout
892+ else:
893+ try:
894+ ofh = open(options.outfile, "w")
895+ except:
896+ sys.exit("ERROR: cannot open file %s for writing" %
897+ options.outfile)
898+
899+ if options.restrictions:
900+ restrictions_list = options.restrictions.split(",")
901+
902+ read_data()
903+
904+ for job in restrictions_list:
905+ if not job in jobs:
906+ sys.exit("ERROR: unknown job %s" % job)
907+
908+ header(ofh)
909+ show_events(ofh)
910+ show_jobs(ofh)
911+ show_edges(ofh)
912+ footer(ofh)
913
914
915 if __name__ == "__main__":
916- main()
917+ main()

Subscribers

People subscribed via source and target branches