Changeset 82

Show
Ignore:
Timestamp:
06/29/06 20:11:31 (3 years ago)
Author:
robin
Message:

--

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • optrun/trunk/lib/optrun/cliapi.py

    r81 r82  
    44__all__=( 
    55        '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 ' 
    78        'build_parser run_wrapper run_common ' 
    89        ).split() 
    910 
    10 import sys, os, logging, optparse 
     11import sys, os, logging, optparse, types 
    1112 
    1213COMMON_OPTS=[ 
     
    5051            opt.append({}) 
    5152        parser.add_option(*opt[:-1], **opt[-1]) 
     53 
     54def 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 
     66def 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:] 
    5288 
    5389def update_options(optlista, optlistb): 
     
    171207    return parser 
    172208 
     209def 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 
    173282 
    174283def run_common(argv, commandmap, msgfmtmap=dict( 
     
    211320    options, args = parser.parse_args(argv[1:]) 
    212321    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) 
    214323  
    215324 
  • optrun/trunk/lib/optrun/clitool.py

    r81 r82  
    11#!/usr/bin/env python 
     2import signal,errno 
    23import os, sys, optparse, logging, itertools 
    34 
    45from optrun.cliapi import * 
    56 
     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() 
    616 
    717def pidfile_name(options, program=None): 
    8     pidfile = options.pidfile 
     18    pidfile = getattr(options, 'pidfile', None) 
    919    if not pidfile: 
    1020        if program is not None: 
     
    1323            argv=sys.argv 
    1424        program = get_program_name(argv) 
    15         pidfile = '%s-%s.pid' % (options.pidfile_prefix, program) 
     25        pidfile = '%s-%s.pid' % (getattr(options, 'pidfile_prefix', ''), program) 
    1626    return pidfile 
    1727 
     
    2131        pidf.write(str(pid)) 
    2232        pidf.close() 
    23         return True 
     33        return (os.path.abspath(pidfile), True) 
    2434    except IOError, e: 
    2535        if log: 
    2636            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     
     39def read_pidfile(pidfile, defaultpid=None): 
    3340    if pidfile and os.path.exists(pidfile): 
    3441        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 
     47def 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    
     68def 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 
    3987    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 
    4395        if readpidfile: 
    4496            log.info("killing pid [%s], read from [%s]" % (pid,pidfile)) 
     
    48100            os.kill(pid, getattr(signal, killsignalname)) 
    49101        except OSError, e: 
    50             if e[0] == errno.ESRCH and readpidfile
     102            if e[0] == errno.ESRCH and readpidfile and allwaysremovestalepidfile
    51103                log.info("removing stale pidfile [%s], process [%s] is gone" % ( 
    52104                        pidfile, pid)) 
    53105                os.remove(pidfile) 
     106                haveremovedpidfile = True 
    54107            elif e[0] == errno.ESRCH: 
    55108                log.info("process [%s] is gone" % pid) 
     109        if removepidfile and readpidfile and not haveremovedpidfile: 
     110            os.remove(pidfile) 
    56111    return pidfile 
    57112 
     
    86141        for ifirst,a in enumerate(sys.argv): 
    87142            if a == args[0]: 
    88                 ifirst -= 1 # the '--' command seperator 
     143                if a == '--': 
     144                    ifirst -= 1 # the '--' command seperator 
     145                print a 
    89146                break 
    90147        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) 
    91152        args = args[:2] + sys.argv[2:ifirst] + args[2:] 
    92153    log.info("launching [%s] len(args)=%s, args: %s" % ( 
     
    100161    return 0 
    101162 
    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 
    137164def start_unix_not_optrun(progname, options, args, conf): 
    138165    """start a process that does not support 'optrun' main options""" 
     
    154181  
    155182def 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: 
    158186        print ('1st argument must be the program name, ' 
    159187                'ie., the first argument passed to start, OR ' 
     
    162190        return -1 
    163191    # prefer pidfile 
    164     program = args and args[0] or Non
     192    program = args and args[0] or prognam
    165193    if not options.firstarg_is_not_pid: # incase the command *is* a number 
    166194        try: 
     
    180208    first order of business 
    181209    """ 
    182     log = get_log(options, args
     210    log = get_log(options, progname
    183211    pidfile = pidfile_name(options, progname) 
    184212    if options.daemonize: 
     
    190218    return 0 
    191219 
    192 def minimal_daemon(): 
     220def minimal_daemon_example(): 
     221    """provided for exposition only.""" 
    193222    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 
    195226        from time import time, sleep 
    196227        try: 
     
    224255STOP_UNIX_OPTS=[ 
    225256    ('--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  
    226260    ('--firstarg-is-not-pid', dict(default=False, action='store_true', 
    227261        help='if your program is an executable file whose name ' 
     
    233267 
    234268COMMANDMAP = dict( 
    235     main = (0, 
     269    main = [0, 
    236270        build_parser, (dict( 
    237271            usage='''usage: %prog mainopts | %prog main mainopts'''), 
    238272            COMMON_OPTS, COMMON_UNIX_OPTS, START_UNIX_OPTS,  
    239273            MAIN_UNIX_OPTS), {}, 
    240         run_wrapper, (main_unix_boilerplate,), {})
    241     start = (1, 
     274        run_wrapper, (main_unix_boilerplate,), {}]
     275    start = [1, 
    242276        build_parser, (dict( 
    243277            usage="""\ 
     
    247281eg., %prog start -- echo 'trvial subprocess'"""), 
    248282           COMMON_OPTS, COMMON_UNIX_OPTS, START_UNIX_OPTS), {}, 
    249        run_wrapper, (start_unix,), {})
    250     stop = (1, 
     283       run_wrapper, (start_unix,), {}]
     284    stop = [1, 
    251285        build_parser, (dict( 
    252286            usage="""\ 
     
    255289stop a unix program"""), 
    256290            COMMON_OPTS, COMMON_UNIX_OPTS, STOP_UNIX_OPTS), {}, 
    257         run_wrapper, (stop_unix,), {})
    258   
     291        run_wrapper, (stop_unix,), {}]
     292 
    259293def run(argv=None): 
    260294   run_common(argv, COMMANDMAP) 
    261  
    262  
    263295if __name__=='__main__': 
    264296    run() 
     297 
     298if 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