def Modules(directory):
  directory = directory.rstrip(os.path.sep)
  addon_info = None
  # Check for module metadata in addon-info.json (if it exists).
  addon_info_path = os.path.join(directory, 'addon-info.json')
  if os.path.isfile(addon_info_path):
    try:
      with open(addon_info_path, 'r') as addon_info_file:
        addon_info = json.loads(addon_info_file.read())
    except (IOError, ValueError) as e:
      warnings.warn(
          'Failed to read file {}. Error was: {}'.format(addon_info_path, e),
          error.InvalidAddonInfo)
  plugin_name = None
  # Use plugin name from addon-info.json if available. Fall back to dir name.
  addon_info = addon_info or {}
  plugin_name = addon_info.get(
      'name', os.path.basename(os.path.abspath(directory)))
  plugin = VimPlugin(plugin_name)

  # Set module metadata from addon-info.json.
  if addon_info is not None:
    # Valid addon-info.json. Apply addon metadata.
    if 'author' in addon_info:
      plugin.author = addon_info['author']
    if 'description' in addon_info:
      plugin.tagline = addon_info['description']

  # Crawl plugin dir and collect parsed blocks for each file path.
  paths_and_blocks = []
  standalone_paths = []
  autoloaddir = os.path.join(directory, 'autoload')
  for (root, dirs, files) in os.walk(directory):
    # Visit files in a stable order, since the ordering of e.g. the Maktaba
    # flags below depends upon the order that we visit the files.
    dirs.sort()
    files.sort()

    # Prune non-standard top-level dirs like 'test'.
    if root == directory:
      dirs[:] = [x for x in dirs if x in DOC_SUBDIRS + ['after']]
    if root == os.path.join(directory, 'after'):
      dirs[:] = [x for x in dirs if x in DOC_SUBDIRS]
    for f in files:
      filename = os.path.join(root, f)
      if os.path.splitext(filename)[1] == '.vim':
        relative_path = os.path.relpath(filename, directory)
        with open(filename) as filehandle:
          lines = list(filehandle)
          blocks = list(parser.ParseBlocks(lines, filename))
          # Define implicit maktaba flags for files that call
          # maktaba#plugin#Enter. These flags have to be special-cased here
          # because there aren't necessarily associated doc comment blocks and
          # the name is computed from the file name.
          if (not relative_path.startswith('autoload' + os.path.sep)
              and relative_path != os.path.join('instant', 'flags.vim')):
            if ContainsMaktabaPluginEnterCall(lines):
              flagpath = relative_path
              if flagpath.startswith('after' + os.path.sep):
                flagpath = os.path.relpath(flagpath, 'after')
              flagblock = Block(vimdoc.FLAG, is_default=True)
              name_parts = os.path.splitext(flagpath)[0].split(os.path.sep)
              flagname = name_parts.pop(0)
              flagname += ''.join('[' + p + ']' for p in name_parts)
              flagblock.Local(name=flagname)
              flagblock.AddLine(
                  'Configures whether {} should be loaded.'.format(
                      relative_path))
              default = 0 if flagname == 'plugin[mappings]' else 1
              # Use unbulleted list to make sure it's on its own line. Use
              # backtick to avoid helpfile syntax highlighting.
              flagblock.AddLine(' - Default: {} `'.format(default))
              blocks.append(flagblock)
        paths_and_blocks.append((relative_path, blocks))
        if filename.startswith(autoloaddir):
          if blocks and blocks[0].globals.get('standalone'):
            standalone_paths.append(relative_path)

  docdir = os.path.join(directory, 'doc')
  if not os.path.isdir(docdir):
    os.mkdir(docdir)

  modules = []

  main_module = Module(plugin_name, plugin)
  for (path, blocks) in paths_and_blocks:
    # Skip standalone paths.
    if GetMatchingStandalonePath(path, standalone_paths) is not None:
      continue
    namespace = None
    if path.startswith('autoload' + os.path.sep):
      namespace = GetAutoloadNamespace(os.path.relpath(path, 'autoload'))
    for block in blocks:
      main_module.Merge(block, namespace=namespace)
  modules.append(main_module)

  # Process standalone modules.
  standalone_modules = {}
  for (path, blocks) in paths_and_blocks:
    standalone_path = GetMatchingStandalonePath(path, standalone_paths)
    # Skip all but standalone paths.
    if standalone_path is None:
      continue
    assert path.startswith('autoload' + os.path.sep)
    namespace = GetAutoloadNamespace(os.path.relpath(path, 'autoload'))
    standalone_module = standalone_modules.get(standalone_path)
    # Initialize module if this is the first file processed from it.
    if standalone_module is None:
      standalone_module = Module(namespace.rstrip('#'), plugin)
      standalone_modules[standalone_path] = standalone_module
      modules.append(standalone_module)
    for block in blocks:
      standalone_module.Merge(block, namespace=namespace)

  for module in modules:
    module.Close()
    yield module
An error in the pre-hook does not call through to Docker and returns
dataset/ETHPy150Open google/vimdoc/vimdoc/module.py/Modules--crossover with--dataset/ETHPy150Open ClusterHQ/powerstrip/powerstrip/test/test_core.py/ProxyTests.test_prehook_error_does_not_call_docker