Package topo :: Package misc :: Module commandline
[hide private]
[frames] | no frames]

Source Code for Module topo.misc.commandline

  1  """ 
  2  Support functions for parsing command-line arguments and providing 
  3  the Topographica command prompt.  Typically called from the 
  4  './topographica' script, but can be called directly if using 
  5  Topographica files within a separate Python. 
  6   
  7  $Id: commandline.py 11301 2010-07-27 16:19:54Z ceball $ 
  8  """ 
  9  __version__='$Revision: 11301 $' 
 10   
 11   
 12  from optparse import OptionParser 
 13   
 14  import sys, __main__, math, os, re 
 15   
 16  import topo 
 17  from param.parameterized import Parameterized,OptionalSingleton 
 18   
 19   
 20  try: 
 21      # By default, use a non-GUI backend for matplotlib. 
 22      from matplotlib import rcParams 
 23      rcParams['backend']='Agg' 
 24      matplotlib_imported=True 
 25  except ImportError: 
 26      matplotlib_imported=False 
 27   
 28   
 29  try: 
 30      import IPython.Shell 
 31      ipython_imported=True 
 32  except ImportError: 
 33      ipython_imported=False 
 34      print "Note: IPython is not available; using basic interactive Python prompt instead." 
 35   
 36   
 37   
 38  # Startup banner 
 39  BANNER = """ 
 40  Welcome to Topographica! 
 41   
 42  Type help() for interactive help with python, help(topo) for general 
 43  information about Topographica, help(commandname) for info on a 
 44  specific command, or topo.about() for info on this release, including 
 45  licensing information. 
 46  """ 
47 48 49 -class GlobalParams(Parameterized,OptionalSingleton):
50 """ 51 A Parameterized class providing script-level parameters. 52 53 Script-level parameters can be set from the commandline by passing 54 via -p, e.g. ./topographica -p retina_density=10 55 56 Within scripts, parameters can be declared by using the add() 57 method. 58 59 60 Example usage in a script: 61 62 from topo.misc.commandline import global_params as p 63 p.add( 64 retina_density=param.Number(default=24.0,bounds=(0,None), 65 inclusive_bounds=(False,True),doc=\""" 66 The nominal_density to use for the retina.\""")) 67 ... 68 topo.sim['Retina']=sheet.GeneratorSheet( 69 nominal_density=p.retina_density) 70 71 72 Further information: 73 74 'context' is usually set to __main__.__dict__ and is used to find 75 the value of a parameter as it is add()ed to this object 76 (i.e. add() has access to values set via the commandline or in 77 scripts). 78 79 Values set via set_in_context() or exec_in_context() (used by -p) 80 are tracked: warnings are issued for overwritten values, and 81 unused values can be warned about via check_for_unused_names(). 82 83 The context is not saved in snapshots, but parameter values are 84 saved. 85 """ 86 context = None 87
88 - def __new__(cls,*args,**kw):
89 return OptionalSingleton.__new__(cls,True)
90
91 - def __init__(self,context=None,**params):
92 self.context = context or {} 93 self.unused_names = set() 94 params['name']="global_params" 95 super(GlobalParams,self).__init__(**params)
96
97 - def __getstate__(self):
98 # context is neither saved nor restored 99 # (in our current usage, the context of the GlobalParams 100 # instance will be set to __main__.__dict__ on startup). 101 state = super(GlobalParams,self).__getstate__() 102 del state['context'] 103 return state
104
105 - def set_in_context(self,**params):
106 """ 107 Set in self.context all name=val pairs specified in **params, 108 tracking new names and warning of any replacements. 109 """ 110 for name,val in params.items(): 111 if name in self.context: 112 self.warning("Replacing previous value of '%s' with '%s'"%(name,val)) 113 self.context[name]=val 114 self.unused_names.add(name)
115
116 - def exec_in_context(self,arg):
117 """ 118 exec arg in self.context, tracking new names and 119 warning of any replacements. 120 """ 121 ## contains elaborate scheme to detect what is specified by 122 ## -s, and to warn about any replacement 123 current_ids = dict([(k,id(v)) for k,v in self.context.items()]) 124 125 exec arg in self.context 126 127 for k,v in self.context.items(): 128 if k in self.unused_names and id(v)!=current_ids[k]: 129 self.warning("Replacing previous value of '%s' with '%s'"%(k,v)) 130 131 new_names = set(self.context.keys()).difference(set(current_ids.keys())) 132 for k in new_names: 133 self.unused_names.add(k)
134
135 - def check_for_unused_names(self):
136 """Warn about any unused names.""" 137 for s in self.unused_names: 138 self.warning("'%s' is unused."%s) 139 140 # warns for param that specified with -c (but also if name gets defined in __main__, 141 # e.g. by default_density=global_params.default_density in a script file 142 ## for name in self.params(): 143 ## if name in self.context: 144 ## self.warning("'%s' still exists in global_params.context"%name) 145 146 # detect duplicate param value that wasn't used (e.g. specified with after script) 147 for name,val in self.params().items(): 148 if name in self.context: 149 if self.context[name]!=self.inspect_value(name): 150 self.warning("'%s=%s' is unused."%(name,self.context[name]))
151 152
153 - def add(self,**kw):
154 """ 155 For each parameter_name=parameter_object specified in kw: 156 * adds the parameter_object to this object's class 157 * if there is an entry in context that has the same name as the parameter, 158 sets the value of the parameter in this object to that value, and then removes 159 the name from context 160 """ 161 for p_name,p_obj in kw.items(): 162 self._add_parameter(p_name,p_obj) 163 if p_name in self.context: 164 setattr(self,p_name,self.context[p_name]) 165 if p_name in self.unused_names: 166 # i.e. remove from __main__ if it was a -p option (but not if -c) 167 del self.context[p_name] 168 self.unused_names.remove(p_name)
169 170 171 global_params=GlobalParams(context=__main__.__dict__)
172 173 174 175 ##### Command-prompt formatting 176 # 177 -class IPCommandPromptHandler(object):
178 """ 179 Allows control over IPython's dynamic command prompts. 180 """ 181 _format = '' 182 _prompt = '' 183 184 @classmethod
185 - def set_format(cls,format):
186 """ 187 Set IPython's prompt template to format. 188 """ 189 import __main__ 190 IP = __main__.__dict__['__IP'] 191 prompt = getattr(IP.outputcache,cls._prompt) 192 prompt.p_template = format 193 prompt.set_p_str() 194 cls._format = format
195 196 @classmethod
197 - def get_format(cls):
198 """ 199 Return the current template. 200 """ 201 return cls._format
202
203 204 -class CommandPrompt(IPCommandPromptHandler):
205 """ 206 Control over input prompt. 207 208 Several predefined formats are provided, and any of these (or any 209 arbitrary string) can be used by calling set_format() with their 210 values. 211 212 See the IPython manual for details: 213 http://ipython.scipy.org/doc/manual/html/config/index.html 214 215 Examples: 216 # Use one of the predefined formats: 217 CommandPrompt.set_format(CommandPrompt.basic_format) 218 # Just print the command number: 219 CommandPrompt.set_format('\# ') 220 # Print the command number but don't use color: 221 CommandPrompt.set_format('\N ') 222 # Print the value of my_var at each prompt: 223 CommandPrompt.set_format('${my_var}>>> ') 224 """ 225 _prompt = 'prompt1' 226 227 # Predefined alternatives 228 basic_format = 'Topographica>>> ' 229 simtime_format = 'topo_t${topo.sim.timestr()}>>> ' 230 simtimecmd_format = 'topo_t${topo.sim.timestr()}_c\\#>>> ' 231 232 _format = simtimecmd_format
233
234 235 -class CommandPrompt2(IPCommandPromptHandler):
236 """ 237 Control over continuation prompt. 238 239 (See CommandPrompt.) 240 """ 241 _prompt = 'prompt2' 242 basic_format = ' .\\D.: ' 243 _format = basic_format
244
245 246 -class OutputPrompt(IPCommandPromptHandler):
247 """ 248 Control over output prompt. 249 250 (See CommandPrompt.) 251 """ 252 _prompt = 'prompt_out' 253 basic_format = 'Out[\#]:' 254 _format = basic_format
255 256 ##### 257 258 259 260 # Use to define global constants 261 global_constants = {'pi':math.pi} 262 263 # Create the topographica parser. 264 usage = "usage: topographica ([<option>]:[<filename>])*\n\ 265 where any combination of options and Python script filenames will be\n\ 266 processed in order left to right." 267 topo_parser = OptionParser(usage=usage)
268 269 270 -def sim_name_from_filename(filename):
271 """ 272 Set the simulation title from the given filename, if none has been 273 set already. 274 """ 275 if topo.sim.name is None: 276 topo.sim.name=re.sub('.ty$','',os.path.basename(filename))
277
278 279 -def boolean_option_action(option,opt_str,value,parser):
280 """Callback function for boolean-valued options that apply to the entire run.""" 281 #print "Processing %s" % (opt_str) 282 setattr(parser.values,option.dest,True)
283
284 285 -def interactive():
286 os.environ['PYTHONINSPECT']='1'
287
288 # CB: note that topographica should stay open if an error occurs 289 # anywhere after a -i (i.e. in a -c command or script) 290 -def i_action(option,opt_str,value,parser):
291 """Callback function for the -i option.""" 292 boolean_option_action(option,opt_str,value,parser) 293 interactive()
294 295 topo_parser.add_option("-i","--interactive",action="callback",callback=i_action, 296 dest="interactive",default=False, 297 help="provide an interactive prompt even if stdin does not appear to be a terminal.")
298 299 300 -def v_action(option,opt_str,value,parser):
301 """Callback function for the -v option.""" 302 import param.parameterized 303 param.parameterized.min_print_level=param.parameterized.VERBOSE 304 print "Enabling verbose message output."
305 306 topo_parser.add_option("-v","--verbose",action="callback",callback=v_action,dest="verbose",default=False,help="""\ 307 enable verbose messaging output.""")
308 309 310 -def d_action(option,opt_str,value,parser):
311 """Callback function for the -d option.""" 312 import param.parameterized 313 param.parameterized.min_print_level=param.parameterized.DEBUG 314 print "Enabling debugging message output."
315 316 topo_parser.add_option("-d","--debug",action="callback",callback=d_action,dest="debug",default=False,help="""\ 317 enable debugging message output (implies --verbose).""")
318 319 320 321 -def l_action(option,opt_str,value,parser):
322 """Callback function for the -l option.""" 323 boolean_option_action(option,opt_str,value,parser) 324 from topo.misc.legacy import install_legacy_support 325 print "Enabling legacy support." 326 install_legacy_support()
327 328 topo_parser.add_option("-l","--legacy",action="callback",callback=l_action,dest="legacy",default=False,help="""\ 329 launch Topographica with legacy support enabled.""")
330 331 332 -def gui(start=True):
333 """Start the GUI as if -g were supplied in the command used to launch Topographica.""" 334 if matplotlib_imported: 335 rcParams['backend']='TkAgg' 336 auto_import_commands() 337 if start: 338 import topo.tkgui 339 topo.tkgui.start()
340
341 342 # Topographica stays open if an error occurs after -g 343 # (see comment by i_action) 344 -def g_action(option,opt_str,value,parser):
345 """Callback function for the -g option.""" 346 boolean_option_action(option,opt_str,value,parser) 347 interactive() 348 gui()
349 350 351 topo_parser.add_option("-g","--gui",action="callback",callback=g_action,dest="gui",default=False,help="""\ 352 launch an interactive graphical user interface; \ 353 equivalent to -c 'from topo.misc.commandline import gui ; gui()'. \ 354 Implies -a.""") 355 356 357 # Keeps track of whether something has been performed, when deciding whether to assume -i 358 something_executed=False
359 360 -def c_action(option,opt_str,value,parser):
361 """Callback function for the -c option.""" 362 #print "Processing %s '%s'" % (opt_str,value) 363 exec value in __main__.__dict__ 364 global something_executed 365 something_executed=True
366 367 topo_parser.add_option("-c","--command",action = "callback",callback=c_action,type="string", 368 default=[],dest="commands",metavar="\"<command>\"", 369 help="string of arbitrary Python code to be executed in the main namespace.")
370 371 372 373 -def p_action(option,opt_str,value,parser):
374 """Callback function for the -p option.""" 375 global_params.exec_in_context(value) 376 global something_executed 377 something_executed=True
378 379 topo_parser.add_option("-p","--set-parameter",action = "callback",callback=p_action,type="string", 380 default=[],dest="commands",metavar="\"<command>\"", 381 help="command specifying value(s) of script-level (global) Parameter(s).")
382 383 384 -def auto_import_commands():
385 """Import the contents of all files in the topo/command/ directory.""" 386 import re,os 387 import topo 388 import __main__ 389 390 # CEBALERT: this kind of thing (topo.__file__) won't work with 391 # py2exe and similar tools 392 topo_path = os.path.join(os.path.split(topo.__file__)[0],"command") 393 for f in os.listdir(topo_path): 394 if re.match('^[^_.].*\.py$',f): 395 modulename = re.sub('\.py$','',f) 396 exec "from topo.command."+modulename+" import *" in __main__.__dict__
397
398 -def a_action(option,opt_str,value,parser):
399 """Callback function for the -a option.""" 400 auto_import_commands()
401 402 topo_parser.add_option("-a","--auto-import-commands",action="callback",callback=a_action,help="""\ 403 import everything from commands/*.py into the main namespace, for convenience; \ 404 equivalent to -c 'from topo.misc.commandline import auto_import_commands ; auto_import_commands()'.""")
405 406 407 408 -def exec_startup_files():
409 """ 410 Execute startup files, looking at appropriate locations for many different platforms. 411 """ 412 home = os.path.expanduser("~") # dotfiles on unix 413 appdata = os.path.expandvars("$APPDATA") # application data on windows 414 appsupport = os.path.join(home,"Library","Application Support") # application support on OS X 415 416 rcpath = os.path.join(home,'.topographicarc') 417 inipath = os.path.join(appdata,'Topographica','topographica.ini') 418 configpath = os.path.join(appsupport,'Topographica','topographica.config') 419 420 for startup_file in (rcpath,configpath,inipath): 421 if os.path.exists(startup_file): 422 print "Executing user startup file %s" % (startup_file) 423 execfile(startup_file,__main__.__dict__)
424
425 426 ### Notes about choices for topographica.rc equivalents on different platforms 427 # 428 ## Windows: 429 # Location -- Most programs use the registry or a folder in %appdata% (which is typically 430 # ~\Application Data). The registry is not easily accessible for users, and %appdata% is 431 # a hidden folder, which means it doesn't appear in Explorer (or file-open dialogs). 432 # Most programs do not have any user-editable configuration files, so this does not matter 433 # to them. Maybe we should just use ~\topographica.ini? 434 # 435 # Name -- Considered topographica.rc, topographica.dat, topographica.cfg, topographica.ini. 436 # Of those, only .ini is registered as standard in Windows. According to Winows Explorer: 437 # "Files with extension 'INI' are of type 'Configuration Settings'" 438 # Importantly, this means they are already setup to be editable by notepad by default, so 439 # they can be double clicked. 440 # 441 # http://mail.python.org/pipermail/python-list/2005-September/341702.html 442 # 443 ## Mac OS: 444 # Location -- Seems like programs use either ~/Library/AppName or (more commonly) 445 # ~/Library/Application Support/AppName (CEBALERT: is there a var. for that on OS X?). 446 # 447 # Name -- there are many different extensions (e.g. dat, config, cfg, ini), none of which 448 # opens with any application by default. Some applications use xml. 449 450 451 452 453 ### Execute what is specified by the options. 454 455 -def process_argv(argv):
456 """ 457 Process command-line arguments (minus argv[0]!), rearrange and execute. 458 """ 459 # Initial preparation 460 import __main__ 461 for (k,v) in global_constants.items(): 462 exec '%s = %s' % (k,v) in __main__.__dict__ 463 464 exec_startup_files() 465 466 # Repeatedly process options, if any, followed by filenames, if any, until nothing is left 467 topo_parser.disable_interspersed_args() 468 args=argv 469 option=None 470 global something_executed 471 while True: 472 # Process options up until the first filename 473 (option,args) = topo_parser.parse_args(args,option) 474 475 # Handle filename 476 if args: 477 filename=args.pop(0) 478 #print "Executing %s" % (filename) 479 filedir = os.path.dirname(os.path.abspath(filename)) 480 sys.path.insert(0,filedir) # Allow imports relative to this file's path 481 sim_name_from_filename(filename) # Default value of topo.sim.name 482 483 execfile(filename,__main__.__dict__) 484 something_executed=True 485 486 if not args: 487 break 488 489 global_params.check_for_unused_names() 490 491 # If no scripts and no commands were given, pretend -i was given. 492 if not something_executed: interactive() 493 494 if option.gui: topo.guimain.title(topo.sim.name) 495 496 ## INTERACTIVE SESSION BEGINS HERE (i.e. can't have anything but 497 ## some kind of cleanup code afterwards) 498 if os.environ.get('PYTHONINSPECT'): 499 print BANNER 500 # CB: should probably allow a way for users to pass things to 501 # IPython? Or at least set up some kind of topographica ipython 502 # config file 503 504 if ipython_imported: 505 # Stop IPython namespace hack? 506 # http://www.nabble.com/__main__-vs-__main__-td14606612.html 507 __main__.__name__="__mynamespace__" 508 509 IPython.Shell.IPShell( 510 ['-noconfirm_exit','-nobanner', 511 '-pi1',CommandPrompt.get_format(), 512 '-pi2',CommandPrompt2.get_format(), 513 '-po',OutputPrompt.get_format()], 514 user_ns=__main__.__dict__).mainloop(sys_exit=1)
515