3 from ShCommands
import Command
, Pipeline
5 def tcl_preprocess(data
):
6 # Tcl has a preprocessing step to replace escaped newlines.
11 # Replace '\\\n' and subsequent whitespace by a single space.
15 while i
< n
and data
[i
] in ' \t':
17 return str + ' ' + data
[i
:]
20 """TclLexer - Lex a string into "words", following the Tcl syntax."""
22 def __init__(self
, data
):
23 self
.data
= tcl_preprocess(data
)
25 self
.end
= len(self
.data
)
28 return self
.pos
== self
.end
31 c
= self
.data
[self
.pos
]
36 return self
.data
[self
.pos
]
38 def maybe_eat(self
, c
):
40 maybe_eat(c) - Consume the character c if it is the next character,
41 returning True if a character was consumed. """
42 if self
.data
[self
.pos
] == c
:
63 raise ValueError,'Invalid quoted character %r' % c
68 # Lex until whitespace or end of string, the opening brace has already
74 raise ValueError,"Unterminated '{' quoted word"
80 str += '{' + self
.lex_braced() + '}'
81 elif c
== '\\' and self
.look() in '{}':
93 raise ValueError,"Unterminated '\"' quoted word"
100 raise ValueError,'Missing quoted character'
102 str += self
.escape(self
.eat())
108 def lex_unquoted(self
, process_all
=False):
109 # Lex until whitespace or end of string.
111 while not self
.at_end():
113 if self
.look().isspace() or self
.look() == ';':
119 raise ValueError,'Missing quoted character'
121 str += self
.escape(self
.eat())
123 raise NotImplementedError, ('Command substitution is '
125 elif c
== '$' and not self
.at_end() and (self
.look().isalpha() or
127 raise NotImplementedError, ('Variable substitution is '
134 def lex_one_token(self
):
135 if self
.maybe_eat('"'):
136 return self
.lex_quoted()
137 elif self
.maybe_eat('{'):
138 # Check for argument substitution.
139 if not self
.maybe_eat('*'):
140 return self
.lex_braced()
142 if not self
.maybe_eat('}'):
143 return '*' + self
.lex_braced()
145 if self
.at_end() or self
.look().isspace():
148 raise NotImplementedError, "Argument substitution is unsupported"
150 return self
.lex_unquoted()
153 while not self
.at_end():
161 yield self
.lex_one_token()
163 class TclExecCommand
:
164 kRedirectPrefixes1
= ('<', '>')
165 kRedirectPrefixes2
= ('<@', '<<', '2>', '>&', '>>', '>@')
166 kRedirectPrefixes3
= ('2>@', '2>>', '>>&', '>&@')
167 kRedirectPrefixes4
= ('2>@1',)
169 def __init__(self
, args
):
170 self
.args
= iter(args
)
174 return self
.args
.next()
175 except StopIteration:
181 self
.args
= itertools
.chain([next
], self
.args
)
184 def parse_redirect(self
, tok
, length
):
185 if len(tok
) == length
:
188 raise ValueError,'Missing argument to %r redirection' % tok
190 tok
,arg
= tok
[:length
],tok
[length
:]
198 def parse_pipeline(self
):
199 if self
.look() is None:
200 raise ValueError,"Expected at least one argument to exec"
202 commands
= [Command([],[])]
208 commands
.append(Command([],[]))
210 # Write this as a redirect of stderr; it must come first because
211 # stdout may have already been redirected.
212 commands
[-1].redirects
.insert(0, (('>&',2),'1'))
213 commands
.append(Command([],[]))
214 elif arg
[:4] in TclExecCommand
.kRedirectPrefixes4
:
215 commands
[-1].redirects
.append(self
.parse_redirect(arg
, 4))
216 elif arg
[:3] in TclExecCommand
.kRedirectPrefixes3
:
217 commands
[-1].redirects
.append(self
.parse_redirect(arg
, 3))
218 elif arg
[:2] in TclExecCommand
.kRedirectPrefixes2
:
219 commands
[-1].redirects
.append(self
.parse_redirect(arg
, 2))
220 elif arg
[:1] in TclExecCommand
.kRedirectPrefixes1
:
221 commands
[-1].redirects
.append(self
.parse_redirect(arg
, 1))
223 commands
[-1].args
.append(arg
)
225 return Pipeline(commands
, False, pipe_err
=True)
234 if not isinstance(next
, str) or next
[0] != '-':
240 elif next
== '-ignorestderr':
242 elif next
== '-keepnewline':
245 raise ValueError,"Invalid exec argument %r" % next
247 return (ignoreStderr
, keepNewline
, self
.parse_pipeline())
253 class TestTclLexer(unittest
.TestCase
):
254 def lex(self
, str, *args
, **kwargs
):
255 return list(TclLexer(str, *args
, **kwargs
).lex())
257 def test_preprocess(self
):
258 self
.assertEqual(tcl_preprocess('a b'), 'a b')
259 self
.assertEqual(tcl_preprocess('a\\\nb c'), 'a b c')
261 def test_unquoted(self
):
262 self
.assertEqual(self
.lex('a b c'),
264 self
.assertEqual(self
.lex(r
'a\nb\tc\ '),
266 self
.assertEqual(self
.lex(r
'a \\\$b c $\\'),
267 ['a', r
'\$b', 'c', '$\\'])
269 def test_braced(self
):
270 self
.assertEqual(self
.lex('a {b c} {}'),
272 self
.assertEqual(self
.lex(r
'a {b {c\n}}'),
274 self
.assertEqual(self
.lex(r
'a {b\{}'),
276 self
.assertEqual(self
.lex(r
'{*}'), ['*'])
277 self
.assertEqual(self
.lex(r
'{*} a'), ['*', 'a'])
278 self
.assertEqual(self
.lex(r
'{*} a'), ['*', 'a'])
279 self
.assertEqual(self
.lex('{a\\\n b}'),
282 def test_quoted(self
):
283 self
.assertEqual(self
.lex('a "b c"'),
286 def test_terminators(self
):
287 self
.assertEqual(self
.lex('a\nb'),
289 self
.assertEqual(self
.lex('a;b'),
291 self
.assertEqual(self
.lex('a ; b'),
294 class TestTclExecCommand(unittest
.TestCase
):
295 def parse(self
, str):
296 return TclExecCommand(list(TclLexer(str).lex())).parse()
298 def test_basic(self
):
299 self
.assertEqual(self
.parse('echo hello'),
301 Pipeline([Command(['echo', 'hello'], [])],
303 self
.assertEqual(self
.parse('echo hello | grep hello'),
305 Pipeline([Command(['echo', 'hello'], []),
306 Command(['grep', 'hello'], [])],
309 def test_redirect(self
):
310 self
.assertEqual(self
.parse('echo hello > a >b >>c 2> d |& e'),
312 Pipeline([Command(['echo', 'hello'],
321 if __name__
== '__main__':