docs: def _readmodule(module, path, inpackage=None):
      '''Do the hard work for readmodule[_ex].
  
      If INPACKAGE is given, it must be the dotted name of the package in
      which we are searching for a submodule, and then PATH must be the
      package search path; otherwise, we are searching for a top-level
      module, and PATH is combined with sys.path.
      '''
      # Compute the full module name (prepending inpackage if set)
      if inpackage is not None:
          fullmodule = "%s.%s" % (inpackage, module)
      else:
          fullmodule = module
  
      # Check in the cache
      if fullmodule in _modules:
          return _modules[fullmodule]
  
      # Initialize the dict for this module's contents
      dict = OrderedDict()
  
      # Check if it is a built-in module; we don't do much for these
      if module in sys.builtin_module_names and inpackage is None:
          _modules[module] = dict
          return dict
  
      # Check for a dotted module name
      i = module.rfind('.')
      if i >= 0:
          package = module[:i]
          submodule = module[i + 1:]
          parent = _readmodule(package, path, inpackage)
          if inpackage is not None:
              package = "%s.%s" % (inpackage, package)
          return _readmodule(submodule, parent['__path__'], package)
  
      # Search the path for the module
      f = None
      if inpackage is not None:
          f, fname, (_s, _m, ty) = imp.find_module(module, path)
      else:
          f, fname, (_s, _m, ty) = imp.find_module(module, path + sys.path)
      if ty == imp.PKG_DIRECTORY:
          dict['__path__'] = [fname]
          path = [fname] + path
          f, fname, (_s, _m, ty) = imp.find_module('__init__', [fname])
      _modules[fullmodule] = dict
      if ty != imp.PY_SOURCE:
          # not Python source, can't do anything with this module
          f.close()
          return dict
  
      stack = []  # stack of (class, indent) pairs
  
      g = tokenize.generate_tokens(f.readline)
      try:
          for tokentype, token, start, _end, _line in g:
              if tokentype == DEDENT:
                  lineno, thisindent = start
                  # close nested classes and defs
                  while stack and stack[-1][1] >= thisindent:
                      del stack[-1]
              elif token == 'def':
                  lineno, thisindent = start
                  # close previous nested classes and defs
                  while stack and stack[-1][1] >= thisindent:
                      del stack[-1]
                  tokentype, meth_name, start = g.next()[0:3]
                  if tokentype != NAME:
                      continue  # Syntax error
                  if stack:
                      cur_class = stack[-1][0]
                      if isinstance(cur_class, Class):
                          # it's a method
                          cur_class._addmethod(meth_name, lineno)
                          # else it's a nested def
                  else:
                      # it's a function
                      dict[meth_name] = Function(fullmodule, meth_name,
                                                 fname, lineno)
                  stack.append((None, thisindent))  # Marker for nested fns
              elif token == 'class':
                  lineno, thisindent = start
                  # close previous nested classes and defs
                  while stack and stack[-1][1] >= thisindent:
                      del stack[-1]
                  tokentype, class_name, start = g.next()[0:3]
                  if tokentype != NAME:
                      continue  # Syntax error
                  # parse what follows the class name
                  tokentype, token, start = g.next()[0:3]
                  inherit = None
                  if token == '(':
                      names = []  # List of superclasses
                      # there's a list of superclasses
                      level = 1
                      super = []  # Tokens making up current superclass
                      while True:
                          tokentype, token, start = g.next()[0:3]
                          if token in (')', ',') and level == 1:
                              n = "".join(super)
                              if n in dict:
                                  # we know this super class
                                  n = dict[n]
                              else:
                                  c = n.split('.')
                                  if len(c) > 1:
                                      # super class is of the form
                                      # module.class: look in module for
                                      # class
                                      m = c[-2]
                                      c = c[-1]
                                      if m in _modules:
                                          d = _modules[m]
                                          if c in d:
                                              n = d[c]
                              names.append(n)
                              super = []
                          if token == '(':
                              level += 1
                          elif token == ')':
                              level -= 1
                              if level == 0:
                                  break
                          elif token == ',' and level == 1:
                              pass
                          # only use NAME and OP (== dot) tokens for type name
                          elif tokentype in (NAME, OP) and level == 1:
                              super.append(token)
                              # expressions in the base list are not supported
                      inherit = names
                  cur_class = Class(fullmodule, class_name, inherit,
                                    fname, lineno)
                  if not stack:
                      dict[class_name] = cur_class
                  stack.append((cur_class, thisindent))
              elif token == 'import' and start[1] == 0:
                  modules = _getnamelist(g)
                  for mod, _mod2 in modules:
                      try:
                          # Recursively read the imported module
                          if inpackage is None:
                              _readmodule(mod, path)
                          else:
                              try:
                                  _readmodule(mod, path, inpackage)
                              except ImportError:
                                  _readmodule(mod, [])
                      except:
                          # If we can't find or parse the imported module,
                          # too bad -- don't die here.
                          pass
              elif token == 'from' and start[1] == 0:
                  mod, token = _getname(g)
                  if not mod or token != "import":
                      continue
                  names = _getnamelist(g)
                  try:
                      # Recursively read the imported module
                      d = _readmodule(mod, path, inpackage)
                  except:
                      # If we can't find or parse the imported module,
                      # too bad -- don't die here.
                      continue
                  # add any classes that were defined in the imported module
                  # to our name space if they were mentioned in the list
                  for n, n2 in names:
                      if n in d:
                          dict[n2 or n] = d[n]
                      elif n == '*':
                          # don't add names that start with _
                          for n in d:
                              if n[0] != '_':
                                  dict[n] = d[n]
              elif tokentype == NAME and start[1] == 0:
                  name = token
                  line = _line
                  tokentype, token = g.next()[0:2]
                  if tokentype == OP and token == "=":
                      dict[name] = Global(fullmodule, name, fname, _line)
      except StopIteration:
          pass
  
      f.close()
      return dict