Auto-generate the configuration options documentation (#498)

* Autogenerate the documentation and strings

* Line limit

* Split

* Happy pre-commit

* Split up workflows

* Generate table function

* raise

* Add gen script

* chore(docs): update TOC

* Add gen script

* path

* Run - name: Install Home Assistant

* chore(docs): update TOC

* add copyright

* Fix branch name

* Run on PRs

* dev

* remove steps

* use matrix

* Run script

* chore(docs): update TOC

* Try fixing CI

* fix

* test

* refactor

* chore(docs): update TOC

* fix

* update

* use v3

* rename

* Fix input

* fix pytest

* fix path

* paths

* Change to github.head_ref

* More backticks

* Add extra text

* chore(docs): update TOC

* backticks

* Update README.md

---------

Co-authored-by: basnijholt <basnijholt@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Bas Nijholt 2023-04-01 23:43:20 -07:00 committed by GitHub
commit cc8ce29a8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 486 additions and 52 deletions

195
.github/update-readme.py vendored Normal file
View file

@ -0,0 +1,195 @@
# Copyright (c) 2023, Bas Nijholt
# All rights reserved.
# When using this code, please cite the original source.
# and include the LICENSE file in your project.
"""Automatically update Markdown files with code block output.
Add code blocks between <!-- START_CODE --> and <!-- END_CODE --> in your Markdown file.
The output will be inserted between <!-- START_OUTPUT --> and <!-- END_OUTPUT -->.
Example:
-------
```
<!-- START_CODE -->
<!-- print('Hello, world!') -->
<!-- END_CODE -->
<!-- START_OUTPUT -->
This will be replaced by the output of the code block above.
<!-- END_OUTPUT -->
```
"""
from __future__ import annotations
import contextlib
import io
from pathlib import Path
def md_comment(text: str) -> str:
"""Format a string as a Markdown comment."""
return f"<!-- {text} -->"
MARKERS = {
"warning": md_comment("THIS CONTENT IS AUTOMATICALLY GENERATED"),
"start_code": md_comment("START_CODE"),
"end_code": md_comment("END_CODE"),
"start_output": md_comment("START_OUTPUT"),
"end_output": md_comment("END_OUTPUT"),
}
def remove_md_comment(commented_text: str) -> str:
"""Remove Markdown comment tags from a string."""
if not (commented_text.startswith("<!-- ") and commented_text.endswith(" -->")):
raise ValueError("Invalid Markdown comment format")
return commented_text[5:-4]
def execute_code_block(code: list[str]) -> list[str]:
"""Execute a code block and return its output as a list of strings."""
f = io.StringIO()
with contextlib.redirect_stdout(f):
exec("\n".join(code)) # noqa: S102
return f.getvalue().split("\n")
def process_markdown(content: list[str]) -> list[str]:
"""Executes code blocks in a list of Markdown-formatted strings and returns the modified list.
Parameters
----------
content
A list of Markdown-formatted strings.
Returns
-------
list[str]
A modified list of Markdown-formatted strings with code block output inserted.
"""
assert isinstance(content, list), "Input must be a list"
new_lines = []
code = []
in_code_block = in_output_block = False
output = None
for line in content:
if MARKERS["start_code"] in line:
in_code_block = True
elif MARKERS["start_output"] in line:
in_output_block = True
new_lines.extend([line, MARKERS["warning"]] + output)
output = None
elif MARKERS["end_output"] in line:
in_output_block = False
elif in_code_block:
if MARKERS["end_code"] in line:
in_code_block = False
output = execute_code_block(code)
code = []
else:
code.append(remove_md_comment(line))
if not in_output_block:
new_lines.append(line)
return new_lines
def update_markdown_file(filepath: Path) -> None:
"""Rewrite a Markdown file by executing and updating code blocks."""
with filepath.open() as f:
original_lines = [line.rstrip("\n") for line in f.readlines()]
new_lines = process_markdown(original_lines)
updated_content = "\n".join(new_lines).rstrip() + "\n"
with filepath.open("w") as f:
f.write(updated_content)
def test_process_markdown():
def assert_process(input_lines, expected_output):
output = process_markdown(input_lines)
assert output == expected_output, f"Expected {expected_output}, got {output}"
# Test case 1: Single code block
input_lines = [
"Some text",
MARKERS["start_code"],
md_comment("print('Hello, world!')"),
MARKERS["end_code"],
MARKERS["start_output"],
"This content will be replaced",
MARKERS["end_output"],
"More text",
]
expected_output = [
"Some text",
MARKERS["start_code"],
md_comment("print('Hello, world!')"),
MARKERS["end_code"],
MARKERS["start_output"],
MARKERS["warning"],
"Hello, world!",
"",
MARKERS["end_output"],
"More text",
]
assert_process(input_lines, expected_output)
# Test case 2: Two code blocks
input_lines = [
"Some text",
MARKERS["start_code"],
md_comment("print('Hello, world!')"),
MARKERS["end_code"],
MARKERS["start_output"],
"This content will be replaced",
MARKERS["end_output"],
"More text",
MARKERS["start_code"],
md_comment("print('Hello again!')"),
MARKERS["end_code"],
MARKERS["start_output"],
"This content will also be replaced",
MARKERS["end_output"],
]
expected_output = [
"Some text",
MARKERS["start_code"],
md_comment("print('Hello, world!')"),
MARKERS["end_code"],
MARKERS["start_output"],
MARKERS["warning"],
"Hello, world!",
"",
MARKERS["end_output"],
"More text",
MARKERS["start_code"],
md_comment("print('Hello again!')"),
MARKERS["end_code"],
MARKERS["start_output"],
MARKERS["warning"],
"Hello again!",
"",
MARKERS["end_output"],
]
assert_process(input_lines, expected_output)
# Test case 3: No code blocks
input_lines = [
"Some text",
"More text",
]
expected_output = [
"Some text",
"More text",
]
assert_process(input_lines, expected_output)
if __name__ == "__main__":
test_process_markdown()
update_markdown_file(Path(__file__).parent.parent / "README.md")

View file

@ -9,6 +9,11 @@ on:
jobs:
docker:
runs-on: ubuntu-latest
strategy:
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
@ -24,6 +29,5 @@ jobs:
with:
# Only push on the master branch
push: ${{ github.ref == 'refs/heads/master' }}
# TODO: fix builds on linux/arm/v7
platforms: linux/amd64,linux/arm64
platforms: ${{ matrix.platform }}
tags: ${{ secrets.DOCKERHUB_USERNAME }}/adaptive-lighting:latest

View file

@ -0,0 +1,36 @@
name: 'Install Dependencies'
description: 'Install Home Assistant and test dependencies'
inputs:
python_version:
description: 'Python version'
required: true
default: '3.10'
runs:
using: "composite"
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3
with:
repository: ${{ github.repository }}
ref: ${{ github.ref }}
persist-credentials: false
fetch-depth: 0
- name: Check out code from GitHub
uses: actions/checkout@v3
with:
repository: home-assistant/core
path: core
- name: Set up Python ${{ inputs.python_version }}
id: python
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ inputs.python_version }}
- name: Install dependencies
shell: bash
run: |
echo "::warning::### WARNING! Deprecation warnings muted with option '--use-pep517' please address this at some point in pytest.yaml. ###"
pip install -r core/requirements.txt --use-pep517
pip install -r core/requirements_test.txt --use-pep517
pip install -e core/ --use-pep517
pip install $(python test_dependencies.py) --use-pep517

View file

@ -6,7 +6,6 @@ on:
pull_request:
jobs:
pytest:
name: Run pytest
runs-on: ubuntu-20.04
@ -16,17 +15,13 @@ jobs:
python-version: ["3.10"]
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.0.2
- name: Check out code from GitHub
uses: actions/checkout@v3.0.2
uses: actions/checkout@v3
- name: Install Home Assistant
uses: ./.github/workflows/install_dependencies
with:
repository: home-assistant/core
path: core
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ matrix.python-version }}
python_version: ${{ matrix.python-version }}
- name: Click here for troubleshooting steps if tests break again.
run: |
echo "::notice::### If tests fail, try these debug steps: ###"
@ -36,13 +31,6 @@ jobs:
echo "::notice::### 4. ERROR:homeassistant.setup:Setup failed for 'component': Unable to import component: No module named ''module'' ###"
echo "::notice::### 5. add 'component'.'module' (without the '') from the above log into the 'required' list inside of 'test_dependencies.py' ###"
echo "::notice::### 6. Try again! If more issues persist they should be easily solvable by reading the verbose logs now. ###"
- name: Install dependencies
run: |
echo "::warning::### WARNING! Deprecation warnings muted with option '--use-pep517' please address this at some point in pytest.yaml. ###"
pip install -r core/requirements.txt --use-pep517
pip install -r core/requirements_test.txt --use-pep517
pip install -e core/ --use-pep517
pip install $(python test_dependencies.py) --use-pep517
- name: Run pytest
timeout-minutes: 60
run: |

44
.github/workflows/update-readme.yml vendored Normal file
View file

@ -0,0 +1,44 @@
name: Update README.md
on:
push:
branches:
- master
paths:
- ".github/update-readme.py"
- "README.md"
- ".github/workflows/update-readme.yml"
- "custom_components/adaptive_lighting/const.py"
pull_request:
jobs:
update_readme:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3
- name: Install Home Assistant
uses: ./.github/workflows/install_dependencies
with:
python_version: "3.10"
- name: Install pandas and tabulate
run: |
pip install pandas tabulate
- name: Run update-readme.py
run: python ./.github/update-readme.py
- name: Commit updated README.md
run: |
git add README.md
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git diff --quiet && git diff --staged --quiet || git commit -m "Update README.md"
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.head_ref }}