1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
|
#!/usr/bin/python
import pprint
import sys
import os.path
import re
import optparse
import glob
import subprocess
import depgraph2dot
solfege_modules = ('solfege')
# The keys of this dict is the module names.
# The values are the module names, for example solfege.application.SolfegeApp
deps = {}
class ModuleInfo(object):
def __init__(self, fn):
assert os.path.isfile(fn)
head, tail = os.path.split(fn)
m, e = os.path.splitext(tail)
assert os.path.isdir(head)
assert e == '.py'
self.m_modulename = ".".join(os.path.splitext(fn)[0].split("/"))
self.m_location = head
self.m_filename = fn
self.m_usage = []
# import os, sys, solfege.mpd
re2 = re.compile("^(?P<imp>import)\s+(?P<modulelist>((\w[\.\w]+,\s*)*)\w[\.\w]+)$")
# import solfege.ElementTree as et
re3 = re.compile("^(?P<imp>import)\s+(?P<module>\w[\.\w]+)\s+as\s+(?P<asmodule>\w+)")
re5 = re.compile("^from\s+(?P<module>\w[\.\w]+)\s+import\s+(?P<imodule>\w[\.\w]+)\s+as\s+(?P<asmodule>\w[\.\w]+)$")
re6 = re.compile("^from\s+(?P<module>solfege(\.\w+)*)\s+import\s+((?P<modulelist>((\w[\.\w]+,\s*)*)\w[\.\w]+)|\*)$")
re_comma = re.compile(",\s*")
def test_re():
m = re2.match("import app")
assert m.group('imp') == 'import'
assert m.group('modulelist') == 'app'
m = re2.match("import app, abstract")
assert m.group('modulelist') == "app, abstract"
m = re2.match("import app, abstract \\")
assert m is None
m = re2.match("import mpd.interval")
assert m.group('modulelist') == "mpd.interval"
m = re2.match("import app, mpd.interval")
assert m.group('modulelist') == "app, mpd.interval"
m = re2.match("import app, .interval")
assert m is None
m = re3.match("import abc as DE")
assert m.group('module') == 'abc'
assert m.group('asmodule') == 'DE'
m = re3.match("import solfege.mpd.musicalpitch as mi")
assert m.group('module') == 'solfege.mpd.musicalpitch'
assert m.group('asmodule') == 'mi'
m = re5.match("from os.path import isfile as asfile")
assert m.group('module') == 'os.path'
assert m.group('imodule') == 'isfile'
assert m.group('asmodule') == 'asfile'
m = re6.match("from solfege import i18n, abstract")
assert m.group('modulelist') == 'i18n, abstract'
m = re6.match("from solfege.mpd import musicalpitch")
assert m.group('module') == 'solfege.mpd'
assert m.group('modulelist') == 'musicalpitch'
m = re6.match("from solfege.mpd.rat import Rat")
assert m.group('module') == 'solfege.mpd.rat'
assert m.group('modulelist') == 'Rat'
def name_of_imported(info, importname):
"""
"""
if "." not in importname:
return "%s.%s" % (info.m_location, importname)
else:#if importname.count(".") == 1:
package, modulename = importname.split(".")
assert package in solfege_modules
return importname
def test_name_of_imported():
info = ModuleInfo("solfege/application.py")
assert name_of_imported(info, "abstract") == "solfege.abstract"
def usage_of_module(info):
f = open(info.m_filename, 'rU')
for line in f.readlines():
line = line.strip("\n")
if line.startswith('from __future__'):
continue
elif line.startswith('import '):
m = re2.match(line)
# re2 is simples import statement. Example:"
# import os, re, solfege.mpd.musicalpitch
if m:
for module in re_comma.split(m.group('modulelist')):
module = module.strip()
if module.startswith("solfege."):
deps[info.m_modulename].m_usage.append(module)
continue
continue
m = re3.match(line)
if m:
if m.group('module').startswith("solfege."):
deps[info.m_modulename].m_usage.append(m.group('module'))
continue
raise Exception("import statement failed.")
elif line.startswith('from '):
m = re6.match(line)
if m:
if line.endswith("import *"):
deps[info.m_modulename].m_usage.append(m.group('module'))
continue
for mod in re_comma.split(m.group('modulelist')):
module_name = "%s.%s" % (m.group('module'), mod.strip())
mfn = "%s.py" % module_name.replace(".", "/")
if os.path.isfile(mfn):
deps[info.m_modulename].m_usage.append(module_name)
else:
deps[info.m_modulename].m_usage.append(m.group('module'))
continue
m = re5.match(line)
if m:
if m.group('module').startswith("solfege."):
raise Exception("Not implemented yet")
deps[info.m_modulename].m_usage.append(name_of_imported(info, m.group('module')))
continue
print "line::::", line
def do_file(fn):
global deps
info = ModuleInfo(fn)
assert info.m_modulename not in deps
deps[info.m_modulename] = info
usage_of_module(info)
opt_parser = optparse.OptionParser(description="""
We use this to create a graph showing how the modules in Solfege
import each other. Typical usage is:
./tools/create_depgraph.py -a | ./tools/depgraph2dot.py | dot -T png -o classhier.png
""")
opt_parser.add_option('-t', action='store_true', dest='run_testsuite',
help="Run small test suite and exit.")
opt_parser.add_option('-o', dest='outfile',
help="Save to OUTFILE instead of STDOUT")
opt_parser.add_option('-a', action='store_true', dest='all_files',
help="Scan all source files")
opt_parser.add_option('-s', action='store_true', dest='simplify')
opt_parser.add_option('-g', action='store_true', dest='do_all',
help="Run all scripts to create the png image")
options, args = opt_parser.parse_args()
if options.run_testsuite:
test_re()
test_name_of_imported()
sys.exit()
if options.all_files:
v = glob.glob("solfege/*.py") + glob.glob("solfege/soundcard/*.py") + glob.glob("solfege/mpd/*.py") + glob.glob("solfege/exercises/*.py")
else:
v = []
for fn in args + v:
do_file(fn)
def replace_modules(to_module, remove_modules):
"""
to_module is the name of the a module
remove_modules is a list of names
"""
for k in deps.keys():
for ex in remove_modules:
if k == ex:
for dep in deps[k].m_usage:
if dep not in deps[to_module].m_usage:
deps[to_module].m_usage.append(dep)
for ex in remove_modules:
del deps[ex]
for k in deps.keys():
for ex in remove_modules:
if ex in deps[k].m_usage:
deps[k].m_usage.remove(ex)
if to_module not in deps[k].m_usage:
deps[k].m_usage.append(to_module)
def remove_module(modulename):
for k in deps.keys():
if k in deps[k].m_usage:
deps[k].m_usage.remove(k)
if modulename in deps:
del deps[modulename]
if options.simplify:
for k, info in deps.items():
for m in 'mpd', 'soundcard', 'exercises':
if k.startswith("solfege.%s" % m) and k != 'solfege.%s.__init__' % m:
del deps[k]
for idx, s in enumerate(info.m_usage):
if s.startswith("solfege.%s" % m):
info.m_usage[idx] = "solfege.%s.__init__" % m
d = {'depgraph': {}, 'types': {}}
for info in deps.values():
nd = {}
for x in info.m_usage:
nd[x] = 1
d['depgraph'][info.m_modulename] = nd
d['types'][info.m_modulename] = 1
if options.outfile:
f = open(options.outfile, "w")
pprint.pprint(d, f)
f.close()
if options.do_all:
dot = depgraph2dot.DD()
dot._data = d
dot.main([])
subprocess.call(('dot', '-T', 'png', '-o', 'classhier.png', 'ut.dots'))
subprocess.call(('eog', 'classhier.png'))
os.remove('ut.dots')
|