Package omero :: Module cli
[hide private]
[frames] | no frames]

Source Code for Module omero.cli

   1  #!/usr/bin/env python 
   2   
   3  """ 
   4  Python driver for OMERO 
   5   
   6  Provides access to various OMERO.blitz server- and client-side 
   7  utilities, including starting and stopping servers, running 
   8  analyses, configuration, and more. 
   9   
  10  Usable via the ./omero script provided with the distribution 
  11  as well as from python via "import omero.cli; omero.cli.argv()" 
  12   
  13  Arguments are taken from (in order of priority): the run method 
  14  arguments, sys.argv, and finally from standard-in using the 
  15  cmd.Cmd.cmdloop method. 
  16   
  17  Josh Moore, josh at glencoesoftware.com 
  18  Copyright (c) 2007, Glencoe Software, Inc. 
  19  See LICENSE for details. 
  20   
  21  """ 
  22   
  23  sys = __import__("sys") 
  24   
  25  import cmd, string, re, os, subprocess, socket, exceptions, traceback, glob, platform, time 
  26  import shlex 
  27  from exceptions import Exception as Exc 
  28  from threading import Thread, Lock 
  29  from path import path 
  30   
  31  from omero_ext.argparse import ArgumentError 
  32  from omero_ext.argparse import ArgumentParser 
  33  from omero_ext.argparse import FileType 
  34  from omero_ext.argparse import Namespace 
  35   
  36  # Help text 
  37  from omero_ext.argparse import ArgumentDefaultsHelpFormatter 
  38  from omero_ext.argparse import RawDescriptionHelpFormatter 
  39  from omero_ext.argparse import RawTextHelpFormatter 
  40  from omero_ext.argparse import SUPPRESS 
  41   
  42  from omero.util.concurrency import get_event 
  43  from omero.util.sessions import SessionsStore 
  44   
  45  import omero 
  46   
  47  # 
  48  # Static setup 
  49  # 
  50   
  51  try: 
  52      from omero_version import omero_version 
  53      VERSION=omero_version 
  54  except ImportError: 
  55      VERSION="Unknown" # Usually during testing 
  56   
  57  DEBUG = 0 
  58  if os.environ.has_key("DEBUG"): 
  59      try: 
  60          DEBUG = int(os.environ["DEBUG"]) 
  61      except ValueError: 
  62          DEBUG = 1 
  63      print "Deprecated warning: use the 'bin/omero --debug=x [args]' to debug" 
  64      print "Running omero with debugging == 1" 
  65   
  66  OMERODOC = """ 
  67  Command-line tool for local and remote interactions with OMERO. 
  68  """ 
  69  OMEROSHELL = """OMERO Python Shell. Version %s""" % str(VERSION) 
  70  OMEROHELP = """Type "help" for more information, "quit" or Ctrl-D to exit""" 
  71  OMEROSUBS = """Use %(prog)s <subcommand> -h for more information.""" 
  72  OMEROSUBM = """<subcommand>""" 
  73  OMEROCLI = path(__file__).expand().dirname() 
  74  OMERODIR = os.getenv('OMERODIR', None) 
  75  if OMERODIR is not None: 
  76      OMERODIR = path(OMERODIR) 
  77  else: 
  78      OMERODIR = OMEROCLI.dirname().dirname().dirname() 
  79   
  80  COMMENT = re.compile("^\s*#") 
  81  RELFILE = re.compile("^\w") 
  82  LINEWSP = re.compile("^\s*\w+\s+") 
  83   
  84  # 
  85  # Possibilities: 
  86  #  - Always return and print any output 
  87  #  - Have a callback on the fired event 
  88  #  - how should state machine work? 
  89  #   -- is the last control stored somwhere? in a stack history[3] 
  90  #   -- or do they all share a central memory? self.ctx["MY_VARIABLE"] 
  91  #  - In almost all cases, mark a flag in the CLI "lastError" and continue, 
  92  #    allowing users to do something of the form: on_success or on_fail 
  93   
  94   
  95  ##################################################### 
  96  # 
  97  # Exceptions 
  98  # 
99 -class NonZeroReturnCode(Exc):
100 - def __init__(self, rv, *args):
101 self.rv = rv 102 Exc.__init__(self, *args)
103 104 105 ##################################################### 106 # 107
108 -class HelpFormatter(RawTextHelpFormatter):
109 """ 110 argparse.HelpFormatter subclass which cleans up our usage, preventing very long 111 lines in subcommands. 112 """ 113
114 - def __init__(self, prog, indent_increment=2, max_help_position=40, width=None):
115 RawTextHelpFormatter.__init__(self, prog, indent_increment, max_help_position, width) 116 self._action_max_length = 20
117
118 - def _split_lines(self, text, width):
119 return [text.splitlines()[0]]
120
121 - class _Section(RawTextHelpFormatter._Section):
122
123 - def __init__(self, formatter, parent, heading=None):
124 #if heading: 125 # heading = "\n%s\n%s" % ("=" * 40, heading) 126 RawTextHelpFormatter._Section.__init__(self, formatter, parent, heading)
127 128
129 -class WriteOnceNamespace(Namespace):
130 """ 131 Namespace subclass which prevents overwriting any values by accident. 132 """
133 - def __setattr__(self, name, value):
134 if hasattr(self, name): 135 raise exceptions.Exception("%s already has field %s" % (self.__class__.__name__, name)) 136 else: 137 return Namespace.__setattr__(self, name, value)
138 139
140 -class Parser(ArgumentParser):
141 """ 142 Extension of ArgumentParser for simplifying the 143 _configure() code in most Controls 144 """ 145
146 - def __init__(self, *args, **kwargs):
147 kwargs["formatter_class"] = HelpFormatter 148 ArgumentParser.__init__(self, *args, **kwargs) 149 self._positionals.title = "Positional Arguments" 150 self._optionals.title = "Optional Arguments" 151 self._optionals.description = "In addition to any higher level options"
152
153 - def sub(self):
154 return self.add_subparsers(title = "Subcommands", description = OMEROSUBS, metavar = OMEROSUBM)
155
156 - def add(self, sub, func, help, **kwargs):
157 parser = sub.add_parser(func.im_func.__name__, help=help, description=help) 158 parser.set_defaults(func=func, **kwargs) 159 return parser
160
161 - def _check_value(self, action, value):
162 # converted value must be one of the choices (if specified) 163 if action.choices is not None and value not in action.choices: 164 msg = 'invalid choice: %r\n\nchoose from:\n' % value 165 choices = sorted(action.choices) 166 msg += self._format_list(choices) 167 raise ArgumentError(action, msg)
168
169 - def _format_list(self, choices):
170 lines = ["\t"] 171 if choices: 172 while len(choices) > 1: 173 choice = choices.pop(0) 174 lines[-1] += ("%s, " % choice) 175 if len(lines[-1]) > 62: 176 lines.append("\t") 177 lines[-1] += choices.pop(0) 178 return "\n".join(lines)
179
180 -class NewFileType(FileType):
181 """ 182 Extension of the argparse.FileType to prevent 183 overwrite existing files. 184 """
185 - def __call__(self, string):
186 if os.path.exists(string): 187 raise ValueError("File exists: %s" % string) 188 return FileType.__call__(self, string)
189 190
191 -class DirectoryType(FileType):
192 """ 193 Extension of the argparse.FileType to only allow 194 existing directories. 195 """
196 - def __call__(self, string):
197 p = path(string) 198 if not p.exists(): 199 raise ValueError("Directory does not exist: %s" % string) 200 elif not p.isdir(): 201 raise ValueError("Path is not a directory: %s" % string) 202 return str(p.abspath())
203 204
205 -class Context:
206 """Simple context used for default logic. The CLI registry which registers 207 the plugins installs itself as a fully functional Context. 208 209 The Context class is designed to increase pluggability. Rather than 210 making calls directly on other plugins directly, the pub() method 211 routes messages to other commands. Similarly, out() and err() should 212 be used for printing statements to the user, and die() should be 213 used for exiting fatally. 214 215 """ 216
217 - def __init__(self, controls = {}, params = {}, prog = sys.argv[0]):
218 self.event = get_event(name="CLI") 219 self.params = {} 220 self.controls = controls 221 self.dir = OMERODIR 222 self.isdebug = DEBUG # This usage will go away and default will be False 223 self.topics = {"debug":""" 224 225 debug options for developers: 226 227 The value to the debug argument is a comma-separated list of commands: 228 229 * 'debug' prints at the "debug" level. Similar to setting DEBUG=1 in the environment. 230 * 'trace' runs the command with tracing enabled. 231 * 'profile' runs the command with profiling enabled. 232 233 Only one of "trace" and "profile" can be chosen. 234 235 Example: 236 237 bin/omero --debug=debug,trace admin start # Debugs at level 1 and prints tracing 238 bin/omero -d1 admin start # Debugs at level 1 239 bin/omero -dp admin start # Prints profiling 240 bin/omero -dt,p admin start # Fails!; can't print tracing and profiling together 241 bin/omero -d0 admin start # Disables debugging 242 """} 243 self.parser = Parser(prog = prog, description = OMERODOC) 244 self.subparsers = self.parser_init(self.parser)
245
246 - def post_process(self):
247 """ 248 Runs further processing once all the controls have been added. 249 """ 250 sessions = self.controls["sessions"] 251 252 login = self.subparsers.add_parser("login", help="Shortcut for 'sessions login'") 253 login.set_defaults(func=lambda args:sessions.login(args)) 254 self.add_login(login) 255 sessions._configure_login(login) 256 257 logout = self.subparsers.add_parser("logout", help="Shortcut for 'sessions logout'") 258 logout.set_defaults(func=lambda args:self.controls["sessions"].logout(args))
259
260 - def add_login(self, parser):
261 parser.add_argument("-C", "--create", action="store_true", help="Create a new session regardless of existing ones") 262 parser.add_argument("-s", "--server") 263 parser.add_argument("-p", "--port") 264 parser.add_argument("-g", "--group") 265 parser.add_argument("-u", "--user") 266 parser.add_argument("-w", "--password") 267 parser.add_argument("-k", "--key", help="UUID of an active session")
268
269 - def parser_init(self, parser):
270 parser.add_argument("-v", "--version", action="version", version="%%(prog)s %s" % VERSION) 271 parser.add_argument("-d", "--debug", help="Use 'help debug' for more information", default = SUPPRESS) 272 parser.add_argument("--path", help="Add file or directory to plugin list. Supports globs.", action = "append") 273 self.add_login(parser) 274 subparsers = parser.add_subparsers(title="Subcommands", description=OMEROSUBS, metavar=OMEROSUBM) 275 return subparsers
276
277 - def get(self, key, defvalue = None):
278 return self.params.get(key, defvalue)
279
280 - def set(self, key, value = True):
281 self.params[key] = value
282
283 - def safePrint(self, text, stream, newline = True):
284 """ 285 Prints text to a given string, caputring any exceptions. 286 """ 287 try: 288 stream.write(str(text) % {"program_name": sys.argv[0]}) 289 if newline: 290 stream.write("\n") 291 else: 292 stream.flush() 293 except: 294 print >>sys.stderr, "Error printing text" 295 print >>sys.stdout, text 296 if self.isdebug: 297 traceback.print_exc()
298
299 - def pythonpath(self):
300 """ 301 Converts the current sys.path to a PYTHONPATH string 302 to be used by plugins which must start a new process. 303 304 Note: this was initially created for running during 305 testing when PYTHONPATH is not properly set. 306 """ 307 path = list(sys.path) 308 for i in range(0,len(path)-1): 309 if path[i] == '': 310 path[i] = os.getcwd() 311 pythonpath = ":".join(path) 312 return pythonpath
313
314 - def userdir(self):
315 """ 316 Returns a user directory (as path.path) which can be used 317 for storing configuration. The directory is guaranteed to 318 exist and be private (700) after execution. 319 """ 320 dir = path(os.path.expanduser("~")) / "omero" / "cli" 321 if not dir.exists(): 322 dir.mkdir() 323 elif not dir.isdir(): 324 raise Exc("%s is not a directory"%dir) 325 dir.chmod(0700) 326 return dir
327
328 - def pub(self, args, strict = False):
329 self.safePrint(str(args), sys.stdout)
330
331 - def input(self, prompt, hidden = False, required = False):
332 """ 333 Reads from standard in. If hidden == True, then 334 uses getpass 335 """ 336 try: 337 while True: 338 if hidden: 339 import getpass 340 defuser = getpass.getuser() 341 rv = getpass.getpass(prompt) 342 else: 343 rv = raw_input(prompt) 344 if required and not rv: 345 self.out("Input required") 346 continue 347 return rv 348 except KeyboardInterrupt: 349 self.die(1, "Cancelled")
350
351 - def out(self, text, newline = True):
352 """ 353 Expects as single string as argument" 354 """ 355 self.safePrint(text, sys.stdout, newline)
356
357 - def err(self, text, newline = True):
358 """ 359 Expects a single string as argument. 360 """ 361 self.safePrint(text, sys.stderr, newline)
362
363 - def dbg(self, text, newline = True, level = 1):
364 """ 365 Passes text to err() if self.isdebug is set 366 """ 367 if self.isdebug >= level: 368 self.err(text, newline)
369
370 - def die(self, rc, args):
371 raise exceptions.Exception((rc,args))
372
373 - def exit(self, args):
374 self.out(args) 375 self.interrupt_loop = True
376
377 - def call(self, args):
378 self.out(str(args))
379
380 - def popen(self, args):
381 self.out(str(args))
382
383 - def sleep(self, time):
384 self.event.wait(time)
385 386 ##################################################### 387 #
388 -class BaseControl:
389 """Controls get registered with a CLI instance on loadplugins(). 390 391 To create a new control, subclass BaseControl, implement _configure, 392 and end your module with:: 393 394 try: 395 register("name", MyControl, HELP) 396 except: 397 if __name__ == "__main__": 398 cli = CLI() 399 cli.register("name", MyControl, HELP) 400 cli.invoke(sys.argv[1:]) 401 402 This module should be put in the omero.plugins package. 403 404 All methods which do NOT begin with "_" are assumed to be accessible 405 to CLI users. 406 """ 407 408 ############################################### 409 # 410 # Mostly reusable code 411 #
412 - def __init__(self, ctx = None, dir = OMERODIR):
413 self.dir = path(dir) # Guaranteed to be a path 414 self.ctx = ctx 415 if self.ctx is None: 416 self.ctx = Context() # Prevents unncessary stop_event creation
417
418 - def _isWindows(self):
419 p_s = platform.system() 420 if p_s == 'Windows': 421 return True 422 else: 423 return False
424
425 - def _host(self):
426 """ 427 Return hostname of current machine. Termed to be the 428 value return from socket.gethostname() up to the first 429 decimal. 430 """ 431 if not hasattr(self, "hostname") or not self.hostname: 432 self.hostname = socket.gethostname() 433 if self.hostname.find(".") > 0: 434 self.hostname = self.hostname.split(".")[0] 435 return self.hostname
436
437 - def _node(self, omero_node = None):
438 """ 439 Return the name of this node, using either the environment 440 vairable OMERO_NODE or _host(). Some subclasses may 441 override this functionality, most notably "admin" commands 442 which assume a node name of "master". 443 444 If the optional argument is not None, then the OMERO_NODE 445 environment variable will be set. 446 """ 447 if omero_node != None: 448 os.environ["OMERO_NODE"] = omero_node 449 450 if os.environ.has_key("OMERO_NODE"): 451 return os.environ["OMERO_NODE"] 452 else: 453 return self._host()
454
455 - def _icedata(self, property):
456 """ 457 General data method for creating a path from an Ice property. 458 """ 459 try: 460 nodepath = self._properties()[property] 461 462 if RELFILE.match(nodepath): 463 nodedata = self.dir / path(nodepath) 464 else: 465 nodedata = path(nodepath) 466 467 created = False 468 if not nodedata.exists(): 469 self.ctx.out("Creating "+nodedata) 470 nodedata.makedirs() 471 created = True 472 return (nodedata, created) 473 474 except KeyError, ke: 475 self.ctx.err(property + " is not configured") 476 self.ctx.die(4, str(ke))
477
478 - def _initDir(self):
479 """ 480 Initialize the directory into which the current node will log. 481 """ 482 props = self._properties() 483 nodedata = self._nodedata() 484 logdata = self.dir / path(props["Ice.StdOut"]).dirname() 485 if not logdata.exists(): 486 self.ctx.out("Initializing %s" % logdata) 487 logdata.makedirs()
488 489
490 - def _nodedata(self):
491 """ 492 Returns the data directory path for this node. This is determined 493 from the "IceGrid.Node.Data" property in the _properties() 494 map. 495 496 The directory will be created if it does not exist. 497 """ 498 data, created = self._icedata("IceGrid.Node.Data") 499 return data
500
501 - def _regdata(self):
502 """ 503 Returns the data directory for the IceGrid registry. 504 This is determined from the "IceGrid.Registry.Data" property 505 in the _properties() map. 506 507 The directory will be created if it does not exist, and 508 a warning issued. 509 """ 510 data, created = self._icedata("IceGrid.Registry.Data")
511
512 - def _pid(self):
513 """ 514 Returns a path of the form "_nodedata() / _node() + ".pid", 515 i.e. a file named NODENAME.pid in the node's data directory. 516 """ 517 pidfile = self._nodedata() / (self._node() + ".pid") 518 return pidfile
519
520 - def _cfglist(self):
521 """ 522 Returns a list of configuration files for this node. This 523 defaults to the internal configuration for all nodes, 524 followed by a file named NODENAME.cfg under the etc/ 525 directory, following by PLATFORM.cfg if it exists. 526 """ 527 cfgs = self.dir / "etc" 528 internal = cfgs / "internal.cfg" 529 owncfg = cfgs / self._node() + ".cfg" 530 results = [internal,owncfg] 531 # Look for <platform>.cfg 532 p_s = platform.system() 533 p_c = cfgs / p_s + ".cfg" 534 if p_c.exists(): 535 results.append(p_c) 536 return results
537
538 - def _icecfg(self):
539 """ 540 Uses _cfglist() to return a string argument of the form 541 "--Ice.Config=..." suitable for passing to omero.client 542 as an argument. 543 """ 544 icecfg = "--Ice.Config=%s" % ",".join(self._cfglist()) 545 return str(icecfg)
546
547 - def _intcfg(self):
548 """ 549 Returns an Ice.Config string with only the internal configuration 550 file for connecting to the IceGrid Locator. 551 """ 552 intcfg = self.dir / "etc" / "internal.cfg" 553 intcfg.abspath() 554 return str("--Ice.Config=%s" % intcfg)
555
556 - def _properties(self, prefix=""):
557 """ 558 Loads all files returned by _cfglist() into a new 559 Ice.Properties instance and return the map from 560 getPropertiesForPrefix(prefix) where the default is 561 to return all properties. 562 """ 563 import Ice 564 if not hasattr(self, "_props") or self._props == None: 565 self._props = Ice.createProperties() 566 for cfg in self._cfglist(): 567 try: 568 self._props.load(str(cfg)) 569 except Exc, exc: 570 self.ctx.die(3, "Could not find file: "+cfg + "\nDid you specify the proper node?") 571 return self._props.getPropertiesForPrefix(prefix)
572
573 - def _ask_for_password(self, reason = "", root_pass = None):
574 while not root_pass or len(root_pass) < 1: 575 root_pass = self.ctx.input("Please enter password%s: "%reason, hidden = True) 576 if root_pass == None or root_pass == "": 577 self.ctx.err("Password cannot be empty") 578 continue 579 confirm = self.ctx.input("Please re-enter password%s: "%reason, hidden = True) 580 if root_pass != confirm: 581 root_pass = None 582 self.ctx.err("Passwords don't match") 583 continue 584 break 585 return root_pass
586 587 ############################################### 588 # 589 # Methods likely to be implemented by subclasses 590 # 591
592 - def _complete_file(self, f, dir = None):
593 """ 594 f: path part 595 """ 596 if dir is None: 597 dir = self.dir 598 else: 599 dir = path(dir) 600 p = path(f) 601 if p.exists() and p.isdir(): 602 if not f.endswith(os.sep): 603 return [p.basename()+os.sep] 604 return [ str(x)[len(f):] for x in p.listdir() ] 605 else: 606 results = [ str(x.basename()) for x in dir.glob(f+"*") ] 607 if len(results) == 1: 608 # Relative to cwd 609 maybe_dir = path(results[0]) 610 if maybe_dir.exists() and maybe_dir.isdir(): 611 return [ results[0] + os.sep ] 612 return results
613
614 - def _complete(self, text, line, begidx, endidx):
615 try: 616 return self._complete2(text, line, begidx, endidx) 617 except: 618 self.ctx.dbg("Complete error: %s" % traceback.format_exc())
619
620 - def _complete2(self, text, line, begidx, endidx):
621 items = shlex.split(line) 622 parser = getattr(self, "parser", None) 623 if parser: 624 result = [] 625 actions = getattr(parser, "_actions") 626 if actions: 627 if len(items) > 1: 628 subparsers = [x for x in actions if x.__class__.__name__ == "_SubParsersAction"] 629 if subparsers: 630 subparsers = subparsers[0] # Guaranteed one 631 choice = subparsers.choices.get(items[-1]) 632 if choice and choice._actions: 633 actions = choice._actions 634 if len(items) > 2: 635 actions = [] # TBD 636 637 for action in actions: 638 if action.__class__.__name__ == "_HelpAction": 639 result.append("-h") 640 elif action.__class__.__name__ == "_SubParsersAction": 641 result.extend(action.choices) 642 643 return ["%s " % x for x in result if (not text or x.startswith(text)) and line.find(" %s " % x) < 0] 644 645 # Fallback 646 completions = [method for method in dir(self) if callable(getattr(self, method)) ] 647 return [ str(method + " ") for method in completions if method.startswith(text) and not method.startswith("_") ]
648 649
650 -class CLI(cmd.Cmd, Context):
651 """ 652 Command line interface class. Supports various styles of executing the 653 registered plugins. Each plugin is given the chance to update this class 654 by adding methods of the form "do_<plugin name>". 655 """ 656
657 - class PluginsLoaded(object):
658 """ 659 Thread-safe class for storing whether or not all the plugins 660 have been loaded 661 """
662 - def __init__(self):
663 self.lock = Lock() 664 self.done = False
665 - def get(self):
666 self.lock.acquire() 667 try: 668 return self.done 669 finally: 670 self.lock.release()
671 - def set(self):
672 self.lock.acquire() 673 try: 674 self.done = True 675 finally: 676 self.lock.release()
677
678 - def __init__(self, prog = sys.argv[0]):
679 """ 680 Also sets the "_client" field for this instance to None. Each cli 681 maintains a single active client. The "session" plugin is responsible 682 for the loading of the client object. 683 """ 684 cmd.Cmd.__init__(self) 685 Context.__init__(self, prog = prog) 686 self.prompt = 'omero> ' 687 self.interrupt_loop = False 688 self.rv = 0 #: Return value to be returned 689 self._stack = [] #: List of commands being processed 690 self._client = None #: Single client for all activities 691 self._plugin_paths = [OMEROCLI / "plugins"] #: Paths to be loaded; initially official plugins 692 self._pluginsLoaded = CLI.PluginsLoaded()
693
694 - def assertRC(self):
695 if self.rv != 0: 696 raise NonZeroReturnCode(self.rv, "assert failed")
697
698 - def invoke(self, line, strict = False, previous_args = None):
699 """ 700 Copied from cmd.py 701 """ 702 try: 703 line = self.precmd(line) 704 stop = self.onecmd(line, previous_args) 705 stop = self.postcmd(stop, line) 706 if strict: 707 self.assertRC() 708 finally: 709 if len(self._stack) == 0: 710 self.close() 711 else: 712 self.dbg("Delaying close for stack: %s" % len(self._stack), level = 2)
713
714 - def invokeloop(self):
715 # First we add a few special commands to the loop 716 class PWD(BaseControl): 717 def __call__(self, args): 718 self.ctx.out(os.getcwd())
719 class LS(BaseControl): 720 def __call__(self, args): 721 for p in sorted(path(os.getcwd()).listdir()): 722 self.ctx.out(str(p.basename())) 723 class CD(BaseControl): 724 def _complete(self, text, line, begidx, endidx): 725 RE = re.compile("\s*cd\s*") 726 m = RE.match(line) 727 if m: 728 replaced = RE.sub('', line) 729 return self._complete_file(replaced, path(os.getcwd())) 730 return [] 731 def _configure(self, parser): 732 parser.set_defaults(func=self.__call__) 733 parser.add_argument("dir", help = "Target directory") 734 def __call__(self, args): 735 os.chdir(args.dir) 736 self.register("pwd", PWD, "Print the current directory") 737 self.register("ls", LS, "Print files in the current directory") 738 self.register("dir", LS, "Alias for 'ls'") 739 self.register("cd", CD, "Change the current directory") 740 741 try: 742 self.selfintro = "\n".join([OMEROSHELL, OMEROHELP]) 743 if not self.stdin.isatty(): 744 self.selfintro = "" 745 self.prompt = "" 746 while not self.interrupt_loop: 747 try: 748 # Calls the same thing as invoke 749 self.cmdloop(self.selfintro) 750 except KeyboardInterrupt, ki: 751 self.selfintro = "" 752 self.out("Use quit to exit") 753 finally: 754 self.close() 755
756 - def postloop(self):
757 # We've done the intro once now. Don't repeat yourself. 758 self.selfintro = ""
759
760 - def onecmd(self, line, previous_args = None):
761 """ 762 Single command logic. Overrides the cmd.Cmd logic 763 by calling execute. Also handles various exception 764 conditions. 765 """ 766 try: 767 # Starting a new command. Reset the return value to 0 768 # If err or die are called, set rv non-0 value 769 self.rv = 0 770 try: 771 self._stack.insert(0, line) 772 self.dbg("Stack+: %s" % len(self._stack), level=2) 773 self.execute(line, previous_args) 774 return True 775 finally: 776 self._stack.pop(0) 777 self.dbg("Stack-: %s" % len(self._stack), level=2) 778 except SystemExit, exc: # Thrown by argparse 779 self.dbg("SystemExit raised\n%s" % traceback.format_exc()) 780 self.rv = exc.code 781 return False 782 # 783 # This was perhaps only needed previously 784 # Omitting for the moment with the new 785 # argparse refactoring 786 # 787 #except AttributeError, ae: 788 # self.err("Possible error in plugin:") 789 # self.err(str(ae)) 790 # if self.isdebug: 791 # traceback.print_exc() 792 except NonZeroReturnCode, nzrc: 793 self.dbg(traceback.format_exc()) 794 self.rv = nzrc.rv 795 return False # Continue
796
797 - def postcmd(self, stop, line):
798 """ 799 Checks interrupt_loop for True and return as much 800 which will end the call to cmdloop. Otherwise use 801 the default postcmd logic (which simply returns stop) 802 """ 803 if self.interrupt_loop: 804 return True 805 return cmd.Cmd.postcmd(self, stop, line)
806
807 - def execute(self, line, previous_args):
808 """ 809 String/list handling as well as EOF and comment handling. 810 Otherwise, parses the arguments as shlexed and runs the 811 function returned by argparse. 812 """ 813 814 if isinstance(line, (str, unicode)): 815 if COMMENT.match(line): 816 return # EARLY EXIT! 817 args = shlex.split(line) 818 elif isinstance(line, (tuple, list)): 819 args = list(line) 820 else: 821 self.die(1, "Bad argument type: %s ('%s')" % (type(line), line)) 822 823 if not args: 824 return 825 elif args == ["EOF"]: 826 self.exit("") 827 return 828 829 args = self.parser.parse_args(args, previous_args) 830 args.prog = self.parser.prog 831 self.waitForPlugins() 832 833 debug_str = getattr(args, "debug", "") 834 debug_opts = set([x.lower() for x in debug_str.split(",")]) 835 if "" in debug_opts: 836 debug_opts.remove("") 837 838 old_debug = self.isdebug 839 if "debug" in debug_opts: 840 self.isdebug = 1 841 debug_opts.remove("debug") 842 elif "0" in debug_opts: 843 self.isdebug = 0 844 debug_opts.remove("0") 845 846 for x in range(1, 9): 847 if str(x) in debug_opts: 848 self.isdebug = x 849 debug_opts.remove(str(x)) 850 851 try: 852 if len(debug_opts) == 0: 853 args.func(args) 854 elif len(debug_opts) > 1: 855 self.die(9, "Conflicting debug options: %s" % ", ".join(debug_opts)) 856 elif "t" in debug_opts or "trace" in debug_opts: 857 import trace 858 tracer = trace.Trace() 859 tracer.runfunc(args.func, args) 860 elif "p" in debug_opts or "profile" in debug_opts: 861 import hotshot 862 from hotshot import stats 863 prof = hotshot.Profile("hotshot_edi_stats") 864 rv = prof.runcall( lambda: args.func(args) ) 865 prof.close() 866 s = stats.load("hotshot_edi_stats") 867 s.sort_stats("time").print_stats() 868 else: 869 self.die(10, "Unknown debug action: %s" % debug_opts) 870 finally: 871 self.isdebug = old_debug
872
873 - def completedefault(self, *args):
874 return []
875
876 - def completenames(self, text, line, begidx, endidx):
877 names = self.controls.keys() 878 return [ str(n + " ") for n in names if n.startswith(line) ]
879 880 ########################################## 881 ## 882 ## Context interface 883 ##
884 - def exit(self, args, newline=True):
885 self.out(args, newline) 886 self.interrupt_loop = True
887
888 - def die(self, rc, text, newline=True):
889 self.err(text, newline) 890 self.rv = rc 891 # self.interrupt_loop = True 892 raise NonZeroReturnCode(rc, "die called: %s" % text)
893
894 - def _env(self):
895 """ 896 Configure environment with PYTHONPATH as 897 setup by bin/omero 898 899 This list needs to be kept in line with OmeroPy/bin/omero 900 901 """ 902 lpy = str(self.dir / "lib" / "python") 903 ipy = str(self.dir / "lib" / "fallback") 904 vlb = str(self.dir / "var" / "lib") 905 paths = os.path.pathsep.join([lpy, vlb, ipy]) 906 907 env = dict(os.environ) 908 pypath = env.get("PYTHONPATH", None) 909 if pypath is None: 910 pypath = paths 911 else: 912 if pypath.endswith(os.path.pathsep): 913 pypath = "%s%s" % (pypath, paths) 914 else: 915 pypath = "%s%s%s" % (pypath, os.path.pathsep, paths) 916 env["PYTHONPATH"] = pypath 917 return env
918
919 - def _cwd(self, cwd):
920 if cwd is None: 921 cwd = str(OMERODIR) 922 else: 923 cwd = str(cwd) 924 return cwd
925
926 - def call(self, args, strict = True, cwd = None):
927 """ 928 Calls the string in a subprocess and dies if the return value is not 0 929 """ 930 self.dbg("Executing: %s" % args) 931 rv = subprocess.call(args, env = self._env(), cwd = self._cwd(cwd)) 932 if strict and not rv == 0: 933 raise NonZeroReturnCode(rv, "%s => %d" % (" ".join(args), rv)) 934 return rv
935
936 - def popen(self, args, cwd = None, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **kwargs):
937 self.dbg("Returning popen: %s" % args) 938 env = self._env() 939 env.update(kwargs) 940 return subprocess.Popen(args, env = env, cwd = self._cwd(cwd), stdout = stdout, stderr = stderr)
941
942 - def readDefaults(self):
943 try: 944 f = path(OMERODIR) / "etc" / "omero.properties" 945 f = f.open() 946 output = "".join(f.readlines()) 947 f.close() 948 except: 949 if self.isdebug: 950 raise 951 print "No omero.properties found" 952 output = "" 953 return output
954
955 - def parsePropertyFile(self, data, output):
956 for line in output.splitlines(): 957 if line.startswith("Listening for transport dt_socket at address"): 958 self.dbg("Ignoring stdout 'Listening for transport' from DEBUG=1") 959 continue 960 parts = line.split("=",1) 961 if len(parts) == 2: 962 data.properties.setProperty(parts[0],parts[1]) 963 self.dbg("Set property: %s=%s" % (parts[0],parts[1]) ) 964 else: 965 self.dbg("Bad property:"+str(parts)) 966 return data
967
968 - def initData(self, properties={}):
969 """ 970 Uses "omero prefs" to create an Ice.InitializationData(). 971 """ 972 from omero.plugins.prefs import getprefs 973 try: 974 output = getprefs(["get"], str(OMERODIR / "lib")) 975 except OSError, err: 976 self.err("Error getting preferences") 977 self.dbg(err) 978 output = "" 979 980 import Ice 981 data = Ice.InitializationData() 982 data.properties = Ice.createProperties() 983 for k,v in properties.items(): 984 data.properties.setProperty(k,v) 985 self.parsePropertyFile(data, output) 986 return data
987
988 - def conn(self, args = None):
989 """ 990 Returns any active _client object. If one is present but 991 not alive, it will be removed. 992 993 If no client is found and arguments are available, 994 will use the current settings to connect. 995 996 If required attributes are missing, will delegate to the login command. 997 998 FIXME: Currently differing setting sessions on the same CLI instance 999 will misuse a client. 1000 """ 1001 if self._client: 1002 self.dbg("Found client") 1003 try: 1004 self._client.getSession().keepAlive(None) 1005 self.dbg("Using client") 1006 return self._client 1007 except KeyboardInterrupt: 1008 raise 1009 except exceptions.Exception, e: 1010 self.dbg("Removing client: %s" % e) 1011 self._client.closeSession() 1012 self._client = None 1013 1014 if args is not None: 1015 self.controls["sessions"].login(args) 1016 1017 return self._client # Possibly added by "login"
1018
1019 - def close(self):
1020 client = self._client 1021 if client: 1022 self.dbg("Closing client: %s" % client) 1023 client.__del__()
1024 1025 ## 1026 ## Plugin registry 1027 ## 1028
1029 - def register(self, name, Control, help):
1030 self.register_only(name, Control, help) 1031 self.configure_plugins()
1032
1033 - def register_only(self, name, Control, help):
1034 """ This method is added to the globals when execfile() is 1035 called on each plugin. A Control class should be 1036 passed to the register method which will be added to the CLI. 1037 """ 1038 self.controls[name] = (Control, help)
1039
1040 - def configure_plugins(self):
1041 """ 1042 Run to instantiate and configure all plugins 1043 which were registered via register_only() 1044 """ 1045 for name in sorted(self.controls): 1046 control = self.controls[name] 1047 if isinstance(control, tuple): 1048 Control = control[0] 1049 help = control[1] 1050 control = Control(ctx = self, dir = self.dir) 1051 self.controls[name] = control 1052 setattr(self, "complete_%s" % name, control._complete) 1053 parser = self.subparsers.add_parser(name, help=help) 1054 parser.description = help 1055 if hasattr(control, "_configure"): 1056 control._configure(parser) 1057 elif hasattr(control, "__call__"): 1058 parser.set_defaults(func=control.__call__) 1059 control.parser = parser
1060
1061 - def waitForPlugins(self):
1062 if True: 1063 return # Disabling. See comment in argv 1064 self.dbg("Starting waitForPlugins") 1065 while not self._pluginsLoaded.get(): 1066 self.dbg("Waiting for plugins...") 1067 time.sleep(0.1)
1068
1069 - def loadplugins(self):
1070 """ 1071 Finds all plugins and gives them a chance to register 1072 themselves with the CLI instance. Here register_only() 1073 is used to guarantee the orderedness of the plugins 1074 in the parser 1075 """ 1076 1077 for plugin_path in self._plugin_paths: 1078 self.loadpath(path(plugin_path)) 1079 1080 self.configure_plugins() 1081 self._pluginsLoaded.set() 1082 self.post_process()
1083
1084 - def loadpath(self, pathobj):
1085 if pathobj.isdir(): 1086 for plugin in pathobj.walkfiles("*.py"): 1087 if -1 == plugin.find("#"): # Omit emacs files 1088 self.loadpath(path(plugin)) 1089 else: 1090 if self.isdebug: 1091 print "Loading %s" % pathobj 1092 try: 1093 loc = {"register": self.register_only} 1094 execfile( str(pathobj), loc ) 1095 except KeyboardInterrupt: 1096 raise 1097 except: 1098 self.err("Error loading: %s" % pathobj) 1099 traceback.print_exc()
1100 1101 ## End Cli 1102 ########################################################### 1103
1104 -def argv(args=sys.argv):
1105 """ 1106 Main entry point for the OMERO command-line interface. First 1107 loads all plugins by passing them the classes defined here 1108 so they can register their methods. 1109 1110 Then the case where arguments are passed on the command line are 1111 handled. 1112 1113 Finally, the cli enters a command loop reading from standard in. 1114 """ 1115 1116 # Modiying the run-time environment 1117 old_ice_config = os.getenv("ICE_CONFIG") 1118 os.unsetenv("ICE_CONFIG") 1119 try: 1120 1121 # Modifying the args list if the name of the file 1122 # has arguments encoded in it 1123 original_executable = path(args[0]) 1124 base_executable = str(original_executable.basename()) 1125 if base_executable.find("-") >= 0: 1126 parts = base_executable.split("-") 1127 for arg in args[1:]: 1128 parts.append(arg) 1129 args = parts 1130 1131 # Now load other plugins. After debugging is turned on, but before tracing. 1132 cli = CLI(prog = original_executable.split("-")[0]) 1133 1134 parser = Parser(add_help = False) 1135 #parser.add_argument("-d", "--debug", help="Use 'help debug' for more information", default = SUPPRESS) 1136 parser.add_argument("--path", help="Add file or directory to plugin list. Supports globs.", action = "append") 1137 ns, args = parser.parse_known_args(args) 1138 if getattr(ns, "path"): 1139 for p in ns.path: 1140 for g in glob.glob(p): 1141 cli._plugin_paths.append(g) 1142 1143 class PluginLoader(Thread): 1144 def run(self): 1145 cli.loadplugins()
1146 # Disabling background loading 1147 # until 2.4 hangs are fixed 1148 PluginLoader().run() # start() 1149 1150 if len(args) > 1: 1151 cli.invoke(args[1:]) 1152 return cli.rv 1153 else: 1154 cli.invokeloop() 1155 return cli.rv 1156 finally: 1157 if old_ice_config: 1158 os.putenv("ICE_CONFIG", old_ice_config) 1159