Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there any way to skip creating the markdown files and nav manually ? #179

Closed
aahnik opened this issue Nov 13, 2020 · 16 comments
Closed

Comments

@aahnik
Copy link

aahnik commented Nov 13, 2020

mkdocstrings supports the injection as said in docs,

::: my_library.my_module.my_class

I also have to create the navigation.

This is great because it gives precise control, but is there any existing way to completely generate all the pages with sane defaults?

Like each module's docs in separate page. And links to sub or super module. Something like pdoc3 but using the material theme.

Many thanks for your help

@pawamoy
Copy link
Member

pawamoy commented Nov 13, 2020

I've been using this ugly Bash script myself:

#!/usr/bin/env bash
cd src || exit 1
rm -rf ../docs/reference
mkdir ../docs/reference
find your_package | grep -v -e pyc\$ -e __pycache__ -e '^your_package$' -e py.typed -e __init__ -e __main__ | sort | while read -r line; do
    indent="$(echo "${line}" | sed -r 's/\w+\//  /g;s/[^ ]+$//')"
    case "${line}" in
        *.py)
            mod=${line##*/}
            line=${line%.py}
            dot=${line//\//.}
            md=${line#your_package/}.md
            dir=${md%/*}
            if [ ! "${dir}" = "${md}" ]; then
                mkdir -p ../docs/reference/${dir}
            fi
            echo "::: ${dot}" > ../docs/reference/${md}
            echo "${indent}- ${mod}: reference/${md}"
        ;;
        *)
            echo "${indent}- ${line##*/}:"
    esac
done

It will regenerate the reference/ directory in the docs/ one, and also output the related nav for mkdocs.yml.

We could consider allowing handlers to provide ways to do this (creating files with ::: and outputing/updating the nav) and provide a common CLI for all handlers.

@aahnik
Copy link
Author

aahnik commented Nov 13, 2020

@pawamoy Thanks a lot.

I was planning to write somekind of script. The script you shared, will perfectly do my work for now.

Having some option in the cli would be great ! ☺️

@oprypin
Copy link
Member

oprypin commented Dec 5, 2020

This will be even more relevant for Crystal handler because Crystal programming language tends to have a ton of classes, and the convention for docs currently is to put one class per doc page.

See my script for generation in the commit message here:
oprypin/athena-website@264e549 😩

I think there could even be some generic MkDocs plugin for file generation, and it would actually work very well to just import and use mkdocstrings.handlers.base.get_handler inside a script, as long as it shares the same Python process the instance will be shared.

@oprypin
Copy link
Member

oprypin commented Dec 6, 2020

oprypin added a commit to oprypin/mkdocstrings that referenced this issue Dec 7, 2020
This makes `handlers_cache` no longer be global but instead be confined to the Plugin. There will be only one instance of the plugin so it doesn't matter anyway. But actually this is also more correct, because what if someone tried to instantiate multiple handlers with different configs? It would work incorrectly previously.

But my main goal for this is to expose `MkdocstringsPlugin.get_handler(name)`. Then someone can use this inside a mkdocs hook:

    def on_files(self, files: Files, config: Config):
        crystal = config['plugins']['mkdocstrings'].get_handler('python').collector

So this is basically a prerequisite for issue mkdocstrings#179: one could query the collector to know which files to generate.
oprypin added a commit to oprypin/mkdocstrings that referenced this issue Dec 7, 2020
This makes `handlers_cache` no longer be global but instead be confined to the Plugin. There will be only one instance of the plugin so it doesn't matter anyway. But actually this is also more correct, because what if someone tried to instantiate multiple handlers with different configs? It would work incorrectly previously.

But my main goal for this is to expose `MkdocstringsPlugin.get_handler(name)`. Then someone can use this inside a mkdocs hook:

    def on_files(self, files: Files, config: Config):
        crystal = config['plugins']['mkdocstrings'].get_handler('python').collector

So this is basically a prerequisite for issue mkdocstrings#179: one could query the collector to know which files to generate.
oprypin added a commit to oprypin/mkdocstrings that referenced this issue Dec 7, 2020
This makes `handlers_cache` no longer be global but instead be confined to the Plugin. There will be only one instance of the plugin so it doesn't matter anyway. But actually this is also more correct, because what if someone tried to instantiate multiple handlers with different configs? It would work incorrectly previously.

But my main goal for this is to expose `MkdocstringsPlugin.get_handler(name)`. Then someone can use this inside a mkdocs hook:

    def on_files(self, files: Files, config: Config):
        crystal = config['plugins']['mkdocstrings'].get_handler('python').collector

So this is basically a prerequisite for issue mkdocstrings#179: one could query the collector to know which files to generate.
@oprypin
Copy link
Member

oprypin commented Dec 8, 2020

I am tackling this issue in a comprehensive way.

Preview of the feature:

Instead of actually adding the files, I generate them with a script

@oprypin
Copy link
Member

oprypin commented Dec 8, 2020

@pawamoy
Copy link
Member

pawamoy commented Dec 8, 2020

This is really interesting @oprypin!

So what your "generate" plugin does is execute the given Python scripts that will create the files themselves, using a collector to know how. In your example, the root attribute of the Crystal collector contains the whole tree, so you can walk it. For Python we could do kind of the same, using pytkdocs to walk the tree searching for modules.

Since users all have different ways of writing the code reference markdown files, I guess it's best to start with something flexible like this, and maybe later add native methods to the handlers themselves for the most common ways (like one page per module in Python, etc.).

Then your plugin inferring the nav is the cherry on top: not only users don't have to write the code reference markdown pages themselves, they don't even have to write the nav either! But if a file is named __init__.md, the item in the nav will be named __init__ right? Then the plugin could likely accept config settings to add suffixes like .py. Just thinking out loud 🙂

I'll take a look at your PR in the next few days!

pawamoy pushed a commit that referenced this issue Dec 8, 2020
This makes `handlers_cache` no longer be global but instead be confined to the Plugin. There will be only one instance of the plugin so it doesn't matter anyway. But actually this is also more correct, because what if someone tried to instantiate multiple handlers with different configs? It would work incorrectly previously.

But my main goal for this is to expose `MkdocstringsPlugin.get_handler(name)`. Then someone can use this inside a mkdocs hook:

    def on_files(self, files: Files, config: Config):
        crystal = config['plugins']['mkdocstrings'].get_handler('python').collector

So this is basically a prerequisite for issue #179: one could query the collector to know which files to generate.

PR #191: #191
@oprypin
Copy link
Member

oprypin commented Jan 9, 2021

Oho, now I am indeed running into trouble with page names in inferred navigation, and I see what you mean.

I'm adding ::: foo.py into foo.md, and the title becomes "Foo". Not ideal. And generating the nav as well is cumbersome, though definitely doable.

@oprypin
Copy link
Member

oprypin commented Feb 24, 2021

I have concluded that the best way forward for Python is to not generate the nav automatically -- only generate the files themselves.

And for that, the solution looks like this:

Via https://github.com/oprypin/mkdocs-gen-files

mkdocs.yml
plugins:
  - gen-files:
      scripts:
        - gen_doc_stubs.py 
gen_doc_stubs.py
from pathlib import Path
import mkdocs_gen_files

src_root = Path("my_module")
for path in src_root.glob("**/*.py"):
    doc_path = Path("reference", path.relative_to(src_root)).with_suffix(".md")

    with mkdocs_gen_files.open(doc_path, "w") as f:
        ident = ".".join(path.with_suffix("").parts)
        print("::: " + ident, file=f)

(also applied it in #242)

@pawamoy
Copy link
Member

pawamoy commented Feb 24, 2021

Could you give more details about that conclusion? Is it too hard to achieve with the current state of our plugins?

@oprypin
Copy link
Member

oprypin commented Feb 24, 2021

It's not too hard, just more boilerplate than you're getting for it.

Reasoning why it's OK to not generate the nav:
The files themselves will always be auto-updated. And there are rarely many files.
Whenever something gets changed, you'll get a warning from MkDocs that either there's an unused page or a nonexistent page is referenced. So you won't forget.

Reasoning why it's hard to generate the nav:
You need to pull in a heavy plugin like awesome-pages or literate-nav and change your way of using the nav.
Generating the nav itself, including directories, is just quite complicated code logic. More than 2x the complexity of the above script.

@pawamoy
Copy link
Member

pawamoy commented Feb 24, 2021

I see, thank you for the explanation 🙂

@pawamoy
Copy link
Member

pawamoy commented Apr 15, 2021

See an example in #271 😛
That was not so straight-forward indeed! If you have something more elegant, please share 😄

pawamoy added a commit that referenced this issue May 1, 2021
Co-authored-by: Oleh Prypin <oleh@pryp.in>
Issue #179: #179
Issue #268: #268
PR #271: #271
@pawamoy
Copy link
Member

pawamoy commented May 1, 2021

I'll close this now. Please take a look at how mkdocstrings generates its own pages and nav, using the mkdocs-gen-files and mkdocs-literate-nav plugins from @oprypin 🙂
We'll document all this in mkdocstrings docs soon.

@rudolfbyker
Copy link

What's currently the best place to look for docs on this?

@pawamoy
Copy link
Member

pawamoy commented Feb 8, 2022

Funny timing, I was exactly going to add a "recipe" for this in a new Recipes page (#378) 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants