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=32, 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 while len(choices) > 1: 172 choice = choices.pop(0) 173 lines[-1] += ("%s, " % choice) 174 if len(lines[-1]) > 62: 175 lines.append("\t") 176 lines[-1] += choices.pop(0) 177 return "\n".join(lines)
178
179 -class NewFileType(FileType):
180 """ 181 Extension of the argparse.FileType to prevent 182 overwrite existing files. 183 """
184 - def __call__(self, string):
185 if os.path.exists(string): 186 raise ValueError("File exists: %s" % string) 187 return FileType.__call__(self, string)
188 189
190 -class DirectoryType(FileType):
191 """ 192 Extension of the argparse.FileType to only allow 193 existing directories. 194 """
195 - def __call__(self, string):
196 p = path(string) 197 if not p.exists(): 198 raise ValueError("Directory does not exist: %s" % string) 199 elif not p.isdir(): 200 raise ValueError("Path is not a directory: %s" % string) 201 return str(p.abspath())
202 203
204 -class Context:
205 """Simple context used for default logic. The CLI registry which registers 206 the plugins installs itself as a fully functional Context. 207 208 The Context class is designed to increase pluggability. Rather than 209 making calls directly on other plugins directly, the pub() method 210 routes messages to other commands. Similarly, out() and err() should 211 be used for printing statements to the user, and die() should be 212 used for exiting fatally. 213 214 """ 215
216 - def __init__(self, controls = {}, params = {}):
217 self.event = get_event() 218 self.params = {} 219 self.controls = controls 220 self.dir = OMERODIR 221 self.isdebug = DEBUG # This usage will go away and default will be False 222 self.topics = {"debug":""" 223 224 debug options for developers: 225 226 The value to the debug argument is a comma-separated list of commands: 227 228 * 'debug' prints at the "debug" level. Similar to setting DEBUG=1 in the environment. 229 * 'trace' runs the command with tracing enabled. 230 * 'profile' runs the command with profiling enabled. 231 232 Only one of "trace" and "profile" can be chosen. 233 234 Example: 235 236 bin/omero --debug=debug,trace admin start # Debugs at level 1 and prints tracing 237 bin/omero -d1 admin start # Debugs at level 1 238 bin/omero -dp admin start # Prints profiling 239 bin/omero -dt,p admin start # Fails!; can't print tracing and profiling together 240 bin/omero -d0 admin start # Disables debugging 241 """} 242 self.parser = Parser(prog = sys.argv[0], 243 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(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 = Context(), dir = OMERODIR):
413 self.dir = path(dir) # Guaranteed to be a path 414 self.ctx = ctx
415
416 - def _isWindows(self):
417 p_s = platform.system() 418 if p_s == 'Windows': 419 return True 420 else: 421 return False
422
423 - def _host(self):
424 """ 425 Return hostname of current machine. Termed to be the 426 value return from socket.gethostname() up to the first 427 decimal. 428 """ 429 if not hasattr(self, "hostname") or not self.hostname: 430 self.hostname = socket.gethostname() 431 if self.hostname.find(".") > 0: 432 self.hostname = self.hostname.split(".")[0] 433 return self.hostname
434
435 - def _node(self, omero_node = None):
436 """ 437 Return the name of this node, using either the environment 438 vairable OMERO_NODE or _host(). Some subclasses may 439 override this functionality, most notably "admin" commands 440 which assume a node name of "master". 441 442 If the optional argument is not None, then the OMERO_NODE 443 environment variable will be set. 444 """ 445 if omero_node != None: 446 os.environ["OMERO_NODE"] = omero_node 447 448 if os.environ.has_key("OMERO_NODE"): 449 return os.environ["OMERO_NODE"] 450 else: 451 return self._host()
452
453 - def _icedata(self, property):
454 """ 455 General data method for creating a path from an Ice property. 456 """ 457 try: 458 nodepath = self._properties()[property] 459 460 if RELFILE.match(nodepath): 461 nodedata = self.dir / path(nodepath) 462 else: 463 nodedata = path(nodepath) 464 465 created = False 466 if not nodedata.exists(): 467 self.ctx.out("Creating "+nodedata) 468 nodedata.makedirs() 469 created = True 470 return (nodedata, created) 471 472 except KeyError, ke: 473 self.ctx.err(property + " is not configured") 474 self.ctx.die(4, str(ke))
475
476 - def _initDir(self):
477 """ 478 Initialize the directory into which the current node will log. 479 """ 480 props = self._properties() 481 nodedata = self._nodedata() 482 logdata = self.dir / path(props["Ice.StdOut"]).dirname() 483 if not logdata.exists(): 484 self.ctx.out("Initializing %s" % logdata) 485 logdata.makedirs()
486 487
488 - def _nodedata(self):
489 """ 490 Returns the data directory path for this node. This is determined 491 from the "IceGrid.Node.Data" property in the _properties() 492 map. 493 494 The directory will be created if it does not exist. 495 """ 496 data, created = self._icedata("IceGrid.Node.Data") 497 return data
498
499 - def _regdata(self):
500 """ 501 Returns the data directory for the IceGrid registry. 502 This is determined from the "IceGrid.Registry.Data" property 503 in the _properties() map. 504 505 The directory will be created if it does not exist, and 506 a warning issued. 507 """ 508 data, created = self._icedata("IceGrid.Registry.Data")
509
510 - def _pid(self):
511 """ 512 Returns a path of the form "_nodedata() / _node() + ".pid", 513 i.e. a file named NODENAME.pid in the node's data directory. 514 """ 515 pidfile = self._nodedata() / (self._node() + ".pid") 516 return pidfile
517
518 - def _cfglist(self):
519 """ 520 Returns a list of configuration files for this node. This 521 defaults to the internal configuration for all nodes, 522 followed by a file named NODENAME.cfg under the etc/ 523 directory, following by PLATFORM.cfg if it exists. 524 """ 525 cfgs = self.dir / "etc" 526 internal = cfgs / "internal.cfg" 527 owncfg = cfgs / self._node() + ".cfg" 528 results = [internal,owncfg] 529 # Look for <platform>.cfg 530 p_s = platform.system() 531 p_c = cfgs / p_s + ".cfg" 532 if p_c.exists(): 533 results.append(p_c) 534 return results
535
536 - def _icecfg(self):
537 """ 538 Uses _cfglist() to return a string argument of the form 539 "--Ice.Config=..." suitable for passing to omero.client 540 as an argument. 541 """ 542 icecfg = "--Ice.Config=%s" % ",".join(self._cfglist()) 543 return str(icecfg)
544
545 - def _intcfg(self):
546 """ 547 Returns an Ice.Config string with only the internal configuration 548 file for connecting to the IceGrid Locator. 549 """ 550 intcfg = self.dir / "etc" / "internal.cfg" 551 intcfg.abspath() 552 return str("--Ice.Config=%s" % intcfg)
553
554 - def _properties(self, prefix=""):
555 """ 556 Loads all files returned by _cfglist() into a new 557 Ice.Properties instance and return the map from 558 getPropertiesForPrefix(prefix) where the default is 559 to return all properties. 560 """ 561 import Ice 562 if not hasattr(self, "_props") or self._props == None: 563 self._props = Ice.createProperties() 564 for cfg in self._cfglist(): 565 try: 566 self._props.load(str(cfg)) 567 except Exc, exc: 568 self.ctx.die(3, "Could not find file: "+cfg + "\nDid you specify the proper node?") 569 return self._props.getPropertiesForPrefix(prefix)
570
571 - def _ask_for_password(self, reason = "", root_pass = None):
572 while not root_pass or len(root_pass) < 1: 573 root_pass = self.ctx.input("Please enter password%s: "%reason, hidden = True) 574 if root_pass == None or root_pass == "": 575 self.ctx.err("Password cannot be empty") 576 continue 577 confirm = self.ctx.input("Please re-enter password%s: "%reason, hidden = True) 578 if root_pass != confirm: 579 root_pass = None 580 self.ctx.err("Passwords don't match") 581 continue 582 break 583 return root_pass
584 585 ############################################### 586 # 587 # Methods likely to be implemented by subclasses 588 # 589
590 - def _complete_file(self, f, dir = None):
591 """ 592 f: path part 593 """ 594 if dir is None: 595 dir = self.dir 596 else: 597 dir = path(dir) 598 p = path(f) 599 if p.exists() and p.isdir(): 600 if not f.endswith(os.sep): 601 return [p.basename()+os.sep] 602 return [ str(x)[len(f):] for x in p.listdir() ] 603 else: 604 results = [ str(x.basename()) for x in dir.glob(f+"*") ] 605 if len(results) == 1: 606 # Relative to cwd 607 maybe_dir = path(results[0]) 608 if maybe_dir.exists() and maybe_dir.isdir(): 609 return [ results[0] + os.sep ] 610 return results
611
612 - def _complete(self, text, line, begidx, endidx):
613 try: 614 return self._complete2(text, line, begidx, endidx) 615 except: 616 self.ctx.dbg("Complete error: %s" % traceback.format_exc())
617
618 - def _complete2(self, text, line, begidx, endidx):
619 items = shlex.split(line) 620 parser = getattr(self, "parser", None) 621 if parser: 622 result = [] 623 actions = getattr(parser, "_actions") 624 if actions: 625 if len(items) > 1: 626 subparsers = [x for x in actions if x.__class__.__name__ == "_SubParsersAction"] 627 if subparsers: 628 subparsers = subparsers[0] # Guaranteed one 629 choice = subparsers.choices.get(items[-1]) 630 if choice and choice._actions: 631 actions = choice._actions 632 if len(items) > 2: 633 actions = [] # TBD 634 635 for action in actions: 636 if action.__class__.__name__ == "_HelpAction": 637 result.append("-h") 638 elif action.__class__.__name__ == "_SubParsersAction": 639 result.extend(action.choices) 640 641 return ["%s " % x for x in result if (not text or x.startswith(text)) and line.find(" %s " % x) < 0] 642 643 # Fallback 644 completions = [method for method in dir(self) if callable(getattr(self, method)) ] 645 return [ str(method + " ") for method in completions if method.startswith(text) and not method.startswith("_") ]
646 647
648 -class CLI(cmd.Cmd, Context):
649 """ 650 Command line interface class. Supports various styles of executing the 651 registered plugins. Each plugin is given the chance to update this class 652 by adding methods of the form "do_<plugin name>". 653 """ 654
655 - class PluginsLoaded(object):
656 """ 657 Thread-safe class for storing whether or not all the plugins 658 have been loaded 659 """
660 - def __init__(self):
661 self.lock = Lock() 662 self.done = False
663 - def get(self):
664 self.lock.acquire() 665 try: 666 return self.done 667 finally: 668 self.lock.release()
669 - def set(self):
670 self.lock.acquire() 671 try: 672 self.done = True 673 finally: 674 self.lock.release()
675
676 - def __init__(self):
677 """ 678 Also sets the "_client" field for this instance to None. Each cli 679 maintains a single active client. The "session" plugin is responsible 680 for the loading of the client object. 681 """ 682 cmd.Cmd.__init__(self) 683 Context.__init__(self) 684 self.prompt = 'omero> ' 685 self.interrupt_loop = False 686 self.rv = 0 #: Return value to be returned 687 self._stack = [] #: List of commands being processed 688 self._client = None #: Single client for all activities 689 self._plugin_paths = [OMEROCLI / "plugins"] #: Paths to be loaded; initially official plugins 690 self._pluginsLoaded = CLI.PluginsLoaded()
691
692 - def assertRC(self):
693 if self.rv != 0: 694 raise NonZeroReturnCode(self.rv, "assert failed")
695
696 - def invoke(self, line, strict = False, previous_args = None):
697 """ 698 Copied from cmd.py 699 """ 700 try: 701 line = self.precmd(line) 702 stop = self.onecmd(line, previous_args) 703 stop = self.postcmd(stop, line) 704 if strict: 705 self.assertRC() 706 finally: 707 if len(self._stack) == 0: 708 self.close() 709 else: 710 self.dbg("Delaying close for stack: %s" % len(self._stack), level = 2)
711
712 - def invokeloop(self):
713 # First we add a few special commands to the loop 714 class PWD(BaseControl): 715 def __call__(self, args): 716 self.ctx.out(os.getcwd())
717 class LS(BaseControl): 718 def __call__(self, args): 719 for p in sorted(path(os.getcwd()).listdir()): 720 self.ctx.out(str(p.basename())) 721 class CD(BaseControl): 722 def _complete(self, text, line, begidx, endidx): 723 RE = re.compile("\s*cd\s*") 724 m = RE.match(line) 725 if m: 726 replaced = RE.sub('', line) 727 return self._complete_file(replaced, path(os.getcwd())) 728 return [] 729 def _configure(self, parser): 730 parser.set_defaults(func=self.__call__) 731 parser.add_argument("dir", help = "Target directory") 732 def __call__(self, args): 733 os.chdir(args.dir) 734 self.register("pwd", PWD, "Print the current directory") 735 self.register("ls", LS, "Print files in the current directory") 736 self.register("dir", LS, "Alias for 'ls'") 737 self.register("cd", CD, "Change the current directory") 738 739 try: 740 self.selfintro = "\n".join([OMEROSHELL, OMEROHELP]) 741 if not self.stdin.isatty(): 742 self.selfintro = "" 743 self.prompt = "" 744 while not self.interrupt_loop: 745 try: 746 # Calls the same thing as invoke 747 self.cmdloop(self.selfintro) 748 except KeyboardInterrupt, ki: 749 self.selfintro = "" 750 self.out("Use quit to exit") 751 finally: 752 self.close() 753
754 - def postloop(self):
755 # We've done the intro once now. Don't repeat yourself. 756 self.selfintro = ""
757
758 - def onecmd(self, line, previous_args = None):
759 """ 760 Single command logic. Overrides the cmd.Cmd logic 761 by calling execute. Also handles various exception 762 conditions. 763 """ 764 try: 765 # Starting a new command. Reset the return value to 0 766 # If err or die are called, set rv non-0 value 767 self.rv = 0 768 try: 769 self._stack.insert(0, line) 770 self.dbg("Stack+: %s" % len(self._stack), level=2) 771 self.execute(line, previous_args) 772 return True 773 finally: 774 self._stack.pop(0) 775 self.dbg("Stack-: %s" % len(self._stack), level=2) 776 except SystemExit, exc: # Thrown by argparse 777 self.dbg("SystemExit raised\n%s" % traceback.format_exc()) 778 self.rv = exc.code 779 return False 780 # 781 # This was perhaps only needed previously 782 # Omitting for the moment with the new 783 # argparse refactoring 784 # 785 #except AttributeError, ae: 786 # self.err("Possible error in plugin:") 787 # self.err(str(ae)) 788 # if self.isdebug: 789 # traceback.print_exc() 790 except NonZeroReturnCode, nzrc: 791 self.dbg(traceback.format_exc()) 792 self.rv = nzrc.rv 793 return False # Continue
794
795 - def postcmd(self, stop, line):
796 """ 797 Checks interrupt_loop for True and return as much 798 which will end the call to cmdloop. Otherwise use 799 the default postcmd logic (which simply returns stop) 800 """ 801 if self.interrupt_loop: 802 return True 803 return cmd.Cmd.postcmd(self, stop, line)
804
805 - def execute(self, line, previous_args):
806 """ 807 String/list handling as well as EOF and comment handling. 808 Otherwise, parses the arguments as shlexed and runs the 809 function returned by argparse. 810 """ 811 812 if isinstance(line, (str, unicode)): 813 if COMMENT.match(line): 814 return # EARLY EXIT! 815 args = shlex.split(line) 816 elif isinstance(line, (tuple, list)): 817 args = list(line) 818 else: 819 self.die(1, "Bad argument type: %s ('%s')" % (type(line), line)) 820 821 if not args: 822 return 823 elif args == ["EOF"]: 824 self.exit("") 825 return 826 827 args = self.parser.parse_args(args, previous_args) 828 args.prog = self.parser.prog 829 self.waitForPlugins() 830 831 debug_str = getattr(args, "debug", "") 832 debug_opts = set([x.lower() for x in debug_str.split(",")]) 833 if "" in debug_opts: 834 debug_opts.remove("") 835 836 old_debug = self.isdebug 837 if "debug" in debug_opts: 838 self.isdebug = 1 839 debug_opts.remove("debug") 840 elif "0" in debug_opts: 841 self.isdebug = 0 842 debug_opts.remove("0") 843 844 for x in range(1, 9): 845 if str(x) in debug_opts: 846 self.isdebug = x 847 debug_opts.remove(str(x)) 848 849 try: 850 if len(debug_opts) == 0: 851 args.func(args) 852 elif len(debug_opts) > 1: 853 self.die(9, "Conflicting debug options: %s" % ", ".join(debug_opts)) 854 elif "t" in debug_opts or "trace" in debug_opts: 855 import trace 856 tracer = trace.Trace() 857 tracer.runfunc(args.func, args) 858 elif "p" in debug_opts or "profile" in debug_opts: 859 import hotshot 860 from hotshot import stats 861 prof = hotshot.Profile("hotshot_edi_stats") 862 rv = prof.runcall( lambda: args.func(args) ) 863 prof.close() 864 s = stats.load("hotshot_edi_stats") 865 s.sort_stats("time").print_stats() 866 else: 867 self.die(10, "Unknown debug action: %s" % debug_opts) 868 finally: 869 self.isdebug = old_debug
870
871 - def completedefault(self, *args):
872 return []
873
874 - def completenames(self, text, line, begidx, endidx):
875 names = self.controls.keys() 876 return [ str(n + " ") for n in names if n.startswith(line) ]
877 878 ########################################## 879 ## 880 ## Context interface 881 ##
882 - def exit(self, args, newline=True):
883 self.out(args, newline) 884 self.interrupt_loop = True
885
886 - def die(self, rc, text, newline=True):
887 self.err(text, newline) 888 self.rv = rc 889 # self.interrupt_loop = True 890 raise NonZeroReturnCode(rc, "die called: %s" % text)
891
892 - def _env(self):
893 """ 894 Configure environment with PYTHONPATH as 895 setup by bin/omero 896 897 This list needs to be kept in line with OmeroPy/bin/omero 898 899 """ 900 lpy = str(self.dir / "lib" / "python") 901 ipy = str(self.dir / "lib" / "fallback") 902 vlb = str(self.dir / "var" / "lib") 903 paths = os.path.pathsep.join([lpy, vlb, ipy]) 904 905 env = dict(os.environ) 906 pypath = env.get("PYTHONPATH", None) 907 if pypath is None: 908 pypath = paths 909 else: 910 if pypath.endswith(os.path.pathsep): 911 pypath = "%s%s" % (pypath, paths) 912 else: 913 pypath = "%s%s%s" % (pypath, os.path.pathsep, paths) 914 env["PYTHONPATH"] = pypath 915 return env
916
917 - def _cwd(self, cwd):
918 if cwd is None: 919 cwd = str(OMERODIR) 920 else: 921 cwd = str(cwd) 922 return cwd
923
924 - def call(self, args, strict = True, cwd = None):
925 """ 926 Calls the string in a subprocess and dies if the return value is not 0 927 """ 928 self.dbg("Executing: %s" % args) 929 rv = subprocess.call(args, env = self._env(), cwd = self._cwd(cwd)) 930 if strict and not rv == 0: 931 raise NonZeroReturnCode(rv, "%s => %d" % (" ".join(args), rv)) 932 return rv
933
934 - def popen(self, args, cwd = None, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **kwargs):
935 self.dbg("Returning popen: %s" % args) 936 env = self._env() 937 env.update(kwargs) 938 return subprocess.Popen(args, env = env, cwd = self._cwd(cwd), stdout = stdout, stderr = stderr)
939
940 - def readDefaults(self):
941 try: 942 f = path(OMERODIR) / "etc" / "omero.properties" 943 f = f.open() 944 output = "".join(f.readlines()) 945 f.close() 946 except: 947 if self.isdebug: 948 raise 949 print "No omero.properties found" 950 output = "" 951 return output
952
953 - def parsePropertyFile(self, data, output):
954 for line in output.splitlines(): 955 if line.startswith("Listening for transport dt_socket at address"): 956 self.dbg("Ignoring stdout 'Listening for transport' from DEBUG=1") 957 continue 958 parts = line.split("=",1) 959 if len(parts) == 2: 960 data.properties.setProperty(parts[0],parts[1]) 961 self.dbg("Set property: %s=%s" % (parts[0],parts[1]) ) 962 else: 963 self.dbg("Bad property:"+str(parts)) 964 return data
965
966 - def initData(self, properties={}):
967 """ 968 Uses "omero prefs" to create an Ice.InitializationData(). 969 """ 970 from omero.plugins.prefs import getprefs 971 try: 972 output = getprefs(["get"], str(OMERODIR / "lib")) 973 except OSError, err: 974 self.err("Error getting preferences") 975 self.dbg(err) 976 output = "" 977 978 import Ice 979 data = Ice.InitializationData() 980 data.properties = Ice.createProperties() 981 for k,v in properties.items(): 982 data.properties.setProperty(k,v) 983 self.parsePropertyFile(data, output) 984 return data
985
986 - def conn(self, args = None):
987 """ 988 Returns any active _client object. If one is present but 989 not alive, it will be removed. 990 991 If no client is found and arguments are available, 992 will use the current settings to connect. 993 994 If required attributes are missing, will delegate to the login command. 995 996 FIXME: Currently differing setting sessions on the same CLI instance 997 will misuse a client. 998 """ 999 if self._client: 1000 self.dbg("Found client") 1001 try: 1002 self._client.getSession().keepAlive(None) 1003 self.dbg("Using client") 1004 return self._client 1005 except KeyboardInterrupt: 1006 raise 1007 except exceptions.Exception, e: 1008 self.dbg("Removing client: %s" % e) 1009 self._client.closeSession() 1010 self._client = None 1011 1012 if args is not None: 1013 self.controls["sessions"].login(args) 1014 1015 return self._client # Possibly added by "login"
1016
1017 - def close(self):
1018 client = self._client 1019 if client: 1020 self.dbg("Closing client: %s" % client) 1021 client.__del__()
1022 1023 ## 1024 ## Plugin registry 1025 ## 1026
1027 - def register(self, name, Control, help):
1028 self.register_only(name, Control, help) 1029 self.configure_plugins()
1030
1031 - def register_only(self, name, Control, help):
1032 """ This method is added to the globals when execfile() is 1033 called on each plugin. A Control class should be 1034 passed to the register method which will be added to the CLI. 1035 """ 1036 self.controls[name] = (Control, help)
1037
1038 - def configure_plugins(self):
1039 """ 1040 Run to instantiate and configure all plugins 1041 which were registered via register_only() 1042 """ 1043 for name in sorted(self.controls): 1044 control = self.controls[name] 1045 if isinstance(control, tuple): 1046 Control = control[0] 1047 help = control[1] 1048 control = Control(ctx = self, dir = self.dir) 1049 self.controls[name] = control 1050 setattr(self, "complete_%s" % name, control._complete) 1051 parser = self.subparsers.add_parser(name, help=help) 1052 parser.description = help 1053 if hasattr(control, "_configure"): 1054 control._configure(parser) 1055 elif hasattr(control, "__call__"): 1056 parser.set_defaults(func=control.__call__) 1057 control.parser = parser
1058
1059 - def waitForPlugins(self):
1060 if True: 1061 return # Disabling. See comment in argv 1062 self.dbg("Starting waitForPlugins") 1063 while not self._pluginsLoaded.get(): 1064 self.dbg("Waiting for plugins...") 1065 time.sleep(0.1)
1066
1067 - def loadplugins(self):
1068 """ 1069 Finds all plugins and gives them a chance to register 1070 themselves with the CLI instance. Here register_only() 1071 is used to guarantee the orderedness of the plugins 1072 in the parser 1073 """ 1074 1075 for plugin_path in self._plugin_paths: 1076 self.loadpath(path(plugin_path)) 1077 1078 self.configure_plugins() 1079 self._pluginsLoaded.set() 1080 self.post_process()
1081
1082 - def loadpath(self, pathobj):
1083 if pathobj.isdir(): 1084 for plugin in pathobj.walkfiles("*.py"): 1085 if -1 == plugin.find("#"): # Omit emacs files 1086 self.loadpath(path(plugin)) 1087 else: 1088 if self.isdebug: 1089 print "Loading %s" % pathobj 1090 try: 1091 loc = {"register": self.register_only} 1092 execfile( str(pathobj), loc ) 1093 except KeyboardInterrupt: 1094 raise 1095 except: 1096 self.err("Error loading: %s" % pathobj) 1097 traceback.print_exc()
1098 1099 ## End Cli 1100 ########################################################### 1101
1102 -def argv(args=sys.argv):
1103 """ 1104 Main entry point for the OMERO command-line interface. First 1105 loads all plugins by passing them the classes defined here 1106 so they can register their methods. 1107 1108 Then the case where arguments are passed on the command line are 1109 handled. 1110 1111 Finally, the cli enters a command loop reading from standard in. 1112 """ 1113 1114 # Modiying the run-time environment 1115 old_ice_config = os.getenv("ICE_CONFIG") 1116 os.unsetenv("ICE_CONFIG") 1117 try: 1118 1119 # Modifying the args list if the name of the file 1120 # has arguments encoded in it 1121 executable = path(args[0]) 1122 executable = str(executable.basename()) 1123 if executable.find("-") >= 0: 1124 parts = executable.split("-") 1125 for arg in args[1:]: 1126 parts.append(arg) 1127 args = parts 1128 1129 # Now load other plugins. After debugging is turned on, but before tracing. 1130 cli = CLI() 1131 1132 parser = Parser(add_help = False) 1133 #parser.add_argument("-d", "--debug", help="Use 'help debug' for more information", default = SUPPRESS) 1134 parser.add_argument("--path", help="Add file or directory to plugin list. Supports globs.", action = "append") 1135 ns, args = parser.parse_known_args(args) 1136 if getattr(ns, "path"): 1137 for p in ns.path: 1138 for g in glob.glob(p): 1139 cli._plugin_paths.append(g) 1140 1141 class PluginLoader(Thread): 1142 def run(self): 1143 cli.loadplugins()
1144 # Disabling background loading 1145 # until 2.4 hangs are fixed 1146 PluginLoader().run() # start() 1147 1148 if len(args) > 1: 1149 cli.invoke(args[1:]) 1150 return cli.rv 1151 else: 1152 cli.invokeloop() 1153 return cli.rv 1154 finally: 1155 if old_ice_config: 1156 os.putenv("ICE_CONFIG", old_ice_config) 1157