Example #1
0
 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')
Example #2
0
    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)
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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