def handleMatch(self, match): """ Process the input file supplied. """ # Update the settings from regex match settings = self.getSettings(match.group(3)) # Build the complete filename. rel_filename = match.group(2) filename = MooseDocs.abspath(rel_filename) # Read the file and create element if not os.path.exists(filename): el = self.createErrorElement( "The input file was not located: {}".format(rel_filename)) elif settings['block'] is None: el = self.createErrorElement( "Use of !input syntax while not providing a block=some_block. If you wish to include the entire file, use !text instead" ) else: parser = ParseGetPot(filename) node = parser.root_node.getNode(settings['block']) if node == None: el = self.createErrorElement('Failed to find {} in {}.'.format( settings['block'], rel_filename)) else: content = node.createString() label = match.group(2) if match.group(2) else rel_filename el = self.createElement(label, content, filename, rel_filename, settings) return el
def __init__(self, **kwargs): super(ListingClangPattern, self).__init__(**kwargs) # The make command to execute self._make_dir = MooseDocs.abspath(kwargs.pop('make_dir')) if not os.path.exists(os.path.join(self._make_dir, 'Makefile')): LOG.error("Invalid path provided for make: %s", self._make_dir)
def handleMatch(self, match): """ Process the text file provided. """ # Update the settings from g match settings = self.getSettings(match.group(3)) # Read the file rel_filename = match.group('filename').lstrip('/') filename = MooseDocs.abspath(rel_filename) if not os.path.exists(filename): return self.createErrorElement("Unable to locate file: {}".format(rel_filename)) # Figure out file extensions if settings['language'] is None: _, ext = os.path.splitext(rel_filename) if ext in ['.C', '.h', '.cpp', '.hpp']: settings['language'] = 'cpp' elif ext == '.py': settings['language'] = 'python' else: settings['language'] = 'text' # Extract the content from the file content = self.extractContent(filename, settings) if content is None: return self.createErrorElement("Failed to extract content from {}.".format(filename)) # Apply additional settings to content content = self.prepareContent(content, settings) # Return the Element object el = self.createElement(content, rel_filename, settings) return el
def check(config_file=None, locations=None, generate=None): """ Performs checks and optionally generates stub pages for missing documentation. """ # Read the configuration app_ext = 'MooseDocs.extensions.app_syntax' config = MooseDocs.load_config(config_file) if app_ext not in config: mooseutils.MooseException("The 'check' utility requires the 'app_syntax' extension.") ext_config = config[app_ext] # Run the executable exe = MooseDocs.abspath(ext_config['executable']) if not os.path.exists(exe): raise IOError('The executable does not exist: {}'.format(exe)) else: LOG.debug("Executing %s to extract syntax.", exe) raw = mooseutils.runExe(exe, '--yaml') yaml = mooseutils.MooseYaml(raw) # Populate the syntax for loc in ext_config['locations']: for key, value in loc.iteritems(): if (locations is None) or (key in locations): value['group'] = key syntax = common.MooseApplicationSyntax(yaml, generate=generate, install=ext_config['install'], **value) LOG.info("Checking documentation for '%s'.", key) syntax.check() return None
def handleMatch(self, match): """ Process the text file provided. """ # Update the settings from regex match settings = self.getSettings(match.group(3)) # Read the file rel_filename = match.group(2).lstrip('/') filename = MooseDocs.abspath(rel_filename) if not os.path.exists(filename): return self.createErrorElement( "Unable to locate file: {}".format(rel_filename)) if settings['line']: content = self.extractLine(filename, settings["line"]) elif settings['start'] or settings['end']: content = self.extractLineRange(filename, settings['start'], settings['end'], settings['include_end']) else: with open(filename) as fid: content = fid.read() if content == None: return self.createErrorElement( "Failed to extract content from {}.".format(filename)) # Return the Element object el = self.createElement(match.group(2), content, filename, rel_filename, settings) return el
def replace(self, match): """ Substitution function for the re.sub function. """ filename = MooseDocs.abspath(match.group(1)) settings = self.getSettings(match.group(2)) if not os.path.exists(filename): msg = "Failed to located filename in following command.\n{}" el = self.createErrorElement(msg.format(match.group(0)), title="Unknown Markdown File") return etree.tostring(el) if settings['start'] or settings['end']: content = ListingPattern.extractLineRange(filename, settings['start'], settings['end'], settings['include-end']) else: with open(filename, 'r') as fid: content = fid.read() if settings['re']: match = re.search(settings['re'], content, flags=re.MULTILINE|re.DOTALL) if not match: msg = "Failed to located regex in following command.\n{}" el = self.createErrorElement(msg.format(settings['re']), title="Failed Regex") return etree.tostring(el) content = match.group(0) self._found = True return content
def _updateSyntax(self, path, objects, actions): """ A helper for populating the syntax/filename/markdown databases. (private) Args: path[str]: A valid source directory to inspect. """ reg_re = r'(?<!\:)register(?!RecoverableData|edError)\w+?\((?P<key>\w+)\);' reg_named_re = r'registerNamed\w+?\((?P<class>\w+),\s*"(?P<key>\w+)"\);' action_re = r'(registerActionSyntax|registerSyntax|registerSyntaxTask)\("(?P<action>\w+)"' \ r'\s*,\s*"(?P<key>.*?)\"[,\);]' # Walk the directory, looking for files with the supplied extension. for root, _, files in os.walk(MooseDocs.abspath(path), topdown=False): for filename in files: fullfile = os.path.join(root, filename) # Inspect source files if filename.endswith('.C') or filename.endswith('.h'): fid = open(fullfile, 'r') content = fid.read() fid.close() # Update class to source definition map if filename.endswith('.h'): for match in re.finditer( r'class\s*(?P<class>\w+)\b[^;]', content): key = match.group('class') self._filenames[key] = [fullfile] src = fullfile.replace('/include/', '/src/')[:-2] + '.C' if os.path.exists(src) and ( src not in self._filenames[key]): self._filenames[key].append(src) # Map of registered objects for match in re.finditer(reg_re, content): key = match.group('key') objects[key] = key # Map of named registered objects for match in re.finditer(reg_named_re, content): name = match.group('class') key = match.group('key') objects[key] = name # Action syntax map for match in re.finditer(action_re, content): key = match.group('key') action = match.group('action') actions[key].add(action) for root, _, files in os.walk(path, topdown=False): for filename in files: fullfile = os.path.join(root, filename) # Inspect source files name, ext = os.path.splitext(filename) if (ext == '.C') and (name in self._filenames): self._filenames[name].append(fullfile)
def __init__(self, **kwargs): super(AppSyntaxExtension, self).__init__(**kwargs) # Storage for the MooseLinkDatabase object self.syntax = None # Create the absolute path to the executable self.setConfig('executable', MooseDocs.abspath(self.getConfig('executable')))
def _updateSyntax(self, path, objects, actions): """ A helper for populating the syntax/filename/markdown databases. (private) Args: path[str]: A valid source directory to inspect. """ reg_re = r'(?<!\:)register(?!RecoverableData|edError)\w+?\((?P<key>\w+)\);' reg_named_re = r'registerNamed\w+?\((?P<class>\w+),\s*"(?P<key>\w+)"\);' action_re = r'(registerActionSyntax|registerSyntax|registerSyntaxTask)\("(?P<action>\w+)"' \ r'\s*,\s*"(?P<key>.*?)\"[,\);]' # Walk the directory, looking for files with the supplied extension. for root, _, files in os.walk(MooseDocs.abspath(path), topdown=False): for filename in files: fullfile = os.path.join(root, filename) # Inspect source files if filename.endswith('.C') or filename.endswith('.h'): fid = open(fullfile, 'r') content = fid.read() fid.close() # Update class to source definition map if filename.endswith('.h'): for match in re.finditer(r'class\s*(?P<class>\w+)\b[^;]', content): key = match.group('class') self._filenames[key] = [fullfile] src = fullfile.replace('/include/', '/src/')[:-2] + '.C' if os.path.exists(src) and (src not in self._filenames[key]): self._filenames[key].append(src) # Map of registered objects for match in re.finditer(reg_re, content): key = match.group('key') objects[key] = key # Map of named registered objects for match in re.finditer(reg_named_re, content): name = match.group('class') key = match.group('key') objects[key] = name # Action syntax map for match in re.finditer(action_re, content): key = match.group('key') action = match.group('action') actions[key].add(action) for root, _, files in os.walk(path, topdown=False): for filename in files: fullfile = os.path.join(root, filename) # Inspect source files name, ext = os.path.splitext(filename) if (ext == '.C') and (name in self._filenames): self._filenames[name].append(fullfile)
def _insertFiles(filenames): """ Helper function for jinja2 to read css file and return as string. """ if isinstance(filenames, str): filenames = [filenames] out = [] for filename in filenames: with open(MooseDocs.abspath(filename), 'r') as fid: out += [fid.read().strip('\n')] return '\n'.join(out)
def arguments(self, template_args, text): #pylint: disable=no-self-use """ Method for modifying the template arguments to be applied to the jinja2 templates engine. Args: template_args[dict]: Template arguments to be applied to jinja2 template. text[str]: Convert markdown to be applied via the 'content' template argument. """ template_args['content'] = text if 'navigation' in template_args: template_args['navigation'] = \ MooseDocs.yaml_load(MooseDocs.abspath(template_args['navigation']))
def parseFilenames(self, filenames_block): """ Parse a set of lines with filenames, image options, and optional captions. Filenames can contain wildcards and glob will be used to expand them. Any CSS styles after the filename (but before caption if it exists) will be applied to the image (image is set as a background in slider). CSS styles listed after the caption will be applied to it. Expected input is similar to: images/1.png caption=My caption color=blue images/2.png background-color=gray caption= Another caption color=red images/other*.png Input: filenames_block[str]: String block to parse Return: list of list of dicts. The list has an entry for each image (including one for each expanded image from glob), each entry contains: 1. dict of "path" which is the filename path 2. dict of attributes to be applied to the image 3. dict of attributes to be applied to the caption Each image will default to fit the slideshow window with white background and no caption if no options are specified. """ lines = filenames_block.split("\n") files = [] regular_expression = re.compile(r'(.*?\s|.*?$)(.*?)(caption.*|$)') for line in lines: line = line.strip() matches = regular_expression.search(line) fname = matches.group(1).strip() # Build separate dictionaries for the image and caption img_settings = self.getSettings(matches.group(2).strip()) img_settings.setdefault('background-size', 'contain') img_settings.setdefault('background-repeat', 'no-repeat') img_settings.setdefault('background-color', 'white') caption_settings = self.getSettings(matches.group(3).strip()) img_settings.pop('counter') caption_settings.pop('counter') new_files = sorted(glob.glob(MooseDocs.abspath(fname))) if not new_files: LOG.error('Parser unable to detect file(s) %s in media.py', fname) return [] for f in new_files: files.append(SliderBlockProcessor.ImageInfo(os.path.relpath(f), img_settings, caption_settings)) return files
def parseBibtexFile(self, bibfile): """ Returns parsed bibtex file. If "macro_files" are supplied in the configuration file, then a temporary file will be made that contains the supplied macros above the original bib file. This temporary combined file can then be parsed by pybtex. """ if self._macro_files: tBib_path = MooseDocs.abspath("tBib.bib") with open(tBib_path, "wb") as tBib: for tFile in self._macro_files: with open(MooseDocs.abspath(tFile.strip()), "rb") as inFile: shutil.copyfileobj(inFile, tBib) with open(bibfile, "rb") as inFile: shutil.copyfileobj(inFile, tBib) data = parse_file(tBib_path) if os.path.isfile(tBib_path): os.remove(tBib_path) else: data = parse_file(bibfile) return data
def parseBibtexFile(self, bibfile): """ Returns parsed bibtex file. If "macro_files" are supplied in the configuration file, then a temporary file will be made that contains the supplied macros above the original bib file. This temporary combined file can then be parsed by pybtex. """ if self._macro_files: t_bib_path = MooseDocs.abspath("tBib.bib") with open(t_bib_path, "wb") as t_bib: for t_file in self._macro_files: with open(MooseDocs.abspath(t_file.strip()), "rb") as in_file: shutil.copyfileobj(in_file, t_bib) with open(bibfile, "rb") as in_file: shutil.copyfileobj(in_file, t_bib) data = parse_file(t_bib_path) if os.path.isfile(t_bib_path): os.remove(t_bib_path) else: data = parse_file(bibfile) return data
def __init__(self, **kwargs): # Storage for the MooseLinkDatabase object self.syntax = None # Define the configuration options self.config = dict() self.config['executable'] = [ '', "The executable to utilize for generating application syntax." ] self.config['locations'] = [ dict(), "The locations to parse for syntax." ] self.config['repo'] = [ '', "The remote repository to create hyperlinks." ] self.config['links'] = [ dict(), "The set of paths for generating input file and source code links to objects." ] self.config['slides'] = [ False, "Enable the parsing for creating reveal.js slides." ] self.config['package'] = [ False, "Enable the use of the MoosePackageParser." ] self.config['graphviz'] = [ '/opt/moose/graphviz/bin', 'The location of graphviz executable for use with diagrams.' ] self.config['dot_ext'] = [ 'svg', "The graphviz/dot output file extension (default: svg)." ] self.config['install'] = [ '', "The location to install system and object documentation." ] self.config['macro_files'] = [ '', "List of paths to files that contain macros to be used in bibtex parsing." ] # Construct the extension object super(MooseMarkdown, self).__init__(**kwargs) # Create the absolute path to the executable self.setConfig('executable', MooseDocs.abspath(self.getConfig('executable')))
def __init__(self, repo=None, links=None, **kwargs): #pylint: disable=unused-argument self._repo = repo self.inputs = collections.OrderedDict() self.children = collections.OrderedDict() for key, paths in links.iteritems(): self.inputs[key] = dict() self.children[key] = dict() for path in paths: for base, _, files in os.walk(MooseDocs.abspath(path), topdown=False): for filename in files: full_name = os.path.join(base, filename) if filename.endswith('.i'): self.search(full_name, self.INPUT_RE, self.inputs[key]) elif filename.endswith('.h'): self.search(full_name, self.HEADER_RE, self.children[key])
def handleMatch(self, match): """ Process the C++ file provided using clang. """ # Update the settings from regex match settings = self.getSettings(match.group(3)) # Extract relative filename rel_filename = match.group(2).lstrip('/') # Error if the clang parser did not load if not HAVE_MOOSE_CPP_PARSER: log.error("Unable to load the MOOSE clang C++ parser.") el = self.createErrorElement( "Failed to load python clang python bindings.") return el # Read the file and create element filename = MooseDocs.abspath(rel_filename) if not os.path.exists(filename): el = self.createErrorElement( "C++ file not found: {}".format(rel_filename)) elif settings['method'] is None: el = self.createErrorElement( "Use of !clang syntax while not providing a method=some_method. If you wish to include the entire file, use !text instead." ) else: log.debug('Parsing method "{}" from {}'.format( settings['method'], filename)) try: parser = mooseutils.MooseSourceParser(self._make_dir) parser.parse(filename) decl, defn = parser.method(settings['method']) el = self.createElement(match.group(2), defn, filename, rel_filename, settings) except: el = self.createErrorElement( 'Failed to parse method using clang, check that the supplied method name exists.' ) # Return the Element object return el
def generate(config_file='moosedocs.yml', generate=True, locations=None, **kwargs): """ Generates MOOSE system and object markdown files from the source code. Args: config_file[str]: (Default: 'moosedocs.yml') The MooseDocs project configuration file. """ # Read the configuration config = MooseDocs.load_config(config_file) _, ext_config = MooseDocs.get_markdown_extensions(config) ext_config = ext_config['MooseDocs.extensions.MooseMarkdown'] # Run the executable exe = MooseDocs.abspath(ext_config['executable']) if not os.path.exists(exe): raise IOError('The executable does not exist: {}'.format(exe)) else: log.debug("Executing {} to extract syntax.".format(exe)) raw = mooseutils.runExe(exe, '--yaml') yaml = mooseutils.MooseYaml(raw) # Populate the syntax for loc in ext_config['locations']: for key, value in loc.iteritems(): if (locations == None) or (key in locations): value['group'] = key syntax = MooseDocs.MooseApplicationSyntax( yaml, generate=generate, install=ext_config['install'], **value) log.info("Checking documentation for '{}'.".format(key)) syntax.check()
def createImageElement(self, rel_filename, settings): """ Create the element containing the image, this is a separate function to allow for other objects (i.e., MooseFigure) to utilize this class to build similar html. Inputs: rel_filename[str]: The path to the image relative to the git repository. settings[dict]: The settings extracted via getSettings() method. """ # Read the file and create element filename = MooseDocs.abspath(rel_filename) if not os.path.exists(filename): return self.createErrorElement('File not found: {}'.format(rel_filename)) # Create the figure element el = self.applyElementSettings(etree.Element('div'), settings) card = etree.SubElement(el, 'div') card.set('class', 'card') img_card = etree.SubElement(card, 'div') img_card.set('class', 'card-image') img = etree.SubElement(img_card, 'img') img.set('src', os.path.relpath(filename, os.getcwd())) img.set('class', 'materialboxed') # Add caption if settings['caption']: caption = etree.SubElement(card, 'div') p = etree.SubElement(caption, 'p') p.set('class', 'moose-caption') p.set('align', "justify") p.text = settings['caption'] return el
def handleMatch(self, match): """ Process the text file provided. """ # Update the settings from g match settings = self.getSettings(match.group(3)) # Read the file rel_filename = match.group('filename').lstrip('/') filename = MooseDocs.abspath(rel_filename) if not os.path.exists(filename): return self.createErrorElement( "Unable to locate file: {}".format(rel_filename)) # Figure out file extensions if settings['language'] is None: _, ext = os.path.splitext(rel_filename) if ext in ['.C', '.h', '.cpp', '.hpp']: settings['language'] = 'cpp' elif ext == '.py': settings['language'] = 'python' else: settings['language'] = 'text' # Extract the content from the file content = self.extractContent(filename, settings) if content is None: return self.createErrorElement( "Failed to extract content from {}.".format(filename)) # Apply additional settings to content content = self.prepareContent(content, settings) # Return the Element object el = self.createElement(content, rel_filename, settings) return el
def handleMatch(self, match): """ Create the element containing the image, this is a separate function to allow for other objects. Inputs: rel_filename[str]: The path to the image relative to the git repository. settings[dict]: The settings extracted via getSettings() method. """ # Extract the filename and settings from regex rel_filename = match.group('filename') settings = self.getSettings(match.group('settings')) # Determine the filename filename = None repo = MooseDocs.abspath(rel_filename) local = os.path.abspath(os.path.join(os.getcwd(), rel_filename)) if rel_filename.startswith('http'): filename = rel_filename elif os.path.exists(repo): filename = os.path.relpath(repo, os.getcwd()) elif os.path.exists(local): filename = os.path.relpath(local, os.getcwd()) else: return self.createErrorElement('File not found: {}'.format(rel_filename)) # Create content div = self.createFloatElement(settings) media_element = self.createMediaElement(filename, settings) div.insert(0, media_element) if settings.get('card', None): self._cardWrapper(div) return div
def __init__(self, yaml_data, paths=None, doxygen=None, name=None, doxygen_name_style='upper', group=None, install=None, generate=False, hide=None): # Defaults if paths is None: paths = [] if hide is None: hide = [] # Public member for syntax object name (i.e., the location name in the configuration file) self._name = name self._group = group self._yaml_data = yaml_data self._hide = hide install = MooseDocs.abspath(install) if install else None self._doxygen = doxygen self._doxygen_name_style = doxygen_name_style self._filenames = dict() self._objects = dict() self._actions = dict() # Update the syntax maps actions = collections.defaultdict(set) objects = dict() for path in paths: full_path = MooseDocs.abspath(path) if not os.path.exists(full_path): LOG.critical("Unknown source directory supplied: %s", full_path) raise IOError(full_path) self._updateSyntax(path, objects, actions) # Create MooseObjectInfo objects for key, value in objects.iteritems(): for node in self._yaml_data['/' + key]: # Skip this node if it has subblocks, which is not possible for MooseObjects if node['subblocks']: continue obj_info = MooseObjectInfo(node, code=self._filenames[value], install=install, group=self._group, generate=generate, hidden=self.hidden(node['name'])) self._objects[obj_info.key] = obj_info # Create MooseActionInfo objects for key, value in actions.iteritems(): for node in self._yaml_data['/' + key]: code = [] for a in value: if a in self._filenames: code += self._filenames[a] info = MooseActionInfo(node, code=code, install=install, group=self._group, generate=generate, hidden=self.hidden(node['name'])) self._actions[info.key] = info # Create MooseActionInfo objects from the MooseObjectInfo # This is needed to allow for the !systems pages to be complete for apps that # do not also include the framework for obj_info in self._objects.itervalues(): action_key = os.path.dirname(obj_info.key) if action_key not in self._actions: for node in self._yaml_data[action_key]: info = MooseActionInfo(node, code=[], install=install, group=self._group, generate=generate, hidden=self.hidden(node['name']), check=False) self._actions[info.key] = info
def run(self, lines): """ Create a bibliography from cite commands. """ # Join the content to enable regex searches throughout entire text content = '\n'.join(lines) # Build the database of bibtex data self._citations = [] # member b/c it is used in substitution function self._bibtex = BibliographyData() # "" bibfiles = [] match = re.search(self.RE_BIBLIOGRAPHY, content) if match: for bfile in match.group(1).split(','): try: bibfiles.append(MooseDocs.abspath(bfile.strip())) data = self.parseBibtexFile(bibfiles[-1]) except UndefinedMacro: LOG.error('Undefined macro in bibtex file: %s, specify macro_files arguments ' \ 'in configuration file (e.g. website.yml)', bfile.strip()) self._bibtex.add_entries(data.entries.iteritems()) else: return lines # Determine the style match = re.search(self.RE_STYLE, content) if match: content = content.replace(match.group(0), '') try: style = find_plugin('pybtex.style.formatting', match.group(1)) except PluginNotFound: LOG.error('Unknown bibliography style "%s"', match.group(1)) return lines else: style = find_plugin('pybtex.style.formatting', 'plain') # Replace citations with author date, as an anchor content = re.sub(self.RE_CITE, self.authors, content) # Create html bibliography if self._citations: # Generate formatted html using pybtex formatted_bibliography = style().format_bibliography(self._bibtex, self._citations) backend = find_plugin('pybtex.backends', 'html') stream = io.StringIO() backend().write_to_stream(formatted_bibliography, stream) # Strip the bib items from the formatted html html = re.findall(r'\<dd\>(.*?)\</dd\>', stream.getvalue(), flags=re.MULTILINE|re.DOTALL) # Produces an ordered list with anchors to the citations output = u'<ol class="moose-bibliography" data-moose-bibfiles="{}">\n' output = output.format(str(bibfiles)) for i, item in enumerate(html): output += u'<li name="{}">{}</li>\n'.format(self._citations[i], item) output += u'</ol>\n' content = re.sub(self.RE_BIBLIOGRAPHY, self.markdown.htmlStash.store(output, safe=True), content) return content.split('\n')
def run(self, lines): """ Create a bibliography from cite commands. """ # Join the content to enable regex searches throughout entire text content = '\n'.join(lines) # Build the database of bibtex data self._citations = [] # member b/c it is used in substitution function self._bibtex = BibliographyData() # "" bibfiles = [] match = re.search(self.RE_BIBLIOGRAPHY, content) if match: bib_string = match.group(0) for bfile in match.group(1).split(','): try: bibfiles.append(MooseDocs.abspath(bfile.strip())) data = self.parseBibtexFile(bibfiles[-1]) except Exception as e: if isinstance(e,undefined_macro_exception): log.error('Undefined macro in bibtex file: {}, '\ 'specify macro_files arguments in configuration file (e.g. moosedocs.yml)'\ .format(bfile.strip())) else: log.error('Failed to parse bibtex file: {}'.format(bfile.strip())) traceback.print_exc(e) return lines self._bibtex.add_entries(data.entries.iteritems()) else: return lines # Determine the style match = re.search(self.RE_STYLE, content) if match: content = content.replace(match.group(0), '') try: style = find_plugin('pybtex.style.formatting', match.group(1)) except: log.error('Unknown bibliography style "{}"'.format(match.group(1))) return lines else: style = find_plugin('pybtex.style.formatting', 'plain') # Replace citations with author date, as an anchor content = re.sub(self.RE_CITE, self.authors, content) # Create html bibliography if self._citations: # Generate formatted html using pybtex formatted_bibliography = style().format_bibliography(self._bibtex, self._citations) backend = find_plugin('pybtex.backends', 'html') stream = io.StringIO() backend().write_to_stream(formatted_bibliography, stream) # Strip the bib items from the formatted html html = re.findall(r'\<dd\>(.*?)\</dd\>', stream.getvalue(), flags=re.MULTILINE|re.DOTALL) # Produces an ordered list with anchors to the citations output = u'<ol class="moose-bibliography" data-moose-bibfiles="{}">\n'.format(str(bibfiles)) for i, item in enumerate(html): output += u'<li name="{}">{}</li>\n'.format(self._citations[i], item) output += u'</ol>\n' content = re.sub(self.RE_BIBLIOGRAPHY, self.markdown.htmlStash.store(output, safe=True), content) return content.split('\n')