def test_meta_schema(self): meta = {'foo': u'bar'} with self.assertRaises(InvalidPageMetadata): page = Page('some-page', None, '.', 'test-project-0.1', meta=meta) Page.meta_schema[Optional('foo')] = And(str, len) page = Page('some-page', None, '.', 'test-project-0.1', meta=meta) self.assertEqual(page.meta.get('foo'), u'bar')
def _formatting_page_cb(self, formatter: Formatter, page: Page) -> None: ''' Replace Meson refman tags Links of the form [[function]] are automatically replaced with valid links to the correct URL. To reference objects / types use the [[@object]] syntax. ''' link_regex = re.compile( r'(\[\[#?@?([ \n\t]*[a-zA-Z0-9_]+[ \n\t]*\.)*[ \n\t]*[a-zA-Z0-9_]+[ \n\t]*\]\])(.)?', re.MULTILINE) for m in link_regex.finditer(page.formatted_contents): i = m.group(1) obj_id: str = i[2:-2] obj_id = re.sub(r'[ \n\t]', '', obj_id) # Remove whitespaces # Marked as inside a code block? in_code_block = False if obj_id.startswith('#'): in_code_block = True obj_id = obj_id[1:] if obj_id not in self._data: warn( 'unknown-refman-link', f'{Path(page.name).name}: Unknown Meson refman link: "{obj_id}"' ) continue # Just replaces [[!file.id]] paths with the page file (no fancy HTML) if obj_id.startswith('!'): page.formatted_contents = page.formatted_contents.replace( i, self._data[obj_id]) continue # Fancy links for functions and methods text = obj_id if text.startswith('@'): text = text[1:] elif in_code_block: if m.group(3) != '(': text = text + '()' else: text = text + '()' if not in_code_block: text = f'<code>{text}</code>' link = f'<a href="{self._data[obj_id]}"><ins>{text}</ins></a>' page.formatted_contents = page.formatted_contents.replace( i, link, 1)
def get_symbol_page(self, symbol_name, symbol_pages, smart_pages, section_links): if symbol_name in symbol_pages: return symbol_pages[symbol_name] symbol = self.app.database.get_symbol(symbol_name) assert symbol is not None if symbol.parent_name and symbol.parent_name != symbol_name: page = self.get_symbol_page( symbol.parent_name, symbol_pages, smart_pages, section_links) else: smart_key = self._get_smart_key(symbol) if smart_key is None: return None if smart_key in smart_pages: page = smart_pages[smart_key] else: pagename = self.get_pagename(smart_key) page = Page(smart_key, True, self.project.sanitized_name, self.extension_name, output_path=os.path.dirname(pagename)) if page.link.ref in section_links: self.warn('output-page-conflict', 'Creating a page for symbol %s would overwrite the page ' 'declared in a toplevel comment (%s)' % (symbol_name, page.link.ref)) page = None else: smart_pages[smart_key] = page if page is not None: symbol_pages[symbol_name] = page return page
def __add_subpage(self, tree, index, source_file, symbols): page_name = self.__get_rel_source_path(source_file) page = tree.get_pages().get(page_name) needs_comment = False if not page: page = Page(page_name, None, os.path.dirname(page_name), tree.project.sanitized_name) page.extension_name = self.extension_name page.generated = True tree.add_page(index, page_name, page) needs_comment = True else: if not source_file.endswith(('.markdown', '.md')) and not \ page.comment: needs_comment = True page.is_stale = True if needs_comment: source_abs = os.path.abspath(source_file) if os.path.exists(source_abs): page.comment = self.app.database.get_comment(source_abs) else: page.comment = self.app.database.get_comment(page_name) page.symbol_names |= symbols
def __add_subpage(self, tree, index, source_file, symbols): page, page_name = self.__get_page(tree, source_file) if not page: comment = self.__get_comment_for_page(source_file, page_name) page = Page(page_name, None, os.path.dirname(page_name), tree.project.sanitized_name) page.set_comment(comment) page.extension_name = self.extension_name page.generated = True tree.add_page(index, page_name, page) else: if not source_file.endswith(('.markdown', '.md')) and not \ page.comment: page.set_comment( self.__get_comment_for_page(source_file, page_name)) page.is_stale = True page.symbol_names |= symbols
def make_pages(self): # All symbol names that no longer need to be assigned to a page dispatched_symbol_names = set() # Map symbol names with pages # This is used for assigning symbols with a parent to the page # where their parent will be rendered, unless the symbol has been # explicitly assigned or ignored symbol_pages = {} smart_pages = OrderedDict() # Map pages with the sources they explicitly import imported_sources = {} # This is simply used as a conflict detection mechanism, see # hotdoc.core.tests.test_doc_tree.TestTree.test_section_and_path_conflict section_links = set() # These are used as a duplicate detection mechanism, map # relocated or ignored symbols to the source files where they were initially # listed relocated_symbols = {} private_symbols = {} # First we make one page per toplevel comment (eg. SECTION comment) # This is the highest priority mechanism for sorting symbols for comment in self._get_toplevel_comments(): # Programming error from extension author assert comment.name symbol_names = comment.meta.pop('symbols', []) private_symbol_names = comment.meta.pop('private-symbols', []) sources = comment.meta.pop('sources', None) page = Page(comment.name, True, self.project.sanitized_name, self.extension_name, comment=comment) for symbol_name in symbol_names: if symbol_name in relocated_symbols: self.warn('symbol-listed-twice', 'Symbol %s listed in %s was already listed in %s' % (symbol_name, comment.filename, relocated_symbols[symbol_name])) continue elif symbol_name in private_symbols: self.warn('symbol-listed-twice', 'Symbol %s listed in %s was marked as private in %s' % (symbol_name, comment.filename, private_symbols[symbol_name])) continue else: page.symbol_names.add(symbol_name) symbol_pages[symbol_name] = page relocated_symbols[symbol_name] = comment.filename dispatched_symbol_names.add(symbol_name) for symbol_name in private_symbol_names: if symbol_name in relocated_symbols: self.warn('symbol-listed-twice', 'Symbol %s marked private in %s was already listed in %s' % (symbol_name, comment.filename, relocated_symbols[symbol_name])) continue elif symbol_name in private_symbols: self.warn('symbol-listed-twice', 'Symbol %s marked as private in %s was ' 'already marked as private in %s' % (symbol_name, comment.filename, private_symbols[symbol_name])) continue private_symbols[symbol_name] = comment.filename symbol_pages[symbol_name] = None dispatched_symbol_names.add(symbol_name) section_links.add(page.link.ref) smart_key = self._get_comment_smart_key(comment) if smart_key in smart_pages: smart_pages[comment.name] = page else: smart_pages[smart_key] = page if sources is not None: abs_sources = [] for source in sources: if os.path.isabs(source): abs_sources.append(source) else: abs_sources.append(os.path.abspath(os.path.join( os.path.dirname(comment.filename), source))) imported_sources[page] = abs_sources # Used as a duplicate detection mechanism relocated_sources = {} # We now browse all the pages with explicitly imported sources # Importing sources has a lower level of priority than importing # symbols, which is why we do that in a separate loop for page, sources in imported_sources.items(): for source in sources: if source not in self._get_all_sources(): self.warn('invalid-relocated-source', 'Source %s does not exist but is relocated in %s' % (source, page.name)) continue if source in relocated_sources: self.warn('invalid-relocated-source', 'Source %s relocated in %s was already relocated in %s' % (source, page.name, relocated_sources[source])) continue if source in self._created_symbols: symbol_names = OrderedSet( self._created_symbols[source]) - dispatched_symbol_names page.symbol_names |= symbol_names dispatched_symbol_names |= symbol_names relocated_sources[source] = page.name # We now browse all the symbols we have created for _, symbol_names in self._created_symbols.items(): for symbol_name in symbol_names: if symbol_name in dispatched_symbol_names: continue page = self.get_symbol_page( symbol_name, symbol_pages, smart_pages, section_links) # Can be None if creating a page to hold the symbol conflicts with # a page explicitly declared in a toplevel comment or a parent has been # marked as private if page is None: continue page.symbol_names.add(symbol_name) dispatched_symbol_names.add(symbol_name) # Finally we make our index page if self.index: index_page = self.project.tree.parse_page( self.index, self.extension_name) else: index_page = Page('%s-index' % self.argument_prefix, True, self.project.sanitized_name, self.extension_name) if not index_page.title: index_page.title = self._get_smart_index_title() smart_pages['%s-index' % self.argument_prefix] = index_page return smart_pages