Package jazzparser :: Package shell :: Module tools
[hide private]
[frames] | no frames]

Source Code for Module jazzparser.shell.tools

  1  """The basic set of tools for the shell. 
  2   
  3  The basic tools for the Jazz Parser interactive shell for examining  
  4  the output of the parser. These are all generic to all formalisms  
  5  are parsers. Other formalism- and parser-specific tools can be defined  
  6  separately. 
  7   
  8  """ 
  9  """ 
 10  ============================== License ======================================== 
 11   Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding 
 12    
 13   This file is part of The Jazz Parser. 
 14    
 15   The Jazz Parser is free software: you can redistribute it and/or modify 
 16   it under the terms of the GNU General Public License as published by 
 17   the Free Software Foundation, either version 3 of the License, or 
 18   (at your option) any later version. 
 19    
 20   The Jazz Parser is distributed in the hope that it will be useful, 
 21   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 23   GNU General Public License for more details. 
 24    
 25   You should have received a copy of the GNU General Public License 
 26   along with The Jazz Parser.  If not, see <http://www.gnu.org/licenses/>. 
 27   
 28  ============================ End license ====================================== 
 29   
 30  """ 
 31  __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"  
 32   
 33  from jazzparser.utils.options import ModuleOption 
 34  from textwrap import wrap 
 35   
36 -class Tool(object):
37 """ 38 Base class for interactive shell tools. A basic set of subclasses 39 is provided here, others may be provided by a formalism, tagger, parser, 40 etc. 41 42 """ 43 name = None 44 commands = [] 45 usage = ('command','help text') 46 help = """ 47 No help text defined for this command. 48 """ 49 tool_options = [] 50
51 - def run(self, args, state):
52 """ 53 Main operation of tool, taking arguments in args and reading 54 and potentially manipulating the shell state. 55 56 By the time this is called, the options dict is available in 57 self.options. 58 59 """ 60 pass
61
62 - def process_option_list(self, options):
65 66 ################################################# 67 #### Core tools 68
69 -class DerivationTraceTool(Tool):
70 """ 71 Shell tool for outputing derivation traces. 72 """ 73 name = "Derivation Trace" 74 commands = ['deriv', 'd'] 75 usage = ('deriv <res>', 'show derivation of numbered result.') 76 help = """ 77 Shows a full derivation trace for a specific result. 78 This includes all possible derivations of the sign. In order to use this, 79 the parser must have been run with the -d option, so that it stored 80 the traces during parsing. 81 Specify the result by its enumeration in the result list. 82 """ 83
84 - def run(self, args, state):
85 results = state.results 86 # We must have an argument 87 from .shell import ShellError 88 if len(args) == 0: 89 raise ShellError, "You must specify the number of a result" 90 # Display the trace for this result 91 result_num = int(args[0]) 92 if result_num < len(results): 93 if results[result_num].derivation_trace is None: 94 raise ShellError, "Derivation traces have not been stored. Run parser with -d flag to create them" 95 else: 96 print "Derivation trace for result %d: %s" % (result_num,results[result_num]) 97 print "\n%s" % results[result_num].derivation_trace 98 else: 99 raise ShellError, "There are only %d results" % len(results)
100
101 -class DerivationTraceExplorerTool(Tool):
102 """ 103 Shell tool for exploring large derivation traces in more detail. 104 """ 105 name = "Derivation Trace Explorer" 106 commands = ['derivex', 'de'] 107 usage = ('derivex <res>', 'explore derivation of numbered result.') 108 help = """ 109 Explores the derivation trace of a particular result. 110 111 Unlike deriv, which shows the full derivation trace, this shows just 112 one level at a time, from the top of the tree. It then recurses 113 interactively. 114 """ 115
116 - def run(self, args, state):
117 results = state.results 118 # We must have an argument 119 from .shell import ShellError 120 if len(args) == 0: 121 raise ShellError, "You must specify the number of a result" 122 # Display the trace for this result 123 result_num = int(args[0]) 124 if result_num < len(results): 125 if results[result_num].derivation_trace is None: 126 raise ShellError, "Derivation traces have not been stored. Run parser with -d flag to create them" 127 else: 128 print "Derivation trace for result %d: %s" % (result_num,results[result_num]) 129 top_trace = results[result_num].derivation_trace 130 root_traces = [top_trace] 131 while True: 132 print 133 trace = root_traces[-1] 134 print " <- ".join([str(t.result) for t in reversed(root_traces)]) 135 if trace.word is not None: 136 print " lexical category for %s" % trace.word 137 root_traces.pop() 138 else: 139 print " derived from:" 140 for i,(rule,traces) in enumerate(trace.rules): 141 print "%d %s ->\t %s" % (i,rule.name,"\t ".join([str(t.result) for t in traces])) 142 cmd = raw_input("Expand number (up: .., stop: q): ") 143 if cmd == "q": 144 return 145 if cmd == "..": 146 root_traces.pop() 147 else: 148 # Recurse to inspect one level down 149 rec_num = int(cmd) 150 next_traces = trace.rules[rec_num][1] 151 # Choose which sign to recurse on 152 arg = raw_input("Which argument (0-%d)? " % (len(next_traces)-1)) 153 arg = int(arg) 154 root_traces.append(next_traces[arg]) 155 else: 156 raise ShellError, "There are only %d results" % len(results)
157
158 -class AtomsOnlyTool(Tool):
159 """ 160 Removes all complex categories from the results list. 161 """ 162 name = "Atoms Only" 163 commands = ['atoms'] 164 usage = ('atoms', "remove any complex category results from the results list.") 165 help = """ 166 Removes any results from the result list that are not atomic categories. 167 Also prints out the resulting result list. 168 Any subsequent commands will operate on this filtered result list. 169 """ 170
171 - def run(self, args, state):
172 from jazzparser.parser import list_results, remove_complex_categories 173 174 state.results = remove_complex_categories(state.results, state.formalism) 175 # Print the new list 176 list_results(state.results, state.options.silent)
177
178 -class ResultListTool(Tool):
179 """ 180 Prints out a particular range of results, or the whole list. 181 """ 182 name = "Result Slice" 183 commands = ['res'] 184 usage = ("res [<start> [<end>]]", "show the results list again, optionally giving a range.") 185 help = """ 186 Prints out the current result list, with result numbers. 187 Optionally, you may specify a valid range of result numbers to display. 188 """ 189
190 - def run(self, args, state):
191 from jazzparser.parser import list_results 192 from .shell import ShellError 193 194 if len(args) == 1: 195 res = int(args[0]) 196 print "Showing result %d" % res 197 result_list = [state.results[res]] 198 elif len(args) == 2: 199 start,end = int(args[0]), int(args[1]) 200 print "Showing results in range [%s:%s]" % (start,end) 201 result_list = state.results[start:end] 202 else: 203 result_list = state.results 204 205 # Display results again 206 if state.options is not None: 207 list_results(result_list, state.options.silent) 208 else: 209 list_results(result_list)
210
211 -class RuleApplicationTool(Tool):
212 """ 213 Manually applies a named rule to signs in the chart. 214 """ 215 name = "Rule Application" 216 commands = ['apply'] 217 usage = ("apply <rule> <sign> [<sign2> [...]]", "manually apply a named rule to "\ 218 "signs in the chart.") 219 help = """ 220 Apply a grammatical rule to signs in the chart. 221 The rule to apply is selected by its short name (type "apply" for a list 222 of commands). The signs to use as input are selected by their position 223 in the chart. Use the syntax x/y/i, where x and y are the starting and 224 ending nodes for the arc and i is the index of the sign to select 225 from that arc. 226 227 See also: 228 chart, to display the current chart contents. 229 """ 230
231 - def run(self, args, state):
232 from .shell import ShellError 233 # Fetch the available rules from the grammar 234 rules_by_name = dict([(rule.internal_name,rule) for rule in state.parser.grammar.rules]) 235 if len(args) == 0: 236 raise ShellError, "You must specify one of the following rules to apply: %s" % ", ".join(rules_by_name.keys()) 237 elif len(args) == 1: 238 raise ShellError, "You must specify at least one sign from the chart to apply the rule to. Specify a sign in the form 'arc_start/arc_end/index'." 239 # Check the given rule name is available 240 if args[0] not in rules_by_name: 241 raise ShellError, "%s is not a valid rule name. You must specify one of the following rules to apply: %s" % (args[0],", ".join(rules_by_name.keys())) 242 # Got a valid rule name. Get the rule that we'll use 243 rule = rules_by_name[args[0]] 244 signs = [] 245 # Get signs from the chart 246 for arg in args[1:]: 247 parts = arg.split("/") 248 if len(parts) != 3: 249 raise ShellError, "%s is not a valid chart sign. Specify a sign in the form 'arc_start/arc_end/index'." % arg 250 parts = [int(p) for p in parts] 251 sign = state.parser.chart.get_sign(parts[0], parts[1], parts[2]) 252 if sign is None: 253 raise ShellError, "There is no sign at %s/%s/%s in the chart" % tuple(parts) 254 signs.append(sign) 255 # Try applying the rule to the signs 256 if len(signs) != rule.arity: 257 raise ShellError, "Rule %s requires %d arguments. Got %d." % (rule.internal_name, rule.arity, len(signs)) 258 result = rule.apply_rule(signs) 259 if result is not None: 260 result = result[0] 261 print "Applied rule %s to %s => %s" % (rule.internal_name,", ".join(["%s" % s for s in signs]),result)
262
263 -class TonalSpaceCoordinatesTool(Tool):
264 name = "Longuet-Higgins tonal space coordinates" 265 commands = ['tscoords', 'ts'] 266 usage = ("tscoords [<result>]", "displays the tonal space coordinates for the numbered result. Show the first result by default.") 267 help = """\ 268 Uses the formalism's function for converting a logical form to tonal space 269 coordinates. Prints out the coordinates for a particular result. 270 271 """ 272
273 - def run(self, args, state):
274 from .shell import ShellError 275 276 if len(args) > 0: 277 # First arg should be a result number 278 resultnum = int(args[0]) 279 else: 280 # Show the first by default 281 resultnum = 0 282 if resultnum >= len(state.results): 283 raise ShellError, "no such result: %d" % resultnum 284 result = state.results[resultnum] 285 # Convert this result's semantics to coordinates 286 coords = state.formalism.semantics_to_coordinates(result.semantics) 287 print coords
288
289 -class LoadResultsTool(Tool):
290 name = "Load results" 291 commands = ['loadresults', 'loadres'] 292 usage = ("loadresults <filename>", "loads a saved parse results file") 293 help = """\ 294 Loads parse results from a file to which they've been saved by the parser on 295 a previous occasion. These results will replace any already in the shell state, 296 so all other tools will henceforth operate on the loaded results. 297 298 """ 299
300 - def run(self, args, state):
301 from .shell import ShellError 302 from jazzparser.data.parsing import ParseResults 303 304 # Load the file 305 pres = ParseResults.from_file(args[0]) 306 307 if not hasattr(pres, "signs") or not pres.signs: 308 raise ShellError, "loaded parse results, but they're stored as "\ 309 "logical forms, not signs, so we can't load them into the "\ 310 "state" 311 # Replace the results in the state 312 state.results = [res for (prob,res) in pres.parses]
313 314
315 -class LogLevelTool(Tool):
316 """ 317 Change the log level from the shell. 318 """ 319 name = "Set Log Level" 320 commands = ['logging'] 321 usage = ("logging <level>", "sets the main logger's log level to the given level name (\"DEBUG\", etc).") 322 help = """ 323 Change the log level. 324 The whole parser uses a main logger to output debugging info, warnings, 325 etc. By default, this will only show warnings and errors, but this 326 command allows you to change its log level. All subsequent commands 327 will output logging at this level. 328 329 See constants in the Python logging module for log level names. 330 """ 331
332 - def run(self, args, state):
333 from .shell import ShellError 334 import logging 335 336 if len(args) != 1: 337 raise ShellError, "Specify a log level to change to" 338 # Change the logging level 339 logging_name = args[0].upper() 340 try: 341 loglevel = getattr(logging, logging_name) 342 except AttributeError: 343 raise ShellError, "%s is not a recognised logging level" % logging_name 344 345 # Get the logger so we can set the log level 346 logger = logging.getLogger("main_logger") 347 logger.setLevel(loglevel) 348 print "Changed the logging level to %s" % logging_name
349
350 -class PythonTool(Tool):
351 """ 352 Excecutes arbitrary python commands. The commands have access to 353 the parser, formalism, options and results in the environment. 354 """ 355 name = "Python" 356 commands = ['python', 'py'] 357 usage = ("python <command>", "run an arbitrary Python command.") 358 help = """ 359 Runs an arbitrary Python command. 360 The given command will just be executed. Various references are 361 available in the environment: 362 results: the results list 363 parser: the parser object 364 formalism: the loaded formalism 365 options: the command-line options 366 Also available is a dictionary called env, containing any further 367 references made available when the shell was started. Usually this 368 will include all local and global names. 369 """ 370
371 - def run(self, args, state):
372 command = " ".join(args) 373 results = state.results 374 parser = state.parser 375 formalism = state.formalism 376 options = state.options 377 env = state.env 378 chart = parser.chart 379 380 # Run the command 381 exec command
382
383 -class HelpTool(Tool):
384 """ 385 Display shell help. 386 """ 387 name = "Help" 388 commands = ['help', 'h'] 389 usage = ("help [<command>]", "print out usage info for commands or help info.") 390 help = """ 391 Display usage info for a particular command. 392 If no command is given, displays brief usage info for all commands. 393 """ 394
395 - def run(self, args, state):
396 from jazzparser.utils.tableprint import pprint_table 397 import sys 398 399 if len(args) == 0: 400 # Print the command usage info 401 table = [] 402 for tool in state.all_tools: 403 if len(tool.commands) > 1: 404 alts = " [Alternatively: %s]" % ", ".join(tool.commands[1:]) 405 else: alts = "" 406 # If the command has options, list them here as well 407 if len(tool.tool_options) != 0: 408 opts = "\nOptions: %s" % ", ".join(\ 409 [opt.name for opt in tool.tool_options]) 410 else: 411 opts = "" 412 table.append([tool.usage[0], tool.usage[1]+alts+opts]) 413 pprint_table(sys.stdout, table, default_just=True, widths=[30,50], \ 414 blank_row=True, hanging_indent=4) 415 print "\nType 'help <command>' for detailed help about a command" 416 else: 417 command = args[0] 418 if command not in state.tools: 419 print "%s is not a valid command." % command 420 print "Type 'help' for a full command list." 421 else: 422 tool = state.tools[command] 423 title = "%s Shell Command" % tool.name 424 # Compile the help text for the tool's options 425 if len(tool.tool_options): 426 opts = "\nOptions:" 427 # Put required options first 428 for opt in [o for o in tool.tool_options if o.required]: 429 opts += "\n %s %s (REQUIRED)\n %s" % \ 430 (opt.name, opt.usage, \ 431 "\n ".join(wrap(opt.help_text, 75))) 432 # Then all the rest 433 for opt in [o for o in tool.tool_options if not o.required]: 434 opts += "\n%s %s\n %s" % \ 435 (opt.name, opt.usage, \ 436 "\n ".join(wrap(opt.help_text, 75))) 437 else: 438 opts = "" 439 # Print out all of the info 440 print """\ 441 %s 442 %s 443 Usage: %s %s 444 Command aliases: %s 445 446 %s%s""" % (title, "=" * len(title), 447 tool.usage[0], tool.usage[1], 448 ", ".join(tool.commands), 449 tool.help, opts)
450
451 -class SaveStateTool(Tool):
452 name = "Save state" 453 commands = ['save'] 454 usage = ("save <name>", "save shell state to a file") 455 help = """\ 456 Saves the shell state to a file. The state can be resumed later using the 457 'shell' option to the JazzParser. 458 """ 459
460 - def run(self, args, state):
461 name = args[0] 462 state.save(name)
463