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