pyshyacc.py 23 KB

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