diff --git a/.github/workflows/update-api-docs.yml b/.github/workflows/update-api-docs.yml index 3125ee2a5..daa338aa8 100644 --- a/.github/workflows/update-api-docs.yml +++ b/.github/workflows/update-api-docs.yml @@ -28,6 +28,15 @@ jobs: with: python-version: '3.11' + - name: Read retained Chinese API reference + uses: actions/checkout@v4 + with: + ref: gh-pages + path: previous-pages + sparse-checkout: zh/api.html + sparse-checkout-cone-mode: false + persist-credentials: false + - name: Generate API docs run: python3 scripts/gen_api_docs.py @@ -40,6 +49,7 @@ jobs: python3 web-pages/product-site/build.py --output /tmp/funasr-docs-site python3 web-pages/product-site/validate.py /tmp/funasr-docs-site python3 web-pages/product-site/export_docs.py --site /tmp/funasr-docs-site --output gh-pages-output + python3 web-pages/product-site/link_api_guides.py --output gh-pages-output --previous previous-pages - name: Deploy to gh-pages uses: peaceiris/actions-gh-pages@v3 diff --git a/web-pages/product-site/link_api_guides.py b/web-pages/product-site/link_api_guides.py new file mode 100644 index 000000000..17e6fd94f --- /dev/null +++ b/web-pages/product-site/link_api_guides.py @@ -0,0 +1,46 @@ +"""Connect API references to maintained guides without renumbering entries.""" + +import argparse +from pathlib import Path + +from bs4 import BeautifulSoup + + +def link_api_guides(output: Path, previous: Path) -> None: + rendered = {} + for route, prefix, labels in ( + ('api.html', 'en/', ('Native Transformers quickstart', 'Python SDK guide')), + ('zh/api.html', '', ('原生 Transformers 入门', 'Python SDK 指南')), + ): + target = output / route + # The Chinese reference is retained by keep_files, not generated on main. + source = target if target.exists() else previous / route + soup = BeautifulSoup(source.read_text(encoding='utf-8'), 'html.parser') + welcome = soup.select('#api-welcome') + existing = soup.select('#api-practical-guides') + if len(welcome) != 1 or len(existing) > 1: + raise ValueError(f'{route}: expected one api-welcome and at most one guide entry') + if existing: + existing[0].decompose() + guides = soup.new_tag('p', id='api-practical-guides') + for index, (slug, label) in enumerate(zip(('native-transformers', 'python-api'), labels)): + if index: + guides.append(' | ') + link = soup.new_tag('a', href=f'https://www.funasr.com/{prefix}docs/{slug}.html') + link.string = label + guides.append(link) + welcome[0].append(guides) + rendered[target] = str(soup) + + # Validate both inputs before changing the outgoing publication directory. + for target, content in rendered.items(): + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content, encoding='utf-8') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', type=Path, required=True) + parser.add_argument('--previous', type=Path, required=True) + args = parser.parse_args() + link_api_guides(args.output, args.previous) diff --git a/web-pages/product-site/tests/test_api_guide_links.py b/web-pages/product-site/tests/test_api_guide_links.py new file mode 100644 index 000000000..ab670cabe --- /dev/null +++ b/web-pages/product-site/tests/test_api_guide_links.py @@ -0,0 +1,77 @@ +from pathlib import Path +import sys + +from bs4 import BeautifulSoup +import pytest + +SITE = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(SITE)) + + +def test_api_guides_preserve_entries_and_localize_destinations(tmp_path): + from link_api_guides import link_api_guides + + output, previous = tmp_path / 'output', tmp_path / 'previous' + output.mkdir() + (previous / 'zh').mkdir(parents=True) + original = '

API

AutoModel

a < b
AutoModel
' + (output / 'api.html').write_text(original) + (previous / 'zh/api.html').write_text(original) + link_api_guides(output, previous) + first = {} + for route, prefix, label in [('api.html', 'en/', 'Python SDK guide'), ('zh/api.html', '', 'Python SDK 指南')]: + text = (output / route).read_text() + first[route] = text + soup = BeautifulSoup(text, 'html.parser') + before = BeautifulSoup(original, 'html.parser') + assert str(soup.select_one('#e3')) == str(before.select_one('#e3')) + assert soup.script.string == before.script.string + assert soup.select_one('a[href="#e3"]') + links = soup.select('#api-practical-guides a') + assert [a['href'] for a in links] == [ + f'https://www.funasr.com/{prefix}docs/native-transformers.html', + f'https://www.funasr.com/{prefix}docs/python-api.html', + ] + assert links[1].get_text() == label + link_api_guides(output, previous) + assert all((output / route).read_text() == text for route, text in first.items()) + assert (previous / 'zh/api.html').read_text() == original + + +def test_invalid_legacy_page_does_not_partially_write(tmp_path): + from link_api_guides import link_api_guides + + output, previous = tmp_path / 'output', tmp_path / 'previous' + output.mkdir() + (previous / 'zh').mkdir(parents=True) + original = '
API
' + (output / 'api.html').write_text(original) + (previous / 'zh/api.html').write_text('
No API welcome
') + with pytest.raises(ValueError, match='api-welcome'): + link_api_guides(output, previous) + assert (output / 'api.html').read_text() == original + assert not (output / 'zh/api.html').exists() + + +def test_pages_workflow_restores_only_the_legacy_api_input(): + workflow = (SITE.parents[1] / '.github/workflows/update-api-docs.yml').read_text() + assert 'ref: gh-pages' in workflow + assert 'sparse-checkout: zh/api.html' in workflow + assert 'link_api_guides.py --output gh-pages-output --previous previous-pages' in workflow + + +@pytest.mark.parametrize('bad', [None, '
']) +def test_missing_or_duplicate_legacy_welcome_fails_closed(tmp_path, bad): + from link_api_guides import link_api_guides + + output, previous = tmp_path / 'output', tmp_path / 'previous' + output.mkdir() + (previous / 'zh').mkdir(parents=True) + original = '
API
' + (output / 'api.html').write_text(original) + if bad is not None: + (previous / 'zh/api.html').write_text(bad) + with pytest.raises((ValueError, FileNotFoundError)): + link_api_guides(output, previous) + assert (output / 'api.html').read_text() == original + assert not (output / 'zh/api.html').exists()