1
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 as pyshlex
27 from exceptions import Exception as Exc
28 from threading import Thread, Lock
29 from omero_version import omero_version
30 from path import path
31
32
33
34
35
36 VERSION=omero_version
37 DEBUG = False
38 if os.environ.has_key("DEBUG"):
39 print "Deprecated warning: use the 'bin/omero debug [args]' to debug"
40 print "Running omero with debugging on"
41 DEBUG = True
42 TEXT="""
43 OMERO Python Shell. Version %s
44 Type "help" for more information, "quit" or Ctrl-D to exit
45 """ % str(VERSION)
46
47 OMEROCLI = path(__file__).expand().dirname()
48 OMERODIR = os.getenv('OMERODIR', None)
49 if OMERODIR is not None:
50 OMERODIR = path(OMERODIR)
51 else:
52 OMERODIR = OMEROCLI.dirname().dirname().dirname()
53
54 COMMENT = re.compile("^\s*#")
55 RELFILE = re.compile("^\w")
56 LINEWSP = re.compile("^\s*\w+\s+")
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
79 self.rv = rv
80 Exc.__init__(self, *args)
81
82
83
84
86 """
87 Wrapper for arguments in all controls. All non-"_" control methods are
88 assumed to take some representation of the command line. This can either
89 be:
90
91 - the line as a string
92 - the shlex'd line as a string list
93
94 To simplify usage, this class can be used at the beginning of every
95 method so::
96
97 def method(self, args):
98 args = Arguments(args)
99
100 and it will handle the above cases as well as wrapping other Argument
101 instances. If the method takes varargs and it is desired to test for
102 single argument of the above type, then use::
103
104 args = Arguments(*args)
105
106 """
107
109 if args == None:
110 self.args = []
111 self.argmap = {}
112 elif isinstance(args, Arguments):
113 self.args = args.args
114 self.argmap = args.argmap
115 elif isinstance(args, str):
116 self.args = self.shlex(args)
117 self.make_argmap()
118 elif isinstance(args, list):
119 for l in args:
120 assert (isinstance(l, str) or isinstance(l, unicode))
121 self.args = args
122 self.make_argmap()
123 else:
124 raise exceptions.Exception("Unknown argument: %s" % args)
125
127 self.argmap = {}
128 for arg in self.args:
129 parts = arg.split("=", 1)
130 if len(parts) == 1:
131 self.argmap[parts[0]] = True
132 else:
133 self.argmap[parts[0]] = parts[1]
134
136 if len(self.args) == 0:
137 return (None,[])
138 elif len(self.args) == 1:
139 return (self.args[0], [])
140 else:
141 return (self.args[0], self.args[1:])
142
144 return self.args.pop(0)
145
147 """
148 Used to split a string argument via shlex.split(). If the
149 argument is not a string, then it is returned unchnaged.
150 This is useful since the arg argument to all plugins can
151 be either a list or a string.
152 """
153 if None == input:
154 return []
155 elif isinstance(input, str):
156 return pyshlex.split(input)
157 else:
158 return input
159
160
161
162
163
165 return iter(self.args)
167 return len(self.args)
169 return ", ".join(self.args)
170 - def join(self, text):
171 return text.join(self.args)
173 """
174 For every argument without an "=" we return True. Otherwise,
175 the value following the first "=" is returned.
176 """
177 return self.argmap[idx]
178
179
180
182 """Simple context used for default logic. The CLI registry which registers
183 the plugins installs itself as a fully functional Context.
184
185 The Context class is designed to increase pluggability. Rather than
186 making calls directly on other plugins directly, the pub() method
187 routes messages to other commands. Similarly, out() and err() should
188 be used for printing statements to the user, and die() should be
189 used for exiting fatally.
190
191 """
192
193 - def __init__(self, controls = {}):
194 self.controls = controls
195 self.dir = OMERODIR
196 self.isdebug = DEBUG
197
198 - def setdebug(self):
200
201 - def safePrint(self, text, stream, newline = True):
202 """
203 Prints text to a given string, caputring any exceptions.
204 """
205 try:
206 stream.write(text % {"program_name": sys.argv[0]})
207 if newline:
208 stream.write("\n")
209 else:
210 stream.flush()
211 except:
212 print >>sys.stderr, "Error printing text"
213 print >>sys.stdout, text
214 if self.isdebug:
215 traceback.print_exc()
216
217 - def pythonpath(self):
218 """
219 Converts the current sys.path to a PYTHONPATH string
220 to be used by plugins which must start a new process.
221
222 Note: this was initially created for running during
223 testing when PYTHONPATH is not properly set.
224 """
225 path = list(sys.path)
226 for i in range(0,len(path)-1):
227 if path[i] == '':
228 path[i] = os.getcwd()
229 pythonpath = ":".join(path)
230 return pythonpath
231
233 """
234 Returns a user directory (as path.path) which can be used
235 for storing configuration. The directory is guaranteed to
236 exist and be private (700) after execution.
237 """
238 dir = path(os.path.expanduser("~")) / "omero" / "cli"
239 if not dir.exists():
240 dir.mkdir()
241 elif not dir.isdir():
242 raise Exc("%s is not a directory"%dir)
243 dir.chmod(0700)
244 return dir
245
246 - def pub(self, args):
247 self.safePrint(str(args), sys.stdout)
248
249 - def input(self, prompt, hidden = False):
250 """
251 Reads from standard in. If hidden == True, then
252 uses getpass
253 """
254 if hidden:
255 import getpass
256 defuser = getpass.getuser()
257 return getpass.getpass(prompt)
258 else:
259 return raw_input(prompt)
260
261 - def out(self, text, newline = True):
262 """
263 Expects as single string as argument"
264 """
265 self.safePrint(text, sys.stdout, newline)
266
267 - def err(self, text, newline = True):
268 """
269 Expects a single string as argument.
270 """
271 self.safePrint(text, sys.stderr, newline)
272
273 - def dbg(self, text, newline = True):
274 """
275 Passes text to err() if self.isdebug is set
276 """
277 if self.isdebug:
278 self.err(text, newline)
279
280 - def die(self, rc, args):
281 raise exceptions.Exception((rc,args))
282
283 - def exit(self, args):
284 self.out(args)
285 self.interrupt_loop = True
286
287 - def call(self, args):
289
290 - def popen(self, args):
292
294 raise NotImplementedException()
295
296
297
298
299
301 """Controls get registered with a CLI instance on loadplugins().
302
303 To create a new control, subclass BaseControl and end your module with::
304
305 try:
306 registry("name", MyControl)
307 except:
308 MyControl()._main()
309
310 This module should be put in the omero.plugins package.
311
312 All methods which do NOT begin with "_" are assumed to be accessible
313 to CLI users.
314 """
315
316
317
318
319
321 self.dir = path(dir)
322 self.ctx = ctx
323
325 p_s = platform.system()
326 if p_s == 'Windows':
327 return True
328 else:
329 return False
330
332 """
333 Return hostname of current machine. Termed to be the
334 value return from socket.gethostname() up to the first
335 decimal.
336 """
337 if not hasattr(self, "hostname") or not self.hostname:
338 self.hostname = socket.gethostname()
339 if self.hostname.find(".") > 0:
340 self.hostname = self.hostname.split(".")[0]
341 return self.hostname
342
343 - def _node(self, omero_node = None):
344 """
345 Return the name of this node, using either the environment
346 vairable OMERO_NODE or _host(). Some subclasses may
347 override this functionality, most notably "admin" commands
348 which assume a node name of "master".
349
350 If the optional argument is not None, then the OMERO_NODE
351 environment variable will be set.
352 """
353 if omero_node != None:
354 os.environ["OMERO_NODE"] = omero_node
355
356 if os.environ.has_key("OMERO_NODE"):
357 return os.environ["OMERO_NODE"]
358 else:
359 return self._host()
360
362 """
363 General data method for creating a path from an Ice property.
364 """
365 try:
366 nodepath = self._properties()[property]
367
368 if RELFILE.match(nodepath):
369 nodedata = self.dir / path(nodepath)
370 else:
371 nodedata = path(nodepath)
372
373 created = False
374 if not nodedata.exists():
375 self.ctx.out("Creating "+nodedata)
376 nodedata.makedirs()
377 created = True
378 return (nodedata, created)
379
380 except KeyError, ke:
381 self.ctx.err(property + " is not configured")
382 self.ctx.die(4, str(ke))
383
385 """
386 Initialize the directory into which the current node will log.
387 """
388 props = self._properties()
389 nodedata = self._nodedata()
390 logdata = self.dir / path(props["Ice.StdOut"]).dirname()
391 if not logdata.exists():
392 self.ctx.out("Initializing %s" % logdata)
393 logdata.makedirs()
394
395
397 """
398 Returns the data directory path for this node. This is determined
399 from the "IceGrid.Node.Data" property in the _properties()
400 map.
401
402 The directory will be created if it does not exist.
403 """
404 data, created = self._icedata("IceGrid.Node.Data")
405 return data
406
408 """
409 Returns the data directory for the IceGrid registry.
410 This is determined from the "IceGrid.Registry.Data" property
411 in the _properties() map.
412
413 The directory will be created if it does not exist, and
414 a warning issued.
415 """
416 data, created = self._icedata("IceGrid.Registry.Data")
417
419 """
420 Returns a path of the form "_nodedata() / _node() + ".pid",
421 i.e. a file named NODENAME.pid in the node's data directory.
422 """
423 pidfile = self._nodedata() / (self._node() + ".pid")
424 return pidfile
425
427 """
428 Returns a list of configuration files for this node. This
429 defaults to the internal configuration for all nodes,
430 followed by a file named NODENAME.cfg under the etc/
431 directory, following by PLATFORM.cfg if it exists.
432 """
433 cfgs = self.dir / "etc"
434 internal = cfgs / "internal.cfg"
435 owncfg = cfgs / self._node() + ".cfg"
436 results = [internal,owncfg]
437
438 p_s = platform.system()
439 p_c = cfgs / p_s + ".cfg"
440 if p_c.exists():
441 results.append(p_c)
442 return results
443
445 """
446 Uses _cfglist() to return a string argument of the form
447 "--Ice.Config=..." suitable for passing to omero.client
448 as an argument.
449 """
450 icecfg = "--Ice.Config=%s" % ",".join(self._cfglist())
451 return str(icecfg)
452
454 """
455 Returns an Ice.Config string with only the internal configuration
456 file for connecting to the IceGrid Locator.
457 """
458 intcfg = self.dir / "etc" / "internal.cfg"
459 intcfg.abspath()
460 return str("--Ice.Config=%s" % intcfg)
461
463 """
464 Loads all files returned by _cfglist() into a new
465 Ice.Properties instance and return the map from
466 getPropertiesForPrefix(prefix) where the default is
467 to return all properties.
468 """
469 import Ice
470 if not hasattr(self, "_props") or self._props == None:
471 self._props = Ice.createProperties()
472 for cfg in self._cfglist():
473 try:
474 self._props.load(str(cfg))
475 except Exc, exc:
476 self.ctx.die(3, "Could not find file: "+cfg + "\nDid you specify the proper node?")
477 return self._props.getPropertiesForPrefix(prefix)
478
480 while not root_pass or len(root_pass) < 1:
481 root_pass = self.ctx.input("Please enter password%s: "%reason, hidden = True)
482 if root_pass == None or root_pass == "":
483 self.ctx.err("Password cannot be empty")
484 continue
485 confirm = self.ctx.input("Please re-enter password%s: "%reason, hidden = True)
486 if root_pass != confirm:
487 root_pass = None
488 self.ctx.err("Passwords don't match")
489 continue
490 break
491 return root_pass
492
493
494
495
496
497 - def help(self, args = []):
498 return """ Help not implemented """
499
500 - def _complete(self, text, line, begidx, endidx):
501 try:
502 import readline
503
504 except ImportError, ie:
505 self.ctx.err("No readline")
506 return []
507
508
509
510
511 completions = [method for method in dir(self) if callable(getattr(self, method)) ]
512 completions = [ str(method + " ") for method in completions if method.startswith(text) and not method.startswith("_") ]
513 return completions
514
516 """
517 Checks whether or not it is likely for the given args
518 to be run successfully by the given command. This is
519 useful for plugins which have significant start up
520 times.
521
522 Simply return True is a possible solution. The default
523 implementation checks that the subclass has a method
524 matching the first argument, such that the default
525 __call__() implementation could dispatch to it. Or if
526 no arguments are given, True is returned since self._noargs()
527 can be called.
528 """
529 args = Arguments(args)
530 first, other = args.firstOther()
531 if first == None or hasattr(self, first):
532 return True
533 return False
534
536 """
537 Main dispatch method for a control instance. The default
538 implementation assumes that the *args consists of either
539 no elements or exactly one list of strings ==> (["str"],)
540
541 If no args are present, _noargs is called. Subclasses may want
542 to read from stdin or drop into a shell from _noargs().
543
544 Otherwise, the rest of the arguments are passed to the method
545 named by the first argument, if _likes() returns True.
546 """
547 args = Arguments(*args)
548 first,other = args.firstOther()
549 if first == None:
550 self._noargs()
551 else:
552 if not self._likes(args):
553 if self.ctx.isdebug:
554
555
556 raise Exc("Bad arguments: " + str(args))
557 self.ctx.err("Bad arguments: " + ",".join(args))
558 self.help()
559 self.ctx.die(8, "Exiting.")
560 else:
561 m = getattr(self, first)
562 return m(other)
563
565 """
566 Method called when __call__() is called without any arguments. Some implementations
567 may want to drop the user into a shell or read from standard in. By default, help()
568 is printed.
569 """
570 self.help()
571
573 """
574 Simple _main() logic which is reusable by subclasses to do something when the control
575 is executed directly. It is unlikely that such an excution will function properly,
576 but it may be useful for testing purposes.
577 """
578 if __name__ == "__main__":
579 if not self._likes(sys.argv[1:]):
580 self.help()
581 else:
582 self.__call__(sys.argv[1:])
583
585 """
586 Defined here since the background loading might be too
587 slow to have all help available
588 """
589
590 - def _complete(self, text, line, begidx, endidx):
591 """
592 This is something of a hack. This should either be a part
593 of the context interface, or we should put it somewhere
594 in a utility. FIXME.
595 """
596 return self.ctx.completenames(text, line, begidx, endidx)
597
598 - def help(self, args = None):
599 self.out("Print help")
600
602
603 args = Arguments(*args)
604 first, other = args.firstOther()
605
606 self.ctx.waitForPlugins()
607 controls = self.ctx.controls.keys()
608 controls.sort()
609
610 if not first:
611 print """OmeroCli client, version %(version)s
612
613 Usage: %(program_name)s <command> [options] args
614 See 'help <command>' for more information on syntax
615 Type 'quit' to exit
616
617 Available commands:
618 """ % {"program_name":sys.argv[0],"version":VERSION}
619
620 for name in controls:
621 print """ %s""" % name
622 print """
623 For additional information, see http://trac.openmicroscopy.org.uk/omero/wiki/OmeroCli"""
624
625 else:
626 try:
627 self.ctx.controls[first].help_method()
628
629
630
631 except KeyError, ke:
632 self.ctx.unknown_command(first)
633
634 -class CLI(cmd.Cmd, Context):
635 """
636 Command line interface class. Supports various styles of executing the
637 registered plugins. Each plugin is given the chance to update this class
638 by adding methods of the form "do_<plugin name>".
639 """
640
642 """
643 Thread-safe class for storing whether or not all the plugins
644 have been loaded
645 """
647 self.lock = Lock()
648 self.done = False
650 self.lock.acquire()
651 try:
652 return self.done
653 finally:
654 self.lock.release()
656 self.lock.acquire()
657 try:
658 self.done = True
659 finally:
660 self.lock.release()
661
663 """
664 Also sets the "_client" field for this instance to None. Each cli
665 maintains a single active client.
666 """
667 cmd.Cmd.__init__(self)
668 Context.__init__(self)
669 self.prompt = 'omero> '
670 self.interrupt_loop = False
671 self._client = None
672 self._pluginsLoaded = CLI.PluginsLoaded()
673 self.rv = 0
674
682
684 self.selfintro = TEXT
685 if not self.stdin.isatty():
686 self.selfintro = ""
687 self.prompt = ""
688 while not self.interrupt_loop:
689 try:
690 self.cmdloop(self.selfintro)
691 except KeyboardInterrupt, ki:
692
693 self.selfintro = ""
694 try:
695 import readline
696 if len(readline.get_line_buffer()) > 0:
697 self.out("")
698 else:
699 self.out("Use quit to exit")
700 except ImportError:
701 self.out("Use quit to exit")
702
708
710 args = Arguments(line)
711 try:
712
713
714 self.rv = 0
715 return cmd.Cmd.onecmd(self, args)
716 except AttributeError, ae:
717 self.err("Possible error in plugin:")
718 self.err(str(ae))
719 if self.isdebug:
720 traceback.print_exc()
721 except NonZeroReturnCode, nzrc:
722 self.rv = nzrc.rv
723 return False
724
725 - def postcmd(self, stop, line):
726 return self.interrupt_loop
727
730
732 """
733 Overrides the parseline functionality of cmd.py in order to
734 take command line parameters without shlex'ing and unshlex'ing
735 them. If "line" is an array, then the first element will be
736 returned as "cmd" and the rest as "args".
737 """
738 if isinstance(line,list):
739 if not line:
740 return (None, None, None)
741 elif len(line) == 0:
742 return (None, None, "")
743 elif len(line) == 1:
744 return (line[0],None,line[0])
745 else:
746 return (line[0],line[1:],Arguments(line))
747 elif isinstance(line, Arguments):
748 first,other = line.firstOther()
749 return (first, other, line)
750 else:
751 return cmd.Cmd.parseline(self,line)
752
754 arg = Arguments(arg)
755 try:
756 arg["EOF"]
757 self.exit("")
758 except KeyError:
759 first, other = arg.firstOther()
760 file = OMEROCLI / "plugins" / (first + ".py")
761 loc = {"register": self.register}
762 try:
763 execfile( str(file), loc )
764 except Exc, ex:
765 self.dbg("Could not load %s: %s" % (first, ex))
766 self.waitForPlugins()
767
768 if self.controls.has_key(first):
769 return self.invoke(arg.args)
770 else:
771 self.unknown_command(first)
772
774 self.err("""Unknown command: "%s" Try "help".""" % first)
775
777 names = self.controls.keys()
778 return [ str(n + " ") for n in names if n.startswith(line) ]
779
780
782 """
783 Alias for "node start"
784 """
785 args = pyshlex.split(args)
786 if not args:
787 args = ["node","start"]
788 else:
789 args = ["node","start"] + args
790 self.pub(args)
791
792
793
794
795
796 - def exit(self, args):
797 self.out(args)
798 self.interrupt_loop = True
799
800 - def die(self, rc, text):
801 self.err(text)
802 self.rv = rc
803 self.interrupt_loop = True
804 raise NonZeroReturnCode(rc, "die called")
805
806 - def pub(self, args):
807 """
808 Publishes the command, using the first argument as routing
809 information, i.e. the name of the plugin to be instantiated,
810 and the rest as the arguments to its __call__() method.
811 """
812 try:
813 args = Arguments(args)
814 first, other = args.firstOther()
815 if first == None:
816 self.ctx.die(2, "No plugin given. Giving up")
817 else:
818 control = self.controls[first]
819 control(other)
820 except KeyError, ke:
821 self.die(11, "Missing required plugin: "+ str(ke))
822
823 - def call(self, args, strict = True):
824 """
825 Calls the string in a subprocess and dies if the return value is not 0
826 If stdout is True, then rather than executing
827 Yes, stdout is something of a misnomer.
828 """
829 self.dbg("Executing: %s" % args)
830 rv = subprocess.call(args, env = os.environ, cwd = OMERODIR)
831 if strict and not rv == 0:
832 raise NonZeroReturnCode(rv, "%s => %d" % (" ".join(args), rv))
833 return rv
834
836 self.dbg("Returning popen: %s" % args)
837 return subprocess.Popen(args, env = os.environ, cwd = OMERODIR, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
838
840 try:
841 f = path(OMERODIR) / "etc" / "omero.properties"
842 f = f.open()
843 output = "".join(f.readlines())
844 f.close()
845 except:
846 if self.isdebug:
847 raise
848 print "No omero.properties found"
849 output = ""
850 return output
851
853 for line in output.splitlines():
854 if line.startswith("Listening for transport dt_socket at address"):
855 self.dbg("Ignoring stdout 'Listening for transport' from DEBUG=1")
856 continue
857 parts = line.split("=",1)
858 if len(parts) == 2:
859 data.properties.setProperty(parts[0],parts[1])
860 self.dbg("Set property: %s=%s" % (parts[0],parts[1]) )
861 else:
862 self.dbg("Bad property:"+str(parts))
863 return data
864
866 """
867 Uses "omero prefs" to create an Ice.InitializationData().
868 """
869 from omero.plugins.prefs import getprefs
870 output = getprefs(["get"], str(OMERODIR / "lib"))
871
872 import Ice
873 data = Ice.InitializationData()
874 data.properties = Ice.createProperties()
875 for k,v in properties.items():
876 data.properties.setProperty(k,v)
877 self.parsePropertyFile(data, output)
878 return data
879
880
881 - def conn(self, properties={}, profile=None):
882 """
883 Either creates or returns the exiting omero.client instance.
884 Uses the comm() method with the same signature.
885 """
886
887 if self._client:
888 return self._client
889
890 import omero
891 try:
892 data = self.initData(properties)
893 self._client = omero.client(sys.argv, id = data)
894 self._client.createSession()
895 return self._client
896 except Exc, exc:
897 self._client = None
898 raise exc
899
900
901
902
903
905 """ This method is added to the globals when execfile() is
906 called on each plugin. An instance of the control should be
907 passed to the register method which will be added to the CLI.
908 """
909
910 class Wrapper:
911 def __init__(self, ctx, control):
912 self.ctx = ctx
913 self.Control = Control
914 self.control = None
915 def _setup(self):
916 if self.control == None:
917 self.control = self.Control(ctx = self.ctx)
918 def do_method(self, *args):
919 try:
920 self._setup()
921 return self.control.__call__(*args)
922 except NonZeroReturnCode, nzrc:
923 raise
924 except Exc, exc:
925 if self.ctx.isdebug:
926 traceback.print_exc()
927
928 self.ctx.die(10, str(exc))
929 def complete_method(self, *args):
930 try:
931 self._setup()
932 return self.control._complete(*args)
933 except Exc, exc:
934 self.ctx.err("Completion error:"+str(exc))
935 def help_method(self, *args):
936 try:
937 self._setup()
938 return self.control.help(*args)
939 except Exc, exc:
940 self.ctx.err("Help error:"+str(exc))
941 def __call__(self, *args):
942 """
943 If the wrapper gets treated like the control
944 instance, and __call__()'d, then pass the *args
945 to do_method()
946 """
947 return self.do_method(*args)
948
949 wrapper = Wrapper(self, Control)
950 setattr(self, "do_" + name, wrapper.do_method)
951 setattr(self, "complete_" + name, wrapper.complete_method)
952 setattr(self, "help_" + name, wrapper.help_method)
953 self.controls[name] = wrapper
954
956 self.dbg("Starting waitForPlugins")
957 while not self._pluginsLoaded.get():
958 self.dbg("Waiting for plugins...")
959 time.sleep(0.1)
960
962 """ Finds all plugins and gives them a chance to register
963 themselves with the CLI instance """
964
965 loc = {"register": self.register}
966
967 plugins = OMEROCLI / "plugins"
968 for plugin in plugins.walkfiles("*.py"):
969 if self.isdebug:
970 print "Loading " + plugin
971 if -1 == plugin.find("#"):
972 try:
973 execfile( plugin, loc )
974 except KeyboardInterrupt:
975 raise
976 except:
977 self.err("Error loading:"+plugin)
978 traceback.print_exc()
979 self._pluginsLoaded.set()
980
981
982
983
985 """
986 Main entry point for the OMERO command-line interface. First
987 loads all plugins by passing them the classes defined here
988 so they can register their methods.
989
990 Then the case where arguments are passed on the command line are
991 handled.
992
993 Finally, the cli enters a command loop reading from standard in.
994 """
995
996
997 old_ice_config = os.getenv("ICE_CONFIG")
998 os.unsetenv("ICE_CONFIG")
999 try:
1000
1001
1002
1003 executable = path(args[0])
1004 executable = str(executable.basename())
1005 if executable.find("-") >= 0:
1006 parts = executable.split("-")
1007 for arg in args[1:]:
1008 parts.append(arg)
1009 args = parts
1010
1011 cli = CLI()
1012 cli.register("help", HelpControl)
1013 class PluginLoader(Thread):
1014 def run(self):
1015 cli.loadplugins()
1016
1017
1018 PluginLoader().run()
1019
1020 if len(args) > 1:
1021 cli.invoke(args[1:])
1022 return cli.rv
1023 else:
1024 cli.invokeloop()
1025 return cli.rv
1026 finally:
1027 if old_ice_config:
1028 os.putenv("ICE_CONFIG", old_ice_config)
1029