Home | History | Annotate | Download | only in scripts
      1 #! /usr/bin/env python

      2 
      3 # pdeps

      4 #

      5 # Find dependencies between a bunch of Python modules.

      6 #

      7 # Usage:

      8 #       pdeps file1.py file2.py ...

      9 #

     10 # Output:

     11 # Four tables separated by lines like '--- Closure ---':

     12 # 1) Direct dependencies, listing which module imports which other modules

     13 # 2) The inverse of (1)

     14 # 3) Indirect dependencies, or the closure of the above

     15 # 4) The inverse of (3)

     16 #

     17 # To do:

     18 # - command line options to select output type

     19 # - option to automatically scan the Python library for referenced modules

     20 # - option to limit output to particular modules

     21 
     22 
     23 import sys
     24 import re
     25 import os
     26 
     27 
     28 # Main program

     29 #

     30 def main():
     31     args = sys.argv[1:]
     32     if not args:
     33         print 'usage: pdeps file.py file.py ...'
     34         return 2
     35     #

     36     table = {}
     37     for arg in args:
     38         process(arg, table)
     39     #

     40     print '--- Uses ---'
     41     printresults(table)
     42     #

     43     print '--- Used By ---'
     44     inv = inverse(table)
     45     printresults(inv)
     46     #

     47     print '--- Closure of Uses ---'
     48     reach = closure(table)
     49     printresults(reach)
     50     #

     51     print '--- Closure of Used By ---'
     52     invreach = inverse(reach)
     53     printresults(invreach)
     54     #

     55     return 0
     56 
     57 
     58 # Compiled regular expressions to search for import statements

     59 #

     60 m_import = re.compile('^[ \t]*from[ \t]+([^ \t]+)[ \t]+')
     61 m_from = re.compile('^[ \t]*import[ \t]+([^#]+)')
     62 
     63 
     64 # Collect data from one file

     65 #

     66 def process(filename, table):
     67     fp = open(filename, 'r')
     68     mod = os.path.basename(filename)
     69     if mod[-3:] == '.py':
     70         mod = mod[:-3]
     71     table[mod] = list = []
     72     while 1:
     73         line = fp.readline()
     74         if not line: break
     75         while line[-1:] == '\\':
     76             nextline = fp.readline()
     77             if not nextline: break
     78             line = line[:-1] + nextline
     79         if m_import.match(line) >= 0:
     80             (a, b), (a1, b1) = m_import.regs[:2]
     81         elif m_from.match(line) >= 0:
     82             (a, b), (a1, b1) = m_from.regs[:2]
     83         else: continue
     84         words = line[a1:b1].split(',')
     85         # print '#', line, words

     86         for word in words:
     87             word = word.strip()
     88             if word not in list:
     89                 list.append(word)
     90 
     91 
     92 # Compute closure (this is in fact totally general)

     93 #

     94 def closure(table):
     95     modules = table.keys()
     96     #

     97     # Initialize reach with a copy of table

     98     #

     99     reach = {}
    100     for mod in modules:
    101         reach[mod] = table[mod][:]
    102     #

    103     # Iterate until no more change

    104     #

    105     change = 1
    106     while change:
    107         change = 0
    108         for mod in modules:
    109             for mo in reach[mod]:
    110                 if mo in modules:
    111                     for m in reach[mo]:
    112                         if m not in reach[mod]:
    113                             reach[mod].append(m)
    114                             change = 1
    115     #

    116     return reach
    117 
    118 
    119 # Invert a table (this is again totally general).

    120 # All keys of the original table are made keys of the inverse,

    121 # so there may be empty lists in the inverse.

    122 #

    123 def inverse(table):
    124     inv = {}
    125     for key in table.keys():
    126         if not inv.has_key(key):
    127             inv[key] = []
    128         for item in table[key]:
    129             store(inv, item, key)
    130     return inv
    131 
    132 
    133 # Store "item" in "dict" under "key".

    134 # The dictionary maps keys to lists of items.

    135 # If there is no list for the key yet, it is created.

    136 #

    137 def store(dict, key, item):
    138     if dict.has_key(key):
    139         dict[key].append(item)
    140     else:
    141         dict[key] = [item]
    142 
    143 
    144 # Tabulate results neatly

    145 #

    146 def printresults(table):
    147     modules = table.keys()
    148     maxlen = 0
    149     for mod in modules: maxlen = max(maxlen, len(mod))
    150     modules.sort()
    151     for mod in modules:
    152         list = table[mod]
    153         list.sort()
    154         print mod.ljust(maxlen), ':',
    155         if mod in list:
    156             print '(*)',
    157         for ref in list:
    158             print ref,
    159         print
    160 
    161 
    162 # Call main and honor exit status

    163 if __name__ == '__main__':
    164     try:
    165         sys.exit(main())
    166     except KeyboardInterrupt:
    167         sys.exit(1)
    168