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
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)