#!/usr/bin/python import re import os import os.path from optparse import OptionParser # note: I should have used the append() action def opt_include(option, opt, value, parser): # print "hasattr?", ("yes" if hasattr(parser.values, "idirs") else "no") if not hasattr(parser.values, "idirs"): parser.values.idirs = [] parser.values.idirs.append(value) def option_parse(): parser = OptionParser() parser.add_option("-I", type="string", action="callback", callback=opt_include, help="add an include path", metavar="PATH") parser.add_option("--ddl", type="int", action="store", dest="ddl", help="dots for duplicate leaves") parser.add_option("--tree", action="store_true", dest="tree", help="display a tree of includes (default)") parser.add_option("--list", action="store_false", dest="tree", help="display a list of includes") parser.add_option("--list-mix", action="store_true", dest="list_mix", help="when displaying a list, mix with the entries " "that were not found") parser.set_defaults(idirs=[]) parser.set_defaults(ddl=1) parser.set_defaults(tree=True) parser.set_defaults(list_mix=False) (options, args) = parser.parse_args() if not (0 <= options.ddl <= 3): parser.error("ddl must be between 0 and 3") return (options, args) class Context(object): def __init__(self, options): self.options = options # already_seen: # key: filename # value: number of children self.already_seen = {} # nf_list: # "not found" list # only filled if not options.tree and not options.list_mix self.nf_list = [] self.nf_set = set() def mode(self, indent): return indent if self.options.tree else "" # @own_dir can be None or the directory containing the file trying to # do this include if it uses "" delimiters def search(subpath, gcontext, own_dir): if own_dir: filepath = os.path.join(own_dir, subpath) if os.path.isfile(filepath): return filepath for idir in gcontext.options.idirs: filepath = os.path.join(idir, subpath) if os.path.isfile(filepath): return filepath def inc_indent(indent): return indent + " " def print_already_seen(indent, filepath, ddl, cn): if not cn: print ("%s%s " % (indent, filepath)) + ("." * ddl) elif cn > 0: print ("%s%s ... (%d children)" % (indent, filepath, cn)) else: print ("%s%s ..." % (indent, filepath)) def doinc(filepath, gcontext, indent=""): if filepath in gcontext.already_seen: if gcontext.options.tree: print_already_seen(indent, filepath, gcontext.options.ddl, gcontext.already_seen[filepath]) return else: print "%s%s" % (gcontext.mode(indent), filepath) # avoid indirect recursion: gcontext.already_seen[filepath] = -1 cn = 0 for line in file(filepath): line = line.strip() mo_sys = re.match(r'\#\s*include\s*\<([^>]+)\>', line) mo_own = re.match(r'\#\s*include\s*\"([^>]+)\"', line) if mo_sys: include_name = mo_sys.group(1) found_path = search(include_name, gcontext, None) elif mo_own: own_dir = os.path.dirname(filepath) include_name = mo_own.group(1) found_path = search(include_name, gcontext, own_dir) else: continue cn += 1 if found_path is None: if gcontext.options.tree: print "%s@@@ %s .." % (inc_indent(indent), include_name) elif include_name not in gcontext.nf_set: gcontext.nf_set.add(include_name) if gcontext.options.list_mix: print "@@@", include_name else: gcontext.nf_list.append(include_name) else: doinc(found_path, gcontext, inc_indent(indent)) gcontext.already_seen[filepath] = cn def main(): (options, args) = option_parse() gcontext = Context(options) for each in args: doinc(each, gcontext) if gcontext.nf_list: print for include_name in gcontext.nf_list: print "@@@", include_name main()