##// END OF EJS Templates
dagparser: use absolute_import
Gregory Szorc -
r25941:a75cda2d default
parent child Browse files
Show More
@@ -1,476 +1,480
1 # dagparser.py - parser and generator for concise description of DAGs
1 # dagparser.py - parser and generator for concise description of DAGs
2 #
2 #
3 # Copyright 2010 Peter Arrenbrecht <peter@arrenbrecht.ch>
3 # Copyright 2010 Peter Arrenbrecht <peter@arrenbrecht.ch>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re, string
8 from __future__ import absolute_import
9 import util
9
10 from i18n import _
10 import re
11 import string
12
13 from .i18n import _
14 from . import util
11
15
12 def parsedag(desc):
16 def parsedag(desc):
13 '''parses a DAG from a concise textual description; generates events
17 '''parses a DAG from a concise textual description; generates events
14
18
15 "+n" is a linear run of n nodes based on the current default parent
19 "+n" is a linear run of n nodes based on the current default parent
16 "." is a single node based on the current default parent
20 "." is a single node based on the current default parent
17 "$" resets the default parent to -1 (implied at the start);
21 "$" resets the default parent to -1 (implied at the start);
18 otherwise the default parent is always the last node created
22 otherwise the default parent is always the last node created
19 "<p" sets the default parent to the backref p
23 "<p" sets the default parent to the backref p
20 "*p" is a fork at parent p, where p is a backref
24 "*p" is a fork at parent p, where p is a backref
21 "*p1/p2/.../pn" is a merge of parents p1..pn, where the pi are backrefs
25 "*p1/p2/.../pn" is a merge of parents p1..pn, where the pi are backrefs
22 "/p2/.../pn" is a merge of the preceding node and p2..pn
26 "/p2/.../pn" is a merge of the preceding node and p2..pn
23 ":name" defines a label for the preceding node; labels can be redefined
27 ":name" defines a label for the preceding node; labels can be redefined
24 "@text" emits an annotation event for text
28 "@text" emits an annotation event for text
25 "!command" emits an action event for the current node
29 "!command" emits an action event for the current node
26 "!!my command\n" is like "!", but to the end of the line
30 "!!my command\n" is like "!", but to the end of the line
27 "#...\n" is a comment up to the end of the line
31 "#...\n" is a comment up to the end of the line
28
32
29 Whitespace between the above elements is ignored.
33 Whitespace between the above elements is ignored.
30
34
31 A backref is either
35 A backref is either
32 * a number n, which references the node curr-n, where curr is the current
36 * a number n, which references the node curr-n, where curr is the current
33 node, or
37 node, or
34 * the name of a label you placed earlier using ":name", or
38 * the name of a label you placed earlier using ":name", or
35 * empty to denote the default parent.
39 * empty to denote the default parent.
36
40
37 All string valued-elements are either strictly alphanumeric, or must
41 All string valued-elements are either strictly alphanumeric, or must
38 be enclosed in double quotes ("..."), with "\" as escape character.
42 be enclosed in double quotes ("..."), with "\" as escape character.
39
43
40 Generates sequence of
44 Generates sequence of
41
45
42 ('n', (id, [parentids])) for node creation
46 ('n', (id, [parentids])) for node creation
43 ('l', (id, labelname)) for labels on nodes
47 ('l', (id, labelname)) for labels on nodes
44 ('a', text) for annotations
48 ('a', text) for annotations
45 ('c', command) for actions (!)
49 ('c', command) for actions (!)
46 ('C', command) for line actions (!!)
50 ('C', command) for line actions (!!)
47
51
48 Examples
52 Examples
49 --------
53 --------
50
54
51 Example of a complex graph (output not shown for brevity):
55 Example of a complex graph (output not shown for brevity):
52
56
53 >>> len(list(parsedag("""
57 >>> len(list(parsedag("""
54 ...
58 ...
55 ... +3 # 3 nodes in linear run
59 ... +3 # 3 nodes in linear run
56 ... :forkhere # a label for the last of the 3 nodes from above
60 ... :forkhere # a label for the last of the 3 nodes from above
57 ... +5 # 5 more nodes on one branch
61 ... +5 # 5 more nodes on one branch
58 ... :mergethis # label again
62 ... :mergethis # label again
59 ... <forkhere # set default parent to labeled fork node
63 ... <forkhere # set default parent to labeled fork node
60 ... +10 # 10 more nodes on a parallel branch
64 ... +10 # 10 more nodes on a parallel branch
61 ... @stable # following nodes will be annotated as "stable"
65 ... @stable # following nodes will be annotated as "stable"
62 ... +5 # 5 nodes in stable
66 ... +5 # 5 nodes in stable
63 ... !addfile # custom command; could trigger new file in next node
67 ... !addfile # custom command; could trigger new file in next node
64 ... +2 # two more nodes
68 ... +2 # two more nodes
65 ... /mergethis # merge last node with labeled node
69 ... /mergethis # merge last node with labeled node
66 ... +4 # 4 more nodes descending from merge node
70 ... +4 # 4 more nodes descending from merge node
67 ...
71 ...
68 ... """)))
72 ... """)))
69 34
73 34
70
74
71 Empty list:
75 Empty list:
72
76
73 >>> list(parsedag(""))
77 >>> list(parsedag(""))
74 []
78 []
75
79
76 A simple linear run:
80 A simple linear run:
77
81
78 >>> list(parsedag("+3"))
82 >>> list(parsedag("+3"))
79 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
83 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
80
84
81 Some non-standard ways to define such runs:
85 Some non-standard ways to define such runs:
82
86
83 >>> list(parsedag("+1+2"))
87 >>> list(parsedag("+1+2"))
84 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
88 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
85
89
86 >>> list(parsedag("+1*1*"))
90 >>> list(parsedag("+1*1*"))
87 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
91 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
88
92
89 >>> list(parsedag("*"))
93 >>> list(parsedag("*"))
90 [('n', (0, [-1]))]
94 [('n', (0, [-1]))]
91
95
92 >>> list(parsedag("..."))
96 >>> list(parsedag("..."))
93 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
97 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
94
98
95 A fork and a join, using numeric back references:
99 A fork and a join, using numeric back references:
96
100
97 >>> list(parsedag("+2*2*/2"))
101 >>> list(parsedag("+2*2*/2"))
98 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
102 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
99
103
100 >>> list(parsedag("+2<2+1/2"))
104 >>> list(parsedag("+2<2+1/2"))
101 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
105 [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
102
106
103 Placing a label:
107 Placing a label:
104
108
105 >>> list(parsedag("+1 :mylabel +1"))
109 >>> list(parsedag("+1 :mylabel +1"))
106 [('n', (0, [-1])), ('l', (0, 'mylabel')), ('n', (1, [0]))]
110 [('n', (0, [-1])), ('l', (0, 'mylabel')), ('n', (1, [0]))]
107
111
108 An empty label (silly, really):
112 An empty label (silly, really):
109
113
110 >>> list(parsedag("+1:+1"))
114 >>> list(parsedag("+1:+1"))
111 [('n', (0, [-1])), ('l', (0, '')), ('n', (1, [0]))]
115 [('n', (0, [-1])), ('l', (0, '')), ('n', (1, [0]))]
112
116
113 Fork and join, but with labels instead of numeric back references:
117 Fork and join, but with labels instead of numeric back references:
114
118
115 >>> list(parsedag("+1:f +1:p2 *f */p2"))
119 >>> list(parsedag("+1:f +1:p2 *f */p2"))
116 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
120 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
117 ('n', (2, [0])), ('n', (3, [2, 1]))]
121 ('n', (2, [0])), ('n', (3, [2, 1]))]
118
122
119 >>> list(parsedag("+1:f +1:p2 <f +1 /p2"))
123 >>> list(parsedag("+1:f +1:p2 <f +1 /p2"))
120 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
124 [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
121 ('n', (2, [0])), ('n', (3, [2, 1]))]
125 ('n', (2, [0])), ('n', (3, [2, 1]))]
122
126
123 Restarting from the root:
127 Restarting from the root:
124
128
125 >>> list(parsedag("+1 $ +1"))
129 >>> list(parsedag("+1 $ +1"))
126 [('n', (0, [-1])), ('n', (1, [-1]))]
130 [('n', (0, [-1])), ('n', (1, [-1]))]
127
131
128 Annotations, which are meant to introduce sticky state for subsequent nodes:
132 Annotations, which are meant to introduce sticky state for subsequent nodes:
129
133
130 >>> list(parsedag("+1 @ann +1"))
134 >>> list(parsedag("+1 @ann +1"))
131 [('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))]
135 [('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))]
132
136
133 >>> list(parsedag('+1 @"my annotation" +1'))
137 >>> list(parsedag('+1 @"my annotation" +1'))
134 [('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))]
138 [('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))]
135
139
136 Commands, which are meant to operate on the most recently created node:
140 Commands, which are meant to operate on the most recently created node:
137
141
138 >>> list(parsedag("+1 !cmd +1"))
142 >>> list(parsedag("+1 !cmd +1"))
139 [('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))]
143 [('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))]
140
144
141 >>> list(parsedag('+1 !"my command" +1'))
145 >>> list(parsedag('+1 !"my command" +1'))
142 [('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))]
146 [('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))]
143
147
144 >>> list(parsedag('+1 !!my command line\\n +1'))
148 >>> list(parsedag('+1 !!my command line\\n +1'))
145 [('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))]
149 [('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))]
146
150
147 Comments, which extend to the end of the line:
151 Comments, which extend to the end of the line:
148
152
149 >>> list(parsedag('+1 # comment\\n+1'))
153 >>> list(parsedag('+1 # comment\\n+1'))
150 [('n', (0, [-1])), ('n', (1, [0]))]
154 [('n', (0, [-1])), ('n', (1, [0]))]
151
155
152 Error:
156 Error:
153
157
154 >>> try: list(parsedag('+1 bad'))
158 >>> try: list(parsedag('+1 bad'))
155 ... except Exception, e: print e
159 ... except Exception, e: print e
156 invalid character in dag description: bad...
160 invalid character in dag description: bad...
157
161
158 '''
162 '''
159 if not desc:
163 if not desc:
160 return
164 return
161
165
162 wordchars = string.ascii_letters + string.digits
166 wordchars = string.ascii_letters + string.digits
163
167
164 labels = {}
168 labels = {}
165 p1 = -1
169 p1 = -1
166 r = 0
170 r = 0
167
171
168 def resolve(ref):
172 def resolve(ref):
169 if not ref:
173 if not ref:
170 return p1
174 return p1
171 elif ref[0] in string.digits:
175 elif ref[0] in string.digits:
172 return r - int(ref)
176 return r - int(ref)
173 else:
177 else:
174 return labels[ref]
178 return labels[ref]
175
179
176 chiter = (c for c in desc)
180 chiter = (c for c in desc)
177
181
178 def nextch():
182 def nextch():
179 return next(chiter, '\0')
183 return next(chiter, '\0')
180
184
181 def nextrun(c, allow):
185 def nextrun(c, allow):
182 s = ''
186 s = ''
183 while c in allow:
187 while c in allow:
184 s += c
188 s += c
185 c = nextch()
189 c = nextch()
186 return c, s
190 return c, s
187
191
188 def nextdelimited(c, limit, escape):
192 def nextdelimited(c, limit, escape):
189 s = ''
193 s = ''
190 while c != limit:
194 while c != limit:
191 if c == escape:
195 if c == escape:
192 c = nextch()
196 c = nextch()
193 s += c
197 s += c
194 c = nextch()
198 c = nextch()
195 return nextch(), s
199 return nextch(), s
196
200
197 def nextstring(c):
201 def nextstring(c):
198 if c == '"':
202 if c == '"':
199 return nextdelimited(nextch(), '"', '\\')
203 return nextdelimited(nextch(), '"', '\\')
200 else:
204 else:
201 return nextrun(c, wordchars)
205 return nextrun(c, wordchars)
202
206
203 c = nextch()
207 c = nextch()
204 while c != '\0':
208 while c != '\0':
205 while c in string.whitespace:
209 while c in string.whitespace:
206 c = nextch()
210 c = nextch()
207 if c == '.':
211 if c == '.':
208 yield 'n', (r, [p1])
212 yield 'n', (r, [p1])
209 p1 = r
213 p1 = r
210 r += 1
214 r += 1
211 c = nextch()
215 c = nextch()
212 elif c == '+':
216 elif c == '+':
213 c, digs = nextrun(nextch(), string.digits)
217 c, digs = nextrun(nextch(), string.digits)
214 n = int(digs)
218 n = int(digs)
215 for i in xrange(0, n):
219 for i in xrange(0, n):
216 yield 'n', (r, [p1])
220 yield 'n', (r, [p1])
217 p1 = r
221 p1 = r
218 r += 1
222 r += 1
219 elif c in '*/':
223 elif c in '*/':
220 if c == '*':
224 if c == '*':
221 c = nextch()
225 c = nextch()
222 c, pref = nextstring(c)
226 c, pref = nextstring(c)
223 prefs = [pref]
227 prefs = [pref]
224 while c == '/':
228 while c == '/':
225 c, pref = nextstring(nextch())
229 c, pref = nextstring(nextch())
226 prefs.append(pref)
230 prefs.append(pref)
227 ps = [resolve(ref) for ref in prefs]
231 ps = [resolve(ref) for ref in prefs]
228 yield 'n', (r, ps)
232 yield 'n', (r, ps)
229 p1 = r
233 p1 = r
230 r += 1
234 r += 1
231 elif c == '<':
235 elif c == '<':
232 c, ref = nextstring(nextch())
236 c, ref = nextstring(nextch())
233 p1 = resolve(ref)
237 p1 = resolve(ref)
234 elif c == ':':
238 elif c == ':':
235 c, name = nextstring(nextch())
239 c, name = nextstring(nextch())
236 labels[name] = p1
240 labels[name] = p1
237 yield 'l', (p1, name)
241 yield 'l', (p1, name)
238 elif c == '@':
242 elif c == '@':
239 c, text = nextstring(nextch())
243 c, text = nextstring(nextch())
240 yield 'a', text
244 yield 'a', text
241 elif c == '!':
245 elif c == '!':
242 c = nextch()
246 c = nextch()
243 if c == '!':
247 if c == '!':
244 cmd = ''
248 cmd = ''
245 c = nextch()
249 c = nextch()
246 while c not in '\n\r\0':
250 while c not in '\n\r\0':
247 cmd += c
251 cmd += c
248 c = nextch()
252 c = nextch()
249 yield 'C', cmd
253 yield 'C', cmd
250 else:
254 else:
251 c, cmd = nextstring(c)
255 c, cmd = nextstring(c)
252 yield 'c', cmd
256 yield 'c', cmd
253 elif c == '#':
257 elif c == '#':
254 while c not in '\n\r\0':
258 while c not in '\n\r\0':
255 c = nextch()
259 c = nextch()
256 elif c == '$':
260 elif c == '$':
257 p1 = -1
261 p1 = -1
258 c = nextch()
262 c = nextch()
259 elif c == '\0':
263 elif c == '\0':
260 return # in case it was preceded by whitespace
264 return # in case it was preceded by whitespace
261 else:
265 else:
262 s = ''
266 s = ''
263 i = 0
267 i = 0
264 while c != '\0' and i < 10:
268 while c != '\0' and i < 10:
265 s += c
269 s += c
266 i += 1
270 i += 1
267 c = nextch()
271 c = nextch()
268 raise util.Abort(_('invalid character in dag description: '
272 raise util.Abort(_('invalid character in dag description: '
269 '%s...') % s)
273 '%s...') % s)
270
274
271 def dagtextlines(events,
275 def dagtextlines(events,
272 addspaces=True,
276 addspaces=True,
273 wraplabels=False,
277 wraplabels=False,
274 wrapannotations=False,
278 wrapannotations=False,
275 wrapcommands=False,
279 wrapcommands=False,
276 wrapnonlinear=False,
280 wrapnonlinear=False,
277 usedots=False,
281 usedots=False,
278 maxlinewidth=70):
282 maxlinewidth=70):
279 '''generates single lines for dagtext()'''
283 '''generates single lines for dagtext()'''
280
284
281 def wrapstring(text):
285 def wrapstring(text):
282 if re.match("^[0-9a-z]*$", text):
286 if re.match("^[0-9a-z]*$", text):
283 return text
287 return text
284 return '"' + text.replace('\\', '\\\\').replace('"', '\"') + '"'
288 return '"' + text.replace('\\', '\\\\').replace('"', '\"') + '"'
285
289
286 def gen():
290 def gen():
287 labels = {}
291 labels = {}
288 run = 0
292 run = 0
289 wantr = 0
293 wantr = 0
290 needroot = False
294 needroot = False
291 for kind, data in events:
295 for kind, data in events:
292 if kind == 'n':
296 if kind == 'n':
293 r, ps = data
297 r, ps = data
294
298
295 # sanity check
299 # sanity check
296 if r != wantr:
300 if r != wantr:
297 raise util.Abort(_("expected id %i, got %i") % (wantr, r))
301 raise util.Abort(_("expected id %i, got %i") % (wantr, r))
298 if not ps:
302 if not ps:
299 ps = [-1]
303 ps = [-1]
300 else:
304 else:
301 for p in ps:
305 for p in ps:
302 if p >= r:
306 if p >= r:
303 raise util.Abort(_("parent id %i is larger than "
307 raise util.Abort(_("parent id %i is larger than "
304 "current id %i") % (p, r))
308 "current id %i") % (p, r))
305 wantr += 1
309 wantr += 1
306
310
307 # new root?
311 # new root?
308 p1 = r - 1
312 p1 = r - 1
309 if len(ps) == 1 and ps[0] == -1:
313 if len(ps) == 1 and ps[0] == -1:
310 if needroot:
314 if needroot:
311 if run:
315 if run:
312 yield '+' + str(run)
316 yield '+' + str(run)
313 run = 0
317 run = 0
314 if wrapnonlinear:
318 if wrapnonlinear:
315 yield '\n'
319 yield '\n'
316 yield '$'
320 yield '$'
317 p1 = -1
321 p1 = -1
318 else:
322 else:
319 needroot = True
323 needroot = True
320 if len(ps) == 1 and ps[0] == p1:
324 if len(ps) == 1 and ps[0] == p1:
321 if usedots:
325 if usedots:
322 yield "."
326 yield "."
323 else:
327 else:
324 run += 1
328 run += 1
325 else:
329 else:
326 if run:
330 if run:
327 yield '+' + str(run)
331 yield '+' + str(run)
328 run = 0
332 run = 0
329 if wrapnonlinear:
333 if wrapnonlinear:
330 yield '\n'
334 yield '\n'
331 prefs = []
335 prefs = []
332 for p in ps:
336 for p in ps:
333 if p == p1:
337 if p == p1:
334 prefs.append('')
338 prefs.append('')
335 elif p in labels:
339 elif p in labels:
336 prefs.append(labels[p])
340 prefs.append(labels[p])
337 else:
341 else:
338 prefs.append(str(r - p))
342 prefs.append(str(r - p))
339 yield '*' + '/'.join(prefs)
343 yield '*' + '/'.join(prefs)
340 else:
344 else:
341 if run:
345 if run:
342 yield '+' + str(run)
346 yield '+' + str(run)
343 run = 0
347 run = 0
344 if kind == 'l':
348 if kind == 'l':
345 rid, name = data
349 rid, name = data
346 labels[rid] = name
350 labels[rid] = name
347 yield ':' + name
351 yield ':' + name
348 if wraplabels:
352 if wraplabels:
349 yield '\n'
353 yield '\n'
350 elif kind == 'c':
354 elif kind == 'c':
351 yield '!' + wrapstring(data)
355 yield '!' + wrapstring(data)
352 if wrapcommands:
356 if wrapcommands:
353 yield '\n'
357 yield '\n'
354 elif kind == 'C':
358 elif kind == 'C':
355 yield '!!' + data
359 yield '!!' + data
356 yield '\n'
360 yield '\n'
357 elif kind == 'a':
361 elif kind == 'a':
358 if wrapannotations:
362 if wrapannotations:
359 yield '\n'
363 yield '\n'
360 yield '@' + wrapstring(data)
364 yield '@' + wrapstring(data)
361 elif kind == '#':
365 elif kind == '#':
362 yield '#' + data
366 yield '#' + data
363 yield '\n'
367 yield '\n'
364 else:
368 else:
365 raise util.Abort(_("invalid event type in dag: %s")
369 raise util.Abort(_("invalid event type in dag: %s")
366 % str((type, data)))
370 % str((type, data)))
367 if run:
371 if run:
368 yield '+' + str(run)
372 yield '+' + str(run)
369
373
370 line = ''
374 line = ''
371 for part in gen():
375 for part in gen():
372 if part == '\n':
376 if part == '\n':
373 if line:
377 if line:
374 yield line
378 yield line
375 line = ''
379 line = ''
376 else:
380 else:
377 if len(line) + len(part) >= maxlinewidth:
381 if len(line) + len(part) >= maxlinewidth:
378 yield line
382 yield line
379 line = ''
383 line = ''
380 elif addspaces and line and part != '.':
384 elif addspaces and line and part != '.':
381 line += ' '
385 line += ' '
382 line += part
386 line += part
383 if line:
387 if line:
384 yield line
388 yield line
385
389
386 def dagtext(dag,
390 def dagtext(dag,
387 addspaces=True,
391 addspaces=True,
388 wraplabels=False,
392 wraplabels=False,
389 wrapannotations=False,
393 wrapannotations=False,
390 wrapcommands=False,
394 wrapcommands=False,
391 wrapnonlinear=False,
395 wrapnonlinear=False,
392 usedots=False,
396 usedots=False,
393 maxlinewidth=70):
397 maxlinewidth=70):
394 '''generates lines of a textual representation for a dag event stream
398 '''generates lines of a textual representation for a dag event stream
395
399
396 events should generate what parsedag() does, so:
400 events should generate what parsedag() does, so:
397
401
398 ('n', (id, [parentids])) for node creation
402 ('n', (id, [parentids])) for node creation
399 ('l', (id, labelname)) for labels on nodes
403 ('l', (id, labelname)) for labels on nodes
400 ('a', text) for annotations
404 ('a', text) for annotations
401 ('c', text) for commands
405 ('c', text) for commands
402 ('C', text) for line commands ('!!')
406 ('C', text) for line commands ('!!')
403 ('#', text) for comment lines
407 ('#', text) for comment lines
404
408
405 Parent nodes must come before child nodes.
409 Parent nodes must come before child nodes.
406
410
407 Examples
411 Examples
408 --------
412 --------
409
413
410 Linear run:
414 Linear run:
411
415
412 >>> dagtext([('n', (0, [-1])), ('n', (1, [0]))])
416 >>> dagtext([('n', (0, [-1])), ('n', (1, [0]))])
413 '+2'
417 '+2'
414
418
415 Two roots:
419 Two roots:
416
420
417 >>> dagtext([('n', (0, [-1])), ('n', (1, [-1]))])
421 >>> dagtext([('n', (0, [-1])), ('n', (1, [-1]))])
418 '+1 $ +1'
422 '+1 $ +1'
419
423
420 Fork and join:
424 Fork and join:
421
425
422 >>> dagtext([('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])),
426 >>> dagtext([('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])),
423 ... ('n', (3, [2, 1]))])
427 ... ('n', (3, [2, 1]))])
424 '+2 *2 */2'
428 '+2 *2 */2'
425
429
426 Fork and join with labels:
430 Fork and join with labels:
427
431
428 >>> dagtext([('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])),
432 >>> dagtext([('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])),
429 ... ('l', (1, 'p2')), ('n', (2, [0])), ('n', (3, [2, 1]))])
433 ... ('l', (1, 'p2')), ('n', (2, [0])), ('n', (3, [2, 1]))])
430 '+1 :f +1 :p2 *f */p2'
434 '+1 :f +1 :p2 *f */p2'
431
435
432 Annotations:
436 Annotations:
433
437
434 >>> dagtext([('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))])
438 >>> dagtext([('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))])
435 '+1 @ann +1'
439 '+1 @ann +1'
436
440
437 >>> dagtext([('n', (0, [-1])),
441 >>> dagtext([('n', (0, [-1])),
438 ... ('a', 'my annotation'),
442 ... ('a', 'my annotation'),
439 ... ('n', (1, [0]))])
443 ... ('n', (1, [0]))])
440 '+1 @"my annotation" +1'
444 '+1 @"my annotation" +1'
441
445
442 Commands:
446 Commands:
443
447
444 >>> dagtext([('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))])
448 >>> dagtext([('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))])
445 '+1 !cmd +1'
449 '+1 !cmd +1'
446
450
447 >>> dagtext([('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))])
451 >>> dagtext([('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))])
448 '+1 !"my command" +1'
452 '+1 !"my command" +1'
449
453
450 >>> dagtext([('n', (0, [-1])),
454 >>> dagtext([('n', (0, [-1])),
451 ... ('C', 'my command line'),
455 ... ('C', 'my command line'),
452 ... ('n', (1, [0]))])
456 ... ('n', (1, [0]))])
453 '+1 !!my command line\\n+1'
457 '+1 !!my command line\\n+1'
454
458
455 Comments:
459 Comments:
456
460
457 >>> dagtext([('n', (0, [-1])), ('#', ' comment'), ('n', (1, [0]))])
461 >>> dagtext([('n', (0, [-1])), ('#', ' comment'), ('n', (1, [0]))])
458 '+1 # comment\\n+1'
462 '+1 # comment\\n+1'
459
463
460 >>> dagtext([])
464 >>> dagtext([])
461 ''
465 ''
462
466
463 Combining parsedag and dagtext:
467 Combining parsedag and dagtext:
464
468
465 >>> dagtext(parsedag('+1 :f +1 :p2 *f */p2'))
469 >>> dagtext(parsedag('+1 :f +1 :p2 *f */p2'))
466 '+1 :f +1 :p2 *f */p2'
470 '+1 :f +1 :p2 *f */p2'
467
471
468 '''
472 '''
469 return "\n".join(dagtextlines(dag,
473 return "\n".join(dagtextlines(dag,
470 addspaces,
474 addspaces,
471 wraplabels,
475 wraplabels,
472 wrapannotations,
476 wrapannotations,
473 wrapcommands,
477 wrapcommands,
474 wrapnonlinear,
478 wrapnonlinear,
475 usedots,
479 usedots,
476 maxlinewidth))
480 maxlinewidth))
General Comments 0
You need to be logged in to leave comments. Login now