pdeps.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. #! /usr/bin/env python3
  2. # pdeps
  3. #
  4. # Find dependencies between a bunch of Python modules.
  5. #
  6. # Usage:
  7. # pdeps file1.py file2.py ...
  8. #
  9. # Output:
  10. # Four tables separated by lines like '--- Closure ---':
  11. # 1) Direct dependencies, listing which module imports which other modules
  12. # 2) The inverse of (1)
  13. # 3) Indirect dependencies, or the closure of the above
  14. # 4) The inverse of (3)
  15. #
  16. # To do:
  17. # - command line options to select output type
  18. # - option to automatically scan the Python library for referenced modules
  19. # - option to limit output to particular modules
  20. import sys
  21. import re
  22. import os
  23. # Main program
  24. #
  25. def main():
  26. args = sys.argv[1:]
  27. if not args:
  28. print('usage: pdeps file.py file.py ...')
  29. return 2
  30. #
  31. table = {}
  32. for arg in args:
  33. process(arg, table)
  34. #
  35. print('--- Uses ---')
  36. printresults(table)
  37. #
  38. print('--- Used By ---')
  39. inv = inverse(table)
  40. printresults(inv)
  41. #
  42. print('--- Closure of Uses ---')
  43. reach = closure(table)
  44. printresults(reach)
  45. #
  46. print('--- Closure of Used By ---')
  47. invreach = inverse(reach)
  48. printresults(invreach)
  49. #
  50. return 0
  51. # Compiled regular expressions to search for import statements
  52. #
  53. m_import = re.compile('^[ \t]*from[ \t]+([^ \t]+)[ \t]+')
  54. m_from = re.compile('^[ \t]*import[ \t]+([^#]+)')
  55. # Collect data from one file
  56. #
  57. def process(filename, table):
  58. fp = open(filename, 'r')
  59. mod = os.path.basename(filename)
  60. if mod[-3:] == '.py':
  61. mod = mod[:-3]
  62. table[mod] = list = []
  63. while 1:
  64. line = fp.readline()
  65. if not line: break
  66. while line[-1:] == '\\':
  67. nextline = fp.readline()
  68. if not nextline: break
  69. line = line[:-1] + nextline
  70. m_found = m_import.match(line) or m_from.match(line)
  71. if m_found:
  72. (a, b), (a1, b1) = m_found.regs[:2]
  73. else: continue
  74. words = line[a1:b1].split(',')
  75. # print '#', line, words
  76. for word in words:
  77. word = word.strip()
  78. if word not in list:
  79. list.append(word)
  80. fp.close()
  81. # Compute closure (this is in fact totally general)
  82. #
  83. def closure(table):
  84. modules = list(table.keys())
  85. #
  86. # Initialize reach with a copy of table
  87. #
  88. reach = {}
  89. for mod in modules:
  90. reach[mod] = table[mod][:]
  91. #
  92. # Iterate until no more change
  93. #
  94. change = 1
  95. while change:
  96. change = 0
  97. for mod in modules:
  98. for mo in reach[mod]:
  99. if mo in modules:
  100. for m in reach[mo]:
  101. if m not in reach[mod]:
  102. reach[mod].append(m)
  103. change = 1
  104. #
  105. return reach
  106. # Invert a table (this is again totally general).
  107. # All keys of the original table are made keys of the inverse,
  108. # so there may be empty lists in the inverse.
  109. #
  110. def inverse(table):
  111. inv = {}
  112. for key in table.keys():
  113. if key not in inv:
  114. inv[key] = []
  115. for item in table[key]:
  116. store(inv, item, key)
  117. return inv
  118. # Store "item" in "dict" under "key".
  119. # The dictionary maps keys to lists of items.
  120. # If there is no list for the key yet, it is created.
  121. #
  122. def store(dict, key, item):
  123. if key in dict:
  124. dict[key].append(item)
  125. else:
  126. dict[key] = [item]
  127. # Tabulate results neatly
  128. #
  129. def printresults(table):
  130. modules = sorted(table.keys())
  131. maxlen = 0
  132. for mod in modules: maxlen = max(maxlen, len(mod))
  133. for mod in modules:
  134. list = sorted(table[mod])
  135. print(mod.ljust(maxlen), ':', end=' ')
  136. if mod in list:
  137. print('(*)', end=' ')
  138. for ref in list:
  139. print(ref, end=' ')
  140. print()
  141. # Call main and honor exit status
  142. if __name__ == '__main__':
  143. try:
  144. sys.exit(main())
  145. except KeyboardInterrupt:
  146. sys.exit(1)