Changeset 82
- Timestamp:
- 06/29/06 20:11:31 (3 years ago)
- Files:
-
- optrun/trunk/lib/optrun/cliapi.py (modified) (4 diffs)
- optrun/trunk/lib/optrun/clitool.py (modified) (14 diffs)
- optrun/trunk/lib/optrun/noseplug.py (added)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
optrun/trunk/lib/optrun/cliapi.py
r81 r82 4 4 __all__=( 5 5 'COMMON_OPTS fmt_msg get_log get_program_name ' 6 'add_options add_options_common update_options ' 6 'add_options add_options_common update_options option_defaults ' 7 'prune_shortopts prefix_longopts ' 7 8 'build_parser run_wrapper run_common ' 8 9 ).split() 9 10 10 import sys, os, logging, optparse 11 import sys, os, logging, optparse, types 11 12 12 13 COMMON_OPTS=[ … … 50 51 opt.append({}) 51 52 parser.add_option(*opt[:-1], **opt[-1]) 53 54 def prune_shortopts(options): 55 for opt in options: 56 if opt[0].startswith('--'): 57 if len(opt) > 2: 58 yield (opt[0],) + opt[2:] 59 else: 60 yield opt 61 elif len(opt) > 2: 62 yield opt[1:] 63 # else: only a short option was provided, so prune the complete option 64 # DO NOT guess based on opt[2]['dest'] 65 66 def prefix_longopts(prefix, options, applytodestname=True): 67 """decorate each option by inserting prefix after the '--' 68 69 If the optparse kwargs contain `dest` and `applytodestname` is True 70 then the prefix is also applied to the `dest` kw value. Otherwise, 71 we leave it to optparse (it will define `dest` based on the 72 longopt name by default, and as we decorate before calling `add_option` 73 everything works out. be aware that if you set `applytodest` to False 74 AND you use need the reverse transform YOU are responsible for 75 normalising the option value attribute names prior to performing 76 the reverse transform). 77 78 NOTE: assumes options does not contain any short options. see 79 `prune_shortopts` 80 """ 81 for opt in options: 82 if applytodestname: 83 opt[1].__setitem__('dest', 84 '%s%s' % (prefix.lower().replace('-','_'), 85 opt[1].get('dest', 86 opt[0][2:].lower().replace('-','_')))) 87 yield ('--%s%s' % (prefix, opt[0][2:]),) + opt[1:] 52 88 53 89 def update_options(optlista, optlistb): … … 171 207 return parser 172 208 209 def option_defaults(notsetmarker=AttributeError, *optlists): 210 """Extracts the default values from a set of option lists. 211 212 returns a type instance that has attributes corresponding to each 213 of the unique options in optlists. both long and short options are 214 reflected.eg., :: 215 216 options = option_defaults( 217 [('-q','--quiet', dict(default=True), 218 ('-v','--Verbose-Output', dict(default=False)], 219 [('-u','--user', dict(default='anon'), 220 ('--host', '-h' dict(default='localhost')), 221 ('-P', dict(default='8080')), 222 ('--password', dict())]) 223 assert options.q == True and options.quiet is options.q 224 # case is *NOT* preserved on long options, and '-' becomes '_' 225 assert options.v == False and options.verbose_output is options.v 226 assert options.u == 'anon' and options.user is options.u 227 assert options.h == 'localhost' and options.host is options.h 228 assert options.P == '8080' # case is preserved on short options. 229 assert not hasattr(options, 'password') 230 # if any value for which `value is AttributeError` evaluates false 231 # as the first argument to option_defaults then you can avoid 232 # AttributeError for options that have no default specified. 233 234 the type instance is created as `type('options', (), defaults)` where 235 defaults is the dictionary of default values keyed by short options and 236 long options with the leanding '-' chars removed and underscores replaced 237 with '_'. 238 239 **NOTE** All long options names are filtered through `lower`, short options 240 are NOT. 241 242 `notsetmarker`: If `notsetmarker` *is* AttributeError, then no value 243 is set on the returned object. If you attempt to read the value you 244 will get `AttributeError` from the python runtime. 245 246 IN ALL OTHER CASES, ie notsetmarker *is not* AttributeError, the 247 value of `notsetmarker` is set on the returned instance. 248 249 """ 250 251 optlist=reduce(update_options, optlists) 252 defaults={} 253 for opt in optlist: 254 loptk=shoptk=None 255 if opt[0].startswith('--'): 256 loptk=opt[0][2:].lower().replace('-','_') 257 if len(opt) > 2: 258 shoptk=opt[1][1:] # dont lower, and assume len(opt[1]) == 2 259 default=opt[2].get('default', notsetmarker) 260 else: 261 default=opt[1].get('default', notsetmarker) 262 elif opt[0].startswith('-'): 263 shoptk=opt[0][1:] # dont lower, and assume len(opt[1]) == 2 264 if len(opt) > 2: 265 loptk=opt[1][2:].lower().replace('-','_') 266 default=opt[2].get('default', notsetmarker) 267 else: 268 default=opt[1].get('default', notsetmarker) 269 else: 270 raise ValueError, ( 271 'One or more argument optlist have incorrect ' 272 'format.' 273 ) 274 if default is AttributeError: 275 continue 276 if loptk: 277 defaults[loptk]=default 278 if shoptk: 279 defaults[shoptk]=default 280 return type('options',(),defaults) 281 173 282 174 283 def run_common(argv, commandmap, msgfmtmap=dict( … … 211 320 options, args = parser.parse_args(argv[1:]) 212 321 runner, runxargs, runxkwargs = commandmap[command][4:] 213 return runner(parser. prog, options, args, *runxargs, **runxkwargs)322 return runner(parser.get_prog_name(), options, args, *runxargs, **runxkwargs) 214 323 215 324 optrun/trunk/lib/optrun/clitool.py
r81 r82 1 1 #!/usr/bin/env python 2 import signal,errno 2 3 import os, sys, optparse, logging, itertools 3 4 4 5 from optrun.cliapi import * 5 6 7 __all__=( 8 'pidfile_name write_pidfile read_pidfile pollpid_unix kill_unix ' 9 'daemonize_posix ' 10 'start_unix start_unix_not_optrun stop_unix main_unix_boilerplate ' 11 'minimal_daemon_example ' 12 'COMMON_UNIX_OPTS START_UNIX_OPTS STOP_UNIX_OPTS MAIN_UNIX_OPTS ' 13 #COMMANDMAP is typicaly defined by many modules so its not included in 14 #all 15 ).split() 6 16 7 17 def pidfile_name(options, program=None): 8 pidfile = options.pidfile18 pidfile = getattr(options, 'pidfile', None) 9 19 if not pidfile: 10 20 if program is not None: … … 13 23 argv=sys.argv 14 24 program = get_program_name(argv) 15 pidfile = '%s-%s.pid' % ( options.pidfile_prefix, program)25 pidfile = '%s-%s.pid' % (getattr(options, 'pidfile_prefix', ''), program) 16 26 return pidfile 17 27 … … 21 31 pidf.write(str(pid)) 22 32 pidf.close() 23 return True33 return (os.path.abspath(pidfile), True) 24 34 except IOError, e: 25 35 if log: 26 36 log.warning(str(e)) 27 return False 28 29 def kill_unix(log, options, program=None, killsignalname='SIGINT'): 30 import signal,errno 31 readpidfile = False 32 pidfile = pidfile_name(options, program) 37 return (os.path.abspath(pidfile), False) 38 39 def read_pidfile(pidfile, defaultpid=None): 33 40 if pidfile and os.path.exists(pidfile): 34 41 try: 35 pid = int(open(pidfile).read()) 36 readpidfile=True 37 except ValueError: 38 pid=getattr(options, 'pid', None) 42 return (int(open(pidfile).read()), True) 43 except (IOError, ValueError), e: 44 pass 45 return (defaultpid,False) 46 47 def pollpid_unix(pid): 48 """Determine if a process has terminated. 49 50 largely looted from python2.4.subprocess.Poll.poll 51 52 """ 53 54 rcode = None 55 try: 56 pid, sts = os.waitpid(pid, os.WNOHANG) 57 if os.WIFSIGNALED(sts): 58 rcode = -os.WTERMSIG(sts) 59 elif os.WIFEXITED(sts): 60 rcode = os.WEXITSTATUS(sts) 61 else: 62 # Should never happen 63 raise RuntimeError("Unknown child exit status!") 64 except os.error: 65 pass 66 return rcode 67 68 def kill_unix(log, 69 options, 70 program=None, 71 pid=None, 72 killsignalname='SIGINT', 73 abspidfile=None, # usualy you pass pid or derive pidfile from options 74 # but sometimes it is a lot more convenient (and 75 # correct) to pass the *absoloute* pid file. The file 76 # named by this parameter *must* exist and *should* be 77 # an absoloute path. 78 removepidfile=False, 79 allwaysremovestalepidfile=True 80 ): 81 haveremovedpidfile=False 82 if abspidfile and not os.path.isfile(abspidfile): 83 raise IOError( 84 'abspidfile does not exist [%s]' % abspidfile) 85 if abspidfile is not None: 86 pidfile = abspidfile 39 87 else: 40 pid=getattr(options, 'pid', None) 41 42 if pid: 88 pidfile = pidfile_name(options, program) 89 if pid is None: 90 # prefer file contents but fall back on options.pid 91 pid,readpidfile=read_pidfile(pidfile, getattr(options, 'pid', None)) 92 else: 93 readpidfile=False 94 if pid and pid != os.getpid(): # Incase the parent wrote the pidfile 43 95 if readpidfile: 44 96 log.info("killing pid [%s], read from [%s]" % (pid,pidfile)) … … 48 100 os.kill(pid, getattr(signal, killsignalname)) 49 101 except OSError, e: 50 if e[0] == errno.ESRCH and readpidfile :102 if e[0] == errno.ESRCH and readpidfile and allwaysremovestalepidfile: 51 103 log.info("removing stale pidfile [%s], process [%s] is gone" % ( 52 104 pidfile, pid)) 53 105 os.remove(pidfile) 106 haveremovedpidfile = True 54 107 elif e[0] == errno.ESRCH: 55 108 log.info("process [%s] is gone" % pid) 109 if removepidfile and readpidfile and not haveremovedpidfile: 110 os.remove(pidfile) 56 111 return pidfile 57 112 … … 86 141 for ifirst,a in enumerate(sys.argv): 87 142 if a == args[0]: 88 ifirst -= 1 # the '--' command seperator 143 if a == '--': 144 ifirst -= 1 # the '--' command seperator 145 print a 89 146 break 90 147 assert ifirst 148 # if the sub command is not found in sys.argv the caller has 149 # intercepted start and munged args 150 if ifirst == len(sys.argv) - 1 and sys.argv[ifirst] != args[0]: 151 ifirst = len(sys.argv) 91 152 args = args[:2] + sys.argv[2:ifirst] + args[2:] 92 153 log.info("launching [%s] len(args)=%s, args: %s" % ( … … 100 161 return 0 101 162 102 if 0: 103 keepopts = dict([a.split('=')[0] for a in args[1:] if not a[0] == '-']) 104 forwardargs = [] 105 for opt in itertools.chain( 106 COMMON_OPTS, COMMON_UNIX_OPTS, START_UNIX_OPTS, MAIN_UNIX_OPTS): 107 # think optparse supports both shortopt, longopt, and longopt, shortopt 108 # as the first two arguments so we don't predjudice that by defining 109 # the order here by implication of our test logic. 110 longopt=None 111 shortopt=None 112 if opt[0][1] == '-': 113 longopt=opt[0] 114 else: 115 shortopt=opt[0] 116 if not isinstance(opt[1], dict): 117 if opt[1][1] == '-': 118 assert not longopt 119 longopt=opt[1] 120 else: 121 assert not shortopt 122 shortopt=opt[1] 123 shortopt=opt[1] 124 if longopt in keepopts or shortopt in keepopts: 125 continue 126 value = (longopt and options.get_option(longopt) or 127 shortopt and options.get_option(shortopt)) 128 if longopt and shortopt or longopt: 129 forwardargs.append("%s=%s" % (longopt, str(value))) 130 else: 131 forward 132 value=options.get_option(opt[0]) or options.get_option(o1) 133 forwardargs.append(opt[0]) 134 if o1 is not opt[0]: 135 forwardopts.append(opt[1]) 136 163 137 164 def start_unix_not_optrun(progname, options, args, conf): 138 165 """start a process that does not support 'optrun' main options""" … … 154 181 155 182 def stop_unix(progname, options, args, conf): 156 log=get_log(options, args) 157 if not args and options.pidfile is None and options.pid is None: 183 log=get_log(options, progname) 184 print progname 185 if not args and progname is None and options.pid is None: 158 186 print ('1st argument must be the program name, ' 159 187 'ie., the first argument passed to start, OR ' … … 162 190 return -1 163 191 # prefer pidfile 164 program = args and args[0] or None192 program = args and args[0] or progname 165 193 if not options.firstarg_is_not_pid: # incase the command *is* a number 166 194 try: … … 180 208 first order of business 181 209 """ 182 log = get_log(options, args)210 log = get_log(options, progname) 183 211 pidfile = pidfile_name(options, progname) 184 212 if options.daemonize: … … 190 218 return 0 191 219 192 def minimal_daemon(): 220 def minimal_daemon_example(): 221 """provided for exposition only.""" 193 222 def main(progname, options, args, conf): 194 main_unix_boilerplate(progname,options,args,conf) 223 rcode = main_unix_boilerplate(progname,options,args,conf) 224 if rcode: 225 return rcode 195 226 from time import time, sleep 196 227 try: … … 224 255 STOP_UNIX_OPTS=[ 225 256 ('--pid',dict(default=None,help='process id to kill',type='int')), 257 ('--pidfile-prefix',dict(default='optrun')), 258 ('--pidfile',dict(default=None,help='where to persist the process id')), 259 226 260 ('--firstarg-is-not-pid', dict(default=False, action='store_true', 227 261 help='if your program is an executable file whose name ' … … 233 267 234 268 COMMANDMAP = dict( 235 main = (0,269 main = [0, 236 270 build_parser, (dict( 237 271 usage='''usage: %prog mainopts | %prog main mainopts'''), 238 272 COMMON_OPTS, COMMON_UNIX_OPTS, START_UNIX_OPTS, 239 273 MAIN_UNIX_OPTS), {}, 240 run_wrapper, (main_unix_boilerplate,), {} ),241 start = (1,274 run_wrapper, (main_unix_boilerplate,), {}], 275 start = [1, 242 276 build_parser, (dict( 243 277 usage="""\ … … 247 281 eg., %prog start -- echo 'trvial subprocess'"""), 248 282 COMMON_OPTS, COMMON_UNIX_OPTS, START_UNIX_OPTS), {}, 249 run_wrapper, (start_unix,), {} ),250 stop = (1,283 run_wrapper, (start_unix,), {}], 284 stop = [1, 251 285 build_parser, (dict( 252 286 usage="""\ … … 255 289 stop a unix program"""), 256 290 COMMON_OPTS, COMMON_UNIX_OPTS, STOP_UNIX_OPTS), {}, 257 run_wrapper, (stop_unix,), {} ))258 291 run_wrapper, (stop_unix,), {}]) 292 259 293 def run(argv=None): 260 294 run_common(argv, COMMANDMAP) 261 262 263 295 if __name__=='__main__': 264 296 run() 297 298 if 0: 299 keepopts = dict([a.split('=')[0] for a in args[1:] if not a[0] == '-']) 300 forwardargs = [] 301 for opt in itertools.chain( 302 COMMON_OPTS, COMMON_UNIX_OPTS, START_UNIX_OPTS, MAIN_UNIX_OPTS): 303 # think optparse supports both shortopt, longopt, and longopt, shortopt 304 # as the first two arguments so we don't predjudice that by defining 305 # the order here by implication of our test logic. 306 longopt=None 307 shortopt=None 308 if opt[0][1] == '-': 309 longopt=opt[0] 310 else: 311 shortopt=opt[0] 312 if not isinstance(opt[1], dict): 313 if opt[1][1] == '-': 314 assert not longopt 315 longopt=opt[1] 316 else: 317 assert not shortopt 318 shortopt=opt[1] 319 shortopt=opt[1] 320 if longopt in keepopts or shortopt in keepopts: 321 continue 322 value = (longopt and options.get_option(longopt) or 323 shortopt and options.get_option(shortopt)) 324 if longopt and shortopt or longopt: 325 forwardargs.append("%s=%s" % (longopt, str(value))) 326 else: 327 forward 328 value=options.get_option(opt[0]) or options.get_option(o1) 329 forwardargs.append(opt[0]) 330 if o1 is not opt[0]: 331 forwardopts.append(opt[1]) 332