4 from ShCommands
import Command
, Pipeline
, Seq
7 def __init__(self
, data
, win32Escapes
= False):
11 self
.win32Escapes
= win32Escapes
14 c
= self
.data
[self
.pos
]
19 return self
.data
[self
.pos
]
21 def maybe_eat(self
, c
):
23 maybe_eat(c) - Consume the character c if it is the next character,
24 returning True if a character was consumed. """
25 if self
.data
[self
.pos
] == c
:
30 def lex_arg_fast(self
, c
):
31 # Get the leading whitespace free section.
32 chunk
= self
.data
[self
.pos
- 1:].split(None, 1)[0]
34 # If it has special characters, the fast path failed.
35 if ('|' in chunk
or '&' in chunk
or
36 '<' in chunk
or '>' in chunk
or
37 "'" in chunk
or '"' in chunk
or
41 self
.pos
= self
.pos
- 1 + len(chunk
)
44 def lex_arg_slow(self
, c
):
46 str = self
.lex_arg_quoted(c
)
49 while self
.pos
!= self
.end
:
51 if c
.isspace() or c
in "|&":
54 # This is an annoying case; we treat '2>' as a single token so
55 # we don't have to track whitespace tokens.
57 # If the parse string isn't an integer, do the usual thing.
61 # Otherwise, lex the operator and convert to a redirection
64 tok
= self
.lex_one_token()
65 assert isinstance(tok
, tuple) and len(tok
) == 1
69 str += self
.lex_arg_quoted('"')
70 elif not self
.win32Escapes
and c
== '\\':
71 # Outside of a string, '\\' escapes everything.
73 if self
.pos
== self
.end
:
74 Util
.warning("escape at end of quoted argument in: %r" %
82 def lex_arg_quoted(self
, delim
):
84 while self
.pos
!= self
.end
:
88 elif c
== '\\' and delim
== '"':
89 # Inside a '"' quoted string, '\\' only escapes the quote
90 # character and backslash, otherwise it is preserved.
91 if self
.pos
== self
.end
:
92 Util
.warning("escape at end of quoted argument in: %r" %
104 Util
.warning("missing quote character in %r" % self
.data
)
107 def lex_arg_checked(self
, c
):
109 res
= self
.lex_arg_fast(c
)
113 reference
= self
.lex_arg_slow(c
)
116 raise ValueError,"Fast path failure: %r != %r" % (res
, reference
)
118 raise ValueError,"Fast path failure: %r != %r" % (self
.pos
, end
)
121 def lex_arg(self
, c
):
122 return self
.lex_arg_fast(c
) or self
.lex_arg_slow(c
)
124 def lex_one_token(self
):
126 lex_one_token - Lex a single 'sh' token. """
132 if self
.maybe_eat('|'):
136 if self
.maybe_eat('&'):
138 if self
.maybe_eat('>'):
142 if self
.maybe_eat('&'):
144 if self
.maybe_eat('>'):
148 if self
.maybe_eat('&'):
150 if self
.maybe_eat('>'):
154 return self
.lex_arg(c
)
157 while self
.pos
!= self
.end
:
158 if self
.look().isspace():
161 yield self
.lex_one_token()
166 def __init__(self
, data
, win32Escapes
= False):
168 self
.tokens
= ShLexer(data
, win32Escapes
= win32Escapes
).lex()
172 return self
.tokens
.next()
173 except StopIteration:
179 self
.tokens
= itertools
.chain([next
], self
.tokens
)
182 def parse_command(self
):
185 raise ValueError,"empty command!"
186 if isinstance(tok
, tuple):
187 raise ValueError,"syntax error near unexpected token %r" % tok
[0]
198 # If this is an argument, just add it to the current command.
199 if isinstance(tok
, str):
200 args
.append(self
.lex())
203 # Otherwise see if it is a terminator.
204 assert isinstance(tok
, tuple)
205 if tok
[0] in ('|',';','&','||','&&'):
208 # Otherwise it must be a redirection.
212 raise ValueError,"syntax error near token %r" % op
[0]
213 redirects
.append((op
, arg
))
215 return Command(args
, redirects
)
217 def parse_pipeline(self
):
219 if self
.look() == ('!',):
223 commands
= [self
.parse_command()]
224 while self
.look() == ('|',):
226 commands
.append(self
.parse_command())
227 return Pipeline(commands
, negate
)
230 lhs
= self
.parse_pipeline()
233 operator
= self
.lex()
234 assert isinstance(operator
, tuple) and len(operator
) == 1
237 raise ValueError, "missing argument to operator %r" % operator
[0]
239 # FIXME: Operator precedence!!
240 lhs
= Seq(lhs
, operator
[0], self
.parse_pipeline())
248 class TestShLexer(unittest
.TestCase
):
249 def lex(self
, str, *args
, **kwargs
):
250 return list(ShLexer(str, *args
, **kwargs
).lex())
252 def test_basic(self
):
253 self
.assertEqual(self
.lex('a|b>c&d<e'),
254 ['a', ('|',), 'b', ('>',), 'c', ('&',), 'd',
257 def test_redirection_tokens(self
):
258 self
.assertEqual(self
.lex('a2>c'),
260 self
.assertEqual(self
.lex('a 2>c'),
263 def test_quoting(self
):
264 self
.assertEqual(self
.lex(""" 'a' """),
266 self
.assertEqual(self
.lex(""" "hello\\"world" """),
268 self
.assertEqual(self
.lex(""" "hello\\'world" """),
270 self
.assertEqual(self
.lex(""" "hello\\\\world" """),
272 self
.assertEqual(self
.lex(""" he"llo wo"rld """),
274 self
.assertEqual(self
.lex(""" a\\ b a\\\\b """),
276 self
.assertEqual(self
.lex(""" "" "" """),
278 self
.assertEqual(self
.lex(""" a\\ b """, win32Escapes
= True),
281 class TestShParse(unittest
.TestCase
):
282 def parse(self
, str):
283 return ShParser(str).parse()
285 def test_basic(self
):
286 self
.assertEqual(self
.parse('echo hello'),
287 Pipeline([Command(['echo', 'hello'], [])], False))
288 self
.assertEqual(self
.parse('echo ""'),
289 Pipeline([Command(['echo', ''], [])], False))
291 def test_redirection(self
):
292 self
.assertEqual(self
.parse('echo hello > c'),
293 Pipeline([Command(['echo', 'hello'],
294 [((('>'),), 'c')])], False))
295 self
.assertEqual(self
.parse('echo hello > c >> d'),
296 Pipeline([Command(['echo', 'hello'], [(('>',), 'c'),
297 (('>>',), 'd')])], False))
298 self
.assertEqual(self
.parse('a 2>&1'),
299 Pipeline([Command(['a'], [(('>&',2), '1')])], False))
301 def test_pipeline(self
):
302 self
.assertEqual(self
.parse('a | b'),
303 Pipeline([Command(['a'], []),
307 self
.assertEqual(self
.parse('a | b | c'),
308 Pipeline([Command(['a'], []),
313 self
.assertEqual(self
.parse('! a'),
314 Pipeline([Command(['a'], [])],
318 self
.assertEqual(self
.parse('a ; b'),
319 Seq(Pipeline([Command(['a'], [])], False),
321 Pipeline([Command(['b'], [])], False)))
323 self
.assertEqual(self
.parse('a & b'),
324 Seq(Pipeline([Command(['a'], [])], False),
326 Pipeline([Command(['b'], [])], False)))
328 self
.assertEqual(self
.parse('a && b'),
329 Seq(Pipeline([Command(['a'], [])], False),
331 Pipeline([Command(['b'], [])], False)))
333 self
.assertEqual(self
.parse('a || b'),
334 Seq(Pipeline([Command(['a'], [])], False),
336 Pipeline([Command(['b'], [])], False)))
338 self
.assertEqual(self
.parse('a && b || c'),
339 Seq(Seq(Pipeline([Command(['a'], [])], False),
341 Pipeline([Command(['b'], [])], False)),
343 Pipeline([Command(['c'], [])], False)))
345 if __name__
== '__main__':