| 1 | """ |
|---|
| 2 | Code parsing and evaluation for the twill mini-language. |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | import sys |
|---|
| 6 | from cStringIO import StringIO |
|---|
| 7 | |
|---|
| 8 | from errors import TwillAssertionError, TwillNameError |
|---|
| 9 | from pyparsing import OneOrMore, Word, printables, quotedString, Optional, \ |
|---|
| 10 | alphas, alphanums, ParseException, ZeroOrMore, restOfLine, Combine, \ |
|---|
| 11 | Literal, Group, removeQuotes, CharsNotIn |
|---|
| 12 | |
|---|
| 13 | import twill.commands as commands |
|---|
| 14 | import namespaces |
|---|
| 15 | import re |
|---|
| 16 | |
|---|
| 17 | ### pyparsing stuff |
|---|
| 18 | |
|---|
| 19 | # basically, a valid Python identifier: |
|---|
| 20 | command = Word(alphas + "_", alphanums + "_") |
|---|
| 21 | command = command.setResultsName('command') |
|---|
| 22 | command.setName("command") |
|---|
| 23 | |
|---|
| 24 | # arguments to it. |
|---|
| 25 | |
|---|
| 26 | # we need to reimplement all this junk from pyparsing because pcre's |
|---|
| 27 | # idea of escapable characters contains a lot more than the C-like |
|---|
| 28 | # thing pyparsing implements |
|---|
| 29 | _bslash = "\\" |
|---|
| 30 | _sglQuote = Literal("'") |
|---|
| 31 | _dblQuote = Literal('"') |
|---|
| 32 | _escapables = printables |
|---|
| 33 | _escapedChar = Word(_bslash, _escapables, exact=2) |
|---|
| 34 | dblQuotedString = Combine( _dblQuote + ZeroOrMore( CharsNotIn('\\"\n\r') | _escapedChar | '""' ) + _dblQuote ).streamline().setName("string enclosed in double quotes") |
|---|
| 35 | sglQuotedString = Combine( _sglQuote + ZeroOrMore( CharsNotIn("\\'\n\r") | _escapedChar | "''" ) + _sglQuote ).streamline().setName("string enclosed in single quotes") |
|---|
| 36 | quotedArg = ( dblQuotedString | sglQuotedString ) |
|---|
| 37 | quotedArg.setParseAction(removeQuotes) |
|---|
| 38 | quotedArg.setName("quotedArg") |
|---|
| 39 | |
|---|
| 40 | plainArgChars = printables.replace('#', '').replace('"', '').replace("'", "") |
|---|
| 41 | plainArg = Word(plainArgChars) |
|---|
| 42 | plainArg.setName("plainArg") |
|---|
| 43 | |
|---|
| 44 | arguments = Group(ZeroOrMore(quotedArg | plainArg)) |
|---|
| 45 | arguments = arguments.setResultsName('arguments') |
|---|
| 46 | arguments.setName("arguments") |
|---|
| 47 | |
|---|
| 48 | # comment line. |
|---|
| 49 | comment = Literal('#') + restOfLine |
|---|
| 50 | comment = comment.suppress() |
|---|
| 51 | comment.setName('comment') |
|---|
| 52 | |
|---|
| 53 | full_command = ( |
|---|
| 54 | comment |
|---|
| 55 | | (command + arguments + Optional(comment)) |
|---|
| 56 | ) |
|---|
| 57 | full_command.setName('full_command') |
|---|
| 58 | |
|---|
| 59 | ### |
|---|
| 60 | |
|---|
| 61 | command_list = [] # filled in by namespaces.init_global_dict(). |
|---|
| 62 | |
|---|
| 63 | ### command/argument handling. |
|---|
| 64 | |
|---|
| 65 | def process_args(args, globals_dict, locals_dict): |
|---|
| 66 | """ |
|---|
| 67 | Take a list of string arguments parsed via pyparsing and evaluate |
|---|
| 68 | the special variables ('__*'). |
|---|
| 69 | |
|---|
| 70 | Return a new list. |
|---|
| 71 | """ |
|---|
| 72 | newargs = [] |
|---|
| 73 | for arg in args: |
|---|
| 74 | # __variable substitution. |
|---|
| 75 | if arg.startswith('__'): |
|---|
| 76 | try: |
|---|
| 77 | val = eval(arg, globals_dict, locals_dict) |
|---|
| 78 | except NameError: # not in dictionary; don't interpret. |
|---|
| 79 | val = arg |
|---|
| 80 | |
|---|
| 81 | |
|---|
| 82 | print '*** VAL IS', val, 'FOR', arg |
|---|
| 83 | |
|---|
| 84 | if isinstance(val, str): |
|---|
| 85 | newargs.append(val) |
|---|
| 86 | else: |
|---|
| 87 | newargs.extend(val) |
|---|
| 88 | |
|---|
| 89 | # $variable substitution |
|---|
| 90 | elif arg.startswith('$') and not arg.startswith('${'): |
|---|
| 91 | try: |
|---|
| 92 | val = eval(arg[1:], globals_dict, locals_dict) |
|---|
| 93 | except NameError: # not in dictionary; don't interpret. |
|---|
| 94 | val = arg |
|---|
| 95 | newargs.append(val) |
|---|
| 96 | else: |
|---|
| 97 | newargs.append(variable_substitution(arg, globals_dict, locals_dict)) |
|---|
| 98 | |
|---|
| 99 | newargs = [ i.replace('\\n', '\n') for i in newargs ] |
|---|
| 100 | |
|---|
| 101 | return newargs |
|---|
| 102 | |
|---|
| 103 | ### |
|---|
| 104 | |
|---|
| 105 | def execute_command(cmd, args, globals_dict, locals_dict, cmdinfo): |
|---|
| 106 | """ |
|---|
| 107 | Actually execute the command. |
|---|
| 108 | |
|---|
| 109 | Side effects: __args__ is set to the argument tuple, __cmd__ is set to |
|---|
| 110 | the command. |
|---|
| 111 | """ |
|---|
| 112 | global command_list # all supported commands: |
|---|
| 113 | # execute command. |
|---|
| 114 | locals_dict['__cmd__'] = cmd |
|---|
| 115 | locals_dict['__args__'] = args |
|---|
| 116 | |
|---|
| 117 | if cmd not in command_list: |
|---|
| 118 | raise TwillNameError("unknown twill command: '%s'" % (cmd,)) |
|---|
| 119 | |
|---|
| 120 | eval_str = "%s(*__args__)" % (cmd,) |
|---|
| 121 | |
|---|
| 122 | # compile the code object so that we can get 'cmdinfo' into the |
|---|
| 123 | # error tracebacks. |
|---|
| 124 | codeobj = compile(eval_str, cmdinfo, 'eval') |
|---|
| 125 | |
|---|
| 126 | # eval the codeobj in the appropriate dictionary. |
|---|
| 127 | result = eval(codeobj, globals_dict, locals_dict) |
|---|
| 128 | |
|---|
| 129 | # set __url__ |
|---|
| 130 | locals_dict['__url__'] = commands.browser.get_url() |
|---|
| 131 | |
|---|
| 132 | return result |
|---|
| 133 | |
|---|
| 134 | ### |
|---|
| 135 | |
|---|
| 136 | _print_commands = False |
|---|
| 137 | |
|---|
| 138 | def parse_command(line, globals_dict, locals_dict): |
|---|
| 139 | """ |
|---|
| 140 | Parse command. |
|---|
| 141 | """ |
|---|
| 142 | res = full_command.parseString(line) |
|---|
| 143 | if res: |
|---|
| 144 | if _print_commands: |
|---|
| 145 | print>>commands.OUT, "twill: executing cmd '%s'" % (line.strip(),) |
|---|
| 146 | |
|---|
| 147 | args = process_args(res.arguments.asList(), globals_dict, locals_dict) |
|---|
| 148 | return (res.command, args) |
|---|
| 149 | |
|---|
| 150 | return None, None # e.g. a comment |
|---|
| 151 | |
|---|
| 152 | ### |
|---|
| 153 | |
|---|
| 154 | def execute_string(buf, **kw): |
|---|
| 155 | """ |
|---|
| 156 | Execute commands from a string buffer. |
|---|
| 157 | """ |
|---|
| 158 | fp = StringIO(buf) |
|---|
| 159 | |
|---|
| 160 | kw['source'] = ['<string buffer>'] |
|---|
| 161 | if not kw.has_key('no_reset'): |
|---|
| 162 | kw['no_reset'] = True |
|---|
| 163 | |
|---|
| 164 | _execute_script(fp, **kw) |
|---|
| 165 | |
|---|
| 166 | def execute_file(filename, **kw): |
|---|
| 167 | """ |
|---|
| 168 | Execute commands from a file. |
|---|
| 169 | """ |
|---|
| 170 | # read the input lines |
|---|
| 171 | if filename == "-": |
|---|
| 172 | inp = sys.stdin |
|---|
| 173 | else: |
|---|
| 174 | inp = open(filename) |
|---|
| 175 | |
|---|
| 176 | kw['source'] = filename |
|---|
| 177 | |
|---|
| 178 | _execute_script(inp, **kw) |
|---|
| 179 | |
|---|
| 180 | def _execute_script(inp, **kw): |
|---|
| 181 | """ |
|---|
| 182 | Execute lines taken from a file-like iterator. |
|---|
| 183 | """ |
|---|
| 184 | # initialize new local dictionary & get global + current local |
|---|
| 185 | namespaces.new_local_dict() |
|---|
| 186 | globals_dict, locals_dict = namespaces.get_twill_glocals() |
|---|
| 187 | |
|---|
| 188 | locals_dict['__url__'] = commands.browser.get_url() |
|---|
| 189 | |
|---|
| 190 | # reset browser |
|---|
| 191 | if not kw.get('no_reset'): |
|---|
| 192 | commands.reset_browser() |
|---|
| 193 | |
|---|
| 194 | # go to a specific URL? |
|---|
| 195 | init_url = kw.get('initial_url') |
|---|
| 196 | if init_url: |
|---|
| 197 | commands.go(init_url) |
|---|
| 198 | locals_dict['__url__'] = commands.browser.get_url() |
|---|
| 199 | |
|---|
| 200 | # should we catch exceptions on failure? |
|---|
| 201 | catch_errors = False |
|---|
| 202 | if kw.get('never_fail'): |
|---|
| 203 | catch_errors = True |
|---|
| 204 | |
|---|
| 205 | # sourceinfo stuff |
|---|
| 206 | sourceinfo = kw.get('source', "<input>") |
|---|
| 207 | |
|---|
| 208 | try: |
|---|
| 209 | |
|---|
| 210 | for n, line in enumerate(inp): |
|---|
| 211 | if not line.strip(): # skip empty lines |
|---|
| 212 | continue |
|---|
| 213 | |
|---|
| 214 | cmdinfo = "%s:%d" % (sourceinfo, n,) |
|---|
| 215 | print 'AT LINE:', cmdinfo |
|---|
| 216 | |
|---|
| 217 | cmd, args = parse_command(line, globals_dict, locals_dict) |
|---|
| 218 | if cmd is None: |
|---|
| 219 | continue |
|---|
| 220 | |
|---|
| 221 | try: |
|---|
| 222 | execute_command(cmd, args, globals_dict, locals_dict, cmdinfo) |
|---|
| 223 | except SystemExit: |
|---|
| 224 | # abort script execution, if a SystemExit is raised. |
|---|
| 225 | return |
|---|
| 226 | except TwillAssertionError, e: |
|---|
| 227 | print>>commands.ERR, '''\ |
|---|
| 228 | Oops! Twill assertion error on line %d of '%s' while executing |
|---|
| 229 | |
|---|
| 230 | >> %s |
|---|
| 231 | |
|---|
| 232 | %s |
|---|
| 233 | ''' % (n, sourceinfo, line.strip(), e) |
|---|
| 234 | if not catch_errors: |
|---|
| 235 | raise |
|---|
| 236 | except Exception, e: |
|---|
| 237 | print>>commands.ERR, '''\ |
|---|
| 238 | EXCEPTION raised at line %d of '%s' |
|---|
| 239 | |
|---|
| 240 | %s |
|---|
| 241 | |
|---|
| 242 | Error message: '%s' |
|---|
| 243 | |
|---|
| 244 | ''' % (n, sourceinfo, line.strip(),str(e).strip(),) |
|---|
| 245 | |
|---|
| 246 | if not catch_errors: |
|---|
| 247 | raise |
|---|
| 248 | |
|---|
| 249 | finally: |
|---|
| 250 | namespaces.pop_local_dict() |
|---|
| 251 | |
|---|
| 252 | ### |
|---|
| 253 | |
|---|
| 254 | def debug_print_commands(flag): |
|---|
| 255 | """ |
|---|
| 256 | Turn on/off printing of commands as they are executed. 'flag' is bool. |
|---|
| 257 | """ |
|---|
| 258 | global _print_commands |
|---|
| 259 | _print_commands = bool(flag) |
|---|
| 260 | |
|---|
| 261 | |
|---|
| 262 | variable_expression = re.compile("\${(.*?)}") |
|---|
| 263 | |
|---|
| 264 | def variable_substitution(raw_str, globals_dict, locals_dict): |
|---|
| 265 | str='' |
|---|
| 266 | pos = 0 |
|---|
| 267 | for m in variable_expression.finditer(raw_str): |
|---|
| 268 | str = str+raw_str[pos:m.start()] |
|---|
| 269 | try: |
|---|
| 270 | str = str + eval(m.group(1), globals_dict, locals_dict) |
|---|
| 271 | except NameError: |
|---|
| 272 | str = str + m.group() |
|---|
| 273 | pos = m.end() |
|---|
| 274 | |
|---|
| 275 | str = str+raw_str[pos:] |
|---|
| 276 | |
|---|
| 277 | return str |
|---|
| 278 | |
|---|