(root)/
Python-3.11.7/
Tools/
scripts/
pdeps.py
       1  #! /usr/bin/env python3
       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      with open(filename, encoding='utf-8') as fp:
      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              m_found = m_import.match(line) or m_from.match(line)
      80              if m_found:
      81                  (a, b), (a1, b1) = m_found.regs[:2]
      82              else: continue
      83              words = line[a1:b1].split(',')
      84              # print '#', line, words
      85              for word in words:
      86                  word = word.strip()
      87                  if word not in list:
      88                      list.append(word)
      89  
      90  
      91  # Compute closure (this is in fact totally general)
      92  #
      93  def closure(table):
      94      modules = list(table.keys())
      95      #
      96      # Initialize reach with a copy of table
      97      #
      98      reach = {}
      99      for mod in modules:
     100          reach[mod] = table[mod][:]
     101      #
     102      # Iterate until no more change
     103      #
     104      change = 1
     105      while change:
     106          change = 0
     107          for mod in modules:
     108              for mo in reach[mod]:
     109                  if mo in modules:
     110                      for m in reach[mo]:
     111                          if m not in reach[mod]:
     112                              reach[mod].append(m)
     113                              change = 1
     114      #
     115      return reach
     116  
     117  
     118  # Invert a table (this is again totally general).
     119  # All keys of the original table are made keys of the inverse,
     120  # so there may be empty lists in the inverse.
     121  #
     122  def inverse(table):
     123      inv = {}
     124      for key in table.keys():
     125          if key not in inv:
     126              inv[key] = []
     127          for item in table[key]:
     128              store(inv, item, key)
     129      return inv
     130  
     131  
     132  # Store "item" in "dict" under "key".
     133  # The dictionary maps keys to lists of items.
     134  # If there is no list for the key yet, it is created.
     135  #
     136  def store(dict, key, item):
     137      if key in dict:
     138          dict[key].append(item)
     139      else:
     140          dict[key] = [item]
     141  
     142  
     143  # Tabulate results neatly
     144  #
     145  def printresults(table):
     146      modules = sorted(table.keys())
     147      maxlen = 0
     148      for mod in modules: maxlen = max(maxlen, len(mod))
     149      for mod in modules:
     150          list = sorted(table[mod])
     151          print(mod.ljust(maxlen), ':', end=' ')
     152          if mod in list:
     153              print('(*)', end=' ')
     154          for ref in list:
     155              print(ref, end=' ')
     156          print()
     157  
     158  
     159  # Call main and honor exit status
     160  if __name__ == '__main__':
     161      try:
     162          sys.exit(main())
     163      except KeyboardInterrupt:
     164          sys.exit(1)