Example #1
0
    def test_partial_ordering(self):
        """Always respect explicit order and prefer built-in ordering.

    Undeclared built-in sections will be inserted into explicit order according
    to default built-in ordering. The about section should come after custom
    sections unless explicitly ordered."""
        plugin = module.VimPlugin('myplugin')
        main_module = module.Module('myplugin', plugin)
        intro = Block(vimdoc.SECTION)
        intro.Local(name='Introduction', id='intro')
        # Configure explicit order.
        intro.Global(order=['custom1', 'intro', 'custom2'])
        commands = Block(vimdoc.SECTION)
        commands.Local(name='Commands', id='commands')
        about = Block(vimdoc.SECTION)
        about.Local(name='About', id='about')
        custom1 = Block(vimdoc.SECTION)
        custom1.Local(name='Custom1', id='custom1')
        custom2 = Block(vimdoc.SECTION)
        custom2.Local(name='Custom2', id='custom2')
        # Merge in arbitrary order.
        for section in [commands, custom2, about, intro, custom1]:
            main_module.Merge(section)
        main_module.Close()
        self.assertEqual([custom1, intro, commands, custom2, about],
                         list(main_module.Chunks()))
Example #2
0
 def test_duplicate_section(self):
     plugin = module.VimPlugin('myplugin')
     main_module = module.Module('myplugin', plugin)
     intro = Block(vimdoc.SECTION)
     intro.Local(name='Introduction', id='intro')
     main_module.Merge(intro)
     intro2 = Block(vimdoc.SECTION)
     intro2.Local(name='Intro', id='intro')
     with self.assertRaises(error.DuplicateSection) as cm:
         main_module.Merge(intro2)
     self.assertEqual(('Duplicate section intro defined.', ),
                      cm.exception.args)
Example #3
0
 def test_missing_parent(self):
     """Parent sections should exist."""
     plugin = module.VimPlugin('myplugin')
     main_module = module.Module('myplugin', plugin)
     first = Block(vimdoc.SECTION)
     first.Local(name='Section 1', id='first')
     second = Block(vimdoc.SECTION)
     second.Local(name='Section 2', id='second', parent_id='missing')
     main_module.Merge(first)
     main_module.Merge(second)
     with self.assertRaises(error.NoSuchParentSection) as cm:
         main_module.Close()
     expected = ('Section Section 2 has non-existent parent missing. '
                 'Try setting the id of the parent section explicitly.')
     self.assertEqual((expected, ), cm.exception.args)
Example #4
0
 def test_default_section_ordering(self):
     """Sections should be ordered according to documented built-in ordering."""
     plugin = module.VimPlugin('myplugin')
     main_module = module.Module('myplugin', plugin)
     intro = Block(vimdoc.SECTION)
     intro.Local(name='Introduction', id='intro')
     commands = Block(vimdoc.SECTION)
     commands.Local(name='Commands', id='commands')
     about = Block(vimdoc.SECTION)
     about.Local(name='About', id='about')
     # Merge in arbitrary order.
     main_module.Merge(commands)
     main_module.Merge(about)
     main_module.Merge(intro)
     main_module.Close()
     self.assertEqual([intro, commands, about], list(main_module.Chunks()))
Example #5
0
 def test_ordered_child(self):
     """Child sections should not be included in @order."""
     plugin = module.VimPlugin('myplugin')
     main_module = module.Module('myplugin', plugin)
     first = Block(vimdoc.SECTION)
     first.Local(name='Section 1', id='first')
     second = Block(vimdoc.SECTION)
     second.Local(name='Section 2', id='second', parent_id='first')
     first.Global(order=['first', 'second'])
     main_module.Merge(first)
     main_module.Merge(second)
     with self.assertRaises(error.OrderedChildSections) as cm:
         main_module.Close()
     self.assertEqual(
         ("Child section second included in ordering ['first', 'second'].",
          ), cm.exception.args)
Example #6
0
 def test_section(self):
     plugin = module.VimPlugin('myplugin')
     main_module = module.Module('myplugin', plugin)
     intro = Block(vimdoc.SECTION)
     intro.Local(name='Introduction', id='intro')
     main_module.Merge(intro)
     main_module.Close()
     self.assertEqual([intro], list(main_module.Chunks()))
Example #7
0
 def test_manual_section_ordering(self):
     """Sections should be ordered according to explicitly configured order."""
     plugin = module.VimPlugin('myplugin')
     main_module = module.Module('myplugin', plugin)
     intro = Block(vimdoc.SECTION)
     intro.Local(name='Introduction', id='intro')
     # Configure explicit order.
     intro.Global(order=['commands', 'about', 'intro'])
     commands = Block(vimdoc.SECTION)
     commands.Local(name='Commands', id='commands')
     about = Block(vimdoc.SECTION)
     about.Local(name='About', id='about')
     # Merge in arbitrary order.
     main_module.Merge(commands)
     main_module.Merge(about)
     main_module.Merge(intro)
     main_module.Close()
     self.assertEqual([commands, about, intro], list(main_module.Chunks()))
Example #8
0
 def _AddMaktabaFlagHelp(self):
   """If any maktaba flags were documented, add a default configuration section
    to explain how to use them.
   """
   if self.GetCollection(vimdoc.FLAG):
     block = Block(vimdoc.SECTION, is_default=True)
     block.Local(id='config', name='Configuration')
     block.AddLine(
         'This plugin uses maktaba flags for configuration. Install Glaive'
         ' (https://github.com/google/glaive) and use the @command(Glaive)'
         ' command to configure them.')
     self.Merge(block)
Example #9
0
    def Close(self):
        """Closes the module.

    All default sections that have not been overridden will be created.
    """
        if self.GetCollection(
                vimdoc.FUNCTION) and 'functions' not in self.sections:
            functions = Block(vimdoc.SECTION)
            functions.Local(id='functions', name='Functions')
            self.Merge(functions)
        if (self.GetCollection(vimdoc.EXCEPTION)
                and 'exceptions' not in self.sections):
            exceptions = Block(vimdoc.SECTION)
            exceptions.Local(id='exceptions', name='Exceptions')
            self.Merge(exceptions)
        if self.GetCollection(
                vimdoc.COMMAND) and 'commands' not in self.sections:
            commands = Block(vimdoc.SECTION)
            commands.Local(id='commands', name='Commands')
            self.Merge(commands)
        if self.GetCollection(
                vimdoc.DICTIONARY) and 'dicts' not in self.sections:
            dicts = Block(vimdoc.SECTION)
            dicts.Local(id='dicts', name='Dictionaries')
            self.Merge(dicts)
        if self.GetCollection(vimdoc.FLAG):
            # If any maktaba flags were documented, add a default configuration
            # section to explain how to use them.
            config = Block(vimdoc.SECTION, is_default=True)
            config.Local(id='config', name='Configuration')
            config.AddLine(
                'This plugin uses maktaba flags for configuration. Install Glaive'
                ' (https://github.com/google/glaive) and use the @command(Glaive)'
                ' command to configure them.')
            self.Merge(config)
        if ((self.GetCollection(vimdoc.FLAG)
             or self.GetCollection(vimdoc.SETTING))
                and 'config' not in self.sections):
            config = Block(vimdoc.SECTION)
            config.Local(id='config', name='Configuration')
            self.Merge(config)

        for backmatter in self.backmatters:
            if backmatter not in self.sections:
                raise error.NoSuchSection(backmatter)
        # Use explicit order as partial ordering and merge with default section
        # ordering. All custom sections must be ordered explicitly.
        self.order = self._GetSectionOrder(self.order, self.sections)

        known = set(self.sections)
        neglected = sorted(known.difference(self.order))
        if neglected:
            raise error.NeglectedSections(neglected, self.order)
        # Sections are now in order.
        for key in self.order:
            if key in self.sections:
                # Move to end.
                self.sections[key] = self.sections.pop(key)
Example #10
0
 def LookupTag(self, typ, name):
     """Returns the tag name for the given type and name."""
     # Support both @command(Name) and @command(:Name).
     if typ == vimdoc.COMMAND:
         fullname = name.lstrip(':')
     elif typ == vimdoc.SETTING:
         scope_match = regex.setting_scope.match(name)
         fullname = scope_match and name or 'g:' + name
     else:
         fullname = name
     block = None
     if typ in self.collections:
         collection = self.collections[typ]
         candidates = [x for x in collection if x.FullName() == fullname]
         if len(candidates) > 1:
             raise KeyError('Found multiple %ss named %s' % (typ, name))
         if candidates:
             block = candidates[0]
     if block is None:
         # Create a dummy block to get default tag.
         block = Block(typ)
         block.Local(name=fullname)
     return block.TagName()
Example #11
0
 def test_child_sections(self):
     """Sections should be ordered after their parents."""
     plugin = module.VimPlugin('myplugin')
     main_module = module.Module('myplugin', plugin)
     first = Block(vimdoc.SECTION)
     first.Local(name='Section 1', id='first')
     # Configure explicit order.
     first.Global(order=['first', 'second', 'third'])
     second = Block(vimdoc.SECTION)
     second.Local(name='Section 2', id='second')
     third = Block(vimdoc.SECTION)
     third.Local(name='Section 3', id='third')
     child11 = Block(vimdoc.SECTION)
     child11.Local(name='child11', id='child11', parent_id='first')
     child12 = Block(vimdoc.SECTION)
     child12.Local(name='child12', id='child12', parent_id='first')
     child21 = Block(vimdoc.SECTION)
     child21.Local(name='child21', id='child21', parent_id='second')
     # Merge in arbitrary order.
     for m in [second, child12, third, child11, first, child21]:
         main_module.Merge(m)
     main_module.Close()
     self.assertEqual([first, child11, child12, second, child21, third],
                      list(main_module.Chunks()))
Example #12
0
def Modules(directory):
    """Creates modules from a plugin directory.

  Note that there can be many, if a plugin has standalone parts that merit their
  own helpfiles.

  Args:
    directory: The plugin directory.
  Yields:
    Module objects as necessary.
  """
    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
Example #13
0
  def Close(self):
    """Closes the module.

    All default sections that have not been overridden will be created.
    """
    # ----------------------------------------------------------
    # Add default sections.

    # type : (id, name)
    default_sections = [
        (vimdoc.FUNCTION, 'functions', 'Functions'),
        (vimdoc.EXCEPTION, 'exceptions', 'Exceptions'),
        (vimdoc.COMMAND, 'commands', 'Commands'),
        (vimdoc.DICTIONARY, 'dicts', 'Dictionaries'),
        (vimdoc.FLAG, 'config', 'Configuration'),
        (vimdoc.SETTING, 'config', 'Configuration'),
      ]

    for (typ, id, name) in default_sections:
      if typ == vimdoc.FLAG:
        self._AddMaktabaFlagHelp()

      if self.GetCollection(typ) and (id not in self.sections):
        # Create the section if it does not exist.
        block = Block(vimdoc.SECTION)
        block.Local(id=id, name=name)
        self.Merge(block)


    # ----------------------------------------------------------
    # Add backmatter.

    for backmatter in self.backmatters:
      if backmatter not in self.sections:
        raise error.NoSuchSection(backmatter)

    # ----------------------------------------------------------
    # Expand child sections and order the section list for output.

    # Use explicit order as partial ordering and merge with default section
    # ordering. All custom sections must be ordered explicitly.
    self.order = self._GetSectionOrder(self.order, self.sections)

    # Child section collection
    to_delete = []
    for key in self.sections:
      section = self.sections[key]
      parent_id = section.locals.get('parent_id', None)
      if parent_id:
        if parent_id not in self.sections:
          raise error.NoSuchParentSection(
                  section.locals['name'], parent_id)
        parent = self.sections[parent_id]
        parent.locals.setdefault('children', []).append(section)
        to_delete.append(key)

    for key in to_delete:
      self.sections.pop(key)

    # Check that all top-level sections are included in ordering.
    known = set(self.sections)
    neglected = sorted(known.difference(self.order))
    if neglected:
      raise error.NeglectedSections(neglected, self.order)

    # Reinsert top-level sections in the correct order, expanding the tree of
    # child sections along the way so we have a linear list of sections to pass
    # to the output functions.

    # Helper function to recursively add children to self.sections.
    # We add a 'level' variable to locals so that WriteTableOfContents can keep
    # track of the nesting.
    def _AddChildSections(section):
      section.locals.setdefault('level', 0)
      if 'children' in section.locals:
        sort_key = lambda s: s.locals['name']
        for child in sorted(section.locals['children'], key=sort_key):
          child.locals['level'] = section.locals['level'] + 1
          self.sections[child.locals['id']] = child
          _AddChildSections(child)

    # Insert sections according to the @order directive
    for key in self.order:
      if key in self.sections:
        section = self.sections.pop(key)
        if 'parent_id' in section.locals and section.locals['parent_id']:
          raise error.OrderedChildSections(section.locals['id'], self.order)
        # Move to end.
        self.sections[key] = section
        _AddChildSections(section)