Mercurial > python-cmd2
comparison cmd2.py @ 153:5c5c458a6b70
parsing going well
author | catherine@dellzilla |
---|---|
date | Fri, 21 Nov 2008 14:33:15 -0500 |
parents | 693d11072e8e |
children | 606ad25c7f7e |
comparison
equal
deleted
inserted
replaced
152:693d11072e8e | 153:5c5c458a6b70 |
---|---|
197 """Lists single-key shortcuts available.""" | 197 """Lists single-key shortcuts available.""" |
198 result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in self.shortcuts.items()) | 198 result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in self.shortcuts.items()) |
199 self.stdout.write("Single-key shortcuts for other commands:\n%s\n" % (result)) | 199 self.stdout.write("Single-key shortcuts for other commands:\n%s\n" % (result)) |
200 | 200 |
201 commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment]) | 201 commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment]) |
202 commentGrammars.addParseAction(lambda x: '') | |
202 commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd) | 203 commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd) |
203 terminators = [';', '\n\n'] | 204 terminators = [';', '\n\n'] |
204 argSeparatorPattern = pyparsing.Word(pyparsing.printables)('command') \ | 205 multilineCommands = [] |
205 + pyparsing.SkipTo(pyparsing.StringEnd())('args') | |
206 filenamePattern = pyparsing.Word(pyparsing.alphanums + '#$-_~{},.!:\\/') | |
207 integerPattern = pyparsing.Word(pyparsing.nums).setParseAction( lambda s,l,t: [ int(t[0]) ] ) | |
208 pipePattern = pyparsing.Literal('|')('pipe') + pyparsing.restOfLine('pipeTo') | |
209 redirectOutPattern = (pyparsing.Literal('>>') ^ '>')('output') \ | |
210 + pyparsing.Optional(filenamePattern)('outputTo') | |
211 redirectInPattern = pyparsing.Literal('<')('input') \ | |
212 + pyparsing.Optional(filenamePattern)('inputFrom') | |
213 punctuationPattern = pipePattern ^ redirectInPattern ^ redirectOutPattern | |
214 | |
215 | 206 |
216 def _init_parser(self): | 207 def _init_parser(self): |
217 ''' | 208 ''' |
218 >>> c = Cmd() | 209 >>> c = Cmd() |
210 >>> c.multilineCommands = ['multiline'] | |
211 >>> c.caseInsensitive = True | |
212 >>> c._init_parser() | |
219 >>> print c.parser.parseString('barecommand').dump() | 213 >>> print c.parser.parseString('barecommand').dump() |
220 >>> print c.parser.parseString('command with args').dump() | 214 ['barecommand', ''] |
215 - args: | |
216 - command: barecommand | |
217 - statement: ['barecommand', ''] | |
218 - args: | |
219 - command: barecommand | |
220 >>> print c.parser.parseString('COMmand with args').dump() | |
221 ['command', ' with args'] | |
222 - args: with args | |
223 - command: command | |
224 - statement: ['command', ' with args'] | |
225 - args: with args | |
226 - command: command | |
221 >>> print c.parser.parseString('command with args and terminator; and suffix').dump() | 227 >>> print c.parser.parseString('command with args and terminator; and suffix').dump() |
228 ['command', 'with args and terminator', ';', ' and suffix'] | |
229 - args: with args and terminator | |
230 - command: command | |
231 - statement: ['command', 'with args and terminator', ';'] | |
232 - args: with args and terminator | |
233 - command: command | |
234 - terminator: ; | |
235 - suffix: and suffix | |
236 - terminator: ; | |
237 >>> print c.parser.parseString('simple | piped').dump() | |
238 ['simple', '', '|', ' piped'] | |
239 - args: | |
240 - command: simple | |
241 - pipeDest: piped | |
242 - statement: ['simple', ''] | |
243 - args: | |
244 - command: simple | |
222 >>> print c.parser.parseString('command with args, terminator;sufx | piped').dump() | 245 >>> print c.parser.parseString('command with args, terminator;sufx | piped').dump() |
223 >>> print c.parser.parseString('simple | piped').dump() | 246 ['command', 'with args, terminator', ';', 'sufx', '|', ' piped'] |
247 - args: with args, terminator | |
248 - command: command | |
249 - pipeDest: piped | |
250 - statement: ['command', 'with args, terminator', ';'] | |
251 - args: with args, terminator | |
252 - command: command | |
253 - terminator: ; | |
254 - suffix: sufx | |
255 - terminator: ; | |
224 >>> print c.parser.parseString('output into > afile.txt').dump() | 256 >>> print c.parser.parseString('output into > afile.txt').dump() |
257 ['output', ' into', '>', ' afile.txt'] | |
258 - args: into | |
259 - command: output | |
260 - output: > | |
261 - outputDest: afile.txt | |
262 - statement: ['output', ' into'] | |
263 - args: into | |
264 - command: output | |
225 >>> print c.parser.parseString('output into;sufx | pipethrume plz > afile.txt').dump() | 265 >>> print c.parser.parseString('output into;sufx | pipethrume plz > afile.txt').dump() |
266 ['output', 'into', ';', 'sufx', '|', ' pipethrume plz', '>', ' afile.txt'] | |
267 - args: into | |
268 - command: output | |
269 - output: > | |
270 - outputDest: afile.txt | |
271 - pipeDest: pipethrume plz | |
272 - statement: ['output', 'into', ';'] | |
273 - args: into | |
274 - command: output | |
275 - terminator: ; | |
276 - suffix: sufx | |
277 - terminator: ; | |
226 >>> print c.parser.parseString('output to paste buffer >> ').dump() | 278 >>> print c.parser.parseString('output to paste buffer >> ').dump() |
279 ['output', ' to paste buffer', '>>', ''] | |
280 - args: to paste buffer | |
281 - command: output | |
282 - output: >> | |
283 - outputDest: | |
284 - statement: ['output', ' to paste buffer'] | |
285 - args: to paste buffer | |
286 - command: output | |
227 >>> print c.parser.parseString('ignore the /* commented | > */ stuff;').dump() | 287 >>> print c.parser.parseString('ignore the /* commented | > */ stuff;').dump() |
228 >>> print c.parser.parseString('do not parse > when formally terminated;').dump() | 288 ['ignore', 'the /* commented | > */ stuff', ';', ''] |
229 >>> print c.parser.parseString('do not parse > when formally terminated;').dump() | 289 - args: the /* commented | > */ stuff |
230 ''' | 290 - command: ignore |
291 - statement: ['ignore', 'the /* commented | > */ stuff', ';'] | |
292 - args: the /* commented | > */ stuff | |
293 - command: ignore | |
294 - terminator: ; | |
295 - suffix: | |
296 - terminator: ; | |
297 >>> print c.parser.parseString('has > inside;').dump() | |
298 ['has', '> inside', ';', ''] | |
299 - args: > inside | |
300 - command: has | |
301 - statement: ['has', '> inside', ';'] | |
302 - args: > inside | |
303 - command: has | |
304 - terminator: ; | |
305 - suffix: | |
306 - terminator: ; | |
307 >>> print c.parser.parseString('multiline has > inside an unfinished command').dump() | |
308 ['multiline', 'has > inside an unfinished command'] | |
309 - multilineCommand: multiline | |
310 >>> print c.parser.parseString('multiline has > inside;').dump() | |
311 ['multiline', 'has > inside', ';', ''] | |
312 - args: has > inside | |
313 - multilineCommand: multiline | |
314 - statement: ['multiline', 'has > inside', ';'] | |
315 - args: has > inside | |
316 - multilineCommand: multiline | |
317 - terminator: ; | |
318 - suffix: | |
319 - terminator: ; | |
320 >>> print c.parser.parseString('multiline command /* with comment in progress;').dump() | |
321 ['multiline', 'command /* with comment in progress;'] | |
322 - multilineCommand: multiline | |
323 >>> print c.parser.parseString('multiline command /* with comment complete */ is done;').dump() | |
324 ['multiline', 'command /* with comment complete */ is done', ';', ''] | |
325 - args: command /* with comment complete */ is done | |
326 - multilineCommand: multiline | |
327 - statement: ['multiline', 'command /* with comment complete */ is done', ';'] | |
328 - args: command /* with comment complete */ is done | |
329 - multilineCommand: multiline | |
330 - terminator: ; | |
331 - suffix: | |
332 - terminator: ; | |
333 ''' | |
231 outputParser = pyparsing.oneOf(['>>','>'])('output') | 334 outputParser = pyparsing.oneOf(['>>','>'])('output') |
232 terminatorParser = pyparsing.oneOf(self.terminators)('terminator') | 335 terminatorParser = pyparsing.oneOf(self.terminators)('terminator') |
233 stringEnd = pyparsing.stringEnd ^ '\nEOF' | 336 stringEnd = pyparsing.stringEnd ^ '\nEOF' |
234 command = pyparsing.Word(pyparsing.printables)('command') | 337 multilineCommand = pyparsing.Or([pyparsing.Keyword(c, caseless=self.caseInsensitive) for c in self.multilineCommands])('multilineCommand') |
338 oneLineCommand = pyparsing.Word(pyparsing.printables)('command') | |
339 afterElements = \ | |
340 pyparsing.Optional('|' + pyparsing.SkipTo(outputParser ^ stringEnd)('pipeDest')) + \ | |
341 pyparsing.Optional(outputParser + pyparsing.SkipTo(stringEnd)('outputDest')) | |
235 if self.caseInsensitive: | 342 if self.caseInsensitive: |
236 command.setParseAction(lambda x: x[0].lower()) | 343 multilineCommand.setParseAction(lambda x: x[0].lower()) |
237 statementParser = \ | 344 oneLineCommand.setParseAction(lambda x: x[0].lower()) |
238 (command + | 345 self.parser = ( |
239 pyparsing.SkipTo(terminatorParser)('args') + | 346 (((multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(terminatorParser)('args') + terminatorParser)('statement') + |
240 terminatorParser | 347 pyparsing.SkipTo(outputParser ^ '|' ^ stringEnd)('suffix') + afterElements) |
241 )('statement') ^ \ | 348 ^ |
242 (command + | 349 multilineCommand + pyparsing.SkipTo(pyparsing.stringEnd) |
243 pyparsing.SkipTo(terminatorParser ^ '|' ^ outputParser ^ stringEnd)('args') + | 350 ^ |
244 pyparsing.Optional(terminatorParser) | 351 ((oneLineCommand + pyparsing.SkipTo(terminatorParser ^ stringEnd ^ '|' ^ outputParser)('args'))('statement') + |
245 )('statement') | 352 afterElements) |
353 ) | |
246 self.commentGrammars.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).setParseAction(lambda x: '') | 354 self.commentGrammars.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).setParseAction(lambda x: '') |
247 self.commentInProgress.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(pyparsing.cStyleComment) | 355 self.commentInProgress.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(pyparsing.cStyleComment) |
248 self.parser = statementParser + \ | |
249 pyparsing.SkipTo(outputParser ^ '|' ^ stringEnd)('suffix') + \ | |
250 pyparsing.Optional('|' + pyparsing.SkipTo(outputParser ^ stringEnd)('pipeDest')) + \ | |
251 pyparsing.Optional(outputParser + pyparsing.SkipTo(stringEnd)('outputDest')) | |
252 self.parser.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(self.commentGrammars).ignore(self.commentInProgress) | 356 self.parser.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(self.commentGrammars).ignore(self.commentInProgress) |
253 | 357 |
254 def parsed(self, s, assumeComplete=False): | 358 def parsed(self, s): |
255 pass | 359 result = self.parser.parseString(s) |
256 ''' | 360 result['command'] = result.multilineCommand or result.command |
257 >>> c = Cmd() | 361 result['cleanArgs'] = self.commentGrammars.transformString(result.args) |
258 >>> r = c.parsed('quotes "are > ignored" < inp.txt') | 362 result['statement'] = ' '.join(result.statement) |
259 >>> r.statement, r.input, r.inputFrom, r.output, r.outputFrom | |
260 ('quotes "are > ignored" ', '<', 'inp.txt', '', '') | |
261 >>> r = c.parsed('very complex; < from.txt >> to.txt etc.') | |
262 >>> r.statement, r.terminator, r.input, r.inputFrom, r.output, r.outputTo | |
263 ('very complex;', ';', '<', 'from.txt', '>>', 'to.txt') | |
264 >>> c.parsed('nothing to parse').statement | |
265 'nothing to parse' | |
266 >>> r = c.parsed('ignore > within a terminated statement; > out.txt') | |
267 >>> r.statement, r.terminator, r.input, r.inputFrom, r.output, r.outputTo | |
268 ('ignore > within a terminated statement;', ';', '', '', '>', 'out.txt') | |
269 >>> r = c.parsed('send it to | sort | wc') | |
270 >>> r.statement, r.pipe, r.pipeTo | |
271 ('send it to ', '|', ' sort | wc') | |
272 >>> r = c.parsed('got from < thisfile.txt plus blah blah') | |
273 >>> r.statement, r.input, r.inputFrom | |
274 ('got from ', '<', 'thisfile.txt') | |
275 ''' | |
276 if isinstance(s, pyparsing.ParseResults): | |
277 return s | |
278 result = (pyparsing.SkipTo(pyparsing.StringEnd()))("fullStatement").parseString(s) | |
279 s = self.commentGrammars.transformString(s) | |
280 command = s.split()[0] | |
281 if self.caseInsensitive: | |
282 command = command.lower() | |
283 result['command'] = command | |
284 if command in self.noSpecialParse: | |
285 result['statement'] = s | |
286 return result | |
287 | |
288 if s[0] in self.shortcuts: | |
289 s = self.shortcuts[s[0]] + ' ' + s[1:] | |
290 result['statement'] = s | |
291 result['parseable'] = s | |
292 result += parseSearchResults(self.terminatorPattern, s) | |
293 if result.terminator: | |
294 result['statement'] = result.upToIncluding | |
295 result['unterminated'] = result.before | |
296 result['parseable'] = result.after | |
297 else: | |
298 # does not catch output marks | |
299 if (not assumeComplete) and (command in self.multilineCommands): | |
300 return result # don't bother with the rest, we're still collecting input | |
301 result += parseSearchResults(self.punctuationPattern, s) | |
302 result['statement'] = result['unterminated'] = result.before | |
303 result += parseSearchResults(self.pipePattern, result.parseable) | |
304 result += parseSearchResults(self.redirectInPattern, result.parseable) | |
305 result += parseSearchResults(self.redirectOutPattern, result.parseable) | |
306 result += parseSearchResults(self.argSeparatorPattern, result.statement) | |
307 if self.caseInsensitive: | |
308 result['command'] = result.command.lower() | |
309 result['statement'] = '%s %s' % (result.command, result.args) | |
310 return result | 363 return result |
311 | 364 |
312 def extractCommand(self, statement): | 365 def extractCommand(self, statement): |
313 try: | 366 try: |
314 (command, args) = statement.split(None,1) | 367 (command, args) = statement.split(None,1) |
822 def tearDown(self): | 875 def tearDown(self): |
823 if self.CmdApp: | 876 if self.CmdApp: |
824 self.outputTrap.tearDown() | 877 self.outputTrap.tearDown() |
825 | 878 |
826 if __name__ == '__main__': | 879 if __name__ == '__main__': |
827 doctest.testmod() | 880 doctest.testmod(optionflags = doctest.NORMALIZE_WHITESPACE) |