blob: de565dc9af363ef1d32f32cea26d8fdc69121e7d [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# pyshyacc.py - PLY grammar definition for pysh
2#
3# Copyright 2007 Patrick Mezard
4#
5# This software may be used and distributed according to the terms
6# of the GNU General Public License, incorporated herein by reference.
7
8"""PLY grammar file.
9"""
10import os.path
11import sys
12
Patrick Williamsc0f7c042017-02-23 20:41:17 -060013import bb.pysh.pyshlex as pyshlex
Patrick Williamsc124f4f2015-09-15 14:41:29 -050014tokens = pyshlex.tokens
15
16from ply import yacc
Patrick Williamsc0f7c042017-02-23 20:41:17 -060017import bb.pysh.sherrors as sherrors
Patrick Williamsc124f4f2015-09-15 14:41:29 -050018
19class IORedirect:
20 def __init__(self, op, filename, io_number=None):
21 self.op = op
22 self.filename = filename
23 self.io_number = io_number
24
25class HereDocument:
26 def __init__(self, op, name, content, io_number=None):
27 self.op = op
28 self.name = name
29 self.content = content
30 self.io_number = io_number
31
32def make_io_redirect(p):
33 """Make an IORedirect instance from the input 'io_redirect' production."""
34 name, io_number, io_target = p
35 assert name=='io_redirect'
36
37 if io_target[0]=='io_file':
38 io_type, io_op, io_file = io_target
39 return IORedirect(io_op, io_file, io_number)
40 elif io_target[0]=='io_here':
41 io_type, io_op, io_name, io_content = io_target
42 return HereDocument(io_op, io_name, io_content, io_number)
43 else:
44 assert False, "Invalid IO redirection token %s" % repr(io_type)
45
46class SimpleCommand:
47 """
48 assigns contains (name, value) pairs.
49 """
50 def __init__(self, words, redirs, assigns):
51 self.words = list(words)
52 self.redirs = list(redirs)
53 self.assigns = list(assigns)
54
55class Pipeline:
56 def __init__(self, commands, reverse_status=False):
57 self.commands = list(commands)
58 assert self.commands #Grammar forbids this
59 self.reverse_status = reverse_status
60
61class AndOr:
62 def __init__(self, op, left, right):
63 self.op = str(op)
64 self.left = left
65 self.right = right
66
67class ForLoop:
68 def __init__(self, name, items, cmds):
69 self.name = str(name)
70 self.items = list(items)
71 self.cmds = list(cmds)
72
73class WhileLoop:
74 def __init__(self, condition, cmds):
75 self.condition = list(condition)
76 self.cmds = list(cmds)
77
78class UntilLoop:
79 def __init__(self, condition, cmds):
80 self.condition = list(condition)
81 self.cmds = list(cmds)
82
83class FunDef:
84 def __init__(self, name, body):
85 self.name = str(name)
86 self.body = body
87
88class BraceGroup:
89 def __init__(self, cmds):
90 self.cmds = list(cmds)
91
92class IfCond:
93 def __init__(self, cond, if_cmds, else_cmds):
94 self.cond = list(cond)
95 self.if_cmds = if_cmds
96 self.else_cmds = else_cmds
97
98class Case:
99 def __init__(self, name, items):
100 self.name = name
101 self.items = items
102
103class SubShell:
104 def __init__(self, cmds):
105 self.cmds = cmds
106
107class RedirectList:
108 def __init__(self, cmd, redirs):
109 self.cmd = cmd
110 self.redirs = list(redirs)
111
112def get_production(productions, ptype):
113 """productions must be a list of production tuples like (name, obj) where
114 name is the production string identifier.
115 Return the first production named 'ptype'. Raise KeyError if None can be
116 found.
117 """
118 for production in productions:
119 if production is not None and production[0]==ptype:
120 return production
121 raise KeyError(ptype)
122
123#-------------------------------------------------------------------------------
124# PLY grammar definition
125#-------------------------------------------------------------------------------
126
127def p_multiple_commands(p):
128 """multiple_commands : newline_sequence
129 | complete_command
130 | multiple_commands complete_command"""
131 if len(p)==2:
132 if p[1] is not None:
133 p[0] = [p[1]]
134 else:
135 p[0] = []
136 else:
137 p[0] = p[1] + [p[2]]
138
139def p_complete_command(p):
140 """complete_command : list separator
141 | list"""
142 if len(p)==3 and p[2] and p[2][1] == '&':
143 p[0] = ('async', p[1])
144 else:
145 p[0] = p[1]
146
147def p_list(p):
148 """list : list separator_op and_or
149 | and_or"""
150 if len(p)==2:
151 p[0] = [p[1]]
152 else:
153 #if p[2]!=';':
154 # raise NotImplementedError('AND-OR list asynchronous execution is not implemented')
155 p[0] = p[1] + [p[3]]
156
157def p_and_or(p):
158 """and_or : pipeline
159 | and_or AND_IF linebreak pipeline
160 | and_or OR_IF linebreak pipeline"""
161 if len(p)==2:
162 p[0] = p[1]
163 else:
164 p[0] = ('and_or', AndOr(p[2], p[1], p[4]))
165
166def p_maybe_bang_word(p):
167 """maybe_bang_word : Bang"""
168 p[0] = ('maybe_bang_word', p[1])
169
170def p_pipeline(p):
171 """pipeline : pipe_sequence
172 | bang_word pipe_sequence"""
173 if len(p)==3:
174 p[0] = ('pipeline', Pipeline(p[2][1:], True))
175 else:
176 p[0] = ('pipeline', Pipeline(p[1][1:]))
177
178def p_pipe_sequence(p):
179 """pipe_sequence : command
180 | pipe_sequence PIPE linebreak command"""
181 if len(p)==2:
182 p[0] = ['pipe_sequence', p[1]]
183 else:
184 p[0] = p[1] + [p[4]]
185
186def p_command(p):
187 """command : simple_command
188 | compound_command
189 | compound_command redirect_list
190 | function_definition"""
191
192 if p[1][0] in ( 'simple_command',
193 'for_clause',
194 'while_clause',
195 'until_clause',
196 'case_clause',
197 'if_clause',
198 'function_definition',
199 'subshell',
200 'brace_group',):
201 if len(p) == 2:
202 p[0] = p[1]
203 else:
204 p[0] = ('redirect_list', RedirectList(p[1], p[2][1:]))
205 else:
206 raise NotImplementedError('%s command is not implemented' % repr(p[1][0]))
207
208def p_compound_command(p):
209 """compound_command : brace_group
210 | subshell
211 | for_clause
212 | case_clause
213 | if_clause
214 | while_clause
215 | until_clause"""
216 p[0] = p[1]
217
218def p_subshell(p):
219 """subshell : LPARENS compound_list RPARENS"""
220 p[0] = ('subshell', SubShell(p[2][1:]))
221
222def p_compound_list(p):
223 """compound_list : term
224 | newline_list term
225 | term separator
226 | newline_list term separator"""
227 productions = p[1:]
228 try:
229 sep = get_production(productions, 'separator')
230 if sep[1]!=';':
231 raise NotImplementedError()
232 except KeyError:
233 pass
234 term = get_production(productions, 'term')
235 p[0] = ['compound_list'] + term[1:]
236
237def p_term(p):
238 """term : term separator and_or
239 | and_or"""
240 if len(p)==2:
241 p[0] = ['term', p[1]]
242 else:
243 if p[2] is not None and p[2][1] == '&':
244 p[0] = ['term', ('async', p[1][1:])] + [p[3]]
245 else:
246 p[0] = p[1] + [p[3]]
247
248def p_maybe_for_word(p):
249 # Rearrange 'For' priority wrt TOKEN. See p_for_word
250 """maybe_for_word : For"""
251 p[0] = ('maybe_for_word', p[1])
252
253def p_for_clause(p):
254 """for_clause : for_word name linebreak do_group
255 | for_word name linebreak in sequential_sep do_group
256 | for_word name linebreak in wordlist sequential_sep do_group"""
257 productions = p[1:]
258 do_group = get_production(productions, 'do_group')
259 try:
260 items = get_production(productions, 'in')[1:]
261 except KeyError:
262 raise NotImplementedError('"in" omission is not implemented')
263
264 try:
265 items = get_production(productions, 'wordlist')[1:]
266 except KeyError:
267 items = []
268
269 name = p[2]
270 p[0] = ('for_clause', ForLoop(name, items, do_group[1:]))
271
272def p_name(p):
273 """name : token""" #Was NAME instead of token
274 p[0] = p[1]
275
276def p_in(p):
277 """in : In"""
278 p[0] = ('in', p[1])
279
280def p_wordlist(p):
281 """wordlist : wordlist token
282 | token"""
283 if len(p)==2:
284 p[0] = ['wordlist', ('TOKEN', p[1])]
285 else:
286 p[0] = p[1] + [('TOKEN', p[2])]
287
288def p_case_clause(p):
289 """case_clause : Case token linebreak in linebreak case_list Esac
290 | Case token linebreak in linebreak case_list_ns Esac
291 | Case token linebreak in linebreak Esac"""
292 if len(p) < 8:
293 items = []
294 else:
295 items = p[6][1:]
296 name = p[2]
297 p[0] = ('case_clause', Case(name, [c[1] for c in items]))
298
299def p_case_list_ns(p):
300 """case_list_ns : case_list case_item_ns
301 | case_item_ns"""
302 p_case_list(p)
303
304def p_case_list(p):
305 """case_list : case_list case_item
306 | case_item"""
307 if len(p)==2:
308 p[0] = ['case_list', p[1]]
309 else:
310 p[0] = p[1] + [p[2]]
311
312def p_case_item_ns(p):
313 """case_item_ns : pattern RPARENS linebreak
314 | pattern RPARENS compound_list linebreak
315 | LPARENS pattern RPARENS linebreak
316 | LPARENS pattern RPARENS compound_list linebreak"""
317 p_case_item(p)
318
319def p_case_item(p):
320 """case_item : pattern RPARENS linebreak DSEMI linebreak
321 | pattern RPARENS compound_list DSEMI linebreak
322 | LPARENS pattern RPARENS linebreak DSEMI linebreak
323 | LPARENS pattern RPARENS compound_list DSEMI linebreak"""
324 if len(p) < 7:
325 name = p[1][1:]
326 else:
327 name = p[2][1:]
328
329 try:
330 cmds = get_production(p[1:], "compound_list")[1:]
331 except KeyError:
332 cmds = []
333
334 p[0] = ('case_item', (name, cmds))
335
336def p_pattern(p):
337 """pattern : token
338 | pattern PIPE token"""
339 if len(p)==2:
340 p[0] = ['pattern', ('TOKEN', p[1])]
341 else:
342 p[0] = p[1] + [('TOKEN', p[2])]
343
344def p_maybe_if_word(p):
345 # Rearrange 'If' priority wrt TOKEN. See p_if_word
346 """maybe_if_word : If"""
347 p[0] = ('maybe_if_word', p[1])
348
349def p_maybe_then_word(p):
350 # Rearrange 'Then' priority wrt TOKEN. See p_then_word
351 """maybe_then_word : Then"""
352 p[0] = ('maybe_then_word', p[1])
353
354def p_if_clause(p):
355 """if_clause : if_word compound_list then_word compound_list else_part Fi
356 | if_word compound_list then_word compound_list Fi"""
357 else_part = []
358 if len(p)==7:
359 else_part = p[5]
360 p[0] = ('if_clause', IfCond(p[2][1:], p[4][1:], else_part))
361
362def p_else_part(p):
363 """else_part : Elif compound_list then_word compound_list else_part
364 | Elif compound_list then_word compound_list
365 | Else compound_list"""
366 if len(p)==3:
367 p[0] = p[2][1:]
368 else:
369 else_part = []
370 if len(p)==6:
371 else_part = p[5]
372 p[0] = ('elif', IfCond(p[2][1:], p[4][1:], else_part))
373
374def p_while_clause(p):
375 """while_clause : While compound_list do_group"""
376 p[0] = ('while_clause', WhileLoop(p[2][1:], p[3][1:]))
377
378def p_maybe_until_word(p):
379 # Rearrange 'Until' priority wrt TOKEN. See p_until_word
380 """maybe_until_word : Until"""
381 p[0] = ('maybe_until_word', p[1])
382
383def p_until_clause(p):
384 """until_clause : until_word compound_list do_group"""
385 p[0] = ('until_clause', UntilLoop(p[2][1:], p[3][1:]))
386
387def p_function_definition(p):
388 """function_definition : fname LPARENS RPARENS linebreak function_body"""
389 p[0] = ('function_definition', FunDef(p[1], p[5]))
390
391def p_function_body(p):
392 """function_body : compound_command
393 | compound_command redirect_list"""
394 if len(p)!=2:
395 raise NotImplementedError('functions redirections lists are not implemented')
396 p[0] = p[1]
397
398def p_fname(p):
399 """fname : TOKEN""" #Was NAME instead of token
400 p[0] = p[1]
401
402def p_brace_group(p):
403 """brace_group : Lbrace compound_list Rbrace"""
404 p[0] = ('brace_group', BraceGroup(p[2][1:]))
405
406def p_maybe_done_word(p):
407 #See p_assignment_word for details.
408 """maybe_done_word : Done"""
409 p[0] = ('maybe_done_word', p[1])
410
411def p_maybe_do_word(p):
412 """maybe_do_word : Do"""
413 p[0] = ('maybe_do_word', p[1])
414
415def p_do_group(p):
416 """do_group : do_word compound_list done_word"""
417 #Do group contains a list of AndOr
418 p[0] = ['do_group'] + p[2][1:]
419
420def p_simple_command(p):
421 """simple_command : cmd_prefix cmd_word cmd_suffix
422 | cmd_prefix cmd_word
423 | cmd_prefix
424 | cmd_name cmd_suffix
425 | cmd_name"""
426 words, redirs, assigns = [], [], []
427 for e in p[1:]:
428 name = e[0]
429 if name in ('cmd_prefix', 'cmd_suffix'):
430 for sube in e[1:]:
431 subname = sube[0]
432 if subname=='io_redirect':
433 redirs.append(make_io_redirect(sube))
434 elif subname=='ASSIGNMENT_WORD':
435 assigns.append(sube)
436 else:
437 words.append(sube)
438 elif name in ('cmd_word', 'cmd_name'):
439 words.append(e)
440
441 cmd = SimpleCommand(words, redirs, assigns)
442 p[0] = ('simple_command', cmd)
443
444def p_cmd_name(p):
445 """cmd_name : TOKEN"""
446 p[0] = ('cmd_name', p[1])
447
448def p_cmd_word(p):
449 """cmd_word : token"""
450 p[0] = ('cmd_word', p[1])
451
452def p_maybe_assignment_word(p):
453 #See p_assignment_word for details.
454 """maybe_assignment_word : ASSIGNMENT_WORD"""
455 p[0] = ('maybe_assignment_word', p[1])
456
457def p_cmd_prefix(p):
458 """cmd_prefix : io_redirect
459 | cmd_prefix io_redirect
460 | assignment_word
461 | cmd_prefix assignment_word"""
462 try:
463 prefix = get_production(p[1:], 'cmd_prefix')
464 except KeyError:
465 prefix = ['cmd_prefix']
466
467 try:
468 value = get_production(p[1:], 'assignment_word')[1]
469 value = ('ASSIGNMENT_WORD', value.split('=', 1))
470 except KeyError:
471 value = get_production(p[1:], 'io_redirect')
472 p[0] = prefix + [value]
473
474def p_cmd_suffix(p):
475 """cmd_suffix : io_redirect
476 | cmd_suffix io_redirect
477 | token
478 | cmd_suffix token
479 | maybe_for_word
480 | cmd_suffix maybe_for_word
481 | maybe_done_word
482 | cmd_suffix maybe_done_word
483 | maybe_do_word
484 | cmd_suffix maybe_do_word
485 | maybe_until_word
486 | cmd_suffix maybe_until_word
487 | maybe_assignment_word
488 | cmd_suffix maybe_assignment_word
489 | maybe_if_word
490 | cmd_suffix maybe_if_word
491 | maybe_then_word
492 | cmd_suffix maybe_then_word
493 | maybe_bang_word
494 | cmd_suffix maybe_bang_word"""
495 try:
496 suffix = get_production(p[1:], 'cmd_suffix')
497 token = p[2]
498 except KeyError:
499 suffix = ['cmd_suffix']
500 token = p[1]
501
502 if isinstance(token, tuple):
503 if token[0]=='io_redirect':
504 p[0] = suffix + [token]
505 else:
506 #Convert maybe_* to TOKEN if necessary
507 p[0] = suffix + [('TOKEN', token[1])]
508 else:
509 p[0] = suffix + [('TOKEN', token)]
510
511def p_redirect_list(p):
512 """redirect_list : io_redirect
513 | redirect_list io_redirect"""
514 if len(p) == 2:
515 p[0] = ['redirect_list', make_io_redirect(p[1])]
516 else:
517 p[0] = p[1] + [make_io_redirect(p[2])]
518
519def p_io_redirect(p):
520 """io_redirect : io_file
521 | IO_NUMBER io_file
522 | io_here
523 | IO_NUMBER io_here"""
524 if len(p)==3:
525 p[0] = ('io_redirect', p[1], p[2])
526 else:
527 p[0] = ('io_redirect', None, p[1])
528
529def p_io_file(p):
530 #Return the tuple (operator, filename)
531 """io_file : LESS filename
532 | LESSAND filename
533 | GREATER filename
534 | GREATAND filename
535 | DGREAT filename
536 | LESSGREAT filename
537 | CLOBBER filename"""
538 #Extract the filename from the file
539 p[0] = ('io_file', p[1], p[2][1])
540
541def p_filename(p):
542 #Return the filename
543 """filename : TOKEN"""
544 p[0] = ('filename', p[1])
545
546def p_io_here(p):
547 """io_here : DLESS here_end
548 | DLESSDASH here_end"""
549 p[0] = ('io_here', p[1], p[2][1], p[2][2])
550
551def p_here_end(p):
552 """here_end : HERENAME TOKEN"""
553 p[0] = ('here_document', p[1], p[2])
554
555def p_newline_sequence(p):
556 # Nothing in the grammar can handle leading NEWLINE productions, so add
557 # this one with the lowest possible priority relatively to newline_list.
558 """newline_sequence : newline_list"""
559 p[0] = None
560
561def p_newline_list(p):
562 """newline_list : NEWLINE
563 | newline_list NEWLINE"""
564 p[0] = None
565
566def p_linebreak(p):
567 """linebreak : newline_list
568 | empty"""
569 p[0] = None
570
571def p_separator_op(p):
572 """separator_op : COMMA
573 | AMP"""
574 p[0] = p[1]
575
576def p_separator(p):
577 """separator : separator_op linebreak
578 | newline_list"""
579 if len(p)==2:
580 #Ignore newlines
581 p[0] = None
582 else:
583 #Keep the separator operator
584 p[0] = ('separator', p[1])
585
586def p_sequential_sep(p):
587 """sequential_sep : COMMA linebreak
588 | newline_list"""
589 p[0] = None
590
591# Low priority TOKEN => for_word conversion.
592# Let maybe_for_word be used as a token when necessary in higher priority
593# rules.
594def p_for_word(p):
595 """for_word : maybe_for_word"""
596 p[0] = p[1]
597
598def p_if_word(p):
599 """if_word : maybe_if_word"""
600 p[0] = p[1]
601
602def p_then_word(p):
603 """then_word : maybe_then_word"""
604 p[0] = p[1]
605
606def p_done_word(p):
607 """done_word : maybe_done_word"""
608 p[0] = p[1]
609
610def p_do_word(p):
611 """do_word : maybe_do_word"""
612 p[0] = p[1]
613
614def p_until_word(p):
615 """until_word : maybe_until_word"""
616 p[0] = p[1]
617
618def p_assignment_word(p):
619 """assignment_word : maybe_assignment_word"""
620 p[0] = ('assignment_word', p[1][1])
621
622def p_bang_word(p):
623 """bang_word : maybe_bang_word"""
624 p[0] = ('bang_word', p[1][1])
625
626def p_token(p):
627 """token : TOKEN
628 | Fi"""
629 p[0] = p[1]
630
631def p_empty(p):
632 'empty :'
633 p[0] = None
634
635# Error rule for syntax errors
636def p_error(p):
637 msg = []
638 w = msg.append
Brad Bishop19323692019-04-05 15:28:33 -0400639 if p:
640 w('%r\n' % p)
641 w('followed by:\n')
642 for i in range(5):
643 n = yacc.token()
644 if not n:
645 break
646 w(' %r\n' % n)
647 else:
648 w('Unexpected EOF')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500649 raise sherrors.ShellSyntaxError(''.join(msg))
650
651# Build the parser
652try:
653 import pyshtables
654except ImportError:
655 outputdir = os.path.dirname(__file__)
656 if not os.access(outputdir, os.W_OK):
657 outputdir = ''
658 yacc.yacc(tabmodule = 'pyshtables', outputdir = outputdir, debug = 0)
659else:
660 yacc.yacc(tabmodule = 'pysh.pyshtables', write_tables = 0, debug = 0)
661
662
663def parse(input, eof=False, debug=False):
664 """Parse a whole script at once and return the generated AST and unconsumed
665 data in a tuple.
666
667 NOTE: eof is probably meaningless for now, the parser being unable to work
668 in pull mode. It should be set to True.
669 """
670 lexer = pyshlex.PLYLexer()
671 remaining = lexer.add(input, eof)
672 if lexer.is_empty():
673 return [], remaining
674 if debug:
675 debug = 2
676 return yacc.parse(lexer=lexer, debug=debug), remaining
677
678#-------------------------------------------------------------------------------
679# AST rendering helpers
680#-------------------------------------------------------------------------------
681
682def format_commands(v):
683 """Return a tree made of strings and lists. Make command trees easier to
684 display.
685 """
686 if isinstance(v, list):
687 return [format_commands(c) for c in v]
688 if isinstance(v, tuple):
689 if len(v)==2 and isinstance(v[0], str) and not isinstance(v[1], str):
690 if v[0] == 'async':
691 return ['AsyncList', map(format_commands, v[1])]
692 else:
693 #Avoid decomposing tuples like ('pipeline', Pipeline(...))
694 return format_commands(v[1])
695 return format_commands(list(v))
696 elif isinstance(v, IfCond):
697 name = ['IfCond']
698 name += ['if', map(format_commands, v.cond)]
699 name += ['then', map(format_commands, v.if_cmds)]
700 name += ['else', map(format_commands, v.else_cmds)]
701 return name
702 elif isinstance(v, ForLoop):
703 name = ['ForLoop']
704 name += [repr(v.name)+' in ', map(str, v.items)]
705 name += ['commands', map(format_commands, v.cmds)]
706 return name
707 elif isinstance(v, AndOr):
708 return [v.op, format_commands(v.left), format_commands(v.right)]
709 elif isinstance(v, Pipeline):
710 name = 'Pipeline'
711 if v.reverse_status:
712 name = '!' + name
713 return [name, format_commands(v.commands)]
714 elif isinstance(v, Case):
715 name = ['Case']
716 name += [v.name, format_commands(v.items)]
717 elif isinstance(v, SimpleCommand):
718 name = ['SimpleCommand']
719 if v.words:
720 name += ['words', map(str, v.words)]
721 if v.assigns:
722 assigns = [tuple(a[1]) for a in v.assigns]
723 name += ['assigns', map(str, assigns)]
724 if v.redirs:
725 name += ['redirs', map(format_commands, v.redirs)]
726 return name
727 elif isinstance(v, RedirectList):
728 name = ['RedirectList']
729 if v.redirs:
730 name += ['redirs', map(format_commands, v.redirs)]
731 name += ['command', format_commands(v.cmd)]
732 return name
733 elif isinstance(v, IORedirect):
734 return ' '.join(map(str, (v.io_number, v.op, v.filename)))
735 elif isinstance(v, HereDocument):
736 return ' '.join(map(str, (v.io_number, v.op, repr(v.name), repr(v.content))))
737 elif isinstance(v, SubShell):
738 return ['SubShell', map(format_commands, v.cmds)]
739 else:
740 return repr(v)
741
742def print_commands(cmds, output=sys.stdout):
743 """Pretty print a command tree."""
744 def print_tree(cmd, spaces, output):
745 if isinstance(cmd, list):
746 for c in cmd:
747 print_tree(c, spaces + 3, output)
748 else:
749 print >>output, ' '*spaces + str(cmd)
750
751 formatted = format_commands(cmds)
752 print_tree(formatted, 0, output)
753
754
755def stringify_commands(cmds):
756 """Serialize a command tree as a string.
757
758 Returned string is not pretty and is currently used for unit tests only.
759 """
760 def stringify(value):
761 output = []
762 if isinstance(value, list):
763 formatted = []
764 for v in value:
765 formatted.append(stringify(v))
766 formatted = ' '.join(formatted)
767 output.append(''.join(['<', formatted, '>']))
768 else:
769 output.append(value)
770 return ' '.join(output)
771
772 return stringify(format_commands(cmds))
773
774
775def visit_commands(cmds, callable):
776 """Visit the command tree and execute callable on every Pipeline and
777 SimpleCommand instances.
778 """
779 if isinstance(cmds, (tuple, list)):
780 map(lambda c: visit_commands(c,callable), cmds)
781 elif isinstance(cmds, (Pipeline, SimpleCommand)):
782 callable(cmds)