Changeset 807

Show
Ignore:
Timestamp:
05/12/08 08:45:53 (8 months ago)
Author:
robin
Message:

merge changes from hg tip

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • pyrun/trunk/pyrun.html

    r796 r807  
    297297<tr class="field"><th class="docinfo-name">License:</th><td class="field-body">MIT</td> 
    298298</tr> 
    299 <tr class="field"><th class="docinfo-name">Name:</th><td class="field-body">pyrun</td> 
     299<tr class="field"><th class="docinfo-name">Name:</th><td class="field-body">runtools</td> 
    300300</tr> 
    301301<tr><th class="docinfo-name">Version:</th> 
    302 <td>0.1.1b</td></tr> 
     302<td>0.0.2.dev</td></tr> 
    303303<tr><th class="docinfo-name">Author:</th> 
    304304<td>Robin Bryce</td></tr> 
     
    310310<tr><th class="docinfo-name">Copyright:</th> 
    311311<td><a class="first last reference" href="#copyright-c-2007-robin-bryce-all-rights-reserved">Copyright (c) 2007 Robin Bryce, All rights reserved</a></td></tr> 
    312 <tr class="field"><th class="docinfo-name">URL:</th><td class="field-body"><a class="reference" href="http://trac.wiretooth.com/public/wiki/pyrun">http://trac.wiretooth.com/public/wiki/pyrun</a></td> 
    313 </tr> 
    314 <tr class="field"><th class="docinfo-name">Download-URL:</th><td class="field-body"><a class="reference" href="http://svn.wiretooth.com/svn/open/pyrun/trunk/pyrun.py">http://svn.wiretooth.com/svn/open/pyrun/trunk/pyrun.py</a></td> 
    315 </tr> 
    316312<tr class="field"><th class="docinfo-name">Classifiers:</th><td class="field-body">License :: OSI Approved :: MIT License</td> 
    317313</tr> 
     
    674670<h1><a class="toc-backref" href="#id5" id="changelog" name="changelog">ChangeLog</a></h1> 
    675671<dl class="docutils"> 
     672<dt>0.1.2:</dt> 
     673<dd>Changed distribution project name to runtools. pyrun remains as a standalone 
     674script. The runtools package contains a number of utilities that are commonly 
     675usefull in conjunction with pyrun.</dd> 
    676676<dt>0.1.1:</dt> 
    677677<dd><ul class="first last"> 
  • pyrun/trunk/pyrun.py

    r806 r807  
    22# Copyright (c) 2007 Robin Bryce 
    33# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 
     4"""\ 
     5%prog  [-nidDpP] [BASEPATH(s)][-m mod.name | '--'] [TARGET-OPTIONS] 
     6 
     7Discover python packages and modules under BASEPATH(s). Run the first module 
     8file named in `BASEPATH(s)` *OR* explicitly nominated using the `-m` option. 
     9 
     10In most cases the solo '--' is not required. It tends to be useful when you 
     11implicitly select the module to run AND you want to pass a non option argument 
     12as the first value in the command line for that module. It can also be 
     13necessary when the target module has short options, without long-name 
     14alternatives, which collide with those defined for pyrun. 
     15 
     16NOTE: Any option that is marked [NYI] is Not Yet Implemented.""" 
     17 
    418 
    519import os, sys, types, re, traceback, inspect, imp, compiler 
    620 
    7 from os.path import join, dirname, basename, isfile, isdir, exists 
    8 from os.path import splitext, abspath, normpath, expandvars, expanduser 
     21from os.path import ( 
     22        join, dirname, basename, isfile, isdir, exists, splitext, abspath, 
     23        normpath, isabs, expandvars, expanduser) 
     24 
     25from textwrap import dedent 
    926 
    1027import logging 
     
    1734except ImportError: 
    1835    pass 
     36 
     37 
     38OPTIONS_runex=[ 
     39('--log-level', dict(default='WARNING', metavar='LEVEL', help= 
     40"""[default:%default] set the logging level, any string which names 
     41a log level which is defined by the logging package is allowed. 
     42For example any of CRITICAL, WARNING, INFO and DEBUG (in 
     43increasing order of verbosity)""")), 
     44('-q', dict(default=False, action='store_true', metavar='QUIET', help= 
     45"""Suppress all warnings about missing paths etc. Useful when you are 
     46using speculative paths and are using -p or -P to print the discoverd 
     47path.""")), 
     48 
     49('-p', dict(default=False, action='store_true', metavar='PRINTPATH', help= 
     50"Print the discovered path")), 
     51 
     52('-P', dict(default=False, action='store_true', metavar='PRINTPATH', help= 
     53"Print the discovered path in a PYTHONPATH compatible format")), 
     54 
     55 
     56('-n', dict(default=False, action='store_true', metavar='NORUN', help= 
     57"""NORUN. Don't run any of the modules implied by module file references in 
     58 the discovery path.""")), 
     59 
     60('-C', dict(default=None, type='string', metavar='SCRIPT', help= 
     61"""Identify a *python* SCRIPT to execute. The script need not have file 
     62extension but it must contain leagal python code. This option trumps -m. This 
     63option should only be necessary when the launcher for the python program you 
     64wish to run contains significant functionality. No additions are made to the 
     65discovery path or sys.path as a result of using this option. If the target 
     66script imports a related package you will need to include additional non option 
     67arguments to discover its path.""")), 
     68 
     69('-m', dict(default='', metavar='MODULE', help= 
     70"Explicitly select a module to run. (trumped by -S)")), 
     71 
     72('-d', dict(default=False, action='store_true', metavar='DEBUG', help= 
     73"""DEBUG session. Use `pdb.runeval` on the module code in order to enter 
     74an interactive debug session at the first python statement of the 
     75target module""")), 
     76 
     77('-D', dict(default=False, action='store_true', metavar='DEBUG', help= 
     78"""POSTMORTEM debugging. If the target raises an exception, start a 
     79    postmortem pdb debugging session.""")), 
     80 
     81('-i', dict(default=False, action='store_true', metavar='INTERACTIVE', help= 
     82"INTERACTIVE session with prepared sys.argv and sys.path.")), 
     83 
     84('-c', dict(default=False, metavar='STATEMENT', help= 
     85"""Update sys.argv and sys.path then execute the statement in a new, clean, 
     86module context.""")), 
     87 
     88('-x', dict(default="", metavar='EXCLUDE', help= 
     89"""Exclude one or more directories, separated by "%s", from the discovery 
     90path.""" % os.pathsep)), 
     91('-X', dict(default=[], metavar='PRUNE', action="append", type="string", help= 
     92"""Prune all paths which contain this value from the set of paths which *were* 
     93discovered. Specify multiple -X options if you wish too prune based on 
     94more than one string.""")) 
     95 
     96    ] 
    1997 
    2098 
     
    288366    def allow_descent(path): 
    289367        for e in exclude: 
    290             if path.startswith(e): 
     368            if path.startswith(e) or ( 
     369                    not isabs(e) and path.startswith(join('.', e))): 
    291370                log.info('Excluding: "%s" as it startswith "%s"' % ( 
    292371                    path, e 
     
    468547                pthextend.extend(newpaths) 
    469548                findpaths[:] = [] 
    470             pthdir = normpath(abspath(dirname(a))) 
    471             for ln in file(a): 
    472                 ln = ln.strip() 
    473                 pth = join(abspath(expandvars(expanduser(ln)))) 
    474                 if exists(pth): 
    475                     pthextend.append(pth) 
    476                 else: 
    477                     doesnotexist.append(ln) 
    478  
     549            basedir = dirname(normpath(abspath(a))) 
     550            for pth in file(a): 
     551                pth = pth.strip() 
     552                if not pth: continue 
     553                pth = expanduser(expandvars(pth)) 
     554                pth = normpath(abspath(join(basedir, pth))) 
     555                if not exists(pth): 
     556                    doesnotexist.append(pth) 
     557                    continue 
     558                pthextend.append(pth) 
    479559            continue 
    480560 
     
    674754the sys.argv the module sees.""" 
    675755 
    676 def int_log_level(level): 
    677     """Coerce a log level to an integer. 
    678  
    679     In a manner cognizant of run time configured level names.""" 
    680  
    681     try: 
    682         return int(level) 
    683     except ValueError: 
    684         try: 
    685             return getattr(logging, level) 
    686         except AttributeError: 
    687             level = logging.getLevelName(level) 
    688             level = logging.getLevelName(level) 
    689             assert isinstance(level, int) 
    690             return level 
    691  
    692  
    693 def runex(argv=None): 
    694     """Provides Extened `pyrun` features on the command line.""" 
    695  
    696     # Process the command line arguments. 
    697     import optparse 
    698     parser = optparse.OptionParser(usage=USAGE_runex) 
    699     default_opts = {} 
    700     for shrt, kw in OPTIONS_runex: 
    701         default_opts[shrt[1]] = kw['default'] 
    702         parser.add_option(shrt, **kw) 
    703  
    704     parser.disable_interspersed_args() 
    705  
    706     opts, args = parser.parse_args() 
    707     argv = args[:] 
     756 
     757def pyrun_opts(**kw): 
     758    """Convert keyword arguments to opts instance for `pyrun' 
     759 
     760    raises TypeError if any keyword is present which is not a long or short 
     761    option defined for the command line tool. Default values are filled in 
     762    based on those used for the command line tool. 
     763 
     764 
     765    :Returns: 
     766        opts 
     767            An instance, synthesized using `type', whose attributes correspond 
     768            to the provided keywords. The default value for any pyrun option 
     769            which is not provided via `kw' is included. 
     770        defaultset 
     771            A list of those options (co-erced to legal attribute names) which 
     772            took on default values. 
     773        notset 
     774            A list of those options (co-erced to legal attribute names) which 
     775            where not provided and for which no default exists. 
     776 
     777    """ 
     778 
     779    # pyrun options are all *either* short options or long options, none 
     780    # have both sort and long forms. 
     781    optdefs = dict(OPTIONS_runex) 
     782    present = dict() 
     783    def k_to_opt(k): 
     784        if len(k) == 1: 
     785            return '-' + k 
     786        else: 
     787            return '--' + k.replace('_', '-') 
     788    def opt_to_k(opt): 
     789        if opt.startswith('--'): 
     790            return opt[2:].replace('-', '_') 
     791        else: 
     792            return opt[1] 
     793 
     794    for k, v in kw.iteritems(): 
     795        opt = k_to_opt(k) 
     796        if opt not in optdefs: 
     797            raise TypeError( 
     798                'Option "%s" not supported.' % opt) 
     799        else: 
     800            print 'present', opt 
     801            present[k] = v 
     802            continue 
     803 
     804    opts = present.copy() 
     805    defaultset = [] 
     806    notset = [] 
     807    for absent in set(optdefs.keys()) - set(present.keys()): 
     808        kabsent = opt_to_k(absent) 
     809        if 'default' in optdefs[absent]: 
     810            opts[kabsent] = optdefs[absent]['default'] 
     811            defaultset.append(kabsent) 
     812        else: 
     813            notset.append(kabsent) 
     814 
     815    return type('PyRunOptions', (), opts)(), defaultset, notset 
     816 
     817 
     818def pyrun(opts, discovery_args): 
     819 
     820    default_opts = get_default_opts() 
     821 
     822    argv = discovery_args[:] 
    708823    argv.insert(0, None) 
    709824 
    710     logging.basicConfig( 
    711             level=int_log_level(getattr(opts, 'log_level', 'WARNING')), 
    712             format='%(message)s' 
    713             ) 
    714  
    715     exclude = [] 
     825    def filter_empty(sequence, warningmsg, reportmsg=log.warning): 
     826        for e in sequence: 
     827            if not e: 
     828                reportmsg(warningmsg) 
     829                continue 
     830            yield e 
     831 
     832    exclude, prune =[], [] 
    716833    if opts.x: 
    717         for e in opts.x.split(os.pathsep): 
    718             if not e: 
    719                 log.warning( 
    720                 'Warning: empty path found in (and removed from) your ' 
    721                 'exclusion path (-x)' 
    722                 ) 
    723                 continue 
    724             exclude.append(e) 
    725  
    726     prune = [] 
    727     for p in opts.X: 
    728         if not p: 
    729             log.warning( 
    730             'Warning: empty path found in (and removed from) your ' 
    731             'prune list (-X)' 
    732             ) 
    733             continue 
    734         prune.append(p) 
    735  
     834        exclude = list(filter_empty(opts.x.split(os.pathsep), dedent('''\ 
     835                    Warning: empty path found in (and removed from) your 
     836                    exclusion path (-x)'''))) 
     837    prune = list(filter_empty(opts.X, dedent('''\ 
     838                Warning: An empty string was specified using -X, 
     839                as this would prune *all* paths it will be ignored.'''))) 
    736840    try: 
    737841        source = False 
     
    753857        pthextend, minfos, ia, doesnotexist = discover_path( 
    754858                exclude, 1, *argv) 
    755  
    756859        if not opts.q and doesnotexist: 
    757860            log.warning(striplines('''\ 
    758861            Warning: your discovery path arguments referenced the following 
    759             files or directories which do not exist on the file system: 
     862            files or directories which do not exist on the file system:\ 
    760863            ''') 
    761864            ) 
    762             log.warning('\t' + '\n\t'.join(doesnotexist)) 
    763  
     865            log.warning('\t' + '\n\t'.join(doesnotexist) + '\n') 
    764866 
    765867        # If there are no unconsumed arguments: We have already determined 
    766868        # the user does not want to run a module. So we are done. 
    767869        inferior_argv = get_inferior_argv( 
    768                 opts, default_opts, flagOPTIONS_runex, argv, ia) 
     870                opts, default_opts, _get_flag_opts(), argv, ia) 
    769871 
    770872        if opts.c and opts.C and not opts.q: 
     
    785887        # prune and paths which contain *non empty* strings spefcified by -X 
    786888        for X in prune: 
    787             if not X: 
    788                 log.warning( 
    789                 'Warning: An empty string was specified using -X, ' 
    790                 'as this would prune *all* paths it will be ignored.' 
    791                 ) 
    792                 continue 
    793             pthextend[:] = [p for p in pthextend if X not in p] 
     889            pthextend[:] = [p for p in pthextend if not p.startswith(X)] 
    794890 
    795891        # Allways update the sys path. pthextend is the record of what we have 
     
    847943            import code 
    848944            code.interact(banner=banner, local=locals()) 
    849  
    850         # If we were not in interactive mode and the print path options 
    851         # is set, print it. 
    852         if pthextend and opts.p and not opts.i: 
    853             for p in pthextend: 
    854                 print p 
    855  
    856         # If we were not in interactive mode and the print PYTHONPATH 
    857         # option is set, print it 
    858         if pthextend and opts.P and not opts.i: 
    859             print os.pathsep.join(pthextend) 
    860  
     945        # The -p and -P options are ignored when -i (interactive) is in 
     946        # effect. 
     947        if not opts.i: 
     948            # If  the print path option is set, print the paths we discovered 
     949            # as a new line separated list. 
     950            if pthextend and opts.p: 
     951                for p in pthextend: 
     952                    print p 
     953 
     954            # If the print PYTHONPATH option is set, print the discovered path 
     955            # in a format suitable for storing in an environment variable. 
     956            if pthextend and opts.P: 
     957                print os.pathsep.join(pthextend) 
     958 
     959        # If neither the "don't execute" option (-n) or the "interactive" 
     960        # option are set AND we have a module name or a source file, then we 
     961        # have code to execute. 
    861962        if not (opts.n or opts.i) and (modname or source): 
    862963            exitval = run() 
     
    872973        msg = exc_string(einfo=einfo) 
    873974        print msg 
     975        # If the post mortem debug option is set (-D) and the interactive 
     976        # debug option (-d) is *not* set, drop into a pdb session. 
    874977        if opts.D and not opts.d: 
    875978            import pdb 
    876979            pdb.post_mortem(einfo[2]) 
    877980        return -1 
     981 
     982 
     983def int_log_level(level): 
     984    """Coerce a log level to an integer. 
     985 
     986    In a manner cognizant of run time configured level names.""" 
     987 
     988    try: 
     989        return int(level) 
     990    except ValueError: 
     991        try: 
     992            return getattr(logging, level) 
     993        except AttributeError: 
     994            level = logging.getLevelName(level) 
     995            level = logging.getLevelName(level) 
     996            assert isinstance(level, int) 
     997            return level 
     998 
     999 
     1000def _pyrun_cl_parse_log_init(argv=None): 
     1001    """Command line argument parsing and logging intialisation. 
     1002 
     1003    :Returns: 
     1004        opts 
     1005            optparse.OptionValues instance containing the pyrun options 
     1006            which preceded the discover path 
     1007        args 
     1008            In order, pyrun discovery path, pyrun terminating options, and the 
     1009            options and arguments for the inferior programs. 
     1010 
     1011    """ 
     1012    # Process the command line arguments. 
     1013    import optparse 
     1014    parser = optparse.OptionParser(usage=__doc__) 
     1015    for shrt, kw in OPTIONS_runex: 
     1016        parser.add_option(shrt, **kw) 
     1017 
     1018    parser.disable_interspersed_args() 
     1019 
     1020    opts, args = parser.parse_args(argv or sys.argv[1:]) 
     1021 
     1022    logging.basicConfig( 
     1023            level=int_log_level(getattr(opts, 'log_level', 'WARNING')), 
     1024            format='%(message)s' 
     1025            ) 
     1026    return opts, args 
     1027 
     1028 
     1029def get_default_opts(): 
     1030    """Return a dictionary containing the default pyrun option values.""" 
     1031    default_opts = {} 
     1032    for shrt, kw in OPTIONS_runex: 
     1033        default_opts[shrt[1]] = kw['default'] 
     1034    return default_opts 
     1035 
     1036 
     1037def runex(argv=None): 
     1038    """Provides Extened `pyrun` features on the command line.""" 
     1039 
     1040    # Process the command line arguments which appear *before* the discovery 
     1041    # path. 
     1042    opts, discovery_args = _pyrun_cl_parse_log_init(argv) 
     1043    return pyrun(opts, discovery_args) 
    8781044 
    8791045 
     
    9051071    return rval 
    9061072 
    907 USAGE_runex="""\ 
    908 %prog  [-nidDpP] [BASEPATH(s)][-m mod.name | '--' | '-' ] [TARGET-OPTIONS] 
    909  
    910 Discover python packages and modules under BASEPATH(s). Run the first module 
    911 file named in `BASEPATH(s)` *OR* explicitly nominated using the `-m` option. 
    912 BASEPATH can reference python .pth files, in which case the paths in that file 
    913 are inserted. 
    914  
    915 Note that the 'import ' lines are not supported in .pth files, that the paths 
    916 are interpreted as relative to the directory containing the .pth file and that 
    917 environment variables and '~' are fully expanded. 
    918  
    919 In most cases the solo '--' or '-' is not required. It tends to be useful when 
    920 you implicitly select the module to run AND you want to pass a non option 
    921 argument as the first value in the command line for that module. It can also be 
    922 necessary when the target module has short options, without long-name 
    923 alternatives, which collide with those defined for pyrun. 
    924  
    925 NOTE: Any option that is marked [NYI] is Not Yet Implemented.""" 
    926  
    927 OPTIONS_runex=[ 
    928 ('--log-level', dict(default='WARNING', metavar='LEVEL', help= 
    929 """[default:%default] set the logging level, any string which names 
    930 a log level which is defined by the logging package is allowed. 
    931 For example any of CRITICAL, WARNING, INFO and DEBUG (in 
    932 increasing order of verbosity)""")), 
    933 ('-q', dict(default=False, action='store_true', metavar='QUIET', help= 
    934 """Suppress all warnings about missing paths etc. Useful when you are 
    935 using speculative paths and are using -p or -P to print the discoverd 
    936 path.""")), 
    937  
    938 ('-p', dict(default=False, action='store_true', metavar='PRINTPATH', help= 
    939 "Print the discovered path")), 
    940  
    941 ('-P', dict(default=False, action='store_true', metavar='PRINTPATH', help= 
    942 "Print the discovered path in a PYTHONPATH compatible format")), 
    943  
    944  
    945 ('-n', dict(default=False, action='store_true', metavar='NORUN', help= 
    946 """NORUN. Don't run any of the modules implied by module file references in 
    947  the discovery path.""")), 
    948  
    949 ('-C', dict(default=None, type='string', metavar='SCRIPT', help= 
    950 """Identify a *python* SCRIPT to execute. The script need not have file 
    951 extension but it must contain leagal python code. This option trumps -m. This 
    952 option should only be necessary when the launcher for the python program you 
    953 wish to run contains significant functionality. No additions are made to the 
    954 discovery path or sys.path as a result of using this option. If the target 
    955 script imports a related package you will need to include additional non option 
    956 arguments to discover its path.""")), 
    957  
    958 ('-m', dict(default='', metavar='MODULE', help= 
    959 "Explicitly select a module to run. (trumped by -S)")), 
    960  
    961 ('-d', dict(default=False, action='store_true', metavar='DEBUG', help= 
    962 """DEBUG session. Use `pdb.runeval` on the module code in order to enter 
    963 an interactive debug session at the first python statement of the 
    964 target module""")), 
    965  
    966 ('-D', dict(default=False, action='store_true', metavar='DEBUG', help= 
    967 """POSTMORTEM debugging. If the target raises an exception, start a 
    968     postmortem pdb debugging session.""")), 
    969  
    970 ('-i', dict(default=False, action='store_true', metavar='INTERACTIVE', help= 
    971 "INTERACTIVE session with prepared sys.argv and sys.path.")), 
    972  
    973 ('-c', dict(default=False, metavar='STATEMENT', help= 
    974 """Update sys.argv and sys.path then execute the statement in a new, clean, 
    975 module context.""")), 
    976  
    977 ('-x', dict(default="", metavar='EXCLUDE', help= 
    978 """Exclude one or more directories, separated by "%s", from the discovery 
    979 path.""" % os.pathsep)), 
    980 ('-X', dict(default=[], metavar='PRUNE', action="append", type="string", help= 
    981 """Prune all paths which contain this value from the set of paths which *were* 
    982 discovered. Specify multiple -X options if you wish too prune based on 
    983 more than one string.""")) 
    984  
    985     ] 
    9861073 
    9871074def _get_flag_opts(): 
     
    9901077    ) 
    9911078 
    992 flagOPTIONS_runex=_get_flag_opts() 
    993  
    9941079 
    9951080if __name__=='__main__': 
  • pyrun/trunk/pyrun.rst

    r781 r807  
    66.. contents:: 
    77 
    8 .. include:: README.txt 
     8.. include:: README_pyrun.txt 
    99 
    10 .. include:: INSTALL.txt 
     10.. include:: INSTALL_pyrun.txt 
    1111 
    1212Command line interface