Skip to content

Allow showing only a range of lines for code blocks #2240

Closed

Description

@llucax

Description

I'm using mkdocstrings and Sybil to extract code examples in the documentation and test by running pylint on them via pytest.

This forces me to sometimes add too much boilerplate to examples, which is distracting from the main idea I want to show in an example, but it is also really important that examples stay up to date and (mostly) working.

An alternative would be to move the examples to separate files and use the snippet extension line selection feature, but I want to keep the examples in the docstrings, so they are also accessible in code editors, as python include all the docs in the code itself.

Related links

Extra:

Benefits

Being able to write examples that are verifiable using linting tools to ensure they stay up to date, but still only being able to show the relevant parts that make sense for the documentation, avoiding distracting, boilerplate code.

Solution Idea

I would like code blocks to accept selecting which lines to show, similar to what the snippet line selection or the highlight specific lines feature does. For example:

Example:
    ``` py show_lines="4:"
    # Bolierplate setup code
    # More boiler plate setup code
    # And some more
    this is the_juicy_part
    # more useful code
    ```

I'm suggesting the snippet extension syntax (with :) instead of the highlight specific lines feature syntax because it is more natural for specifying open-ended ranges.

Optional: There could be some button to expand the example and show also the boilerplate code, so users could copy&paste the whole example and run it if they want.

Activity

  1. facelessuser commented on Nov 17, 2023

    @facelessuser
    Owner

    The requested feature is quite niche, though I see why you would find it useful for your purposes, and I can see a select amount of people using it as well. One of the reasons I added custom fences to SuperFences was to allow people to create specialized behavior for their code blocks without me having to implement and maintain every niche case that was desired.

    What I can provide is some guidance as to how you can accomplish what you want with custom fences. You can simply override the code blocks you desire adding in the options to specify the lines to show. Lastly, you'd preprocess the code, stripping out any lines you did not wish to show before passing them along to the highlighter.

    You may have to wait until I find the time over the weekend to code up an example for you, but you can start reading about custom fences here: https://facelessuser.github.io/pymdown-extensions/extensions/superfences/#custom-fences.

  2. facelessuser commented on Nov 18, 2023

    @facelessuser
    Owner

    Here's an example. We override all languages with * and define our custom formatter and validator. In our custom validator we validate our new option and then feed the great of the inputs through the default code validator. Then in our formatter, we check for our new option, modify the source as needed, and send it through the default highlight logic. That's it. It should work on any language then.

    """Example custom fence."""
    import markdown
    from pymdownx.superfences import highlight_validator
    
    
    def remove_validator(language, inputs, options, attrs, md):
        """Highlight validator."""
    
        # Add validation for special new options
        for k, v in inputs.items():
            if k == 'show_lines':
                try:
                    options[k] = set(int(i) for i in v.split(','))
                except Exception:
                    # Validation failed
                    return False
    
                # Remove handled option from inputs
                del inputs[k]
                break
    
        # Run default validator
        res = highlight_validator(language, inputs, options, attrs, md)
        return res
    
    
    def remove_formatter(src="", language="", class_name=None, options=None, md="", **kwargs):
        """Formatter wrapper."""
    
        # Alter source here
        if 'show_lines' in options:
            show = options['show_lines']
            lines = []
            for e, line in enumerate(src.split('\n'), 1):
                if e in show:
                    lines.append(line)
            src = '\n'.join(lines)
    
        # Run through default highlighter
        return md.preprocessors['fenced_code_block'].highlight(
            src=src,
            class_name=class_name,
            language=language,
            md=md,
            options=options,
            **kwargs
        )
    
    
    MD = """
    ``` py show_lines="1,2,3,8"
    import something
    import something_else
    
    # Some comments
    # that we
    # want to
    # avoid
    something.go()
    ```
    """
    
    html = markdown.markdown(
        MD,
        extensions=['pymdownx.superfences'],
        extension_configs={
            "pymdownx.superfences": {
                "custom_fences": [
                    {
                        'name': '*',
                        'class': 'highlight',
                        'validator': remove_validator,
                        'format': remove_formatter
                    }
                ]
            }
        }
    )
    
    print(html)
  3. llucax commented on Nov 20, 2023

    @llucax
    Author

    Hi @facelessuser, thanks for the explanation and example. I really appreciate it. I will give it a try as soon as possible.

    I have one more question though, since I want to use this in mkdocs-material, I'm not exactly sure how this is plugged into that. I'm also not sure if this is a question I should ask here or in mkdocs-material.

    Thanks again!

  4. facelessuser commented on Nov 20, 2023

    @facelessuser
    Owner

    I can show you how I run them:

    I currently have one in my tools folder: https://github.com/facelessuser/pymdown-extensions/blob/main/tools/pymdownx_md_render.py. I place an __init__.py in tools to treat it as a local module.

    I specify it in mkdocs.yml like so: https://github.com/facelessuser/pymdown-extensions/blob/main/mkdocs.yml#L119. For yours, I would obviously use the name of * and class of highlight.

    Lastly, to find local modules I have to run mkdocs with the -m option: python3 -m mkdocs serve: https://github.com/facelessuser/pymdown-extensions/blob/main/.github/workflows/deploy.yml#L36.

    I don't use Material's docker image, so if you are using that, you might have to ask on mkdocs-material if it is not obvious how to run mkdocs with -m.

    The alternative is that you make a pip installable package with your custom code and then you don't have to load local modules, you would just reference it as a normal installed module.

    Also, the example I provided is just a quick and dirty example. You will have to tweak the code to have it work exactly how you want, but it is a good starting point.

  5. Repository owner locked and limited conversation to collaborators on Nov 20, 2023
  6. converted this issue into a discussion #2242 on Nov 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions