diff --git a/.github/api.py b/.github/api.py index dc2d57a..2e1e9af 100644 --- a/.github/api.py +++ b/.github/api.py @@ -1,27 +1,26 @@ -# -*- coding: utf-8 -*- -# -# This script parses the README.md and generates a machine-readable -# data structure from it, for publication as an API. -# -# https://springload.github.io/awesome-wagtail/api/v1/readme.json -# -# It is automatically ran as part of Travis builds, also validating the README -# formatting, and the API endpoint is deployed on successful builds on master. -# -# See also: -# - https://djangopackages.org/api/v3/grids/wagtail-cms/ -# - https://github.com/awesomerank/rank - -from __future__ import absolute_import, unicode_literals +""" +This script parses the README.md and generates a machine-readable +data structure from it, for publication as an API. + +https://springload.github.io/awesome-wagtail/api/v1/readme.json + +It is automatically ran as part of Travis builds, also validating the README +formatting, and the API endpoint is deployed on successful builds on master. + +See also: +- https://djangopackages.org/api/v3/grids/wagtail-cms/ +- https://github.com/awesomerank/rank +""" import json -import codecs -import datetime +from datetime import datetime, timezone +from pathlib import Path API_PATH = '/api/v1/readme.json' -def parse_line(line, category): +def parse_line(line: str, category: str) -> dict[str, str]: + """Parse a single line from the README into a structured dictionary.""" print(line) name = line.split('](')[0][3:] url = line.split('](')[1].split(')')[0] @@ -35,11 +34,13 @@ def parse_line(line, category): } -def parse_section(section, category=''): - return [parse_line(l, category) for l in section.split('\n')] +def parse_section(section: str, category: str = '') -> list[dict[str, str]]: + """Parse a section of lines into a list of structured items.""" + return [parse_line(line, category) for line in section.split('\n')] -def parse_subsections(section): +def parse_subsections(section: str) -> list[dict[str, str]]: + """Parse a section containing multiple subsections.""" subsections = section.split('### ')[1:] items = [] @@ -53,33 +54,46 @@ def parse_subsections(section): return items -def cut_section(start): - return readme.split('## %s\n\n' % start)[1].split('\n\n## ')[0] +def cut_section(readme: str, start: str) -> str: + """Extract a specific section from the README.""" + return readme.split(f'## {start}\n\n')[1].split('\n\n## ')[0] -def parse_readme(readme): +def parse_readme(readme: str) -> dict: + """Parse the entire README into a structured dictionary.""" return { - 'apps': parse_subsections(cut_section('Apps')), - 'tools': parse_subsections(cut_section('Tools')), - 'resources': parse_subsections(cut_section('Resources')), - 'sites': parse_section(cut_section('Open-source sites')), + 'apps': parse_subsections(cut_section(readme, 'Apps')), + 'tools': parse_subsections(cut_section(readme, 'Tools')), + 'resources': parse_subsections(cut_section(readme, 'Resources')), + 'sites': parse_section(cut_section(readme, 'Open-source sites')), 'metadata': { - 'updated': '%sZ' % datetime.datetime.utcnow().isoformat(), + 'updated': datetime.now(timezone.utc).isoformat(), }, } if __name__ == '__main__': - readme = open('README.md', 'r').read() + readme_path = Path('README.md') try: + readme = readme_path.read_text(encoding='utf-8') parsed_readme = parse_readme(readme) - json_path = './dist%s' % API_PATH - with codecs.open(json_path, mode='w+', encoding='utf8') as f: - readme_payload = json.dumps(parsed_readme, indent=True, ensure_ascii=False) - print(readme_payload) - f.write(readme_payload) - except: + json_path = Path(f'./dist{API_PATH}') + json_path.parent.mkdir(parents=True, exist_ok=True) + + readme_payload = json.dumps(parsed_readme, indent=2, ensure_ascii=False) + print(readme_payload) + + json_path.write_text(readme_payload, encoding='utf-8') + + except FileNotFoundError as e: + print(f'Error: Could not find file - {e}') + raise + except (KeyError, IndexError) as e: + print(f'Error: README formatting issue - {e}') print('Is the README well formatted?') raise + except Exception as e: + print(f'Unexpected error: {e}') + raise diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5de0461..7fd803a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,28 +1,37 @@ name: CI on: - push: {branches: ["*", "*/*"]} + push: + branches: master + pull_request: jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-ruby@v1 - with: - ruby-version: '2.x' - - run: gem install awesome_bot - - run: awesome_bot README.md --request-delay 0.1 --allow-redirect --allow-dupe --allow-ssl - - uses: actions/setup-python@v1 - with: - python-version: '3.x' - architecture: 'x64' - - run: mkdir -p dist/api/v1/ && python .github/api.py - - name: deploy - if: github.ref == 'refs/heads/master' - uses: peaceiris/actions-gh-pages@v2.1.0 - env: - ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} - PUBLISH_BRANCH: gh-pages - PUBLISH_DIR: ./dist + - uses: actions/checkout@v6 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + - run: gem install awesome_bot + - run: awesome_bot README.md --request-delay 0.1 --allow-redirect --allow-dupe --allow-ssl + - uses: actions/setup-python@v5 + with: + python-version: "3.14" + - run: python .github/api.py + - uses: actions/upload-pages-artifact@v3 + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + with: + path: ./dist + + deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/deploy-pages@v4 + id: deployment diff --git a/.gitignore b/.gitignore index e827dcf..589fabd 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ bower_components .idea .vagrant .anaconda +.claude # ------------------------------------------------- # Generated files