diff --git a/.gitattributes b/.gitattributes index 6ef460a36..d7c45b362 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,4 +18,7 @@ makefile text *.ttf binary *.mp3 binary *.gif binary -*.wav binary \ No newline at end of file +*.wav binary + +# Tell github that pyrightconfig.json is JSONC and allows comments +pyrightconfig.json linguist-language=JSONC diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 36ac57c3e..cc583c5da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,12 +31,21 @@ jobs: run: | python -m pip install -U pip wheel setuptools - name: wheel + id: wheel run: | python -m pip install -e .[dev] - - name: code-inspection + - name: "code-inspection: mypy" + if: ${{ (success() || failure()) && steps.wheel.outcome == 'success' }} run: | - mypy arcade - ruff arcade + python ./make.py mypy + - name: "code-inspection: pyright" + if: ${{ (success() || failure()) && steps.wheel.outcome == 'success' }} + run: | + python ./make.py pyright + - name: "code-inspection: ruff" + if: ${{ (success() || failure()) && steps.wheel.outcome == 'success' }} + run: | + python ./make.py ruff - name: build-docs run: | sphinx-build doc build -W diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..b5b861a7b --- /dev/null +++ b/Makefile @@ -0,0 +1,133 @@ +# DO NOT EDIT BY HAND: is automatically re-generated by make.py +.PHONY: default +default: + python ./make.py --help + +.PHONY: clean +clean: + python ./make.py clean + +.PHONY: html +html: + python ./make.py html + +.PHONY: serve +serve: + python ./make.py serve + +.PHONY: dirhtml +dirhtml: + python ./make.py dirhtml + +.PHONY: singlehtml +singlehtml: + python ./make.py singlehtml + +.PHONY: pickle +pickle: + python ./make.py pickle + +.PHONY: json +json: + python ./make.py json + +.PHONY: htmlhelp +htmlhelp: + python ./make.py htmlhelp + +.PHONY: qthelp +qthelp: + python ./make.py qthelp + +.PHONY: applehelp +applehelp: + python ./make.py applehelp + +.PHONY: devhelp +devhelp: + python ./make.py devhelp + +.PHONY: epub +epub: + python ./make.py epub + +.PHONY: latex +latex: + python ./make.py latex + +.PHONY: latexpdf +latexpdf: + python ./make.py latexpdf + +.PHONY: latexpdfja +latexpdfja: + python ./make.py latexpdfja + +.PHONY: text +text: + python ./make.py text + +.PHONY: man +man: + python ./make.py man + +.PHONY: texinfo +texinfo: + python ./make.py texinfo + +.PHONY: info +info: + python ./make.py info + +.PHONY: gettext +gettext: + python ./make.py gettext + +.PHONY: changes +changes: + python ./make.py changes + +.PHONY: linkcheck +linkcheck: + python ./make.py linkcheck + +.PHONY: doctest +doctest: + python ./make.py doctest + +.PHONY: coverage +coverage: + python ./make.py coverage + +.PHONY: xml +xml: + python ./make.py xml + +.PHONY: pseudoxml +pseudoxml: + python ./make.py pseudoxml + +.PHONY: lint +lint: + python ./make.py lint + +.PHONY: ruff +ruff: + python ./make.py ruff + +.PHONY: mypy +mypy: + python ./make.py mypy + +.PHONY: pyright +pyright: + python ./make.py pyright + +.PHONY: test_full +test_full: + python ./make.py test_full + +.PHONY: test +test: + python ./make.py test + diff --git a/justfile b/justfile new file mode 100644 index 000000000..6767090a5 --- /dev/null +++ b/justfile @@ -0,0 +1,101 @@ +# DO NOT EDIT BY HAND: is automatically re-generated by make.py +set shell := ["python", "./make.py"] +default: + @{{"--help"}} + +clean: + @clean + +html: + @html + +serve: + @serve + +dirhtml: + @dirhtml + +singlehtml: + @singlehtml + +pickle: + @pickle + +json: + @json + +htmlhelp: + @htmlhelp + +qthelp: + @qthelp + +applehelp: + @applehelp + +devhelp: + @devhelp + +epub: + @epub + +latex: + @latex + +latexpdf: + @latexpdf + +latexpdfja: + @latexpdfja + +text: + @text + +man: + @man + +texinfo: + @texinfo + +info: + @info + +gettext: + @gettext + +changes: + @changes + +linkcheck: + @linkcheck + +doctest: + @doctest + +coverage: + @coverage + +xml: + @xml + +pseudoxml: + @pseudoxml + +lint: + @lint + +ruff: + @ruff + +mypy: + @mypy + +pyright: + @pyright + +test_full: + @test_full + +test: + @test + diff --git a/make.py b/make.py index e4d670f95..e5dd49564 100644 --- a/make.py +++ b/make.py @@ -38,7 +38,8 @@ def _resolve(p: PathLike, strict: bool = False) -> Path: RUFFOPTS = ["arcade"] MYPY = "mypy" MYPYOPTS = ["arcade"] - +PYRIGHT = "pyright" +PYRIGHTOPTS = [] # Testing PYTEST = "pytest" @@ -139,8 +140,37 @@ def run(args: Union[str, List[str]], cd: Optional[PathLike] = None) -> None: def run_doc(args: Union[str, List[str]]) -> None: run(args, cd=FULL_DOC_DIR) - -@app.command() +def emit_makefile(): + with cd_context(PROJECT_ROOT): + with open('Makefile', 'w') as f: + f.write('# DO NOT EDIT BY HAND: is automatically re-generated by make.py\n') + f.write('.PHONY: default\n') + f.write('default:\n') + f.write('\tpython ./make.py --help\n\n') + for command in app.registered_commands: + name = command.callback.__name__ + f.write(f'.PHONY: {name}\n') + f.write(f'{name}:\n') + f.write(f'\tpython ./make.py {name}\n\n') + +def emit_justfile(): + with cd_context(PROJECT_ROOT): + with open('justfile', 'w') as f: + f.write('# DO NOT EDIT BY HAND: is automatically re-generated by make.py\n') + f.write('set shell := ["python", "./make.py"]\n') + f.write('default:\n') + # Wrap with interpolation because otherwise just strips leading `-` + # and triggers error-suppression + # f.write(' {{"--help"}}\n\n') + f.write(' @{{"--help"}}\n\n') + for command in app.registered_commands: + name = command.callback.__name__ + f.write(f'{name}:\n') + f.write(f' @{name}\n\n') + + + +@app.command(rich_help_panel="Docs") def clean(): """ Delete built website files. @@ -150,7 +180,7 @@ def clean(): os.remove(item) if os.path.isfile(item) else rmtree(item) -@app.command() +@app.command(rich_help_panel="Docs") def html(): """ to make standalone HTML files @@ -160,7 +190,7 @@ def html(): print(f"Build finished. The HTML pages are in {FULL_BUILD_PREFIX}/html.") -@app.command() +@app.command(rich_help_panel="Docs") def serve(): """ Build and serve standalone HTML files, with automatic rebuilds and live reload. @@ -168,7 +198,7 @@ def serve(): run_doc([SPHINXAUTOBUILD, *SPHINXAUTOBUILDOPTS, '-b', 'html', *ALLSPHINXOPTS, f'{BUILDDIR}/html']) -@app.command() +@app.command(rich_help_panel="Docs") def dirhtml(): """ to make HTML files named index.html in directories @@ -178,7 +208,7 @@ def dirhtml(): print(f"Build finished. The HTML pages are in {FULL_BUILD_PREFIX}/dirhtml.") -@app.command() +@app.command(rich_help_panel="Docs") def singlehtml(): """ to make a single large HTML file @@ -188,7 +218,7 @@ def singlehtml(): print(f"Build finished. The HTML page is in {FULL_BUILD_PREFIX}/singlehtml.") -@app.command() +@app.command(rich_help_panel="Docs") def pickle(): """ to make pickle files @@ -198,7 +228,7 @@ def pickle(): print("Build finished; now you can process the pickle files.") -@app.command() +@app.command(rich_help_panel="Docs") def json(): """ to make JSON files @@ -208,7 +238,7 @@ def json(): print("Build finished; now you can process the JSON files.") -@app.command() +@app.command(rich_help_panel="Docs") def htmlhelp(): """ to make HTML files and a HTML help project @@ -219,7 +249,7 @@ def htmlhelp(): f".hhp project file in {FULL_BUILD_PREFIX}/htmlhelp.") -@app.command() +@app.command(rich_help_panel="Docs") def qthelp(): """ to make HTML files and a qthelp project @@ -233,7 +263,7 @@ def qthelp(): print(f"# assistant -collectionFile {FULL_BUILD_PREFIX}/qthelp/Arcade.qhc") -@app.command() +@app.command(rich_help_panel="Docs") def applehelp(): """ to make an Apple Help Book @@ -246,7 +276,7 @@ def applehelp(): "bundle.") -@app.command() +@app.command(rich_help_panel="Docs") def devhelp(): """ to make HTML files and a Devhelp project @@ -261,7 +291,7 @@ def devhelp(): print("# devhelp") -@app.command() +@app.command(rich_help_panel="Docs") def epub(): """ to make an epub @@ -271,7 +301,7 @@ def epub(): print(f"Build finished. The epub file is in {FULL_BUILD_PREFIX}/epub.") -@app.command() +@app.command(rich_help_panel="Docs") def latex(): """ to make LaTeX files, you can set PAPER_SIZE=a4 or PAPER_SIZE=letter @@ -283,7 +313,7 @@ def latex(): "(use \`make latexpdf' here to do that automatically).") -@app.command() +@app.command(rich_help_panel="Docs") def latexpdf(): """ to make LaTeX files and run them through pdflatex @@ -294,7 +324,7 @@ def latexpdf(): print(f"pdflatex finished; the PDF files are in {FULL_BUILD_PREFIX}/latex.") -@app.command() +@app.command(rich_help_panel="Docs") def latexpdfja(): """ to make LaTeX files and run them through platex/dvipdfmx @@ -305,7 +335,7 @@ def latexpdfja(): print(f"pdflatex finished; the PDF files are in {FULL_BUILD_PREFIX}/latex.") -@app.command() +@app.command(rich_help_panel="Docs") def text(): """ to make text files @@ -315,7 +345,7 @@ def text(): print(f"Build finished. The text files are in {FULL_BUILD_PREFIX}/text.") -@app.command() +@app.command(rich_help_panel="Docs") def man(): """ to make manual pages @@ -325,7 +355,7 @@ def man(): print(f"Build finished. The manual pages are in {FULL_BUILD_PREFIX}/man.") -@app.command() +@app.command(rich_help_panel="Docs") def texinfo(): """ to make Texinfo files @@ -337,7 +367,7 @@ def texinfo(): "(use \`make info' here to do that automatically).") -@app.command() +@app.command(rich_help_panel="Docs") def info(): """ to make Texinfo files and run them through makeinfo @@ -348,7 +378,7 @@ def info(): print(f"makeinfo finished; the Info files are in {FULL_BUILD_PREFIX}/texinfo.") -@app.command() +@app.command(rich_help_panel="Docs") def gettext(): """ to make PO message catalogs @@ -358,7 +388,7 @@ def gettext(): print(f"Build finished. The message catalogs are in {FULL_BUILD_PREFIX}/locale.") -@app.command() +@app.command(rich_help_panel="Docs") def changes(): """ to make an overview of all changed/added/deprecated items @@ -368,7 +398,7 @@ def changes(): print(f"The overview file is in {FULL_BUILD_PREFIX}/changes.") -@app.command() +@app.command(rich_help_panel="Docs") def linkcheck(): """ to check all external links for integrity @@ -379,7 +409,7 @@ def linkcheck(): f"or in {FULL_BUILD_PREFIX}/linkcheck/output.txt.") -@app.command() +@app.command(rich_help_panel="Docs") def doctest(): """ to run all doctests embedded in the documentation (if enabled) @@ -389,7 +419,7 @@ def doctest(): f"results in {FULL_BUILD_PREFIX}/doctest/output.txt.") -@app.command() +@app.command(rich_help_panel="Docs") def coverage(): """ to run coverage check of the documentation (if enabled) @@ -399,50 +429,61 @@ def coverage(): f"results in {FULL_BUILD_PREFIX}/coverage/python.txt.") -@app.command() +@app.command(rich_help_panel="Docs") def xml(): run_doc([SPHINXBUILD, "-b", "xml", *ALLSPHINXOPTS, f"{BUILDDIR}/xml"]) print() print(f"Build finished. The XML files are in {FULL_BUILD_PREFIX}/xml.") -@app.command() +@app.command(rich_help_panel="Docs") def pseudoxml(): run_doc([SPHINXBUILD, "-b", "pseudoxml", *ALLSPHINXOPTS, f"{BUILDDIR}/pseudoxml"]) print() print(f"Build finished. The pseudo-XML files are in {FULL_BUILD_PREFIX}/pseudoxml.") -@app.command() +@app.command(rich_help_panel="Coding") def lint(): - run([RUFF, *RUFFOPTS]) - print("Ruff Finished.") - run([MYPY, *MYPYOPTS]) - print("Mypy Finished.") + """ + Run all linting tasks: ruff, mypy, and pyright + """ + ruff() + mypy() + pyright() print("Linting Complete.") -@app.command() +@app.command(rich_help_panel="Coding") def ruff(): run([RUFF, *RUFFOPTS]) print("Ruff Finished.") -@app.command() +@app.command(rich_help_panel="Coding") def mypy(): + "Typecheck using mypy" run([MYPY, *MYPYOPTS]) print("MyPy Finished.") +@app.command(rich_help_panel="Coding") +def pyright(): + "Typecheck using pyright" + run([PYRIGHT, *PYRIGHTOPTS]) + print("MyPy Finished.") + -@app.command() +@app.command(rich_help_panel="Coding") def test_full(): run([PYTEST, TESTDIR]) -@app.command() +@app.command(rich_help_panel="Coding") def test(): run([PYTEST, UNITTESTS]) if __name__ == "__main__": + emit_makefile() + emit_justfile() app() diff --git a/pyproject.toml b/pyproject.toml index 435b74204..a9fbddbb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,13 +48,14 @@ dev = [ "pygments==2.14.0", "docutils==0.19", "furo", + "pyright", "pyyaml==6.0", "sphinx==6.1.3", "sphinx-autobuild==2021.3.14", "sphinx-copybutton==0.5.1", # Intentionally kept at 2.3 until this bugfix is published: https://github.com/jdillard/sphinx-sitemap/pull/62 "sphinx-sitemap==2.3.0", - "typer==0.7.0", + "typer[all]==0.7.0", "wheel", ] diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 000000000..e8a9ad5ff --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,28 @@ +{ + "include": ["arcade", "tests"], + + // Sets a variety of strict typechecking rules which are loosened by the + // overrides below. + "typeCheckingMode": "strict", + + // Use type info from pytiled_parser and pyglet, which do not ship `py.typed` file + "useLibraryCodeForTypes": true, + "reportMissingTypeStubs": "none", + + // This flags everywhere we don't *explicitly* annotate types, even when the + // type is being inferred. + // IMO not necessary, since a *different* diagnostic will still flag when + // inference fails. + "reportMissingParameterType": false, + + // Flags every time you access `_` properties from outside of the class. + // Not helpful IMO, too many good reasons for arcade to access its own internals. + "reportPrivateUsage": false, + + "reportUnknownArgumentType": "none", + "reportUnknownParameterType": "none", + "reportUnknownMemberType": "none", + "reportUnknownVariableType": "none", + + "reportUnusedImport": "information" +}