Ejemplo n.º 1
0
    def on_config(self, config: Config, **kwargs) -> Config:
        """
        Hook for the [`on_config` event](https://www.mkdocs.org/user-guide/plugins/#on_config).

        In this hook, we instantiate our [`MkdocstringsExtension`][mkdocstrings.extension.MkdocstringsExtension]
        and add it to the list of Markdown extensions used by `mkdocs`.

        We pass this plugin's configuration dictionary to the extension when instantiating it (it will need it
        later when processing markdown to get handlers and their global configurations).
        """
        if not config["site_url"]:
            log.error(
                "mkdocstrings.plugin: configuration item 'site_url' is required for cross-references"
            )

        log.debug("mkdocstrings.plugin: Adding extension to the list")

        extension_config = dict(
            theme_name=config["theme"].name,
            mdx=config["markdown_extensions"],
            mdx_configs=config["mdx_configs"],
            mkdocstrings=self.config,
        )

        self.mkdocstrings_extension = MkdocstringsExtension(
            config=extension_config)
        config["markdown_extensions"].append(self.mkdocstrings_extension)
        return config
Ejemplo n.º 2
0
    def collect(self, identifier: str, config: dict) -> dict:
        """
        Collect the documentation tree given an identifier and selection options.

        In this method, we feed one line of JSON to the standard input of the subprocess that was opened
        during instantiation of the collector. Then we read one line of JSON on its standard output.

        We load back the JSON text into a Python dictionary.
        If there is a decoding error, we log it as error and raise a CollectionError.

        If the dictionary contains an `error` key, we log it  as error (with the optional `traceback` value),
        and raise a CollectionError.

        If the dictionary values for keys `loading_errors` and `parsing_errors` are not empty,
        we log them as warnings.

        Then we pick up the only object within the `objects` list (there's always only one, because we collect
        them one by one), rebuild it's categories lists
        (see [`rebuild_category_lists()`][mkdocstrings.handlers.python.rebuild_category_lists]),
        and return it.

        Arguments:
            identifier: The dotted-path of a Python object available in the Python path.
            config: Selection options, used to alter the data collection done by `pytkdocs`.

        Returns:
            The collected object-tree.
        """
        final_config = dict(self.DEFAULT_CONFIG)
        final_config.update(config)

        log.debug("mkdocstrings.handlers.python: Preparing input")
        json_input = json.dumps(
            {"objects": [{
                "path": identifier,
                **final_config
            }]})

        log.debug("mkdocstrings.handlers.python: Writing to process' stdin")
        print(json_input, file=self.process.stdin, flush=True)

        log.debug("mkdocstrings.handlers.python: Reading process' stdout")
        stdout = self.process.stdout.readline()

        log.debug(
            "mkdocstrings.handlers.python: Loading JSON output as Python object"
        )
        try:
            result = json.loads(stdout)
        except json.decoder.JSONDecodeError as error:
            log.error(
                f"mkdocstrings.handlers.python: Error while loading JSON: {stdout}"
            )
            raise CollectionError(str(error))

        if "error" in result:
            message = f"mkdocstrings.handlers.python: Collection failed: {result['error']}"
            if "traceback" in result:
                message += f"\n{result['traceback']}"
            log.error(message)
            raise CollectionError(result["error"])

        if result["loading_errors"]:
            for error in result["loading_errors"]:
                log.warning(f"mkdocstrings.handlers.python: {error}")

        if result["parsing_errors"]:
            for path, errors in result["parsing_errors"].items():
                for error in errors:
                    log.warning(f"mkdocstrings.handlers.python: {error}")

        # We always collect only one object at a time
        result = result["objects"][0]

        log.debug(
            "mkdocstrings.handlers.python: Rebuilding categories and children lists"
        )
        rebuild_category_lists(result)

        return result
Ejemplo n.º 3
0
    def run(self, parent: Element, blocks: Element) -> None:
        block = blocks.pop(0)
        m = self.RE.search(str(block))

        if m:
            # removes the first line
            block = block[m.end():]  # type: ignore

        block, the_rest = self.detab(block)

        if m:
            identifier = m.group(1)
            log.debug(f"mkdocstrings.extension: Matched '::: {identifier}'")
            config = yaml.safe_load(str(block)) or {}

            handler_name = self.get_handler_name(config)
            log.debug(
                f"mkdocstrings.extension: Using handler '{handler_name}'")
            handler = get_handler(
                handler_name, self._config["theme_name"],
                self._config["mkdocstrings"]["custom_templates"])

            selection, rendering = self.get_item_configs(handler_name, config)

            log.debug("mkdocstrings.extension: Collecting data")
            try:
                data = handler.collector.collect(identifier, selection)
            except CollectionError:
                log.error(
                    f"mkdocstrings.extension: Could not collect '{identifier}'"
                )
                return

            log.debug("mkdocstrings.extension: Updating renderer's env")
            handler.renderer.update_env(self.md, self._config)

            log.debug("mkdocstrings.extension: Rendering templates")
            try:
                rendered = handler.renderer.render(data, rendering)
            except TemplateNotFound as error:
                theme_name = self._config["theme_name"]
                log.error(
                    f"mkdocstrings.extension: Template '{error.name}' not found "
                    f"for '{handler_name}' handler and theme '{theme_name}'.")
                return

            log.debug(
                "mkdocstrings.extension: Loading HTML back into XML tree")
            try:
                as_xml = XML(rendered)
            except ParseError as error:
                message = f"mkdocstrings.extension: {error}"
                if "mismatched tag" in str(error):
                    line, column = str(error).split(":")[-1].split(", ")

                    lineno = int(line.split(" ")[-1])
                    columnno = int(column.split(" ")[-1])

                    line = rendered.split("\n")[lineno - 1]
                    character = line[columnno]
                    message += (
                        f" (character {character}):\n{line}\n"
                        f"If your Markdown contains angle brackets < >, try to wrap them between backticks `< >`, "
                        f"or replace them with &lt; and &gt;")
                log.error(message)
                return

            as_xml = atomic_brute_cast(as_xml)  # type: ignore
            parent.append(as_xml)

        if the_rest:
            # This block contained unindented line(s) after the first indented
            # line. Insert these lines as the first block of the master blocks
            # list for future processing.
            blocks.insert(0, the_rest)