diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 79091e2d4f2f4f..87c531e903fe20 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -489,9 +489,10 @@ Lib/test/test_html*.py @ezio-melotti Tools/build/parse_html5_entities.py @ezio-melotti # IDLE -Doc/library/idle.rst @terryjreedy -Lib/idlelib/ @terryjreedy -Lib/turtledemo/ @terryjreedy +Doc/library/idle.rst @terryjreedy +Lib/idlelib/ @terryjreedy +Lib/turtledemo/ @terryjreedy +Tools/build/generate_idle_help.py @terryjreedy # importlib.metadata Doc/library/importlib.metadata.rst @jaraco @warsaw diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6769cdd4531a0c..b5ce37a4dbc9d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,6 +49,14 @@ jobs: if: fromJSON(needs.build-context.outputs.run-docs) uses: ./.github/workflows/reusable-docs.yml + check-idle-help-doc: + name: IDLE help doc + needs: [build-context, check-docs] + if: fromJSON(needs.build-context.outputs.run-idle-help-doc) + uses: ./.github/workflows/reusable-idle-help-doc.yml + with: + idle-html-artifact-id: ${{ needs.check-docs.outputs.idle-html-artifact-id }} + check-autoconf-regen: name: 'Check if Autoconf files are up to date' # Don't use ubuntu-latest but a specific version to make the job @@ -711,6 +719,7 @@ jobs: build-ubuntu-ssltests-openssl, test-hypothesis, cifuzz, + check-idle-help-doc, allowed-skips: >- ${{ !fromJSON(needs.build-context.outputs.run-docs) && 'check-docs,' || '' }} ${{ diff --git a/.github/workflows/reusable-context.yml b/.github/workflows/reusable-context.yml index ce5562f2d51fbb..89b9df5f5e5795 100644 --- a/.github/workflows/reusable-context.yml +++ b/.github/workflows/reusable-context.yml @@ -26,6 +26,9 @@ on: # yamllint disable-line rule:truthy run-docs: description: Whether to build the docs value: ${{ jobs.compute-changes.outputs.run-docs }} # bool + run-idle-help-doc: + description: Whether to check the IDLE help doc check + value: ${{ jobs.compute-changes.outputs.run-idle-help-doc }} # bool run-ios: description: Whether to run the iOS tests value: ${{ jobs.compute-changes.outputs.run-ios }} # bool @@ -57,6 +60,7 @@ jobs: run-android: ${{ steps.changes.outputs.run-android }} run-ci-fuzz: ${{ steps.changes.outputs.run-ci-fuzz }} run-docs: ${{ steps.changes.outputs.run-docs }} + run-idle-help-doc: ${{ steps.changes.outputs.run-idle-help-doc }} run-ios: ${{ steps.changes.outputs.run-ios }} run-macos: ${{ steps.changes.outputs.run-macos }} run-tests: ${{ steps.changes.outputs.run-tests }} diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 65154aae4c41d5..d46b6db26fe6dc 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -2,6 +2,10 @@ name: Reusable Docs on: workflow_call: + outputs: + idle-html-artifact-id: + description: 'Artifact ID for the built idle.html' + value: ${{ jobs.build-doc.outputs.idle-html-artifact-id }} workflow_dispatch: permissions: @@ -19,6 +23,8 @@ jobs: name: 'Docs' runs-on: ubuntu-latest timeout-minutes: 60 + outputs: + idle-html-artifact-id: ${{ steps.upload-idle-html.outputs.artifact-id }} env: branch_base: 'origin/${{ github.event.pull_request.base.ref }}' branch_pr: 'origin/${{ github.event.pull_request.head.ref }}' @@ -75,6 +81,13 @@ jobs: --fail-if-regression \ --fail-if-improved \ --fail-if-new-news-nit + - name: 'Upload built idle.html' + id: upload-idle-html + uses: actions/upload-artifact@v4 + with: + name: idle-html + path: Doc/build/html/library/idle.html + retention-days: 1 # Run "doctest" on HEAD as new syntax doesn't exist in the latest stable release doctest: diff --git a/.github/workflows/reusable-idle-help-doc.yml b/.github/workflows/reusable-idle-help-doc.yml new file mode 100644 index 00000000000000..b60c3542d1dc85 --- /dev/null +++ b/.github/workflows/reusable-idle-help-doc.yml @@ -0,0 +1,43 @@ +name: Reusable check IDLE help + +on: + workflow_call: + inputs: + idle-html-artifact-id: + description: 'Artifact ID for the built idle.html' + required: true + type: string + +permissions: {} + +env: + FORCE_COLOR: 1 + +jobs: + check-idle-doc-sync: + name: 'Check if IDLE help needs regenerating' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - name: 'Download built idle.html' + uses: actions/download-artifact@v7 + with: + artifact-ids: ${{ inputs.idle-html-artifact-id }} + path: Doc/build/html/library + - name: 'Set up Python' + uses: actions/setup-python@v6 + with: + python-version: '3' + - name: 'Regenerate Lib/idlelib/help.html' + run: make idlehelp + working-directory: Doc + - name: 'Check for changes' + run: | + git diff --exit-code Lib/idlelib/help.html || { + echo "Lib/idlelib/help.html is not up to date." + echo "Run make idlehelp in the Doc directory." + exit 1 + } diff --git a/Doc/Makefile b/Doc/Makefile index 4d605980a62904..5bb2060ed0183f 100644 --- a/Doc/Makefile +++ b/Doc/Makefile @@ -46,6 +46,7 @@ help: @echo " coverage to check documentation coverage for library and C API" @echo " doctest to run doctests in the documentation" @echo " pydoc-topics to regenerate the pydoc topics file" + @echo " idlehelp to regenerate Lib/idlelib/help.html" @echo " dist to create a \"dist\" directory with archived docs for download" @echo " check to run a check for frequent markup errors" @@ -143,6 +144,10 @@ pydoc-topics: build "cp build/pydoc-topics/topics.py ../Lib/pydoc_data/topics.py" \ "&& cp build/pydoc-topics/module_docs.py ../Lib/pydoc_data/module_docs.py" +.PHONY: idlehelp +idlehelp: build/html/library/idle.html + $(PYTHON) ../Tools/build/generate_idle_help.py + .PHONY: gettext gettext: BUILDER = gettext gettext: override SPHINXOPTS := --doctree-dir build/doctrees-gettext $(SPHINXOPTS) diff --git a/Doc/library/idle.rst b/Doc/library/idle.rst index a16f46ef812400..06c47eff6a6a84 100644 --- a/Doc/library/idle.rst +++ b/Doc/library/idle.rst @@ -13,7 +13,7 @@ IDLE --- Python editor and shell single: Integrated Development Environment .. - Remember to update Lib/idlelib/help.html with idlelib.help.copy_strip() when modifying this file. + Remember to update Lib/idlelib/help.html with make idlehelp when modifying this file. -------------- diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index 48e7eca280ebf8..b44d195312e99d 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -248,43 +248,6 @@ def __init__(self, parent, filename, title): self.grid_rowconfigure(0, weight=1) -def copy_strip(): # pragma: no cover - """Copy the text part of idle.html to idlelib/help.html while stripping trailing whitespace. - - Files with trailing whitespace cannot be pushed to the git cpython - repository. For 3.x (on Windows), help.html is generated, after - editing idle.rst on the master branch, with - sphinx-build -bhtml . build/html - python_d.exe -c "from idlelib.help import copy_strip; copy_strip()" - Check build/html/library/idle.html, the help.html diff, and the text - displayed by Help => IDLE Help. Add a blurb and create a PR. - - It can be worthwhile to occasionally generate help.html without - touching idle.rst. Changes to the master version and to the doc - build system may result in changes that should not change - the displayed text, but might break HelpParser. - - As long as master and maintenance versions of idle.rst remain the - same, help.html can be backported. The internal Python version - number is not displayed. If maintenance idle.rst diverges from - the master version, then instead of backporting help.html from - master, repeat the procedure above to generate a maintenance - version. - """ - src = join(abspath(dirname(dirname(dirname(__file__)))), - 'Doc', 'build', 'html', 'library', 'idle.html') - dst = join(abspath(dirname(__file__)), 'help.html') - - with open(src, 'r', encoding="utf-8") as inn, open(dst, 'w', encoding="utf-8") as out: - copy = False - for line in inn: - if '
' in line: copy = True - if '
' in line: break - if copy: out.write(line.strip() + '\n') - - print(f'{src} copied to {dst}') - - def show_idlehelp(parent): "Create HelpWindow; called from Idle Help event handler." filename = join(abspath(dirname(__file__)), 'help.html') diff --git a/Tools/build/compute-changes.py b/Tools/build/compute-changes.py index 524d3066fbffa7..ca02cece793779 100644 --- a/Tools/build/compute-changes.py +++ b/Tools/build/compute-changes.py @@ -11,7 +11,7 @@ import os import subprocess -from dataclasses import dataclass +from dataclasses import dataclass, fields from pathlib import Path TYPE_CHECKING = False @@ -56,6 +56,7 @@ class Outputs: run_android: bool = False run_ci_fuzz: bool = False run_docs: bool = False + run_idle_help_doc: bool = False run_ios: bool = False run_macos: bool = False run_tests: bool = False @@ -148,6 +149,7 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: run_tests = False run_ci_fuzz = False run_docs = False + run_idle_help_doc = False run_windows_tests = False run_windows_msi = False @@ -165,6 +167,8 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: has_platform_specific_change = False if file.name == "reusable-docs.yml": run_docs = True + if file.name == "reusable-idle-help-doc.yml": + run_idle_help_doc = True if file.name == "reusable-windows-msi.yml": run_windows_msi = True if file.name == "reusable-macos.yml": @@ -201,6 +205,11 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: if doc_file: run_docs = True + # Check for changed IDLE docs + if file in (Path("Doc/library/idle.rst"), + Path("Tools/build/generate_idle_help.py")): + run_idle_help_doc = True + # Check for changed MSI installer-related files if file.parts[:2] == ("Tools", "msi"): run_windows_msi = True @@ -230,6 +239,7 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs: run_android=run_android, run_ci_fuzz=run_ci_fuzz, run_docs=run_docs, + run_idle_help_doc=run_idle_help_doc, run_ios=run_ios, run_macos=run_macos, run_tests=run_tests, @@ -263,16 +273,10 @@ def write_github_output(outputs: Outputs) -> None: return with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: - f.write(f"run-android={bool_lower(outputs.run_android)}\n") - f.write(f"run-ci-fuzz={bool_lower(outputs.run_ci_fuzz)}\n") - f.write(f"run-docs={bool_lower(outputs.run_docs)}\n") - f.write(f"run-ios={bool_lower(outputs.run_ios)}\n") - f.write(f"run-macos={bool_lower(outputs.run_macos)}\n") - f.write(f"run-tests={bool_lower(outputs.run_tests)}\n") - f.write(f"run-ubuntu={bool_lower(outputs.run_ubuntu)}\n") - f.write(f"run-wasi={bool_lower(outputs.run_wasi)}\n") - f.write(f"run-windows-msi={bool_lower(outputs.run_windows_msi)}\n") - f.write(f"run-windows-tests={bool_lower(outputs.run_windows_tests)}\n") + for field in fields(outputs): + name = field.name.replace("_", "-") + val = bool_lower(getattr(outputs, field.name)) + f.write(f"{name}={val}\n") def bool_lower(value: bool, /) -> str: diff --git a/Tools/build/generate_idle_help.py b/Tools/build/generate_idle_help.py new file mode 100644 index 00000000000000..c1338d93dc6f03 --- /dev/null +++ b/Tools/build/generate_idle_help.py @@ -0,0 +1,46 @@ +"""Copy the text part of idle.html to idlelib/help.html while stripping trailing whitespace. + +Files with trailing whitespace cannot be pushed to the git cpython +repository. help.html is regenerated, after +editing idle.rst on the master branch, with, in the Doc directory + make idlehelp +Check build/html/library/idle.html, the help.html diff, and the text +displayed by Help => IDLE Help. Add a blurb and create a PR. + +It can be worthwhile to occasionally generate help.html without +touching idle.rst. Changes to the master version and to the doc +build system may result in changes that should not change +the displayed text, but might break HelpParser. + +As long as master and maintenance versions of idle.rst remain the +same, help.html can be backported. The internal Python version +number is not displayed. If maintenance idle.rst diverges from +the master version, then instead of backporting help.html from +master, repeat the procedure above to generate a maintenance +version. +""" + +from os.path import abspath, dirname, join + + +def copy_strip(): + src = join(abspath(dirname(dirname(dirname(__file__)))), + 'Doc', 'build', 'html', 'library', 'idle.html') + dst = join(abspath(dirname(dirname(dirname(__file__)))), + 'Lib', 'idlelib', 'help.html') + + with open(src, encoding="utf-8") as inn, open(dst, 'w', encoding="utf-8") as out: + copy = False + for line in inn: + if '
' in line: + break + if '
' in line: + copy = True + if copy: + out.write(line.strip() + '\n') + + print(f'{src} copied to {dst}') + + +if __name__ == '__main__': + copy_strip()