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