#! /usr/bin/env python
#  -*- Python -*-
#
#  This script can be used to identify undocumented modules in the Python
#  standard library.  Use it like this:
#
#  .../Doc/tools/listmodules --ignore-from .../Doc/paper-<paper>/modlib.idx

"""%(program)s - list modules in the Python standard library

-a, --annotate	  Annotate the module names with the subdirectory they
                  live in
-c, --categorize  Group the modules by subdirectory
-i <file>,

--ignore-from <file>	Ignore the modules listed in <file>.  <file> may
                  contain a list of module names or a module index file
                  as produced when formatting the Python documentation
                  (.idx or .html flavor).

If neither -a nor -c are given, the modules are listed in alphabetical
order.

Note that -a and -c are mutually exclusive.

Limitation: Modules loadable as shared objects may not be listed,
though this script attempts to locate such modules.

"""

__version__ = '$Revision$'

import getopt
import glob
import os
import re
import string
import sys


REMOVE_DIRS = ["dos-8x3", "encodings", "distutils",
               "lib-old", "lib-stdwin", "test"]


def main():
    args = sys.argv[1:]
    annotate = 0
    builtin = 0
    categorize = 0
    ignore_dict = {}
    ignore = ignore_dict.has_key
    try:
        opts, args = getopt.getopt(
            args, "abchi:",
            ["annotate", "built-in", "categorize", "help", "ignore-from="])
    except getopt.error as msg:
        sys.stdout = sys.stderr
        print msg
        print
        usage()
        sys.exit(2)
    for opt, arg in opts:
        if opt in ("-a", "--annotate"):
            annotate = 1
        elif opt in ("-b", "--built-in"):
            builtin = 1
        elif opt in ("-c", "--categorize"):
            categorize = 1
        elif opt in ("-h", "--help"):
            usage()
            sys.exit()
        elif opt in ("-i", "--ignore-from"):
            data = open(arg).read()
            if data[:1] == "\\":
                ignore_from_idx(data, ignore_dict)
            else:
                ignore_from_modulelist(data, ignore_dict)
    if args or (annotate and categorize):
        usage()
        sys.exit(2)
    #
    # Populate the database:
    #
    srcdir = os.path.normpath(os.path.join(
        os.path.dirname(sys.argv[0]), os.pardir, os.pardir))
    os.chdir(srcdir)
    modules_by_name = {}
    modules_by_dir = {}
    if builtin:
        l = []
        modules_by_dir["<builtin>"] = l
        for name in sys.builtin_module_names:
            if not ignore(name):
                modules_by_name[name] = "<built-in>"
                l.append(name)
    rx = re.compile("Lib/plat-[a-zA-Z0-9]*/")
    fp = os.popen("find Lib -name \*.py -print", "r")
    while 1:
        line = fp.readline()
        if not line:
            break
        m = rx.match(line)
        if m:
            line = "Lib/plat-*/" + line[m.end():]
        line = line[4:-4]                # strip off 'Lib/' and '.py\n'
        dir, name = os.path.split(line)
        dir = dir or "<standard>"
        if ignore(name):
            continue
        if dir not in REMOVE_DIRS:
            modules_by_name[name] = dir
            l = modules_by_dir.get(dir, [])
            modules_by_dir[dir] = l
            if name not in l:
                l.append(name)
    # load up extension modules:
    pwd = os.getcwd()
    try:
        os.chdir("Modules")
        dir = "<extension>"
        for line in glob.glob("*module.c"):
            name = line[:-8]
            if ignore(name) or modules_by_name.has_key(name) or name == "xx":
                continue
            modules_by_name[name] = dir
            l = modules_by_dir.get(dir, [])
            modules_by_dir[dir] = l
            if name not in l:
                l.append(name)
    finally:
        os.chdir(pwd)
    #
    # Dump the results:
    #
    if annotate:
        modules = modules_by_name.items()
        modules.sort()
        width = max(map(len, modules_by_name.keys()))
        format = "%%-%ds  %%s" % width
        for name, dir in modules:
            if dir and dir[0] != "<":
                print format % (name, dir)
            else:
                print name
    elif categorize:
        modules = modules_by_dir.items()
        modules.sort()
        width = max(map(len, modules_by_dir.keys()))
        format = "%%-%ds  %%s" % width
        for dir, names in modules:
            names.sort()
            print format % (dir, names[0])
            for name in names[1:]:
                print format % ('', name)
            print
    else:
        modules = modules_by_name.keys()
        modules.sort()
        print string.join(modules, "\n")


def ignore_from_modulelist(data, ignore_dict):
    for name in string.split(data):
        ignore_dict[name] = name

def ignore_from_idx(data, ignore_dict):
    data = string.replace(data, r"\hackscore  {}", "_")
    rx = re.compile(r"\\indexentry\s*{([^@]*)@")
    for line in string.split(data, "\n"):
        m = rx.match(line)
        if m:
            name = m.group(1)
            ignore_dict[name] = name


def usage():
    vars = {}
    vars["program"] = os.path.basename(sys.argv[0])
    print __doc__ % vars


if __name__ == "__main__":
    main()
