Skip to content

Commit

Permalink
Add documentation, more workflows
Browse files Browse the repository at this point in the history
  • Loading branch information
TillerBurr committed Dec 12, 2023
1 parent 8432027 commit d4b12c6
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 12 deletions.
69 changes: 69 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: "Release"

on:
workflow_dispatch:
inputs:
tag:
description: "The version to tag"
type: string
sha:
description: "The full sha of the commit to be release. If omitted, the most recent commit on the default branch will be used."
default: ""
type: string
jobs:
tag-release:
name: Tag Release
runs-on: ubuntu-latest
if: ${{ inputs.tag }}
needs: pypi-publish
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}
- name: Git tag
run: |
git config user.name "Github Actions"
git config user.email "[email protected]"
git tag -m "${{ inputs.tag }}" "${{ inputs.tag }}"
git push --tags
pypi-publish:
name: upload release to PyPI
runs-on: ubuntu-latest
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write

steps:
# retrieve your distributions here
- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}

- name: Install rye
uses: eifinger/setup-rye@v1
with:
enable-cache: true
- name: Install Dependencies
run: rye sync
- name: Build Project
run: rye build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip_existing: true
packages_dir: dist
verbose: true

publish-release:
name: Publish to Github
runs-on: ubuntu-latest
needs: tag-release
steps:
- uses: softprops/action-gh-release@v1
with:
draft: true
tag_name: ${{ inputs.tag }}

26 changes: 26 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,32 @@ on:
- opened
- synchronize


jobs:
changes:
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
changed: ${{ steps.filter.outputs.changed }}
steps:
- uses: actions/checkout@v3
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
changed:
- 'tests/**'
- 'src/**'
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"

lint:
runs-on: ubuntu-latest
needs: changes
if: ${{needs.changes.outputs.changed == 'true'}}
steps:
- uses: actions/checkout@v3
- name: Install rye
Expand All @@ -26,10 +49,13 @@ jobs:

tests:
runs-on: ubuntu-latest
needs: [changes, lint]
if: ${{needs.changes.outputs.changed == 'true'}}
strategy:
matrix:
python-version: [3.8, 3.9, "3.10","3.11","3.12"]
steps:

- uses: actions/checkout@v3
- name: Install rye
uses: eifinger/setup-rye@v1
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
**/__pycache__/
tests/.coverage
.coverage
dist/
19 changes: 19 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2023 Tyler Baur

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,77 @@
# PyMdown Extensions Blocks

[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
![PyPI - Version](https://img.shields.io/pypi/v/pymdownx-blocks)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pymdownx-blocks)
![Tests](https://img.shields.io/github/actions/workflow/status/TillerBurr/pymdownx-blocks/tests)



These are a collection of blocks for the [PyMdown Extensions](https://facelessuser.github.io/pymdown-extensions) that I find useful.

This project is not affiliated with the PyMdown Extensions project and is currently in a very early stage. Currently, there is only one block: `DirTree`.

## Installation

```bash
pip install pymdownx-blocks
```

## Usage
This can be used in MkDocs or by itself. To use in a Python file, we use the following:

```python
import markdown

yaml_str=...
md=markdown.Markdown(extensions=['pymdownx_blocks.dirtree'])
```

To use in MkDocs, register the extension.

```yaml
...
markdown_extensions:
...
- pymdownx_blocks.dirtree
...
```

In a markdown file,
```
///dirtree
root:
- subdir:
- File
- another subdir:
- anotherfile.txt
- file.csv
///
```


When rendered, this will produce the following tree

<div>
<pre class="admonition note"><p class="admonition-title">Directory Structure</p>
<p>
root
├── subdir
│ └── File
└── another subdir
├── anotherfile.txt
└── file.csv
</p></pre>
</div>


## Contributing

More blocks are always welcome! This project uses [rye](https:rye-up.com) for dependency
management.

1. Fork the repository
2. Create a branch with the name of the block
3. Implement the block
4. Create a pull request.
96 changes: 86 additions & 10 deletions src/pymdownx_blocks/dirtree.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@


class InvalidYAMLError(BaseException):
"""Raised when YAML is invalid"""
"""YAML Cannot be parsed."""


class InvalidTreeError(BaseException):
"""Raised when the YAML is valid, but it not a valid tree"""
"""YAML is valid, but not a valid directory tree."""


PIPE = "│"
Expand All @@ -27,13 +27,35 @@ class InvalidTreeError(BaseException):


def sorter(x: Union[TreeNode, str]) -> tuple[bool, str]:
"""
Args:
x (Union[TreeNode, str]): An element of a TreeNode
Returns:
(tuple[bool, str]): A tuple containing a bool that is `True` if x is a `str`,
and a str containing either `x` or the first (and only) key of `x`
"""
_type = isinstance(x, str)
_value = x if isinstance(x, str) else next(iter(x.keys()))
return _type, _value


class DirTree:
def __init__(self, in_: str):
"""
Create a directory tree from a YAML string.
"""

def __init__(self, in_: str) -> None:
"""Initialize the tree object.
Args:
in_ (str): YAML string
Raises:
InvalidTreeError: Not a valid tree.
"""
try:
self.tree = yaml.safe_load(in_)
if not isinstance(self.tree, dict):
Expand All @@ -43,15 +65,30 @@ def __init__(self, in_: str):
except yaml.error.YAMLError:
raise InvalidYAMLError

def build_output(
def build(
self,
tree: TreeNode,
current_index: int = 0,
prefix: str = "",
parent_siblings: int = 0,
item_sep: str = "",
is_root: bool = True,
):
) -> str:
"""
Build the output. The final result is a string containing the tree.
Args:
tree (TreeNode): A node in the tree. It is a dict containing values that are
a list of strings or dictionaries.
current_index (int): Current index of the item in the sequence.
prefix (str): String that is prepended to each line.
parent_siblings (int): Number of siblings the parent has.
item_sep (str): An Elbow or Tee. Used between lines, next to items.
is_root (bool): Declares the root of the tree.
Returns:
A string containing the parsed tree.
"""
_tree = ""
directory = next(iter(tree.keys()))
contents = tree.get(directory, [])
Expand Down Expand Up @@ -79,7 +116,7 @@ def build_output(
new_prefix = prefix + curr_prefix
if isinstance(element, dict):
# A dict is a subtree, build the subtree
_tree += self.build_output(
_tree += self.build(
element,
item_index,
parent_siblings=num_siblings,
Expand All @@ -89,19 +126,38 @@ def build_output(
)
else:
_tree += f"{new_prefix}{item_sep}{element}\n"
if is_root:
return _tree.rstrip()
return _tree

def build(self) -> str:
final_tree = self.build_output(self.tree).rstrip()
return final_tree
def build_tree(self) -> str:
"""Build the tree, without needing to pass in `self.tree`.
Returns:
(str): The string containing the directory tree.
"""
return self.build(self.tree)


class DirTreeBlock(Block):
"""Block Extension for the DirTree"""

NAME = "dirtree"
ARGUMENT = None
OPTIONS = {"type": ["", type_html_identifier]}

def on_create(self, parent: etree.Element) -> etree.Element:
"""
Args:
parent (xml.etree.ElementTree.Element): The parent element in the XML tree.
Returns:
(xml.etree.ElementTree.Element): A div container with given classes and
title text.
"""
classes = ["admonition"]
self_type = self.options["type"]
if self_type:
Expand All @@ -115,21 +171,41 @@ def on_create(self, parent: etree.Element) -> etree.Element:
return el

def on_end(self, block: etree.Element) -> None:
"""Inserts the DirTree into the container
Args:
block (xml.etree.ElementTree.Element): The block/container created in
`on_create`
"""
yaml_content = block.find("p[2]")
yaml_tree = block.findtext("p[2]")
if yaml_tree and yaml_content is not None:
block.remove(yaml_content)
tree = DirTree(yaml_tree)
dt = etree.SubElement(block, "pre")
dt.text = tree.build()
dt.text = tree.build_tree()


class DirTreeExtension(BlocksExtension):
"""Extension for the DirTree"""

def extendMarkdownBlocks(
self, md: markdown.core.Markdown, block_mgr: BlocksProcessor
) -> None:
"""Register the `DirTreeBlock` for pymdownx.blocks
Args:
md (markdown.core.Markdown): The markdown parser
block_mgr (BlocksProcessor): The Generic Block Processor
"""
block_mgr.register(DirTreeBlock, self.getConfigs())


def makeExtension(*args, **kwargs) -> DirTreeExtension:
"""Register the extension with MkDocs.
Returns:
(DirTreeExtension): The Directory Tree extension.
"""
return DirTreeExtension(*args, **kwargs)
Loading

0 comments on commit d4b12c6

Please sign in to comment.