From 3960d8bc5295742722f5a82524cefce46ba477df Mon Sep 17 00:00:00 2001 From: Calum Young Date: Sat, 28 Dec 2024 11:58:57 +0000 Subject: [PATCH 01/35] Add requires python version --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 7dfb90f..02fc8d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [project] name = "ruff-pre-commit" version = "0.0.0" +requires-python = ">=3.9" dependencies = [ "ruff==0.8.4", ] From 80c35cb8e887faff006c052158898af34bfdb02b Mon Sep 17 00:00:00 2001 From: Calum Young Date: Sat, 28 Dec 2024 12:03:54 +0000 Subject: [PATCH 02/35] Add pre-commit config --- .markdownlint.yaml | 33 +++++++++++++++ .pre-commit-config.yaml | 91 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 .markdownlint.yaml create mode 100644 .pre-commit-config.yaml diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..6ad232a --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,33 @@ +# Aligns with the main ruff repo markdownlint configuration. + +# default to true for all rules +default: true + +# MD007/unordered-list-indent +MD007: + indent: 4 + +# MD033/no-inline-html +MD033: false + +# MD041/first-line-h1 +MD041: false + +# MD013/line-length +MD013: false + +# MD014/commands-show-output +MD014: false + +# MD024/no-duplicate-heading +MD024: + # Allow when nested under different parents e.g. CHANGELOG.md + siblings_only: true + +# MD046/code-block-style +# +# Ignore this because it conflicts with the code block style used in content +# tabs of mkdocs-material which is to add a blank line after the content title. +# +# Ref: https://github.com/astral-sh/ruff/pull/15011#issuecomment-2544790854 +MD046: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b7ffb13 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,91 @@ +# # All hooks in this repo align with the main ruff repo unless otherwise noted. +repos: + - repo: meta + hooks: + # Not in the main ruff repo but useful for checking that the current hooks are relevant + - id: check-hooks-apply + - id: check-useless-excludes + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + # The following hooks are not in the main ruff repo but are useful to checking files are formatted consistently + - id: check-case-conflict + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/abravalheri/validate-pyproject + rev: v0.23 + hooks: + - id: validate-pyproject + + - repo: https://github.com/executablebooks/mdformat + rev: 0.7.21 + hooks: + - id: mdformat + additional_dependencies: + # Additional dependencies added to align with the main ruff repo even though mkdocs is not currently used + - mdformat-mkdocs==4.0.0 + - mdformat-footnote==0.1.1 + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.43.0 + hooks: + - id: markdownlint-fix + + - repo: https://github.com/adamchainz/blacken-docs + rev: 1.19.1 + hooks: + - id: blacken-docs + args: ["--pyi", "--line-length", "130"] + additional_dependencies: + - black==24.10.0 + + - repo: https://github.com/crate-ci/typos + rev: v1.28.4 + hooks: + - id: typos + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + + # Prettier + - repo: https://github.com/rbubley/mirrors-prettier + rev: v3.4.2 + hooks: + - id: prettier + types: [yaml] + + # zizmor detects security vulnerabilities in GitHub Actions workflows. + - repo: https://github.com/woodruffw/zizmor-pre-commit + rev: v0.10.0 + hooks: + - id: zizmor + + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.30.0 + hooks: + - id: check-github-workflows + + # `actionlint` hook, for verifying correct syntax in GitHub Actions workflows. + - repo: https://github.com/rhysd/actionlint + rev: v1.7.4 + hooks: + - id: actionlint + stages: + # This hook is disabled by default, since it's quite slow. + # To run all hooks *including* this hook, use `uvx pre-commit run -a --hook-stage=manual`. + # To run *just* this hook, use `uvx pre-commit run -a actionlint --hook-stage=manual`. + - manual + args: + - "-ignore=SC2129" # ignorable stylistic lint from shellcheck + - "-ignore=SC2016" # another shellcheck lint: seems to have false positives? + additional_dependencies: + # actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions + # and checks these with shellcheck. This is arguably its most useful feature, + # but the integration only works if shellcheck is installed + - "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0" From 9437c5d2e88b9b8149c927059d995d751678a544 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Sat, 28 Dec 2024 12:05:03 +0000 Subject: [PATCH 03/35] Add lint dep group --- pyproject.toml | 5 ++ uv.lock | 183 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml index 02fc8d5..1770d1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,8 @@ requires-python = ">=3.9" dependencies = [ "ruff==0.8.4", ] + +[dependency-groups] +lint = [ + "pre-commit==4.0.1", +] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..3394917 --- /dev/null +++ b/uv.lock @@ -0,0 +1,183 @@ +version = 1 +requires-python = ">=3.9" + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "identify" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/5f/05f0d167be94585d502b4adf8c7af31f1dc0b1c7e14f9938a88fdbbcf4a7/identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02", size = 99179 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/f5/09644a3ad803fae9eca8efa17e1f2aef380c7f0b02f7ec4e8d446e51d64a/identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd", size = 99049 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pre-commit" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, +] + +[[package]] +name = "ruff" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/37/9c02181ef38d55b77d97c68b78e705fd14c0de0e5d085202bb2b52ce5be9/ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8", size = 3402103 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/67/f480bf2f2723b2e49af38ed2be75ccdb2798fca7d56279b585c8f553aaab/ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60", size = 10546415 }, + { url = "https://files.pythonhosted.org/packages/eb/7a/5aba20312c73f1ce61814e520d1920edf68ca3b9c507bd84d8546a8ecaa8/ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac", size = 10346113 }, + { url = "https://files.pythonhosted.org/packages/76/f4/c41de22b3728486f0aa95383a44c42657b2db4062f3234ca36fc8cf52d8b/ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296", size = 9943564 }, + { url = "https://files.pythonhosted.org/packages/0e/f0/afa0d2191af495ac82d4cbbfd7a94e3df6f62a04ca412033e073b871fc6d/ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643", size = 10805522 }, + { url = "https://files.pythonhosted.org/packages/12/57/5d1e9a0fd0c228e663894e8e3a8e7063e5ee90f8e8e60cf2085f362bfa1a/ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e", size = 10306763 }, + { url = "https://files.pythonhosted.org/packages/04/df/f069fdb02e408be8aac6853583572a2873f87f866fe8515de65873caf6b8/ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3", size = 11359574 }, + { url = "https://files.pythonhosted.org/packages/d3/04/37c27494cd02e4a8315680debfc6dfabcb97e597c07cce0044db1f9dfbe2/ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f", size = 12094851 }, + { url = "https://files.pythonhosted.org/packages/81/b1/c5d7fb68506cab9832d208d03ea4668da9a9887a4a392f4f328b1bf734ad/ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604", size = 11655539 }, + { url = "https://files.pythonhosted.org/packages/ef/38/8f8f2c8898dc8a7a49bc340cf6f00226917f0f5cb489e37075bcb2ce3671/ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf", size = 12912805 }, + { url = "https://files.pythonhosted.org/packages/06/dd/fa6660c279f4eb320788876d0cff4ea18d9af7d9ed7216d7bd66877468d0/ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720", size = 11205976 }, + { url = "https://files.pythonhosted.org/packages/a8/d7/de94cc89833b5de455750686c17c9e10f4e1ab7ccdc5521b8fe911d1477e/ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae", size = 10792039 }, + { url = "https://files.pythonhosted.org/packages/6d/15/3e4906559248bdbb74854af684314608297a05b996062c9d72e0ef7c7097/ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7", size = 10400088 }, + { url = "https://files.pythonhosted.org/packages/a2/21/9ed4c0e8133cb4a87a18d470f534ad1a8a66d7bec493bcb8bda2d1a5d5be/ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111", size = 10900814 }, + { url = "https://files.pythonhosted.org/packages/0d/5d/122a65a18955bd9da2616b69bc839351f8baf23b2805b543aa2f0aed72b5/ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8", size = 11268828 }, + { url = "https://files.pythonhosted.org/packages/43/a9/1676ee9106995381e3d34bccac5bb28df70194167337ed4854c20f27c7ba/ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835", size = 8805621 }, + { url = "https://files.pythonhosted.org/packages/10/98/ed6b56a30ee76771c193ff7ceeaf1d2acc98d33a1a27b8479cbdb5c17a23/ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d", size = 9660086 }, + { url = "https://files.pythonhosted.org/packages/13/9f/026e18ca7d7766783d779dae5e9c656746c6ede36ef73c6d934aaf4a6dec/ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08", size = 9074500 }, +] + +[[package]] +name = "ruff-pre-commit" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "ruff" }, +] + +[package.dev-dependencies] +lint = [ + { name = "pre-commit" }, +] + +[package.metadata] +requires-dist = [{ name = "ruff", specifier = "==0.8.4" }] + +[package.metadata.requires-dev] +lint = [{ name = "pre-commit", specifier = "==4.0.1" }] + +[[package]] +name = "virtualenv" +version = "20.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/75/53316a5a8050069228a2f6d11f32046cfa94fbb6cc3f08703f59b873de2e/virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa", size = 7650368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/f9/0919cf6f1432a8c4baa62511f8f8da8225432d22e83e3476f5be1a1edc6e/virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0", size = 4276702 }, +] From 4abd502c9f46b35c007904cad5608ef17a4ee8b6 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Sat, 28 Dec 2024 12:08:06 +0000 Subject: [PATCH 04/35] Add ci workflow --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..583ad19 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + pre-commit: + name: pre-commit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Install pre-commit + run: uv sync --group lint + + - name: Cache pre-commit + uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + + - name: Run pre-commit + run: | + echo '```console' > "$GITHUB_STEP_SUMMARY" + # Enable color output for pre-commit and remove it for the summary + # Use --hook-stage=manual to enable slower pre-commit hooks that are skipped by default + uv run pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \ + tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1 + exit_code="${PIPESTATUS[0]}" + echo '```' >> "$GITHUB_STEP_SUMMARY" + exit "$exit_code" From 9bfe3f457ff6ef5929f49731ee0ccaf8f40472ff Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:23:32 +0000 Subject: [PATCH 05/35] Add ruff config --- pyproject.toml | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1770d1a..55c8969 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,3 +10,59 @@ dependencies = [ lint = [ "pre-commit==4.0.1", ] + +[tool.ruff] +target-version = "py39" + +[tool.ruff.lint] +select = [ + "F", # pyflakes + "E", # pycodestyle (error) + "W", # pycodestyle (warning) + "I", # isort + "N", # pep8-naming + "D", # pydocstyle + "UP", # pyupgrade + "BLE", # flake8-blind-except + "B", # flake8-bugbear + "A", # flake8-builtins + "COM", # flake8-commas + "C4", # flake8-comprehensions + "ISC", # flake8-implicit-str-concat + "INP", # flake8-import-conventions + "PIE", # flake8-pie + "RSE", # flake8-raise + "RET", # flake8-return + "SIM", # flake8-simplify + "ARG", # flake8-unused-arguments + "PTH", # flake8-use-pathlib + "TD", # flake8-todos + "ERA", # eradicate + "PGH", # pygrep-hooks + "PL", # pylint + "TRY", # tryceratops + "FLY", # flynt + "PERF", # perflint + "FURB", # refurb + "RUF", # Ruff-specific rules +] +ignore = [ + "PLR2004", # Ignore consider replacing magic number with constant variable + "TRY003", # Not relevant if only using python inbuilt exceptions + "S101", # Allow use of assert +] +unfixable = [ + "ERA001", # Disable automatic removal of commented-out code + "F401", # Disable automatic removal of unused imports + "F841", # Disable automatic removal of unused variables +] + +[tool.ruff.lint.flake8-unused-arguments] +ignore-variadic-names = true + +[tool.ruff.lint.isort] +combine-as-imports = true +required-imports = ["from __future__ import annotations"] + +[tool.ruff.lint.pydocstyle] +convention = "google" From 1072b613f01022c6b1e175020f28005f1ed476b8 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:20:38 +0000 Subject: [PATCH 06/35] Add type checking config and lint rules --- .pre-commit-config.yaml | 9 +++++++++ pyproject.toml | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b7ffb13..0502af1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,6 +46,15 @@ repos: hooks: - id: typos + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.13.0 + hooks: + - id: mypy + args: [--config-file, pyproject.toml] + additional_dependencies: + - pytest==8.3.3 + - urllib3==2.0.5 + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.8.4 hooks: diff --git a/pyproject.toml b/pyproject.toml index 55c8969..0519dcf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,11 @@ lint = [ "pre-commit==4.0.1", ] +[tool.mypy] +mypy_path = "$MYPY_CONFIG_FILE_DIR/src" +strict = true +warn_unreachable = true + [tool.ruff] target-version = "py39" @@ -23,6 +28,7 @@ select = [ "N", # pep8-naming "D", # pydocstyle "UP", # pyupgrade + "ANN", # flake8-annotations "BLE", # flake8-blind-except "B", # flake8-bugbear "A", # flake8-builtins @@ -34,6 +40,7 @@ select = [ "RSE", # flake8-raise "RET", # flake8-return "SIM", # flake8-simplify + "TC", # flake8-type-checking "ARG", # flake8-unused-arguments "PTH", # flake8-use-pathlib "TD", # flake8-todos From 84f9c3a0ed4bf23056c90eb56f90ff393b47fd60 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:26:00 +0000 Subject: [PATCH 07/35] Fix linting issues --- .github/workflows/main.yml | 2 ++ README.md | 4 ++-- mirror.py | 37 ++++++++++++++++++++++++------------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5925bd8..b214630 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,6 +15,8 @@ jobs: run: sleep 30 - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@v5 diff --git a/README.md b/README.md index 43151fa..237a05b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A [pre-commit](https://pre-commit.com/) hook for [Ruff](https://github.com/astra Distributed as a standalone repository to enable installing Ruff via prebuilt wheels from [PyPI](https://pypi.org/project/ruff/). -### Using Ruff with pre-commit +## Using Ruff with pre-commit To run Ruff's [linter](https://docs.astral.sh/ruff/linter) and [formatter](https://docs.astral.sh/ruff/formatter) (available as of Ruff v0.0.289) via pre-commit, add the following to your `.pre-commit-config.yaml`: @@ -85,6 +85,6 @@ dually licensed as above, without any additional terms or conditions. diff --git a/mirror.py b/mirror.py index a1df4cd..a076903 100644 --- a/mirror.py +++ b/mirror.py @@ -7,19 +7,27 @@ # /// """Update ruff-pre-commit to the latest version of ruff.""" +from __future__ import annotations + import re import subprocess -import tomllib -import typing from pathlib import Path +from typing import TYPE_CHECKING, Any +import tomllib import urllib3 from packaging.requirements import Requirement from packaging.version import Version +if TYPE_CHECKING: + from collections.abc import Sequence -def main(): - with open(Path(__file__).parent / "pyproject.toml", "rb") as f: +ROOT_DIR = Path(__file__).parent + + +def main() -> None: + """Update ruff-pre-commit to the latest version of ruff.""" + with (ROOT_DIR / "pyproject.toml").open("rb") as f: pyproject = tomllib.load(f) all_versions = get_all_versions() @@ -37,6 +45,7 @@ def main(): def get_all_versions() -> list[Version]: + """Fetch all versions of ruff from pypi.""" response = urllib3.request("GET", "https://pypi.org/pypi/ruff/json") if response.status != 200: raise RuntimeError("Failed to fetch versions from pypi") @@ -45,7 +54,8 @@ def get_all_versions() -> list[Version]: return sorted(versions) -def get_current_version(pyproject: dict) -> Version: +def get_current_version(pyproject: dict[str, Any]) -> Version: + """Get the current version of ruff from pyproject.toml.""" requirements = [Requirement(d) for d in pyproject["project"]["dependencies"]] requirement = next((r for r in requirements if r.name == "ruff"), None) assert requirement is not None, "pyproject.toml does not have ruff requirement" @@ -58,7 +68,9 @@ def get_current_version(pyproject: dict) -> Version: return Version(specifiers[0].version) -def process_version(version: Version) -> typing.Sequence[str]: +def process_version(version: Version) -> Sequence[str]: + """Update the version of ruff in pyproject.toml and README.md.""" + def replace_pyproject_toml(content: str) -> str: return re.sub(r'"ruff==.*"', f'"ruff=={version}"', content) @@ -67,17 +79,16 @@ def replace_readme_md(content: str) -> str: return re.sub(r"/ruff/\d+\.\d+\.\d+\.svg", f"/ruff/{version}.svg", content) paths = { - "pyproject.toml": replace_pyproject_toml, - "README.md": replace_readme_md, + ROOT_DIR / "pyproject.toml": replace_pyproject_toml, + ROOT_DIR / "README.md": replace_readme_md, } for path, replacer in paths.items(): - with open(path) as f: - content = replacer(f.read()) - with open(path, mode="w") as f: - f.write(content) + content = path.read_text() + updated_content = replacer(content) + path.write_text(updated_content) - return tuple(paths.keys()) + return tuple([str(path) for path in paths]) if __name__ == "__main__": From b800664a10735faf85c4eb7d74faad858f9111a7 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 02:46:10 +0000 Subject: [PATCH 08/35] Attempt to fix action lint issues --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b214630..d754ffc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,10 +34,10 @@ jobs: UNPUSHED_COMMITS=$(git log origin/main..HEAD) if [ -z "$UNPUSHED_COMMITS" ]; then echo "No unpushed commits found." - echo "changes_exist=false" >> $GITHUB_ENV + echo "changes_exist=false" >> "$GITHUB_ENV" else echo "Unpushed commits found." - echo "changes_exist=true" >> $GITHUB_ENV + echo "changes_exist=true" >> "$GITHUB_ENV" fi - name: push changes if they exist @@ -49,8 +49,8 @@ jobs: - name: create release on new tag if new changes exist if: env.changes_exist == 'true' run: | - TAG_NAME=$(git describe --tags $(git rev-list --tags --max-count=1)) - echo $TAG_NAME + TAG_NAME="$(git describe --tags "$(git rev-list --tags --max-count=1)")" + echo "$TAG_NAME" gh release create "$TAG_NAME" \ --title "$TAG_NAME" \ --notes "See: https://github.com/astral-sh/ruff/releases/tag/${TAG_NAME/v}" \ From 382ec932f3a67d029a5eaefa8533a046940f5b56 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:09:30 +0000 Subject: [PATCH 09/35] Update readme with ruff docs formatter --- README.md | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/README.md b/README.md index 237a05b..2dbfd30 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ repos: - id: ruff # Run the formatter. - id: ruff-format + # Run the formatter on documentation files. + - id: ruff-format-docs ``` To enable lint fixes, add the `--fix` argument to the lint hook: @@ -41,6 +43,8 @@ repos: args: [ --fix ] # Run the formatter. - id: ruff-format + # Run the formatter on documentation files. + - id: ruff-format-docs ``` To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed filetypes: @@ -58,6 +62,8 @@ repos: # Run the formatter. - id: ruff-format types_or: [ python, pyi ] + # Run the formatter on documentation files. + - id: ruff-format-docs # ruff-format-docs does not support Jupyter Notebooks. ``` When running with `--fix`, Ruff's lint hook should be placed _before_ Ruff's formatter hook, and @@ -70,6 +76,189 @@ When running without `--fix`, Ruff's formatter hook can be placed before or afte `ruff format` should never introduce new lint errors, so it's safe to run Ruff's format hook _after_ `ruff check --fix`.) +## Docs Formatter + +The ruff docs formatter is a command line tool that rewrites documentation files in place. It is based on Blacken-docs however it uses Ruff's formatter instead of Black to format code blocks. It supports Markdown, reStructuredText, and LaTex files. Additionally, you can run it on Python files to reformat Markdown and reStructuredText within docstrings. + +The ruff docs formatter is primarily intended to be used as a pre-commit hook; however, it can also be run manually. + +The ruff docs formatter currently passes the following options through to ruff: + +- `--target-version` - The minimum Python version that should be supported. +- `--preview` - Enable preview mode; enables unstable formatting. Use `--no-preview` to disable. +- `--config`- Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`), or a TOML ` = ` pair (such as you might find in a `ruff.toml` configuration file) overriding a specific configuration option. Overrides of individual settings using this option always take precedence over all configuration files, including configuration files that were also specified using `--config`. Note, more than one `--config` option can be used at once. + +It also has the below extra options: + +- `--check` - Avoid writing any formatted code blocks back; instead, exit with a non-zero status code if any code blocks would have been modified, and zero otherwise. +- `--skip-errors` - Don't exit non-zero for errors from Ruff (normally syntax errors). +- `--rst-literal-blocks` - Also format literal blocks in reStructuredText files (more below). + +### Supported code block formats + +Ruff docs formatter formats code blocks matching the following patterns. + +#### Markdown + +In "python" blocks: + +````markdown +```python +def hello(): + print("hello world") +``` +```` + +"pycon" blocks: + +````markdown +```pycon + +>>> def hello(): +... print("hello world") +... + +``` +```` + +And pyi blocks: + +````markdown +```pyi +def hello() -> None: ... +``` +```` + +Prevent formatting within a block using `ruff-format-docs:off` and +`ruff-format-docs:on` comments: + +````markdown + + +```python +# whatever you want +``` + + +```` + +Within Python files, docstrings that contain Markdown code blocks may be +reformatted: + +````python +def f(): + """docstring here + + ```python + print("hello world") + ``` + """ +```` + +#### reStructuredText + +In "python" blocks: + +```rst +.. code-block:: python + + def hello(): + print("hello world") +``` + +In "pycon" blocks: + +```rst +.. code-block:: pycon + + >>> def hello(): + ... print("hello world") + ... +``` + +Prevent formatting within a block using `ruff-format-docs:off` and +`ruff-format-docs:on` comments: + +```rst +.. ruff-format-docs:off + +.. code-block:: python + + # whatever you want + +.. ruff-format-docs:on +``` + +Use `--rst-literal-blocks` to also format [literal +blocks](https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks): + +```rst +An example:: + + def hello(): + print("hello world") +``` + +Literal blocks are marked with `::` and can be any monospaced text by +default. However Sphinx interprets them as Python code [by +default](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-literal-blocks). +If your project uses Sphinx and such a configuration, add +`--rst-literal-blocks` to also format such blocks. + +Within Python files, docstrings that contain reStructuredText code +blocks may be reformatted: + +```python +def f(): + """docstring here + + .. code-block:: python + + print("hello world") + """ +``` + +#### LaTeX + +In minted "python" blocks: + +```latex +\begin{minted}{python} +def hello(): + print("hello world") +\end{minted} +``` + +In minted "pycon" blocks: + +```latex +\begin{minted}{pycon} +>>> def hello(): +... print("hello world") +... +\end{minted} +``` + +In PythonTeX blocks: + +```latex +\begin{pycode} +def hello(): + print("hello world") +\end{pycode} +``` + +Prevent formatting within a block using `ruff-format-docs:off` and +`ruff-format-docs:on` comments: + +```latex +% ruff-format-docs:off +\begin{minted}{python} +# whatever you want +\end{minted} +% ruff-format-docs:on +``` + ## License ruff-pre-commit is licensed under either of From d743a313bfc41cc9c07ea86c391628ae6370b316 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:30:52 +0000 Subject: [PATCH 10/35] Add hook entry point --- .pre-commit-hooks.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 491cc71..f249c21 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -19,3 +19,14 @@ require_serial: true additional_dependencies: [] minimum_pre_commit_version: "2.9.2" + +- id: ruff-format-docs + name: ruff-format-docs + description: Run the Ruff formatter on python code blocks in documentation files + entry: ruff-format-docs + language: python + types_or: [rst, markdown, python, tex] + args: [] + require_serial: true + additional_dependencies: [] + minimum_pre_commit_version: "2.9.2" From 772ee50cbe64b72bbf29c4a917019b0437ec1761 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:35:08 +0000 Subject: [PATCH 11/35] Add blacken docs --- src/ruff_format_docs/__init__.py | 360 ++++++++ src/ruff_format_docs/__main__.py | 6 + tests/__init__.py | 0 tests/test_ruff_format_docs.py | 1365 ++++++++++++++++++++++++++++++ 4 files changed, 1731 insertions(+) create mode 100644 src/ruff_format_docs/__init__.py create mode 100644 src/ruff_format_docs/__main__.py create mode 100644 tests/__init__.py create mode 100644 tests/test_ruff_format_docs.py diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py new file mode 100644 index 0000000..68c181f --- /dev/null +++ b/src/ruff_format_docs/__init__.py @@ -0,0 +1,360 @@ +from __future__ import annotations + +import argparse +import contextlib +import re +import textwrap +from bisect import bisect +from collections.abc import Generator, Sequence +from re import Match + +import black +from black.const import DEFAULT_LINE_LENGTH +from black.mode import TargetVersion + +PYGMENTS_PY_LANGS = frozenset(("python", "py", "sage", "python3", "py3", "numpy")) +PYGMENTS_PY_LANGS_RE_FRAGMENT = f"({'|'.join(PYGMENTS_PY_LANGS)})" +MD_RE = re.compile( + r"(?P^(?P *)```[^\S\r\n]*" + + PYGMENTS_PY_LANGS_RE_FRAGMENT + + r"( .*?)?\n)" + r"(?P.*?)" + r"(?P^(?P=indent)```[^\S\r\n]*$)", + re.DOTALL | re.MULTILINE, +) +MD_PYCON_RE = re.compile( + r"(?P^(?P *)```[^\S\r\n]*pycon( .*?)?\n)" + r"(?P.*?)" + r"(?P^(?P=indent)```[^\S\r\n]*$)", + re.DOTALL | re.MULTILINE, +) +BLOCK_TYPES = "(code|code-block|sourcecode|ipython)" +DOCTEST_TYPES = "(testsetup|testcleanup|testcode)" +RST_RE = re.compile( + rf"(?P" + rf"^(?P *)\.\. (" + rf"jupyter-execute::|" + rf"{BLOCK_TYPES}:: (?P\w+)|" + rf"{DOCTEST_TYPES}::.*" + rf")\n" + rf"((?P=indent) +:.*\n)*" + rf"( *\n)*" + rf")" + rf"(?P(^((?P=indent) +.*)?\n)+)", + re.MULTILINE, +) +RST_LITERAL_BLOCKS_RE = re.compile( + r"(?P" + r"^(?! *\.\. )(?P *).*::\n" + r"((?P=indent) +:.*\n)*" + r"\n*" + r")" + r"(?P(^((?P=indent) +.*)?\n)+)", + re.MULTILINE, +) +RST_PYCON_RE = re.compile( + r"(?P" + r"(?P *)\.\. ((code|code-block):: pycon|doctest::.*)\n" + r"((?P=indent) +:.*\n)*" + r"\n*" + r")" + r"(?P(^((?P=indent) +.*)?(\n|$))+)", + re.MULTILINE, +) +PYCON_PREFIX = ">>> " +PYCON_CONTINUATION_PREFIX = "..." +PYCON_CONTINUATION_RE = re.compile( + rf"^{re.escape(PYCON_CONTINUATION_PREFIX)}( |$)", +) +LATEX_RE = re.compile( + r"(?P^(?P *)\\begin{minted}(\[.*?\])?{python}\n)" + r"(?P.*?)" + r"(?P^(?P=indent)\\end{minted}\s*$)", + re.DOTALL | re.MULTILINE, +) +LATEX_PYCON_RE = re.compile( + r"(?P^(?P *)\\begin{minted}(\[.*?\])?{pycon}\n)" + r"(?P.*?)" + r"(?P^(?P=indent)\\end{minted}\s*$)", + re.DOTALL | re.MULTILINE, +) +PYTHONTEX_LANG = r"(?Ppyblock|pycode|pyconsole|pyverbatim)" +PYTHONTEX_RE = re.compile( + rf"(?P^(?P *)\\begin{{{PYTHONTEX_LANG}}}\n)" + rf"(?P.*?)" + rf"(?P^(?P=indent)\\end{{(?P=lang)}}\s*$)", + re.DOTALL | re.MULTILINE, +) +INDENT_RE = re.compile("^ +(?=[^ ])", re.MULTILINE) +TRAILING_NL_RE = re.compile(r"\n+\Z", re.MULTILINE) +ON_OFF = r"blacken-docs:(on|off)" +ON_OFF_COMMENT_RE = re.compile( + # Markdown + rf"(?:^\s*$)|" + # rST + rf"(?:^\s*\.\. +{ON_OFF}$)|" + # LaTeX + rf"(?:^\s*% {ON_OFF}$)", + re.MULTILINE, +) + + +class CodeBlockError: + def __init__(self, offset: int, exc: Exception) -> None: + self.offset = offset + self.exc = exc + + +def format_str( + src: str, + black_mode: black.FileMode, + *, + rst_literal_blocks: bool = False, +) -> tuple[str, Sequence[CodeBlockError]]: + errors: list[CodeBlockError] = [] + + off_ranges = [] + off_start = None + for comment in re.finditer(ON_OFF_COMMENT_RE, src): + # Check for the "off" value across the multiple (on|off) groups. + if "off" in comment.groups(): + if off_start is None: + off_start = comment.start() + elif off_start is not None: + off_ranges.append((off_start, comment.end())) + off_start = None + if off_start is not None: + off_ranges.append((off_start, len(src))) + + def _within_off_range(code_range: tuple[int, int]) -> bool: + index = bisect(off_ranges, code_range) + try: + off_start, off_end = off_ranges[index - 1] + except IndexError: + return False + code_start, code_end = code_range + return code_start >= off_start and code_end <= off_end + + @contextlib.contextmanager + def _collect_error(match: Match[str]) -> Generator[None]: + try: + yield + except Exception as e: + errors.append(CodeBlockError(match.start(), e)) + + def _md_match(match: Match[str]) -> str: + if _within_off_range(match.span()): + return match[0] + code = textwrap.dedent(match["code"]) + with _collect_error(match): + code = black.format_str(code, mode=black_mode) + code = textwrap.indent(code, match["indent"]) + return f'{match["before"]}{code}{match["after"]}' + + def _rst_match(match: Match[str]) -> str: + if _within_off_range(match.span()): + return match[0] + lang = match["lang"] + if lang is not None and lang not in PYGMENTS_PY_LANGS: + return match[0] + if not match["code"].strip(): + return match[0] + min_indent = min(INDENT_RE.findall(match["code"])) + trailing_ws_match = TRAILING_NL_RE.search(match["code"]) + assert trailing_ws_match + trailing_ws = trailing_ws_match.group() + code = textwrap.dedent(match["code"]) + with _collect_error(match): + code = black.format_str(code, mode=black_mode) + code = textwrap.indent(code, min_indent) + return f'{match["before"]}{code.rstrip()}{trailing_ws}' + + def _rst_literal_blocks_match(match: Match[str]) -> str: + if _within_off_range(match.span()): + return match[0] + if not match["code"].strip(): + return match[0] + min_indent = min(INDENT_RE.findall(match["code"])) + trailing_ws_match = TRAILING_NL_RE.search(match["code"]) + assert trailing_ws_match + trailing_ws = trailing_ws_match.group() + code = textwrap.dedent(match["code"]) + with _collect_error(match): + code = black.format_str(code, mode=black_mode) + code = textwrap.indent(code, min_indent) + return f'{match["before"]}{code.rstrip()}{trailing_ws}' + + def _pycon_match(match: Match[str]) -> str: + code = "" + fragment: str | None = None + + def finish_fragment() -> None: + nonlocal code + nonlocal fragment + + if fragment is not None: + with _collect_error(match): + fragment = black.format_str(fragment, mode=black_mode) + fragment_lines = fragment.splitlines() + code += f"{PYCON_PREFIX}{fragment_lines[0]}\n" + for line in fragment_lines[1:]: + # Skip blank lines to handle Black adding a blank above + # functions within blocks. A blank line would end the REPL + # continuation prompt. + # + # >>> if True: + # ... def f(): + # ... pass + # ... + if line: + code += f"{PYCON_CONTINUATION_PREFIX} {line}\n" + if fragment_lines[-1].startswith(" "): + code += f"{PYCON_CONTINUATION_PREFIX}\n" + fragment = None + + indentation: int | None = None + for line in match["code"].splitlines(): + orig_line, line = line, line.lstrip() + if indentation is None and line: + indentation = len(orig_line) - len(line) + continuation_match = PYCON_CONTINUATION_RE.match(line) + if continuation_match and fragment is not None: + fragment += line[continuation_match.end() :] + "\n" + else: + finish_fragment() + if line.startswith(PYCON_PREFIX): + fragment = line[len(PYCON_PREFIX) :] + "\n" + else: + code += orig_line[indentation:] + "\n" + finish_fragment() + return code + + def _md_pycon_match(match: Match[str]) -> str: + if _within_off_range(match.span()): + return match[0] + code = _pycon_match(match) + code = textwrap.indent(code, match["indent"]) + return f'{match["before"]}{code}{match["after"]}' + + def _rst_pycon_match(match: Match[str]) -> str: + if _within_off_range(match.span()): + return match[0] + code = _pycon_match(match) + if not code.strip(): + return match[0] + min_indent = min(INDENT_RE.findall(match["code"])) + code = textwrap.indent(code, min_indent) + return f'{match["before"]}{code}' + + def _latex_match(match: Match[str]) -> str: + if _within_off_range(match.span()): + return match[0] + code = textwrap.dedent(match["code"]) + with _collect_error(match): + code = black.format_str(code, mode=black_mode) + code = textwrap.indent(code, match["indent"]) + return f'{match["before"]}{code}{match["after"]}' + + def _latex_pycon_match(match: Match[str]) -> str: + if _within_off_range(match.span()): + return match[0] + code = _pycon_match(match) + code = textwrap.indent(code, match["indent"]) + return f'{match["before"]}{code}{match["after"]}' + + src = MD_RE.sub(_md_match, src) + src = MD_PYCON_RE.sub(_md_pycon_match, src) + src = RST_RE.sub(_rst_match, src) + src = RST_PYCON_RE.sub(_rst_pycon_match, src) + if rst_literal_blocks: + src = RST_LITERAL_BLOCKS_RE.sub( + _rst_literal_blocks_match, + src, + ) + src = LATEX_RE.sub(_latex_match, src) + src = LATEX_PYCON_RE.sub(_latex_pycon_match, src) + src = PYTHONTEX_RE.sub(_latex_match, src) + return src, errors + + +def format_file( + filename: str, + black_mode: black.FileMode, + skip_errors: bool, + rst_literal_blocks: bool, + check_only: bool, +) -> int: + with open(filename, encoding="UTF-8") as f: + contents = f.read() + new_contents, errors = format_str( + contents, + black_mode, + rst_literal_blocks=rst_literal_blocks, + ) + for error in errors: + lineno = contents[: error.offset].count("\n") + 1 + print(f"{filename}:{lineno}: code block parse error {error.exc}") + if errors and not skip_errors: + return 2 + if contents == new_contents: + return 0 + if check_only: + print(f"{filename}: Requires a rewrite.") + return 1 + print(f"{filename}: Rewriting...") + with open(filename, "w", encoding="UTF-8") as f: + f.write(new_contents) + return 1 + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "-l", + "--line-length", + type=int, + default=DEFAULT_LINE_LENGTH, + ) + parser.add_argument("--preview", action="store_true") + parser.add_argument( + "-S", + "--skip-string-normalization", + action="store_true", + ) + parser.add_argument( + "-t", + "--target-version", + action="append", + type=lambda v: TargetVersion[v.upper()], + default=[], + help=f"choices: {[v.name.lower() for v in TargetVersion]}", + dest="target_versions", + ) + parser.add_argument("--check", action="store_true") + parser.add_argument("-E", "--skip-errors", action="store_true") + parser.add_argument( + "--rst-literal-blocks", + action="store_true", + ) + parser.add_argument("--pyi", action="store_true") + parser.add_argument("filenames", nargs="*") + args = parser.parse_args(argv) + + black_mode = black.Mode( + target_versions=set(args.target_versions), + line_length=args.line_length, + string_normalization=not args.skip_string_normalization, + is_pyi=args.pyi, + preview=args.preview, + ) + + retv = 0 + for filename in args.filenames: + retv |= format_file( + filename, + black_mode, + skip_errors=args.skip_errors, + rst_literal_blocks=args.rst_literal_blocks, + check_only=args.check, + ) + return retv diff --git a/src/ruff_format_docs/__main__.py b/src/ruff_format_docs/__main__.py new file mode 100644 index 0000000..20be931 --- /dev/null +++ b/src/ruff_format_docs/__main__.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from ruff_format_docs import main + +if __name__ == "__main__": # pragma: no cover + raise SystemExit(main()) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py new file mode 100644 index 0000000..0246481 --- /dev/null +++ b/tests/test_ruff_format_docs.py @@ -0,0 +1,1365 @@ +from __future__ import annotations + +from textwrap import dedent + +import black +from black.const import DEFAULT_LINE_LENGTH + +from src import ruff_format_docs + +BLACK_MODE = black.FileMode(line_length=DEFAULT_LINE_LENGTH) + + +def test_format_src_trivial(): + after, _ = ruff_format_docs.format_str("", BLACK_MODE) + assert after == "" + + +def test_format_src_markdown_simple(): + before = dedent( + """\ + ```python + f(1,2,3) + ``` + """, + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + """\ + ```python + f(1, 2, 3) + ``` + """, + ) + + +def test_format_src_markdown_leading_whitespace(): + before = dedent( + """\ + ``` python + f(1,2,3) + ``` + """, + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + """\ + ``` python + f(1, 2, 3) + ``` + """, + ) + + +def test_format_src_markdown_python_after_newline(): + before = dedent( + """\ + ``` + python --version + echo "python" + ``` + """, + ) + after, errors = ruff_format_docs.format_str(before, BLACK_MODE) + assert errors == [] + assert after == before + + +def test_format_src_markdown_short_name(): + before = dedent( + """\ + ``` py + f(1,2,3) + ``` + """, + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + """\ + ``` py + f(1, 2, 3) + ``` + """, + ) + + +def test_format_src_markdown_options(): + before = dedent( + """\ + ```python title='example.py' + f(1,2,3) + ``` + """, + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + """\ + ```python title='example.py' + f(1, 2, 3) + ``` + """, + ) + + +def test_format_src_markdown_trailing_whitespace(): + before = dedent( + """\ + ```python + f(1,2,3) + ``` \n""", + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + """\ + ```python + f(1, 2, 3) + ``` \n""", + ) + + +def test_format_src_indented_markdown(): + before = dedent( + """\ + - do this pls: + ```python + f(1,2,3) + ``` + - also this + """, + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + """\ + - do this pls: + ```python + f(1, 2, 3) + ``` + - also this + """, + ) + + +def test_format_src_markdown_pycon(): + before = ( + "hello\n" + "\n" + "```pycon\n" + "\n" + " >>> f(1,2,3)\n" + " output\n" + "```\n" + "world\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ("hello\n\n```pycon\n\n>>> f(1, 2, 3)\noutput\n```\nworld\n") + + +def test_format_src_markdown_pycon_after_newline(): + before = dedent( + """\ + ``` + pycon is great + >>> yes it is + ``` + """, + ) + after, errors = ruff_format_docs.format_str(before, BLACK_MODE) + assert errors == [] + assert after == before + + +def test_format_src_markdown_pycon_options(): + before = ( + "hello\n" + "\n" + "```pycon title='Session 1'\n" + "\n" + " >>> f(1,2,3)\n" + " output\n" + "```\n" + "world\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "hello\n" + "\n" + "```pycon title='Session 1'\n" + "\n" + ">>> f(1, 2, 3)\n" + "output\n" + "```\n" + "world\n" + ) + + +def test_format_src_markdown_pycon_twice(): + before = ( + "```pycon\n" + ">>> f(1,2,3)\n" + "output\n" + "```\n" + "example 2\n" + "```pycon\n" + ">>> f(1,2,3)\n" + "output\n" + "```\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "```pycon\n" + ">>> f(1, 2, 3)\n" + "output\n" + "```\n" + "example 2\n" + "```pycon\n" + ">>> f(1, 2, 3)\n" + "output\n" + "```\n" + ) + + +def test_format_src_markdown_comments_disable(): + before = ( + "\n" + "```python\n" + "'single quotes rock'\n" + "```\n" + "\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_markdown_comments_disabled_enabled(): + before = ( + "\n" + "```python\n" + "'single quotes rock'\n" + "```\n" + "\n" + "```python\n" + "'double quotes rock'\n" + "```\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "\n" + "```python\n" + "'single quotes rock'\n" + "```\n" + "\n" + "```python\n" + '"double quotes rock"\n' + "```\n" + ) + + +def test_format_src_markdown_comments_before(): + before = ( + "\n" + "\n" + "```python\n" + "'double quotes rock'\n" + "```\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "\n" + "\n" + "```python\n" + '"double quotes rock"\n' + "```\n" + ) + + +def test_format_src_markdown_comments_after(): + before = ( + "```python\n" + "'double quotes rock'\n" + "```\n" + "\n" + "\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "```python\n" + '"double quotes rock"\n' + "```\n" + "\n" + "\n" + ) + + +def test_format_src_markdown_comments_only_on(): + # fmt: off + before = ( + "\n" + "```python\n" + "'double quotes rock'\n" + "```\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "\n" + "```python\n" + '"double quotes rock"\n' + "```\n" + ) + # fmt: on + + +def test_format_src_markdown_comments_only_off(): + # fmt: off + before = ( + "\n" + "```python\n" + "'single quotes rock'\n" + "```\n" + ) + # fmt: on + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_markdown_comments_multiple(): + before = ( + "\n" # ignored + "\n" + "\n" + "\n" # ignored + "\n" + "\n" # ignored + "```python\n" + "'single quotes rock'\n" + "```\n" # no on comment, off until the end + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_on_off_comments_in_code_blocks(): + before = ( + "````md\n" + "\n" + "```python\n" + "f(1,2,3)\n" + "```\n" + "\n" + "````\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_markdown_comments_disable_pycon(): + before = ( + "\n" + "```pycon\n" + ">>> 'single quotes rock'\n" + "```\n" + "\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_latex_minted(): + before = "hello\n\\begin{minted}{python}\nf(1,2,3)\n\\end{minted}\nworld!" + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "hello\n\\begin{minted}{python}\nf(1, 2, 3)\n\\end{minted}\nworld!" + ) + + +def test_format_src_latex_minted_opt(): + before = ( + "maths!\n" + "\\begin{minted}[mathescape]{python}\n" + "# Returns $\\sum_{i=1}^{n}i$\n" + "def sum_from_one_to(n):\n" + " r = range(1, n+1)\n" + " return sum(r)\n" + "\\end{minted}\n" + "done" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "maths!\n" + "\\begin{minted}[mathescape]{python}\n" + "# Returns $\\sum_{i=1}^{n}i$\n" + "def sum_from_one_to(n):\n" + " r = range(1, n + 1)\n" + " return sum(r)\n" + "\\end{minted}\n" + "done" + ) + + +def test_format_src_latex_minted_indented(): + # Personally I would have minted python code all flush left, + # with only the Python code's own four space indentation: + before = dedent( + """\ + hello + \\begin{minted}{python} + if True: + f(1,2,3) + \\end{minted} + world! + """, + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + """\ + hello + \\begin{minted}{python} + if True: + f(1, 2, 3) + \\end{minted} + world! + """, + ) + + +def test_format_src_latex_minted_pycon(): + before = ( + "Preceding text\n" + "\\begin{minted}[gobble=2,showspaces]{pycon}\n" + ">>> print( 'Hello World' )\n" + "Hello World\n" + "\\end{minted}\n" + "Following text." + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "Preceding text\n" + "\\begin{minted}[gobble=2,showspaces]{pycon}\n" + '>>> print("Hello World")\n' + "Hello World\n" + "\\end{minted}\n" + "Following text." + ) + + +def test_format_src_latex_minted_pycon_indented(): + # Nicer style to put the \begin and \end on new lines, + # but not actually required for the begin line + before = ( + "Preceding text\n" + " \\begin{minted}{pycon}\n" + " >>> print( 'Hello World' )\n" + " Hello World\n" + " \\end{minted}\n" + "Following text." + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "Preceding text\n" + " \\begin{minted}{pycon}\n" + ' >>> print("Hello World")\n' + " Hello World\n" + " \\end{minted}\n" + "Following text." + ) + + +def test_format_src_latex_minted_comments_off(): + before = ( + "% blacken-docs:off\n" + "\\begin{minted}{python}\n" + "'single quotes rock'\n" + "\\end{minted}\n" + "% blacken-docs:on\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_latex_minted_comments_off_pycon(): + before = ( + "% blacken-docs:off\n" + "\\begin{minted}{pycon}\n" + ">>> 'single quotes rock'\n" + "\\end{minted}\n" + "% blacken-docs:on\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_pythontex(): + # fmt: off + before = ( + "hello\n" + "\\begin{pyblock}\n" + "f(1,2,3)\n" + "\\end{pyblock}\n" + "world!" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "hello\n" + "\\begin{pyblock}\n" + "f(1, 2, 3)\n" + "\\end{pyblock}\n" + "world!" + ) + # fmt: on + + +def test_format_src_pythontex_comments_off(): + before = ( + "% blacken-docs:off\n" + "\\begin{pyblock}\n" + "f(1,2,3)\n" + "\\end{pyblock}\n" + "% blacken-docs:on\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_rst(): + before = "hello\n\n.. code-block:: python\n\n f(1,2,3)\n\nworld\n" + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ("hello\n\n.. code-block:: python\n\n f(1, 2, 3)\n\nworld\n") + + +def test_format_src_rst_empty(): + before = "some text\n\n.. code-block:: python\n\n\nsome other text\n" + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_rst_literal_blocks(): + before = dedent( + """\ + hello:: + + f(1,2,3) + + world + """, + ) + after, _ = ruff_format_docs.format_str( + before, + BLACK_MODE, + rst_literal_blocks=True, + ) + assert after == dedent( + """\ + hello:: + + f(1, 2, 3) + + world + """, + ) + + +def test_format_src_rst_literal_block_empty(): + before = dedent( + """\ + hello:: + world + """, + ) + after, _ = ruff_format_docs.format_str( + before, + BLACK_MODE, + rst_literal_blocks=True, + ) + assert after == before + + +def test_format_src_rst_literal_blocks_nested(): + before = dedent( + """ + * hello + + .. warning:: + + don't hello too much + """, + ) + after, errors = ruff_format_docs.format_str( + before, + BLACK_MODE, + rst_literal_blocks=True, + ) + assert after == before + assert errors == [] + + +def test_format_src_rst_literal_blocks_empty(): + before = dedent( + """ + Example:: + + .. warning:: + + There was no example. + """, + ) + after, errors = ruff_format_docs.format_str( + before, + BLACK_MODE, + rst_literal_blocks=True, + ) + assert after == before + assert errors == [] + + +def test_format_src_rst_literal_blocks_comments(): + before = ( + ".. blacken-docs:off\n" + "Example::\n" + "\n" + " 'single quotes rock'\n" + "\n" + ".. blacken-docs:on\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE, rst_literal_blocks=True) + assert after == before + + +def test_format_src_rst_sphinx_doctest(): + before = ( + ".. testsetup:: group1\n" + "\n" + " import parrot \n" + " mock = SomeMock( )\n" + "\n" + ".. testcleanup:: group1\n" + "\n" + " mock.stop( )\n" + "\n" + ".. doctest:: group1\n" + "\n" + " >>> parrot.voom( 3000 )\n" + " This parrot wouldn't voom if you put 3000 volts through it!\n" + "\n" + ".. testcode::\n" + "\n" + " parrot.voom( 3000 )\n" + "\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. testsetup:: group1\n" + "\n" + " import parrot\n" + "\n" + " mock = SomeMock()\n" + "\n" + ".. testcleanup:: group1\n" + "\n" + " mock.stop()\n" + "\n" + ".. doctest:: group1\n" + "\n" + " >>> parrot.voom(3000)\n" + " This parrot wouldn't voom if you put 3000 volts through it!\n" + "\n" + ".. testcode::\n" + "\n" + " parrot.voom(3000)\n" + "\n" + ) + + +def test_format_src_rst_indented(): + before = dedent( + """\ + .. versionadded:: 3.1 + + hello + + .. code-block:: python + + def hi(): + f(1,2,3) + + world + """, + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + """\ + .. versionadded:: 3.1 + + hello + + .. code-block:: python + + def hi(): + f(1, 2, 3) + + world + """, + ) + + +def test_format_src_rst_code_block_indent(): + before = "\n".join( + [ + ".. code-block:: python", + " ", + " f(1,2,3)\n", + ], + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == "\n".join( + [ + ".. code-block:: python", + " ", + " f(1, 2, 3)\n", + ], + ) + + +def test_format_src_rst_with_highlight_directives(): + before = ( + ".. code-block:: python\n" + " :lineno-start: 10\n" + " :emphasize-lines: 11\n" + "\n" + " def foo():\n" + " bar(1,2,3)\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. code-block:: python\n" + " :lineno-start: 10\n" + " :emphasize-lines: 11\n" + "\n" + " def foo():\n" + " bar(1, 2, 3)\n" + ) + + +def test_format_src_rst_python_inside_non_python_code_block(): + before = ( + "blacken-docs does changes like:\n" + "\n" + ".. code-block:: diff\n" + "\n" + " .. code-block:: python\n" + "\n" + " - 'Hello World'\n" + ' + "Hello World"\n' + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_rst_python_comments(): + before = ( + ".. blacken-docs:off\n" + ".. code-block:: python\n" + "\n" + " 'single quotes rock'\n" + "\n" + ".. blacken-docs:on\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_integration_ok(tmp_path, capsys): + f = tmp_path / "f.md" + f.write_text( + "```python\nf(1, 2, 3)\n```\n", + ) + + result = ruff_format_docs.main((str(f),)) + + assert result == 0 + assert not capsys.readouterr()[1] + assert f.read_text() == ("```python\nf(1, 2, 3)\n```\n") + + +def test_integration_modifies(tmp_path, capsys): + f = tmp_path / "f.md" + f.write_text( + "```python\nf(1,2,3)\n```\n", + ) + + result = ruff_format_docs.main((str(f),)) + + assert result == 1 + out, _ = capsys.readouterr() + assert out == f"{f}: Rewriting...\n" + assert f.read_text() == ("```python\nf(1, 2, 3)\n```\n") + + +def test_integration_line_length(tmp_path): + f = tmp_path / "f.md" + f.write_text( + "```python\n" + "foo(very_very_very_very_very_very_very, long_long_long_long_long)\n" + "```\n", + ) + + result = ruff_format_docs.main((str(f), "--line-length=80")) + assert result == 0 + + result2 = ruff_format_docs.main((str(f), "--line-length=50")) + assert result2 == 1 + assert f.read_text() == ( + "```python\n" + "foo(\n" + " very_very_very_very_very_very_very,\n" + " long_long_long_long_long,\n" + ")\n" + "```\n" + ) + + +def test_integration_check(tmp_path): + f = tmp_path / "f.md" + text = dedent( + """\ + ```python + x = 'a' 'b' + ``` + """, + ) + f.write_text(text) + + result = ruff_format_docs.main((str(f), "--check")) + + assert result == 1 + assert f.read_text() == text + + +def test_integration_preview(tmp_path): + f = tmp_path / "f.md" + f.write_text( + dedent( + """\ + ```python + x = 'a' 'b' + ``` + """, + ), + ) + + result = ruff_format_docs.main((str(f), "--preview")) + + assert result == 1 + assert f.read_text() == dedent( + """\ + ```python + x = "a" "b" + ``` + """, + ) + + +def test_integration_pyi(tmp_path): + f = tmp_path / "f.md" + f.write_text( + dedent( + """\ + ```python + class Foo: ... + + + class Bar: ... + ``` + """, + ), + ) + + result = ruff_format_docs.main((str(f), "--pyi")) + + assert result == 1 + assert f.read_text() == dedent( + """\ + ```python + class Foo: ... + class Bar: ... + ``` + """, + ) + + +def test_integration_py36(tmp_path): + f = tmp_path / "f.md" + f.write_text( + "```python\n" + "def very_very_long_function_name(\n" + " very_very_very_very_very_very,\n" + " very_very_very_very_very_very,\n" + " *long_long_long_long_long_long\n" + "):\n" + " pass\n" + "```\n", + ) + + result = ruff_format_docs.main((str(f),)) + assert result == 0 + + result2 = ruff_format_docs.main((str(f), "--target-version=py36")) + + assert result2 == 1 + assert f.read_text() == ( + "```python\n" + "def very_very_long_function_name(\n" + " very_very_very_very_very_very,\n" + " very_very_very_very_very_very,\n" + " *long_long_long_long_long_long,\n" + "):\n" + " pass\n" + "```\n" + ) + + +def test_integration_filename_last(tmp_path): + f = tmp_path / "f.md" + f.write_text( + "```python\n" + "def very_very_long_function_name(\n" + " very_very_very_very_very_very,\n" + " very_very_very_very_very_very,\n" + " *long_long_long_long_long_long\n" + "):\n" + " pass\n" + "```\n", + ) + + result = ruff_format_docs.main((str(f),)) + assert result == 0 + + result2 = ruff_format_docs.main(("--target-version", "py36", str(f))) + + assert result2 == 1 + assert f.read_text() == ( + "```python\n" + "def very_very_long_function_name(\n" + " very_very_very_very_very_very,\n" + " very_very_very_very_very_very,\n" + " *long_long_long_long_long_long,\n" + "):\n" + " pass\n" + "```\n" + ) + + +def test_integration_multiple_target_version(tmp_path): + f = tmp_path / "f.md" + f.write_text( + "```python\n" + "def very_very_long_function_name(\n" + " very_very_very_very_very_very,\n" + " very_very_very_very_very_very,\n" + " *long_long_long_long_long_long\n" + "):\n" + " pass\n" + "```\n", + ) + + result = ruff_format_docs.main((str(f),)) + assert result == 0 + + result2 = ruff_format_docs.main( + ("--target-version", "py35", "--target-version", "py36", str(f)), + ) + assert result2 == 0 + + +def test_integration_skip_string_normalization(tmp_path): + f = tmp_path / "f.md" + f.write_text( + "```python\nf('hi')\n```\n", + ) + + result = ruff_format_docs.main((str(f), "--skip-string-normalization")) + + assert result == 0 + assert f.read_text() == ("```python\nf('hi')\n```\n") + + +def test_integration_syntax_error(tmp_path, capsys): + f = tmp_path / "f.md" + f.write_text( + "```python\nf(\n```\n", + ) + + result = ruff_format_docs.main((str(f),)) + + assert result == 2 + out, _ = capsys.readouterr() + assert out.startswith(f"{f}:1: code block parse error") + assert f.read_text() == ("```python\nf(\n```\n") + + +def test_integration_ignored_syntax_error(tmp_path, capsys): + f = tmp_path / "f.md" + f.write_text( + "```python\nf( )\n```\n\n```python\nf(\n```\n", + ) + + result = ruff_format_docs.main((str(f), "--skip-errors")) + + assert result == 1 + out, _ = capsys.readouterr() + assert f.read_text() == ("```python\nf()\n```\n\n```python\nf(\n```\n") + + +def test_format_src_rst_jupyter_sphinx(): + before = "hello\n\n.. jupyter-execute::\n\n f(1,2,3)\n\nworld\n" + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ("hello\n\n.. jupyter-execute::\n\n f(1, 2, 3)\n\nworld\n") + + +def test_format_src_rst_jupyter_sphinx_with_directive(): + before = ( + "hello\n" + "\n" + ".. jupyter-execute::\n" + " :hide-code:\n" + "\n" + " f(1,2,3)\n" + "\n" + "world\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "hello\n" + "\n" + ".. jupyter-execute::\n" + " :hide-code:\n" + "\n" + " f(1, 2, 3)\n" + "\n" + "world\n" + ) + + +def test_format_src_python_docstring_markdown(): + before = dedent( + '''\ + def f(): + """ + hello world + + ```python + f(1,2,3) + ``` + """ + pass + ''', + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + '''\ + def f(): + """ + hello world + + ```python + f(1, 2, 3) + ``` + """ + pass + ''', + ) + + +def test_format_src_python_docstring_rst(): + before = dedent( + '''\ + def f(): + """ + hello world + + .. code-block:: python + + f(1,2,3) + """ + pass + ''', + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == dedent( + '''\ + def f(): + """ + hello world + + .. code-block:: python + + f(1, 2, 3) + """ + pass + ''', + ) + + +def test_format_src_rst_pycon(): + before = ( + "hello\n" + "\n" + ".. code-block:: pycon\n" + "\n" + " >>> f(1,2,3)\n" + " output\n" + "\n" + "world\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + "hello\n" + "\n" + ".. code-block:: pycon\n" + "\n" + " >>> f(1, 2, 3)\n" + " output\n" + "\n" + "world\n" + ) + + +def test_format_src_rst_pycon_with_continuation(): + before = ( + ".. code-block:: pycon\n" + "\n" + " >>> d = {\n" + ' ... "a": 1,\n' + ' ... "b": 2,\n' + ' ... "c": 3,}\n' + "\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. code-block:: pycon\n" + "\n" + " >>> d = {\n" + ' ... "a": 1,\n' + ' ... "b": 2,\n' + ' ... "c": 3,\n' + " ... }\n" + "\n" + ) + + +def test_format_src_rst_pycon_adds_continuation(): + before = ".. code-block:: pycon\n\n" ' >>> d = {"a": 1,"b": 2,"c": 3,}\n' "\n" + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. code-block:: pycon\n" + "\n" + " >>> d = {\n" + ' ... "a": 1,\n' + ' ... "b": 2,\n' + ' ... "c": 3,\n' + " ... }\n" + "\n" + ) + + +def test_format_src_rst_pycon_preserves_trailing_whitespace(): + before = ( + "hello\n" + "\n" + ".. code-block:: pycon\n" + "\n" + ' >>> d = {"a": 1, "b": 2, "c": 3}\n' + "\n" + "\n" + "\n" + "world\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_rst_pycon_indented(): + before = ( + ".. versionadded:: 3.1\n" + "\n" + " hello\n" + "\n" + " .. code-block:: pycon\n" + "\n" + " >>> def hi():\n" + " ... f(1,2,3)\n" + " ...\n" + "\n" + " world\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. versionadded:: 3.1\n" + "\n" + " hello\n" + "\n" + " .. code-block:: pycon\n" + "\n" + " >>> def hi():\n" + " ... f(1, 2, 3)\n" + " ...\n" + "\n" + " world\n" + ) + + +def test_format_src_rst_pycon_code_block_is_final_line1(): + before = ( + ".. code-block:: pycon\n" + "\n" + " >>> if True:\n" + " ... pass\n" + " ...\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. code-block:: pycon\n" + "\n" + " >>> if True:\n" + " ... pass\n" + " ...\n" + ) + + +def test_format_src_rst_pycon_code_block_is_final_line2(): + before = ".. code-block:: pycon\n\n >>> if True:\n ... pass\n" + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. code-block:: pycon\n" + "\n" + " >>> if True:\n" + " ... pass\n" + " ...\n" + ) + + +def test_format_src_rst_pycon_nested_def1(): + before = ( + ".. code-block:: pycon\n" + "\n" + " >>> if True:\n" + " ... def f(): pass\n" + " ...\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. code-block:: pycon\n" + "\n" + " >>> if True:\n" + " ... def f():\n" + " ... pass\n" + " ...\n" + ) + + +def test_format_src_rst_pycon_nested_def2(): + before = ( + ".. code-block:: pycon\n" + "\n" + " >>> if True:\n" + " ... def f(): pass\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. code-block:: pycon\n" + "\n" + " >>> if True:\n" + " ... def f():\n" + " ... pass\n" + " ...\n" + ) + + +def test_format_src_rst_pycon_empty_line(): + before = ( + ".. code-block:: pycon\n" + "\n" + " >>> l = [\n" + " ...\n" + " ... 1,\n" + " ... ]\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. code-block:: pycon\n" + "\n" + " >>> l = [\n" + " ... 1,\n" + " ... ]\n" + ) + + +def test_format_src_rst_pycon_preserves_output_indentation(): + before = ( + ".. code-block:: pycon\n" + "\n" + " >>> 1 / 0\n" + " Traceback (most recent call last):\n" + ' File "", line 1, in \n' + " ZeroDivisionError: division by zero\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_rst_pycon_elided_traceback(): + before = ( + ".. code-block:: pycon\n" + "\n" + " >>> 1 / 0\n" + " Traceback (most recent call last):\n" + " ...\n" + " ZeroDivisionError: division by zero\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_rst_pycon_no_prompt(): + before = ".. code-block:: pycon\n\n pass\n" + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_rst_pycon_no_trailing_newline(): + before = ".. code-block:: pycon\n\n >>> pass" + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == (".. code-block:: pycon\n\n >>> pass\n") + + +def test_format_src_rst_pycon_comment_before_promopt(): + before = ( + ".. code-block:: pycon\n" + "\n" + " # Comment about next line\n" + " >>> pass\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == ( + ".. code-block:: pycon\n" + "\n" + " # Comment about next line\n" + " >>> pass\n" + ) + + +def test_format_src_rst_pycon_comments(): + before = ( + ".. blacken-docs:off\n" + ".. code-block:: pycon\n" + "\n" + " >>> 'single quotes rock'\n" + "\n" + ".. blacken-docs:on\n" + ) + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before + + +def test_format_src_rst_pycon_empty(): + before = "some text\n\n.. code-block:: pycon\n\n\nsome other text\n" + after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + assert after == before From 3957c18f7e4b99d678fb6e1f16e16833b92fb2f0 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:35:44 +0000 Subject: [PATCH 12/35] Ignore tests from blacken docs --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0502af1..9f2f904 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,6 +40,7 @@ repos: args: ["--pyi", "--line-length", "130"] additional_dependencies: - black==24.10.0 + exclude: tests/test_ruff_format_docs.py - repo: https://github.com/crate-ci/typos rev: v1.28.4 From ae6f442846b46b7c45c801b55a17ecf0e97320df Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:40:14 +0000 Subject: [PATCH 13/35] Add build config --- pyproject.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 0519dcf..20b30f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,16 @@ +[build-system] +build-backend = "setuptools.build_meta" +requires = [ + "setuptools", +] + [project] name = "ruff-pre-commit" version = "0.0.0" +description = "Run the Ruff formatter on Python code blocks in documentation files." +readme = "README.md" requires-python = ">=3.9" +scripts.ruff-format-docs = "ruff_format_docs:main" dependencies = [ "ruff==0.8.4", ] From 9d17f40243d34887b5d99132236fef8425284b22 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:42:49 +0000 Subject: [PATCH 14/35] Add pytest config --- .gitignore | 1 + pyproject.toml | 22 ++++++++++ uv.lock | 107 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 309de57..1df008c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__ *.pyc .venv +*.egg-info/ diff --git a/pyproject.toml b/pyproject.toml index 20b30f6..1fb4a39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,22 @@ dependencies = [ lint = [ "pre-commit==4.0.1", ] +test = [ + "pytest==8.3.4", +] + +[tool.pytest.ini_options] +addopts = [ + "--no-cov-on-fail", # Do not report coverage if tests fail. + "--strict-config", # Any warnings encountered while parsing the `pytest` section of the configuration file raise errors. + "--strict-markers", # Require markers to be registered in the `markers` section below +] +filterwarnings = [ + "error", # Convert warnings to errors +] +python_files = "test_*.py" +testpaths = ["tests"] +xfail_strict = true [tool.mypy] mypy_path = "$MYPY_CONFIG_FILE_DIR/src" @@ -46,6 +62,7 @@ select = [ "ISC", # flake8-implicit-str-concat "INP", # flake8-import-conventions "PIE", # flake8-pie + "PT", # flake8-pytest-style "RSE", # flake8-raise "RET", # flake8-return "SIM", # flake8-simplify @@ -80,5 +97,10 @@ ignore-variadic-names = true combine-as-imports = true required-imports = ["from __future__ import annotations"] +[tool.ruff.lint.per-file-ignores] +"!tests/**/*.py" = [ + "PT", # Ignore pytest-specific rules in non-test files +] + [tool.ruff.lint.pydocstyle] convention = "google" diff --git a/uv.lock b/uv.lock index 3394917..1d155da 100644 --- a/uv.lock +++ b/uv.lock @@ -10,6 +10,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, ] +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + [[package]] name = "distlib" version = "0.3.9" @@ -19,6 +28,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, ] +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + [[package]] name = "filelock" version = "3.16.1" @@ -37,6 +55,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/f5/09644a3ad803fae9eca8efa17e1f2aef380c7f0b02f7ec4e8d446e51d64a/identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd", size = 99049 }, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -46,6 +73,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + [[package]] name = "platformdirs" version = "4.3.6" @@ -55,6 +91,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, ] +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + [[package]] name = "pre-commit" version = "4.0.1" @@ -71,6 +116,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, ] +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -152,7 +214,7 @@ wheels = [ [[package]] name = "ruff-pre-commit" version = "0.0.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "ruff" }, ] @@ -161,12 +223,55 @@ dependencies = [ lint = [ { name = "pre-commit" }, ] +test = [ + { name = "pytest" }, +] [package.metadata] requires-dist = [{ name = "ruff", specifier = "==0.8.4" }] [package.metadata.requires-dev] lint = [{ name = "pre-commit", specifier = "==4.0.1" }] +test = [{ name = "pytest", specifier = "==8.3.4" }] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] [[package]] name = "virtualenv" From 35406be8578f2fb817dca1926d6976518bad7c7b Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:44:19 +0000 Subject: [PATCH 15/35] Add py.typed marker --- src/ruff_format_docs/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/ruff_format_docs/py.typed diff --git a/src/ruff_format_docs/py.typed b/src/ruff_format_docs/py.typed new file mode 100644 index 0000000..e69de29 From 0a52d0cc1788555e18bf03ce769eef106990cd63 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:44:54 +0000 Subject: [PATCH 16/35] Tweak ruff config --- pyproject.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1fb4a39..498f0e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,7 @@ select = [ "RSE", # flake8-raise "RET", # flake8-return "SIM", # flake8-simplify + "TID", # flake8-tidy-imports "TC", # flake8-type-checking "ARG", # flake8-unused-arguments "PTH", # flake8-use-pathlib @@ -90,6 +91,9 @@ unfixable = [ "F841", # Disable automatic removal of unused variables ] +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "all" + [tool.ruff.lint.flake8-unused-arguments] ignore-variadic-names = true @@ -101,6 +105,9 @@ required-imports = ["from __future__ import annotations"] "!tests/**/*.py" = [ "PT", # Ignore pytest-specific rules in non-test files ] +"__init__.py" = [ + "D104", # Ignore - "Missing docstring in public package" for init files +] [tool.ruff.lint.pydocstyle] convention = "google" From a47a8be30d6c82cd74ecb569f049d4bce4ea8157 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:45:44 +0000 Subject: [PATCH 17/35] Add coverage testing --- .gitignore | 1 + pyproject.toml | 23 +++++++++++++ uv.lock | 93 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1df008c..b17319a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ __pycache__ *.pyc .venv *.egg-info/ +.coverage diff --git a/pyproject.toml b/pyproject.toml index 498f0e9..f3dea32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ lint = [ ] test = [ "pytest==8.3.4", + "pytest-cov==6.0.0", ] [tool.pytest.ini_options] @@ -36,6 +37,28 @@ python_files = "test_*.py" testpaths = ["tests"] xfail_strict = true +[tool.coverage.report] +exclude_also = [ + "if TYPE_CHECKING", +] +fail_under = 100 +omit = ["mirror.py"] +show_missing = true +skip_covered = true + +[tool.coverage.run] +branch = true +parallel = true +source = [ + "ruff_format_docs", + "tests", +] + +[tool.coverage.paths] +source = [ + "src", +] + [tool.mypy] mypy_path = "$MYPY_CONFIG_FILE_DIR/src" strict = true diff --git a/uv.lock b/uv.lock index 1d155da..638ba60 100644 --- a/uv.lock +++ b/uv.lock @@ -19,6 +19,80 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "coverage" +version = "7.6.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/12/2a2a923edf4ddabdffed7ad6da50d96a5c126dae7b80a33df7310e329a1e/coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78", size = 207982 }, + { url = "https://files.pythonhosted.org/packages/ca/49/6985dbca9c7be3f3cb62a2e6e492a0c88b65bf40579e16c71ae9c33c6b23/coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c", size = 208414 }, + { url = "https://files.pythonhosted.org/packages/35/93/287e8f1d1ed2646f4e0b2605d14616c9a8a2697d0d1b453815eb5c6cebdb/coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a", size = 236860 }, + { url = "https://files.pythonhosted.org/packages/de/e1/cfdb5627a03567a10031acc629b75d45a4ca1616e54f7133ca1fa366050a/coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165", size = 234758 }, + { url = "https://files.pythonhosted.org/packages/6d/85/fc0de2bcda3f97c2ee9fe8568f7d48f7279e91068958e5b2cc19e0e5f600/coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988", size = 235920 }, + { url = "https://files.pythonhosted.org/packages/79/73/ef4ea0105531506a6f4cf4ba571a214b14a884630b567ed65b3d9c1975e1/coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5", size = 234986 }, + { url = "https://files.pythonhosted.org/packages/c6/4d/75afcfe4432e2ad0405c6f27adeb109ff8976c5e636af8604f94f29fa3fc/coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3", size = 233446 }, + { url = "https://files.pythonhosted.org/packages/86/5b/efee56a89c16171288cafff022e8af44f8f94075c2d8da563c3935212871/coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5", size = 234566 }, + { url = "https://files.pythonhosted.org/packages/f2/db/67770cceb4a64d3198bf2aa49946f411b85ec6b0a9b489e61c8467a4253b/coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244", size = 210675 }, + { url = "https://files.pythonhosted.org/packages/8d/27/e8bfc43f5345ec2c27bc8a1fa77cdc5ce9dcf954445e11f14bb70b889d14/coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e", size = 211518 }, + { url = "https://files.pythonhosted.org/packages/85/d2/5e175fcf6766cf7501a8541d81778fd2f52f4870100e791f5327fd23270b/coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3", size = 208088 }, + { url = "https://files.pythonhosted.org/packages/4b/6f/06db4dc8fca33c13b673986e20e466fd936235a6ec1f0045c3853ac1b593/coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43", size = 208536 }, + { url = "https://files.pythonhosted.org/packages/0d/62/c6a0cf80318c1c1af376d52df444da3608eafc913b82c84a4600d8349472/coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132", size = 240474 }, + { url = "https://files.pythonhosted.org/packages/a3/59/750adafc2e57786d2e8739a46b680d4fb0fbc2d57fbcb161290a9f1ecf23/coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f", size = 237880 }, + { url = "https://files.pythonhosted.org/packages/2c/f8/ef009b3b98e9f7033c19deb40d629354aab1d8b2d7f9cfec284dbedf5096/coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994", size = 239750 }, + { url = "https://files.pythonhosted.org/packages/a6/e2/6622f3b70f5f5b59f705e680dae6db64421af05a5d1e389afd24dae62e5b/coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99", size = 238642 }, + { url = "https://files.pythonhosted.org/packages/2d/10/57ac3f191a3c95c67844099514ff44e6e19b2915cd1c22269fb27f9b17b6/coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd", size = 237266 }, + { url = "https://files.pythonhosted.org/packages/ee/2d/7016f4ad9d553cabcb7333ed78ff9d27248ec4eba8dd21fa488254dff894/coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377", size = 238045 }, + { url = "https://files.pythonhosted.org/packages/a7/fe/45af5c82389a71e0cae4546413266d2195c3744849669b0bab4b5f2c75da/coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8", size = 210647 }, + { url = "https://files.pythonhosted.org/packages/db/11/3f8e803a43b79bc534c6a506674da9d614e990e37118b4506faf70d46ed6/coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609", size = 211508 }, + { url = "https://files.pythonhosted.org/packages/86/77/19d09ea06f92fdf0487499283b1b7af06bc422ea94534c8fe3a4cd023641/coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853", size = 208281 }, + { url = "https://files.pythonhosted.org/packages/b6/67/5479b9f2f99fcfb49c0d5cf61912a5255ef80b6e80a3cddba39c38146cf4/coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078", size = 208514 }, + { url = "https://files.pythonhosted.org/packages/15/d1/febf59030ce1c83b7331c3546d7317e5120c5966471727aa7ac157729c4b/coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0", size = 241537 }, + { url = "https://files.pythonhosted.org/packages/4b/7e/5ac4c90192130e7cf8b63153fe620c8bfd9068f89a6d9b5f26f1550f7a26/coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50", size = 238572 }, + { url = "https://files.pythonhosted.org/packages/dc/03/0334a79b26ecf59958f2fe9dd1f5ab3e2f88db876f5071933de39af09647/coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022", size = 240639 }, + { url = "https://files.pythonhosted.org/packages/d7/45/8a707f23c202208d7b286d78ad6233f50dcf929319b664b6cc18a03c1aae/coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b", size = 240072 }, + { url = "https://files.pythonhosted.org/packages/66/02/603ce0ac2d02bc7b393279ef618940b4a0535b0868ee791140bda9ecfa40/coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0", size = 238386 }, + { url = "https://files.pythonhosted.org/packages/04/62/4e6887e9be060f5d18f1dd58c2838b2d9646faf353232dec4e2d4b1c8644/coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852", size = 240054 }, + { url = "https://files.pythonhosted.org/packages/5c/74/83ae4151c170d8bd071924f212add22a0e62a7fe2b149edf016aeecad17c/coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359", size = 210904 }, + { url = "https://files.pythonhosted.org/packages/c3/54/de0893186a221478f5880283119fc40483bc460b27c4c71d1b8bba3474b9/coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247", size = 211692 }, + { url = "https://files.pythonhosted.org/packages/25/6d/31883d78865529257bf847df5789e2ae80e99de8a460c3453dbfbe0db069/coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9", size = 208308 }, + { url = "https://files.pythonhosted.org/packages/70/22/3f2b129cc08de00c83b0ad6252e034320946abfc3e4235c009e57cfeee05/coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b", size = 208565 }, + { url = "https://files.pythonhosted.org/packages/97/0a/d89bc2d1cc61d3a8dfe9e9d75217b2be85f6c73ebf1b9e3c2f4e797f4531/coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690", size = 241083 }, + { url = "https://files.pythonhosted.org/packages/4c/81/6d64b88a00c7a7aaed3a657b8eaa0931f37a6395fcef61e53ff742b49c97/coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18", size = 238235 }, + { url = "https://files.pythonhosted.org/packages/9a/0b/7797d4193f5adb4b837207ed87fecf5fc38f7cc612b369a8e8e12d9fa114/coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c", size = 240220 }, + { url = "https://files.pythonhosted.org/packages/65/4d/6f83ca1bddcf8e51bf8ff71572f39a1c73c34cf50e752a952c34f24d0a60/coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd", size = 239847 }, + { url = "https://files.pythonhosted.org/packages/30/9d/2470df6aa146aff4c65fee0f87f58d2164a67533c771c9cc12ffcdb865d5/coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e", size = 237922 }, + { url = "https://files.pythonhosted.org/packages/08/dd/723fef5d901e6a89f2507094db66c091449c8ba03272861eaefa773ad95c/coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694", size = 239783 }, + { url = "https://files.pythonhosted.org/packages/3d/f7/64d3298b2baf261cb35466000628706ce20a82d42faf9b771af447cd2b76/coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6", size = 210965 }, + { url = "https://files.pythonhosted.org/packages/d5/58/ec43499a7fc681212fe7742fe90b2bc361cdb72e3181ace1604247a5b24d/coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e", size = 211719 }, + { url = "https://files.pythonhosted.org/packages/ab/c9/f2857a135bcff4330c1e90e7d03446b036b2363d4ad37eb5e3a47bbac8a6/coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe", size = 209050 }, + { url = "https://files.pythonhosted.org/packages/aa/b3/f840e5bd777d8433caa9e4a1eb20503495709f697341ac1a8ee6a3c906ad/coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273", size = 209321 }, + { url = "https://files.pythonhosted.org/packages/85/7d/125a5362180fcc1c03d91850fc020f3831d5cda09319522bcfa6b2b70be7/coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8", size = 252039 }, + { url = "https://files.pythonhosted.org/packages/a9/9c/4358bf3c74baf1f9bddd2baf3756b54c07f2cfd2535f0a47f1e7757e54b3/coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098", size = 247758 }, + { url = "https://files.pythonhosted.org/packages/cf/c7/de3eb6fc5263b26fab5cda3de7a0f80e317597a4bad4781859f72885f300/coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb", size = 250119 }, + { url = "https://files.pythonhosted.org/packages/3e/e6/43de91f8ba2ec9140c6a4af1102141712949903dc732cf739167cfa7a3bc/coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0", size = 249597 }, + { url = "https://files.pythonhosted.org/packages/08/40/61158b5499aa2adf9e37bc6d0117e8f6788625b283d51e7e0c53cf340530/coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf", size = 247473 }, + { url = "https://files.pythonhosted.org/packages/50/69/b3f2416725621e9f112e74e8470793d5b5995f146f596f133678a633b77e/coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2", size = 248737 }, + { url = "https://files.pythonhosted.org/packages/3c/6e/fe899fb937657db6df31cc3e61c6968cb56d36d7326361847440a430152e/coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312", size = 211611 }, + { url = "https://files.pythonhosted.org/packages/1c/55/52f5e66142a9d7bc93a15192eba7a78513d2abf6b3558d77b4ca32f5f424/coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d", size = 212781 }, + { url = "https://files.pythonhosted.org/packages/40/41/473617aadf9a1c15bc2d56be65d90d7c29bfa50a957a67ef96462f7ebf8e/coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a", size = 207978 }, + { url = "https://files.pythonhosted.org/packages/10/f6/480586607768b39a30e6910a3c4522139094ac0f1677028e1f4823688957/coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27", size = 208415 }, + { url = "https://files.pythonhosted.org/packages/f1/af/439bb760f817deff6f4d38fe7da08d9dd7874a560241f1945bc3b4446550/coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4", size = 236452 }, + { url = "https://files.pythonhosted.org/packages/d0/13/481f4ceffcabe29ee2332e60efb52e4694f54a402f3ada2bcec10bb32e43/coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f", size = 234374 }, + { url = "https://files.pythonhosted.org/packages/c5/59/4607ea9d6b1b73e905c7656da08d0b00cdf6e59f2293ec259e8914160025/coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25", size = 235505 }, + { url = "https://files.pythonhosted.org/packages/85/60/d66365723b9b7f29464b11d024248ed3523ce5aab958e4ad8c43f3f4148b/coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315", size = 234616 }, + { url = "https://files.pythonhosted.org/packages/74/f8/2cf7a38e7d81b266f47dfcf137fecd8fa66c7bdbd4228d611628d8ca3437/coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90", size = 233099 }, + { url = "https://files.pythonhosted.org/packages/50/2b/bff6c1c6b63c4396ea7ecdbf8db1788b46046c681b8fcc6ec77db9f4ea49/coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d", size = 234089 }, + { url = "https://files.pythonhosted.org/packages/bf/b5/baace1c754d546a67779358341aa8d2f7118baf58cac235db457e1001d1b/coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18", size = 210701 }, + { url = "https://files.pythonhosted.org/packages/b1/bf/9e1e95b8b20817398ecc5a1e8d3e05ff404e1b9fb2185cd71561698fe2a2/coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59", size = 211482 }, + { url = "https://files.pythonhosted.org/packages/a1/70/de81bfec9ed38a64fc44a77c7665e20ca507fc3265597c28b0d989e4082e/coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f", size = 200223 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + [[package]] name = "distlib" version = "0.3.9" @@ -133,6 +207,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, ] +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -225,6 +312,7 @@ lint = [ ] test = [ { name = "pytest" }, + { name = "pytest-cov" }, ] [package.metadata] @@ -232,7 +320,10 @@ requires-dist = [{ name = "ruff", specifier = "==0.8.4" }] [package.metadata.requires-dev] lint = [{ name = "pre-commit", specifier = "==4.0.1" }] -test = [{ name = "pytest", specifier = "==8.3.4" }] +test = [ + { name = "pytest", specifier = "==8.3.4" }, + { name = "pytest-cov", specifier = "==6.0.0" }, +] [[package]] name = "tomli" From 8e69e1b657d50cf38de984e28d0c2c8f81b8e528 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:43:43 +0000 Subject: [PATCH 18/35] Add testing CI --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 583ad19..cdf8641 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,3 +41,31 @@ jobs: exit_code="${PIPESTATUS[0]}" echo '```' >> "$GITHUB_STEP_SUMMARY" exit "$exit_code" + + test: + name: Test + runs-on: ubuntu-latest + strategy: + matrix: + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: uv sync --group test + + - name: Run tests + run: uv run pytest --cov=. From 11a2fc43344ca769753f5c9075d6189723e333ad Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:51:21 +0000 Subject: [PATCH 19/35] Get mypy working --- .pre-commit-config.yaml | 1 + tests/__init__.py | 9 +++++++++ tests/test_ruff_format_docs.py | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f2f904..3d6b7df 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,6 +55,7 @@ repos: additional_dependencies: - pytest==8.3.3 - urllib3==2.0.5 + - black==24.10.0 - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.8.4 diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..83fa460 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,9 @@ +"""This is required to get tests running and mypy to check test files.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +if (SRC_DIR := (Path(__file__).parent.parent / "src").as_posix()) not in sys.path: + sys.path.append(SRC_DIR) diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index 0246481..5a8e38d 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -5,7 +5,7 @@ import black from black.const import DEFAULT_LINE_LENGTH -from src import ruff_format_docs +import ruff_format_docs BLACK_MODE = black.FileMode(line_length=DEFAULT_LINE_LENGTH) From a444974420abc8509f664fa96b2d871bf261185f Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 00:58:20 +0000 Subject: [PATCH 20/35] Get mypy passing --- tests/test_ruff_format_docs.py | 165 +++++++++++++++++---------------- 1 file changed, 87 insertions(+), 78 deletions(-) diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index 5a8e38d..164fbc8 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -1,21 +1,27 @@ from __future__ import annotations from textwrap import dedent +from typing import TYPE_CHECKING import black from black.const import DEFAULT_LINE_LENGTH import ruff_format_docs +if TYPE_CHECKING: + from pathlib import Path + + from _pytest.capture import CaptureFixture + BLACK_MODE = black.FileMode(line_length=DEFAULT_LINE_LENGTH) -def test_format_src_trivial(): +def test_format_src_trivial() -> None: after, _ = ruff_format_docs.format_str("", BLACK_MODE) assert after == "" -def test_format_src_markdown_simple(): +def test_format_src_markdown_simple() -> None: before = dedent( """\ ```python @@ -33,7 +39,7 @@ def test_format_src_markdown_simple(): ) -def test_format_src_markdown_leading_whitespace(): +def test_format_src_markdown_leading_whitespace() -> None: before = dedent( """\ ``` python @@ -51,7 +57,7 @@ def test_format_src_markdown_leading_whitespace(): ) -def test_format_src_markdown_python_after_newline(): +def test_format_src_markdown_python_after_newline() -> None: before = dedent( """\ ``` @@ -65,7 +71,7 @@ def test_format_src_markdown_python_after_newline(): assert after == before -def test_format_src_markdown_short_name(): +def test_format_src_markdown_short_name() -> None: before = dedent( """\ ``` py @@ -83,7 +89,7 @@ def test_format_src_markdown_short_name(): ) -def test_format_src_markdown_options(): +def test_format_src_markdown_options() -> None: before = dedent( """\ ```python title='example.py' @@ -101,7 +107,7 @@ def test_format_src_markdown_options(): ) -def test_format_src_markdown_trailing_whitespace(): +def test_format_src_markdown_trailing_whitespace() -> None: before = dedent( """\ ```python @@ -117,7 +123,7 @@ def test_format_src_markdown_trailing_whitespace(): ) -def test_format_src_indented_markdown(): +def test_format_src_indented_markdown() -> None: before = dedent( """\ - do this pls: @@ -139,7 +145,7 @@ def test_format_src_indented_markdown(): ) -def test_format_src_markdown_pycon(): +def test_format_src_markdown_pycon() -> None: before = ( "hello\n" "\n" @@ -154,7 +160,7 @@ def test_format_src_markdown_pycon(): assert after == ("hello\n\n```pycon\n\n>>> f(1, 2, 3)\noutput\n```\nworld\n") -def test_format_src_markdown_pycon_after_newline(): +def test_format_src_markdown_pycon_after_newline() -> None: before = dedent( """\ ``` @@ -168,7 +174,7 @@ def test_format_src_markdown_pycon_after_newline(): assert after == before -def test_format_src_markdown_pycon_options(): +def test_format_src_markdown_pycon_options() -> None: before = ( "hello\n" "\n" @@ -192,7 +198,7 @@ def test_format_src_markdown_pycon_options(): ) -def test_format_src_markdown_pycon_twice(): +def test_format_src_markdown_pycon_twice() -> None: before = ( "```pycon\n" ">>> f(1,2,3)\n" @@ -218,7 +224,7 @@ def test_format_src_markdown_pycon_twice(): ) -def test_format_src_markdown_comments_disable(): +def test_format_src_markdown_comments_disable() -> None: before = ( "\n" "```python\n" @@ -230,7 +236,7 @@ def test_format_src_markdown_comments_disable(): assert after == before -def test_format_src_markdown_comments_disabled_enabled(): +def test_format_src_markdown_comments_disabled_enabled() -> None: before = ( "\n" "```python\n" @@ -254,7 +260,7 @@ def test_format_src_markdown_comments_disabled_enabled(): ) -def test_format_src_markdown_comments_before(): +def test_format_src_markdown_comments_before() -> None: before = ( "\n" "\n" @@ -272,7 +278,7 @@ def test_format_src_markdown_comments_before(): ) -def test_format_src_markdown_comments_after(): +def test_format_src_markdown_comments_after() -> None: before = ( "```python\n" "'double quotes rock'\n" @@ -290,7 +296,7 @@ def test_format_src_markdown_comments_after(): ) -def test_format_src_markdown_comments_only_on(): +def test_format_src_markdown_comments_only_on() -> None: # fmt: off before = ( "\n" @@ -308,7 +314,7 @@ def test_format_src_markdown_comments_only_on(): # fmt: on -def test_format_src_markdown_comments_only_off(): +def test_format_src_markdown_comments_only_off() -> None: # fmt: off before = ( "\n" @@ -321,7 +327,7 @@ def test_format_src_markdown_comments_only_off(): assert after == before -def test_format_src_markdown_comments_multiple(): +def test_format_src_markdown_comments_multiple() -> None: before = ( "\n" # ignored "\n" @@ -337,7 +343,7 @@ def test_format_src_markdown_comments_multiple(): assert after == before -def test_on_off_comments_in_code_blocks(): +def test_on_off_comments_in_code_blocks() -> None: before = ( "````md\n" "\n" @@ -351,7 +357,7 @@ def test_on_off_comments_in_code_blocks(): assert after == before -def test_format_src_markdown_comments_disable_pycon(): +def test_format_src_markdown_comments_disable_pycon() -> None: before = ( "\n" "```pycon\n" @@ -363,7 +369,7 @@ def test_format_src_markdown_comments_disable_pycon(): assert after == before -def test_format_src_latex_minted(): +def test_format_src_latex_minted() -> None: before = "hello\n\\begin{minted}{python}\nf(1,2,3)\n\\end{minted}\nworld!" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == ( @@ -371,7 +377,7 @@ def test_format_src_latex_minted(): ) -def test_format_src_latex_minted_opt(): +def test_format_src_latex_minted_opt() -> None: before = ( "maths!\n" "\\begin{minted}[mathescape]{python}\n" @@ -395,7 +401,7 @@ def test_format_src_latex_minted_opt(): ) -def test_format_src_latex_minted_indented(): +def test_format_src_latex_minted_indented() -> None: # Personally I would have minted python code all flush left, # with only the Python code's own four space indentation: before = dedent( @@ -421,7 +427,7 @@ def test_format_src_latex_minted_indented(): ) -def test_format_src_latex_minted_pycon(): +def test_format_src_latex_minted_pycon() -> None: before = ( "Preceding text\n" "\\begin{minted}[gobble=2,showspaces]{pycon}\n" @@ -441,7 +447,7 @@ def test_format_src_latex_minted_pycon(): ) -def test_format_src_latex_minted_pycon_indented(): +def test_format_src_latex_minted_pycon_indented() -> None: # Nicer style to put the \begin and \end on new lines, # but not actually required for the begin line before = ( @@ -463,7 +469,7 @@ def test_format_src_latex_minted_pycon_indented(): ) -def test_format_src_latex_minted_comments_off(): +def test_format_src_latex_minted_comments_off() -> None: before = ( "% blacken-docs:off\n" "\\begin{minted}{python}\n" @@ -475,7 +481,7 @@ def test_format_src_latex_minted_comments_off(): assert after == before -def test_format_src_latex_minted_comments_off_pycon(): +def test_format_src_latex_minted_comments_off_pycon() -> None: before = ( "% blacken-docs:off\n" "\\begin{minted}{pycon}\n" @@ -487,7 +493,7 @@ def test_format_src_latex_minted_comments_off_pycon(): assert after == before -def test_format_src_pythontex(): +def test_format_src_pythontex() -> None: # fmt: off before = ( "hello\n" @@ -507,7 +513,7 @@ def test_format_src_pythontex(): # fmt: on -def test_format_src_pythontex_comments_off(): +def test_format_src_pythontex_comments_off() -> None: before = ( "% blacken-docs:off\n" "\\begin{pyblock}\n" @@ -519,19 +525,19 @@ def test_format_src_pythontex_comments_off(): assert after == before -def test_format_src_rst(): +def test_format_src_rst() -> None: before = "hello\n\n.. code-block:: python\n\n f(1,2,3)\n\nworld\n" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == ("hello\n\n.. code-block:: python\n\n f(1, 2, 3)\n\nworld\n") -def test_format_src_rst_empty(): +def test_format_src_rst_empty() -> None: before = "some text\n\n.. code-block:: python\n\n\nsome other text\n" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == before -def test_format_src_rst_literal_blocks(): +def test_format_src_rst_literal_blocks() -> None: before = dedent( """\ hello:: @@ -557,7 +563,7 @@ def test_format_src_rst_literal_blocks(): ) -def test_format_src_rst_literal_block_empty(): +def test_format_src_rst_literal_block_empty() -> None: before = dedent( """\ hello:: @@ -572,7 +578,7 @@ def test_format_src_rst_literal_block_empty(): assert after == before -def test_format_src_rst_literal_blocks_nested(): +def test_format_src_rst_literal_blocks_nested() -> None: before = dedent( """ * hello @@ -591,7 +597,7 @@ def test_format_src_rst_literal_blocks_nested(): assert errors == [] -def test_format_src_rst_literal_blocks_empty(): +def test_format_src_rst_literal_blocks_empty() -> None: before = dedent( """ Example:: @@ -610,7 +616,7 @@ def test_format_src_rst_literal_blocks_empty(): assert errors == [] -def test_format_src_rst_literal_blocks_comments(): +def test_format_src_rst_literal_blocks_comments() -> None: before = ( ".. blacken-docs:off\n" "Example::\n" @@ -623,7 +629,7 @@ def test_format_src_rst_literal_blocks_comments(): assert after == before -def test_format_src_rst_sphinx_doctest(): +def test_format_src_rst_sphinx_doctest() -> None: before = ( ".. testsetup:: group1\n" "\n" @@ -668,7 +674,7 @@ def test_format_src_rst_sphinx_doctest(): ) -def test_format_src_rst_indented(): +def test_format_src_rst_indented() -> None: before = dedent( """\ .. versionadded:: 3.1 @@ -677,7 +683,7 @@ def test_format_src_rst_indented(): .. code-block:: python - def hi(): + def hi() -> None: f(1,2,3) world @@ -692,7 +698,7 @@ def hi(): .. code-block:: python - def hi(): + def hi() -> None: f(1, 2, 3) world @@ -700,7 +706,7 @@ def hi(): ) -def test_format_src_rst_code_block_indent(): +def test_format_src_rst_code_block_indent() -> None: before = "\n".join( [ ".. code-block:: python", @@ -718,7 +724,7 @@ def test_format_src_rst_code_block_indent(): ) -def test_format_src_rst_with_highlight_directives(): +def test_format_src_rst_with_highlight_directives() -> None: before = ( ".. code-block:: python\n" " :lineno-start: 10\n" @@ -738,7 +744,7 @@ def test_format_src_rst_with_highlight_directives(): ) -def test_format_src_rst_python_inside_non_python_code_block(): +def test_format_src_rst_python_inside_non_python_code_block() -> None: before = ( "blacken-docs does changes like:\n" "\n" @@ -753,7 +759,7 @@ def test_format_src_rst_python_inside_non_python_code_block(): assert after == before -def test_format_src_rst_python_comments(): +def test_format_src_rst_python_comments() -> None: before = ( ".. blacken-docs:off\n" ".. code-block:: python\n" @@ -766,7 +772,7 @@ def test_format_src_rst_python_comments(): assert after == before -def test_integration_ok(tmp_path, capsys): +def test_integration_ok(tmp_path: Path, capsys: CaptureFixture[str]) -> None: f = tmp_path / "f.md" f.write_text( "```python\nf(1, 2, 3)\n```\n", @@ -779,7 +785,7 @@ def test_integration_ok(tmp_path, capsys): assert f.read_text() == ("```python\nf(1, 2, 3)\n```\n") -def test_integration_modifies(tmp_path, capsys): +def test_integration_modifies(tmp_path: Path, capsys: CaptureFixture[str]) -> None: f = tmp_path / "f.md" f.write_text( "```python\nf(1,2,3)\n```\n", @@ -793,7 +799,7 @@ def test_integration_modifies(tmp_path, capsys): assert f.read_text() == ("```python\nf(1, 2, 3)\n```\n") -def test_integration_line_length(tmp_path): +def test_integration_line_length(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( "```python\n" @@ -816,7 +822,7 @@ def test_integration_line_length(tmp_path): ) -def test_integration_check(tmp_path): +def test_integration_check(tmp_path: Path) -> None: f = tmp_path / "f.md" text = dedent( """\ @@ -833,7 +839,7 @@ def test_integration_check(tmp_path): assert f.read_text() == text -def test_integration_preview(tmp_path): +def test_integration_preview(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( dedent( @@ -857,7 +863,7 @@ def test_integration_preview(tmp_path): ) -def test_integration_pyi(tmp_path): +def test_integration_pyi(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( dedent( @@ -885,7 +891,7 @@ class Bar: ... ) -def test_integration_py36(tmp_path): +def test_integration_py36(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( "```python\n" @@ -916,7 +922,7 @@ def test_integration_py36(tmp_path): ) -def test_integration_filename_last(tmp_path): +def test_integration_filename_last(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( "```python\n" @@ -947,7 +953,7 @@ def test_integration_filename_last(tmp_path): ) -def test_integration_multiple_target_version(tmp_path): +def test_integration_multiple_target_version(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( "```python\n" @@ -969,7 +975,7 @@ def test_integration_multiple_target_version(tmp_path): assert result2 == 0 -def test_integration_skip_string_normalization(tmp_path): +def test_integration_skip_string_normalization(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( "```python\nf('hi')\n```\n", @@ -981,7 +987,7 @@ def test_integration_skip_string_normalization(tmp_path): assert f.read_text() == ("```python\nf('hi')\n```\n") -def test_integration_syntax_error(tmp_path, capsys): +def test_integration_syntax_error(tmp_path: Path, capsys: CaptureFixture[str]) -> None: f = tmp_path / "f.md" f.write_text( "```python\nf(\n```\n", @@ -995,7 +1001,10 @@ def test_integration_syntax_error(tmp_path, capsys): assert f.read_text() == ("```python\nf(\n```\n") -def test_integration_ignored_syntax_error(tmp_path, capsys): +def test_integration_ignored_syntax_error( + tmp_path: Path, + capsys: CaptureFixture[str], +) -> None: f = tmp_path / "f.md" f.write_text( "```python\nf( )\n```\n\n```python\nf(\n```\n", @@ -1008,13 +1017,13 @@ def test_integration_ignored_syntax_error(tmp_path, capsys): assert f.read_text() == ("```python\nf()\n```\n\n```python\nf(\n```\n") -def test_format_src_rst_jupyter_sphinx(): +def test_format_src_rst_jupyter_sphinx() -> None: before = "hello\n\n.. jupyter-execute::\n\n f(1,2,3)\n\nworld\n" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == ("hello\n\n.. jupyter-execute::\n\n f(1, 2, 3)\n\nworld\n") -def test_format_src_rst_jupyter_sphinx_with_directive(): +def test_format_src_rst_jupyter_sphinx_with_directive() -> None: before = ( "hello\n" "\n" @@ -1038,7 +1047,7 @@ def test_format_src_rst_jupyter_sphinx_with_directive(): ) -def test_format_src_python_docstring_markdown(): +def test_format_src_python_docstring_markdown() -> None: before = dedent( '''\ def f(): @@ -1068,7 +1077,7 @@ def f(): ) -def test_format_src_python_docstring_rst(): +def test_format_src_python_docstring_rst() -> None: before = dedent( '''\ def f(): @@ -1098,7 +1107,7 @@ def f(): ) -def test_format_src_rst_pycon(): +def test_format_src_rst_pycon() -> None: before = ( "hello\n" "\n" @@ -1122,7 +1131,7 @@ def test_format_src_rst_pycon(): ) -def test_format_src_rst_pycon_with_continuation(): +def test_format_src_rst_pycon_with_continuation() -> None: before = ( ".. code-block:: pycon\n" "\n" @@ -1145,7 +1154,7 @@ def test_format_src_rst_pycon_with_continuation(): ) -def test_format_src_rst_pycon_adds_continuation(): +def test_format_src_rst_pycon_adds_continuation() -> None: before = ".. code-block:: pycon\n\n" ' >>> d = {"a": 1,"b": 2,"c": 3,}\n' "\n" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == ( @@ -1160,7 +1169,7 @@ def test_format_src_rst_pycon_adds_continuation(): ) -def test_format_src_rst_pycon_preserves_trailing_whitespace(): +def test_format_src_rst_pycon_preserves_trailing_whitespace() -> None: before = ( "hello\n" "\n" @@ -1176,7 +1185,7 @@ def test_format_src_rst_pycon_preserves_trailing_whitespace(): assert after == before -def test_format_src_rst_pycon_indented(): +def test_format_src_rst_pycon_indented() -> None: before = ( ".. versionadded:: 3.1\n" "\n" @@ -1206,7 +1215,7 @@ def test_format_src_rst_pycon_indented(): ) -def test_format_src_rst_pycon_code_block_is_final_line1(): +def test_format_src_rst_pycon_code_block_is_final_line1() -> None: before = ( ".. code-block:: pycon\n" "\n" @@ -1224,7 +1233,7 @@ def test_format_src_rst_pycon_code_block_is_final_line1(): ) -def test_format_src_rst_pycon_code_block_is_final_line2(): +def test_format_src_rst_pycon_code_block_is_final_line2() -> None: before = ".. code-block:: pycon\n\n >>> if True:\n ... pass\n" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == ( @@ -1236,7 +1245,7 @@ def test_format_src_rst_pycon_code_block_is_final_line2(): ) -def test_format_src_rst_pycon_nested_def1(): +def test_format_src_rst_pycon_nested_def1() -> None: before = ( ".. code-block:: pycon\n" "\n" @@ -1255,7 +1264,7 @@ def test_format_src_rst_pycon_nested_def1(): ) -def test_format_src_rst_pycon_nested_def2(): +def test_format_src_rst_pycon_nested_def2() -> None: before = ( ".. code-block:: pycon\n" "\n" @@ -1273,7 +1282,7 @@ def test_format_src_rst_pycon_nested_def2(): ) -def test_format_src_rst_pycon_empty_line(): +def test_format_src_rst_pycon_empty_line() -> None: before = ( ".. code-block:: pycon\n" "\n" @@ -1292,7 +1301,7 @@ def test_format_src_rst_pycon_empty_line(): ) -def test_format_src_rst_pycon_preserves_output_indentation(): +def test_format_src_rst_pycon_preserves_output_indentation() -> None: before = ( ".. code-block:: pycon\n" "\n" @@ -1305,7 +1314,7 @@ def test_format_src_rst_pycon_preserves_output_indentation(): assert after == before -def test_format_src_rst_pycon_elided_traceback(): +def test_format_src_rst_pycon_elided_traceback() -> None: before = ( ".. code-block:: pycon\n" "\n" @@ -1318,19 +1327,19 @@ def test_format_src_rst_pycon_elided_traceback(): assert after == before -def test_format_src_rst_pycon_no_prompt(): +def test_format_src_rst_pycon_no_prompt() -> None: before = ".. code-block:: pycon\n\n pass\n" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == before -def test_format_src_rst_pycon_no_trailing_newline(): +def test_format_src_rst_pycon_no_trailing_newline() -> None: before = ".. code-block:: pycon\n\n >>> pass" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == (".. code-block:: pycon\n\n >>> pass\n") -def test_format_src_rst_pycon_comment_before_promopt(): +def test_format_src_rst_pycon_comment_before_promopt() -> None: before = ( ".. code-block:: pycon\n" "\n" @@ -1346,7 +1355,7 @@ def test_format_src_rst_pycon_comment_before_promopt(): ) -def test_format_src_rst_pycon_comments(): +def test_format_src_rst_pycon_comments() -> None: before = ( ".. blacken-docs:off\n" ".. code-block:: pycon\n" @@ -1359,7 +1368,7 @@ def test_format_src_rst_pycon_comments(): assert after == before -def test_format_src_rst_pycon_empty(): +def test_format_src_rst_pycon_empty() -> None: before = "some text\n\n.. code-block:: pycon\n\n\nsome other text\n" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == before From a38e8c188902d9746e10ce57f7c78d0b119087b3 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 01:02:52 +0000 Subject: [PATCH 21/35] Get ruff passing --- src/ruff_format_docs/__init__.py | 33 +++++++++++++++++++++----------- src/ruff_format_docs/__main__.py | 2 ++ tests/test_ruff_format_docs.py | 22 +++++++-------------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py index 68c181f..e2c769c 100644 --- a/src/ruff_format_docs/__init__.py +++ b/src/ruff_format_docs/__init__.py @@ -5,13 +5,17 @@ import re import textwrap from bisect import bisect -from collections.abc import Generator, Sequence +from pathlib import Path from re import Match +from typing import TYPE_CHECKING import black from black.const import DEFAULT_LINE_LENGTH from black.mode import TargetVersion +if TYPE_CHECKING: + from collections.abc import Generator, Sequence + PYGMENTS_PY_LANGS = frozenset(("python", "py", "sage", "python3", "py3", "numpy")) PYGMENTS_PY_LANGS_RE_FRAGMENT = f"({'|'.join(PYGMENTS_PY_LANGS)})" MD_RE = re.compile( @@ -100,17 +104,21 @@ class CodeBlockError: + """An error that occurred while formatting a code block.""" + def __init__(self, offset: int, exc: Exception) -> None: + """Initialize a CodeBlockError.""" self.offset = offset self.exc = exc -def format_str( +def format_str( # noqa: PLR0915 src: str, black_mode: black.FileMode, *, rst_literal_blocks: bool = False, ) -> tuple[str, Sequence[CodeBlockError]]: + """Format all code blocks in a file.""" errors: list[CodeBlockError] = [] off_ranges = [] @@ -139,7 +147,7 @@ def _within_off_range(code_range: tuple[int, int]) -> bool: def _collect_error(match: Match[str]) -> Generator[None]: try: yield - except Exception as e: + except Exception as e: # noqa: BLE001 errors.append(CodeBlockError(match.start(), e)) def _md_match(match: Match[str]) -> str: @@ -214,7 +222,8 @@ def finish_fragment() -> None: indentation: int | None = None for line in match["code"].splitlines(): - orig_line, line = line, line.lstrip() + orig_line = line + line = line.lstrip() # noqa: PLW2901 if indentation is None and line: indentation = len(orig_line) - len(line) continuation_match = PYCON_CONTINUATION_RE.match(line) @@ -278,13 +287,14 @@ def _latex_pycon_match(match: Match[str]) -> str: def format_file( - filename: str, + file: Path, black_mode: black.FileMode, skip_errors: bool, rst_literal_blocks: bool, check_only: bool, ) -> int: - with open(filename, encoding="UTF-8") as f: + """Format a file with ruff.""" + with file.open(encoding="UTF-8") as f: contents = f.read() new_contents, errors = format_str( contents, @@ -293,21 +303,22 @@ def format_file( ) for error in errors: lineno = contents[: error.offset].count("\n") + 1 - print(f"{filename}:{lineno}: code block parse error {error.exc}") + print(f"{file}:{lineno}: code block parse error {error.exc}") if errors and not skip_errors: return 2 if contents == new_contents: return 0 if check_only: - print(f"{filename}: Requires a rewrite.") + print(f"{file}: Requires a rewrite.") return 1 - print(f"{filename}: Rewriting...") - with open(filename, "w", encoding="UTF-8") as f: + print(f"{file}: Rewriting...") + with file.open("w", encoding="UTF-8") as f: f.write(new_contents) return 1 def main(argv: Sequence[str] | None = None) -> int: + """CLI entry-point for ruff docs formatter.""" parser = argparse.ArgumentParser() parser.add_argument( "-l", @@ -351,7 +362,7 @@ def main(argv: Sequence[str] | None = None) -> int: retv = 0 for filename in args.filenames: retv |= format_file( - filename, + Path(filename), black_mode, skip_errors=args.skip_errors, rst_literal_blocks=args.rst_literal_blocks, diff --git a/src/ruff_format_docs/__main__.py b/src/ruff_format_docs/__main__.py index 20be931..2990ca5 100644 --- a/src/ruff_format_docs/__main__.py +++ b/src/ruff_format_docs/__main__.py @@ -1,3 +1,5 @@ +"""Entrypoint for docs formatter.""" + from __future__ import annotations from ruff_format_docs import main diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index 164fbc8..3f37224 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -1,3 +1,7 @@ +"""Test cases for ruff docs formatter.""" + +# ruff: noqa: D103 + from __future__ import annotations from textwrap import dedent @@ -707,21 +711,9 @@ def hi() -> None: def test_format_src_rst_code_block_indent() -> None: - before = "\n".join( - [ - ".. code-block:: python", - " ", - " f(1,2,3)\n", - ], - ) + before = ".. code-block:: python\n \n f(1,2,3)\n" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) - assert after == "\n".join( - [ - ".. code-block:: python", - " ", - " f(1, 2, 3)\n", - ], - ) + assert after == ".. code-block:: python\n \n f(1, 2, 3)\n" def test_format_src_rst_with_highlight_directives() -> None: @@ -1155,7 +1147,7 @@ def test_format_src_rst_pycon_with_continuation() -> None: def test_format_src_rst_pycon_adds_continuation() -> None: - before = ".. code-block:: pycon\n\n" ' >>> d = {"a": 1,"b": 2,"c": 3,}\n' "\n" + before = """.. code-block:: pycon\n\n >>> d = {"a": 1,"b": 2,"c": 3,}\n\n""" after, _ = ruff_format_docs.format_str(before, BLACK_MODE) assert after == ( ".. code-block:: pycon\n" From adaedb0e47010b40635b36b7c70b7c8ddc5c71f5 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 01:04:01 +0000 Subject: [PATCH 22/35] Add no cover to potential sys path update --- tests/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/__init__.py b/tests/__init__.py index 83fa460..518931b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -5,5 +5,6 @@ import sys from pathlib import Path -if (SRC_DIR := (Path(__file__).parent.parent / "src").as_posix()) not in sys.path: +SRC_DIR = (Path(__file__).parent.parent / "src").as_posix() +if SRC_DIR not in sys.path: # pragma: no cover sys.path.append(SRC_DIR) From 880cd5811a4d8419557297058dfabc91ae0c9302 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 01:08:21 +0000 Subject: [PATCH 23/35] Use explicit imports --- src/ruff_format_docs/__init__.py | 22 ++-- tests/test_ruff_format_docs.py | 166 +++++++++++++++---------------- 2 files changed, 94 insertions(+), 94 deletions(-) diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py index e2c769c..f7a11e9 100644 --- a/src/ruff_format_docs/__init__.py +++ b/src/ruff_format_docs/__init__.py @@ -9,7 +9,7 @@ from re import Match from typing import TYPE_CHECKING -import black +from black import FileMode, Mode, format_str from black.const import DEFAULT_LINE_LENGTH from black.mode import TargetVersion @@ -112,9 +112,9 @@ def __init__(self, offset: int, exc: Exception) -> None: self.exc = exc -def format_str( # noqa: PLR0915 +def format_file_contents( # noqa: PLR0915 src: str, - black_mode: black.FileMode, + black_mode: FileMode, *, rst_literal_blocks: bool = False, ) -> tuple[str, Sequence[CodeBlockError]]: @@ -155,7 +155,7 @@ def _md_match(match: Match[str]) -> str: return match[0] code = textwrap.dedent(match["code"]) with _collect_error(match): - code = black.format_str(code, mode=black_mode) + code = format_str(code, mode=black_mode) code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' @@ -173,7 +173,7 @@ def _rst_match(match: Match[str]) -> str: trailing_ws = trailing_ws_match.group() code = textwrap.dedent(match["code"]) with _collect_error(match): - code = black.format_str(code, mode=black_mode) + code = format_str(code, mode=black_mode) code = textwrap.indent(code, min_indent) return f'{match["before"]}{code.rstrip()}{trailing_ws}' @@ -188,7 +188,7 @@ def _rst_literal_blocks_match(match: Match[str]) -> str: trailing_ws = trailing_ws_match.group() code = textwrap.dedent(match["code"]) with _collect_error(match): - code = black.format_str(code, mode=black_mode) + code = format_str(code, mode=black_mode) code = textwrap.indent(code, min_indent) return f'{match["before"]}{code.rstrip()}{trailing_ws}' @@ -202,7 +202,7 @@ def finish_fragment() -> None: if fragment is not None: with _collect_error(match): - fragment = black.format_str(fragment, mode=black_mode) + fragment = format_str(fragment, mode=black_mode) fragment_lines = fragment.splitlines() code += f"{PYCON_PREFIX}{fragment_lines[0]}\n" for line in fragment_lines[1:]: @@ -260,7 +260,7 @@ def _latex_match(match: Match[str]) -> str: return match[0] code = textwrap.dedent(match["code"]) with _collect_error(match): - code = black.format_str(code, mode=black_mode) + code = format_str(code, mode=black_mode) code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' @@ -288,7 +288,7 @@ def _latex_pycon_match(match: Match[str]) -> str: def format_file( file: Path, - black_mode: black.FileMode, + black_mode: FileMode, skip_errors: bool, rst_literal_blocks: bool, check_only: bool, @@ -296,7 +296,7 @@ def format_file( """Format a file with ruff.""" with file.open(encoding="UTF-8") as f: contents = f.read() - new_contents, errors = format_str( + new_contents, errors = format_file_contents( contents, black_mode, rst_literal_blocks=rst_literal_blocks, @@ -351,7 +351,7 @@ def main(argv: Sequence[str] | None = None) -> int: parser.add_argument("filenames", nargs="*") args = parser.parse_args(argv) - black_mode = black.Mode( + black_mode = Mode( target_versions=set(args.target_versions), line_length=args.line_length, string_normalization=not args.skip_string_normalization, diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index 3f37224..b288485 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -7,21 +7,21 @@ from textwrap import dedent from typing import TYPE_CHECKING -import black +from black import FileMode from black.const import DEFAULT_LINE_LENGTH -import ruff_format_docs +from ruff_format_docs import format_file_contents, main if TYPE_CHECKING: from pathlib import Path from _pytest.capture import CaptureFixture -BLACK_MODE = black.FileMode(line_length=DEFAULT_LINE_LENGTH) +BLACK_MODE = FileMode(line_length=DEFAULT_LINE_LENGTH) def test_format_src_trivial() -> None: - after, _ = ruff_format_docs.format_str("", BLACK_MODE) + after, _ = format_file_contents("", BLACK_MODE) assert after == "" @@ -33,7 +33,7 @@ def test_format_src_markdown_simple() -> None: ``` """, ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( """\ ```python @@ -51,7 +51,7 @@ def test_format_src_markdown_leading_whitespace() -> None: ``` """, ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( """\ ``` python @@ -70,7 +70,7 @@ def test_format_src_markdown_python_after_newline() -> None: ``` """, ) - after, errors = ruff_format_docs.format_str(before, BLACK_MODE) + after, errors = format_file_contents(before, BLACK_MODE) assert errors == [] assert after == before @@ -83,7 +83,7 @@ def test_format_src_markdown_short_name() -> None: ``` """, ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( """\ ``` py @@ -101,7 +101,7 @@ def test_format_src_markdown_options() -> None: ``` """, ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( """\ ```python title='example.py' @@ -118,7 +118,7 @@ def test_format_src_markdown_trailing_whitespace() -> None: f(1,2,3) ``` \n""", ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( """\ ```python @@ -137,7 +137,7 @@ def test_format_src_indented_markdown() -> None: - also this """, ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( """\ - do this pls: @@ -160,7 +160,7 @@ def test_format_src_markdown_pycon() -> None: "```\n" "world\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ("hello\n\n```pycon\n\n>>> f(1, 2, 3)\noutput\n```\nworld\n") @@ -173,7 +173,7 @@ def test_format_src_markdown_pycon_after_newline() -> None: ``` """, ) - after, errors = ruff_format_docs.format_str(before, BLACK_MODE) + after, errors = format_file_contents(before, BLACK_MODE) assert errors == [] assert after == before @@ -189,7 +189,7 @@ def test_format_src_markdown_pycon_options() -> None: "```\n" "world\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "hello\n" "\n" @@ -214,7 +214,7 @@ def test_format_src_markdown_pycon_twice() -> None: "output\n" "```\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "```pycon\n" ">>> f(1, 2, 3)\n" @@ -236,7 +236,7 @@ def test_format_src_markdown_comments_disable() -> None: "```\n" "\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -251,7 +251,7 @@ def test_format_src_markdown_comments_disabled_enabled() -> None: "'double quotes rock'\n" "```\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "\n" "```python\n" @@ -272,7 +272,7 @@ def test_format_src_markdown_comments_before() -> None: "'double quotes rock'\n" "```\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "\n" "\n" @@ -290,7 +290,7 @@ def test_format_src_markdown_comments_after() -> None: "\n" "\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "```python\n" '"double quotes rock"\n' @@ -308,7 +308,7 @@ def test_format_src_markdown_comments_only_on() -> None: "'double quotes rock'\n" "```\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "\n" "```python\n" @@ -327,7 +327,7 @@ def test_format_src_markdown_comments_only_off() -> None: "```\n" ) # fmt: on - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -343,7 +343,7 @@ def test_format_src_markdown_comments_multiple() -> None: "'single quotes rock'\n" "```\n" # no on comment, off until the end ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -357,7 +357,7 @@ def test_on_off_comments_in_code_blocks() -> None: "\n" "````\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -369,13 +369,13 @@ def test_format_src_markdown_comments_disable_pycon() -> None: "```\n" "\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before def test_format_src_latex_minted() -> None: before = "hello\n\\begin{minted}{python}\nf(1,2,3)\n\\end{minted}\nworld!" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "hello\n\\begin{minted}{python}\nf(1, 2, 3)\n\\end{minted}\nworld!" ) @@ -392,7 +392,7 @@ def test_format_src_latex_minted_opt() -> None: "\\end{minted}\n" "done" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "maths!\n" "\\begin{minted}[mathescape]{python}\n" @@ -418,7 +418,7 @@ def test_format_src_latex_minted_indented() -> None: world! """, ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( """\ hello @@ -440,7 +440,7 @@ def test_format_src_latex_minted_pycon() -> None: "\\end{minted}\n" "Following text." ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "Preceding text\n" "\\begin{minted}[gobble=2,showspaces]{pycon}\n" @@ -462,7 +462,7 @@ def test_format_src_latex_minted_pycon_indented() -> None: " \\end{minted}\n" "Following text." ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "Preceding text\n" " \\begin{minted}{pycon}\n" @@ -481,7 +481,7 @@ def test_format_src_latex_minted_comments_off() -> None: "\\end{minted}\n" "% blacken-docs:on\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -493,7 +493,7 @@ def test_format_src_latex_minted_comments_off_pycon() -> None: "\\end{minted}\n" "% blacken-docs:on\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -506,7 +506,7 @@ def test_format_src_pythontex() -> None: "\\end{pyblock}\n" "world!" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "hello\n" "\\begin{pyblock}\n" @@ -525,19 +525,19 @@ def test_format_src_pythontex_comments_off() -> None: "\\end{pyblock}\n" "% blacken-docs:on\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before def test_format_src_rst() -> None: before = "hello\n\n.. code-block:: python\n\n f(1,2,3)\n\nworld\n" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ("hello\n\n.. code-block:: python\n\n f(1, 2, 3)\n\nworld\n") def test_format_src_rst_empty() -> None: before = "some text\n\n.. code-block:: python\n\n\nsome other text\n" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -551,7 +551,7 @@ def test_format_src_rst_literal_blocks() -> None: world """, ) - after, _ = ruff_format_docs.format_str( + after, _ = format_file_contents( before, BLACK_MODE, rst_literal_blocks=True, @@ -574,7 +574,7 @@ def test_format_src_rst_literal_block_empty() -> None: world """, ) - after, _ = ruff_format_docs.format_str( + after, _ = format_file_contents( before, BLACK_MODE, rst_literal_blocks=True, @@ -592,7 +592,7 @@ def test_format_src_rst_literal_blocks_nested() -> None: don't hello too much """, ) - after, errors = ruff_format_docs.format_str( + after, errors = format_file_contents( before, BLACK_MODE, rst_literal_blocks=True, @@ -611,7 +611,7 @@ def test_format_src_rst_literal_blocks_empty() -> None: There was no example. """, ) - after, errors = ruff_format_docs.format_str( + after, errors = format_file_contents( before, BLACK_MODE, rst_literal_blocks=True, @@ -629,7 +629,7 @@ def test_format_src_rst_literal_blocks_comments() -> None: "\n" ".. blacken-docs:on\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE, rst_literal_blocks=True) + after, _ = format_file_contents(before, BLACK_MODE, rst_literal_blocks=True) assert after == before @@ -654,7 +654,7 @@ def test_format_src_rst_sphinx_doctest() -> None: " parrot.voom( 3000 )\n" "\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. testsetup:: group1\n" "\n" @@ -693,7 +693,7 @@ def hi() -> None: world """, ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( """\ .. versionadded:: 3.1 @@ -712,7 +712,7 @@ def hi() -> None: def test_format_src_rst_code_block_indent() -> None: before = ".. code-block:: python\n \n f(1,2,3)\n" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ".. code-block:: python\n \n f(1, 2, 3)\n" @@ -725,7 +725,7 @@ def test_format_src_rst_with_highlight_directives() -> None: " def foo():\n" " bar(1,2,3)\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. code-block:: python\n" " :lineno-start: 10\n" @@ -747,7 +747,7 @@ def test_format_src_rst_python_inside_non_python_code_block() -> None: " - 'Hello World'\n" ' + "Hello World"\n' ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -760,7 +760,7 @@ def test_format_src_rst_python_comments() -> None: "\n" ".. blacken-docs:on\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -770,7 +770,7 @@ def test_integration_ok(tmp_path: Path, capsys: CaptureFixture[str]) -> None: "```python\nf(1, 2, 3)\n```\n", ) - result = ruff_format_docs.main((str(f),)) + result = main((str(f),)) assert result == 0 assert not capsys.readouterr()[1] @@ -783,7 +783,7 @@ def test_integration_modifies(tmp_path: Path, capsys: CaptureFixture[str]) -> No "```python\nf(1,2,3)\n```\n", ) - result = ruff_format_docs.main((str(f),)) + result = main((str(f),)) assert result == 1 out, _ = capsys.readouterr() @@ -799,10 +799,10 @@ def test_integration_line_length(tmp_path: Path) -> None: "```\n", ) - result = ruff_format_docs.main((str(f), "--line-length=80")) + result = main((str(f), "--line-length=80")) assert result == 0 - result2 = ruff_format_docs.main((str(f), "--line-length=50")) + result2 = main((str(f), "--line-length=50")) assert result2 == 1 assert f.read_text() == ( "```python\n" @@ -825,7 +825,7 @@ def test_integration_check(tmp_path: Path) -> None: ) f.write_text(text) - result = ruff_format_docs.main((str(f), "--check")) + result = main((str(f), "--check")) assert result == 1 assert f.read_text() == text @@ -843,7 +843,7 @@ def test_integration_preview(tmp_path: Path) -> None: ), ) - result = ruff_format_docs.main((str(f), "--preview")) + result = main((str(f), "--preview")) assert result == 1 assert f.read_text() == dedent( @@ -870,7 +870,7 @@ class Bar: ... ), ) - result = ruff_format_docs.main((str(f), "--pyi")) + result = main((str(f), "--pyi")) assert result == 1 assert f.read_text() == dedent( @@ -896,10 +896,10 @@ def test_integration_py36(tmp_path: Path) -> None: "```\n", ) - result = ruff_format_docs.main((str(f),)) + result = main((str(f),)) assert result == 0 - result2 = ruff_format_docs.main((str(f), "--target-version=py36")) + result2 = main((str(f), "--target-version=py36")) assert result2 == 1 assert f.read_text() == ( @@ -927,10 +927,10 @@ def test_integration_filename_last(tmp_path: Path) -> None: "```\n", ) - result = ruff_format_docs.main((str(f),)) + result = main((str(f),)) assert result == 0 - result2 = ruff_format_docs.main(("--target-version", "py36", str(f))) + result2 = main(("--target-version", "py36", str(f))) assert result2 == 1 assert f.read_text() == ( @@ -958,10 +958,10 @@ def test_integration_multiple_target_version(tmp_path: Path) -> None: "```\n", ) - result = ruff_format_docs.main((str(f),)) + result = main((str(f),)) assert result == 0 - result2 = ruff_format_docs.main( + result2 = main( ("--target-version", "py35", "--target-version", "py36", str(f)), ) assert result2 == 0 @@ -973,7 +973,7 @@ def test_integration_skip_string_normalization(tmp_path: Path) -> None: "```python\nf('hi')\n```\n", ) - result = ruff_format_docs.main((str(f), "--skip-string-normalization")) + result = main((str(f), "--skip-string-normalization")) assert result == 0 assert f.read_text() == ("```python\nf('hi')\n```\n") @@ -985,7 +985,7 @@ def test_integration_syntax_error(tmp_path: Path, capsys: CaptureFixture[str]) - "```python\nf(\n```\n", ) - result = ruff_format_docs.main((str(f),)) + result = main((str(f),)) assert result == 2 out, _ = capsys.readouterr() @@ -1002,7 +1002,7 @@ def test_integration_ignored_syntax_error( "```python\nf( )\n```\n\n```python\nf(\n```\n", ) - result = ruff_format_docs.main((str(f), "--skip-errors")) + result = main((str(f), "--skip-errors")) assert result == 1 out, _ = capsys.readouterr() @@ -1011,7 +1011,7 @@ def test_integration_ignored_syntax_error( def test_format_src_rst_jupyter_sphinx() -> None: before = "hello\n\n.. jupyter-execute::\n\n f(1,2,3)\n\nworld\n" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ("hello\n\n.. jupyter-execute::\n\n f(1, 2, 3)\n\nworld\n") @@ -1026,7 +1026,7 @@ def test_format_src_rst_jupyter_sphinx_with_directive() -> None: "\n" "world\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "hello\n" "\n" @@ -1053,7 +1053,7 @@ def f(): pass ''', ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( '''\ def f(): @@ -1083,7 +1083,7 @@ def f(): pass ''', ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == dedent( '''\ def f(): @@ -1110,7 +1110,7 @@ def test_format_src_rst_pycon() -> None: "\n" "world\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( "hello\n" "\n" @@ -1133,7 +1133,7 @@ def test_format_src_rst_pycon_with_continuation() -> None: ' ... "c": 3,}\n' "\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1148,7 +1148,7 @@ def test_format_src_rst_pycon_with_continuation() -> None: def test_format_src_rst_pycon_adds_continuation() -> None: before = """.. code-block:: pycon\n\n >>> d = {"a": 1,"b": 2,"c": 3,}\n\n""" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1173,7 +1173,7 @@ def test_format_src_rst_pycon_preserves_trailing_whitespace() -> None: "\n" "world\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -1191,7 +1191,7 @@ def test_format_src_rst_pycon_indented() -> None: "\n" " world\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. versionadded:: 3.1\n" "\n" @@ -1215,7 +1215,7 @@ def test_format_src_rst_pycon_code_block_is_final_line1() -> None: " ... pass\n" " ...\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1227,7 +1227,7 @@ def test_format_src_rst_pycon_code_block_is_final_line1() -> None: def test_format_src_rst_pycon_code_block_is_final_line2() -> None: before = ".. code-block:: pycon\n\n >>> if True:\n ... pass\n" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1245,7 +1245,7 @@ def test_format_src_rst_pycon_nested_def1() -> None: " ... def f(): pass\n" " ...\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1263,7 +1263,7 @@ def test_format_src_rst_pycon_nested_def2() -> None: " >>> if True:\n" " ... def f(): pass\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1283,7 +1283,7 @@ def test_format_src_rst_pycon_empty_line() -> None: " ... 1,\n" " ... ]\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1302,7 +1302,7 @@ def test_format_src_rst_pycon_preserves_output_indentation() -> None: ' File "", line 1, in \n' " ZeroDivisionError: division by zero\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before @@ -1315,19 +1315,19 @@ def test_format_src_rst_pycon_elided_traceback() -> None: " ...\n" " ZeroDivisionError: division by zero\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before def test_format_src_rst_pycon_no_prompt() -> None: before = ".. code-block:: pycon\n\n pass\n" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before def test_format_src_rst_pycon_no_trailing_newline() -> None: before = ".. code-block:: pycon\n\n >>> pass" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == (".. code-block:: pycon\n\n >>> pass\n") @@ -1338,7 +1338,7 @@ def test_format_src_rst_pycon_comment_before_promopt() -> None: " # Comment about next line\n" " >>> pass\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1356,11 +1356,11 @@ def test_format_src_rst_pycon_comments() -> None: "\n" ".. blacken-docs:on\n" ) - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before def test_format_src_rst_pycon_empty() -> None: before = "some text\n\n.. code-block:: pycon\n\n\nsome other text\n" - after, _ = ruff_format_docs.format_str(before, BLACK_MODE) + after, _ = format_file_contents(before, BLACK_MODE) assert after == before From 2e6c1a9ffcfafe0b4a89ea7fd6f8e6e3b79e2505 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 01:11:56 +0000 Subject: [PATCH 24/35] Generalise config --- src/ruff_format_docs/__init__.py | 12 +-- tests/test_ruff_format_docs.py | 130 +++++++++++++++---------------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py index f7a11e9..7e455c9 100644 --- a/src/ruff_format_docs/__init__.py +++ b/src/ruff_format_docs/__init__.py @@ -114,7 +114,7 @@ def __init__(self, offset: int, exc: Exception) -> None: def format_file_contents( # noqa: PLR0915 src: str, - black_mode: FileMode, + config: FileMode, *, rst_literal_blocks: bool = False, ) -> tuple[str, Sequence[CodeBlockError]]: @@ -155,7 +155,7 @@ def _md_match(match: Match[str]) -> str: return match[0] code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, mode=black_mode) + code = format_str(code, mode=config) code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' @@ -173,7 +173,7 @@ def _rst_match(match: Match[str]) -> str: trailing_ws = trailing_ws_match.group() code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, mode=black_mode) + code = format_str(code, mode=config) code = textwrap.indent(code, min_indent) return f'{match["before"]}{code.rstrip()}{trailing_ws}' @@ -188,7 +188,7 @@ def _rst_literal_blocks_match(match: Match[str]) -> str: trailing_ws = trailing_ws_match.group() code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, mode=black_mode) + code = format_str(code, mode=config) code = textwrap.indent(code, min_indent) return f'{match["before"]}{code.rstrip()}{trailing_ws}' @@ -202,7 +202,7 @@ def finish_fragment() -> None: if fragment is not None: with _collect_error(match): - fragment = format_str(fragment, mode=black_mode) + fragment = format_str(fragment, mode=config) fragment_lines = fragment.splitlines() code += f"{PYCON_PREFIX}{fragment_lines[0]}\n" for line in fragment_lines[1:]: @@ -260,7 +260,7 @@ def _latex_match(match: Match[str]) -> str: return match[0] code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, mode=black_mode) + code = format_str(code, mode=config) code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index b288485..406bc9f 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -17,11 +17,11 @@ from _pytest.capture import CaptureFixture -BLACK_MODE = FileMode(line_length=DEFAULT_LINE_LENGTH) +FORMATTER_CONFIG = FileMode(line_length=DEFAULT_LINE_LENGTH) def test_format_src_trivial() -> None: - after, _ = format_file_contents("", BLACK_MODE) + after, _ = format_file_contents("", FORMATTER_CONFIG) assert after == "" @@ -33,7 +33,7 @@ def test_format_src_markdown_simple() -> None: ``` """, ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( """\ ```python @@ -51,7 +51,7 @@ def test_format_src_markdown_leading_whitespace() -> None: ``` """, ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( """\ ``` python @@ -70,7 +70,7 @@ def test_format_src_markdown_python_after_newline() -> None: ``` """, ) - after, errors = format_file_contents(before, BLACK_MODE) + after, errors = format_file_contents(before, FORMATTER_CONFIG) assert errors == [] assert after == before @@ -83,7 +83,7 @@ def test_format_src_markdown_short_name() -> None: ``` """, ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( """\ ``` py @@ -101,7 +101,7 @@ def test_format_src_markdown_options() -> None: ``` """, ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( """\ ```python title='example.py' @@ -118,7 +118,7 @@ def test_format_src_markdown_trailing_whitespace() -> None: f(1,2,3) ``` \n""", ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( """\ ```python @@ -137,7 +137,7 @@ def test_format_src_indented_markdown() -> None: - also this """, ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( """\ - do this pls: @@ -160,7 +160,7 @@ def test_format_src_markdown_pycon() -> None: "```\n" "world\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ("hello\n\n```pycon\n\n>>> f(1, 2, 3)\noutput\n```\nworld\n") @@ -173,7 +173,7 @@ def test_format_src_markdown_pycon_after_newline() -> None: ``` """, ) - after, errors = format_file_contents(before, BLACK_MODE) + after, errors = format_file_contents(before, FORMATTER_CONFIG) assert errors == [] assert after == before @@ -189,7 +189,7 @@ def test_format_src_markdown_pycon_options() -> None: "```\n" "world\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "hello\n" "\n" @@ -214,7 +214,7 @@ def test_format_src_markdown_pycon_twice() -> None: "output\n" "```\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "```pycon\n" ">>> f(1, 2, 3)\n" @@ -236,7 +236,7 @@ def test_format_src_markdown_comments_disable() -> None: "```\n" "\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -251,7 +251,7 @@ def test_format_src_markdown_comments_disabled_enabled() -> None: "'double quotes rock'\n" "```\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "\n" "```python\n" @@ -272,7 +272,7 @@ def test_format_src_markdown_comments_before() -> None: "'double quotes rock'\n" "```\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "\n" "\n" @@ -290,7 +290,7 @@ def test_format_src_markdown_comments_after() -> None: "\n" "\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "```python\n" '"double quotes rock"\n' @@ -308,7 +308,7 @@ def test_format_src_markdown_comments_only_on() -> None: "'double quotes rock'\n" "```\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "\n" "```python\n" @@ -327,7 +327,7 @@ def test_format_src_markdown_comments_only_off() -> None: "```\n" ) # fmt: on - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -343,7 +343,7 @@ def test_format_src_markdown_comments_multiple() -> None: "'single quotes rock'\n" "```\n" # no on comment, off until the end ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -357,7 +357,7 @@ def test_on_off_comments_in_code_blocks() -> None: "\n" "````\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -369,13 +369,13 @@ def test_format_src_markdown_comments_disable_pycon() -> None: "```\n" "\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_latex_minted() -> None: before = "hello\n\\begin{minted}{python}\nf(1,2,3)\n\\end{minted}\nworld!" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "hello\n\\begin{minted}{python}\nf(1, 2, 3)\n\\end{minted}\nworld!" ) @@ -392,7 +392,7 @@ def test_format_src_latex_minted_opt() -> None: "\\end{minted}\n" "done" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "maths!\n" "\\begin{minted}[mathescape]{python}\n" @@ -418,7 +418,7 @@ def test_format_src_latex_minted_indented() -> None: world! """, ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( """\ hello @@ -440,7 +440,7 @@ def test_format_src_latex_minted_pycon() -> None: "\\end{minted}\n" "Following text." ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "Preceding text\n" "\\begin{minted}[gobble=2,showspaces]{pycon}\n" @@ -462,7 +462,7 @@ def test_format_src_latex_minted_pycon_indented() -> None: " \\end{minted}\n" "Following text." ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "Preceding text\n" " \\begin{minted}{pycon}\n" @@ -481,7 +481,7 @@ def test_format_src_latex_minted_comments_off() -> None: "\\end{minted}\n" "% blacken-docs:on\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -493,7 +493,7 @@ def test_format_src_latex_minted_comments_off_pycon() -> None: "\\end{minted}\n" "% blacken-docs:on\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -506,7 +506,7 @@ def test_format_src_pythontex() -> None: "\\end{pyblock}\n" "world!" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "hello\n" "\\begin{pyblock}\n" @@ -525,19 +525,19 @@ def test_format_src_pythontex_comments_off() -> None: "\\end{pyblock}\n" "% blacken-docs:on\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst() -> None: before = "hello\n\n.. code-block:: python\n\n f(1,2,3)\n\nworld\n" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ("hello\n\n.. code-block:: python\n\n f(1, 2, 3)\n\nworld\n") def test_format_src_rst_empty() -> None: before = "some text\n\n.. code-block:: python\n\n\nsome other text\n" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -553,7 +553,7 @@ def test_format_src_rst_literal_blocks() -> None: ) after, _ = format_file_contents( before, - BLACK_MODE, + FORMATTER_CONFIG, rst_literal_blocks=True, ) assert after == dedent( @@ -576,7 +576,7 @@ def test_format_src_rst_literal_block_empty() -> None: ) after, _ = format_file_contents( before, - BLACK_MODE, + FORMATTER_CONFIG, rst_literal_blocks=True, ) assert after == before @@ -594,7 +594,7 @@ def test_format_src_rst_literal_blocks_nested() -> None: ) after, errors = format_file_contents( before, - BLACK_MODE, + FORMATTER_CONFIG, rst_literal_blocks=True, ) assert after == before @@ -613,7 +613,7 @@ def test_format_src_rst_literal_blocks_empty() -> None: ) after, errors = format_file_contents( before, - BLACK_MODE, + FORMATTER_CONFIG, rst_literal_blocks=True, ) assert after == before @@ -629,7 +629,7 @@ def test_format_src_rst_literal_blocks_comments() -> None: "\n" ".. blacken-docs:on\n" ) - after, _ = format_file_contents(before, BLACK_MODE, rst_literal_blocks=True) + after, _ = format_file_contents(before, FORMATTER_CONFIG, rst_literal_blocks=True) assert after == before @@ -654,7 +654,7 @@ def test_format_src_rst_sphinx_doctest() -> None: " parrot.voom( 3000 )\n" "\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. testsetup:: group1\n" "\n" @@ -693,7 +693,7 @@ def hi() -> None: world """, ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( """\ .. versionadded:: 3.1 @@ -712,7 +712,7 @@ def hi() -> None: def test_format_src_rst_code_block_indent() -> None: before = ".. code-block:: python\n \n f(1,2,3)\n" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ".. code-block:: python\n \n f(1, 2, 3)\n" @@ -725,7 +725,7 @@ def test_format_src_rst_with_highlight_directives() -> None: " def foo():\n" " bar(1,2,3)\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. code-block:: python\n" " :lineno-start: 10\n" @@ -747,7 +747,7 @@ def test_format_src_rst_python_inside_non_python_code_block() -> None: " - 'Hello World'\n" ' + "Hello World"\n' ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -760,7 +760,7 @@ def test_format_src_rst_python_comments() -> None: "\n" ".. blacken-docs:on\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -1011,7 +1011,7 @@ def test_integration_ignored_syntax_error( def test_format_src_rst_jupyter_sphinx() -> None: before = "hello\n\n.. jupyter-execute::\n\n f(1,2,3)\n\nworld\n" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ("hello\n\n.. jupyter-execute::\n\n f(1, 2, 3)\n\nworld\n") @@ -1026,7 +1026,7 @@ def test_format_src_rst_jupyter_sphinx_with_directive() -> None: "\n" "world\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "hello\n" "\n" @@ -1053,7 +1053,7 @@ def f(): pass ''', ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( '''\ def f(): @@ -1083,7 +1083,7 @@ def f(): pass ''', ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent( '''\ def f(): @@ -1110,7 +1110,7 @@ def test_format_src_rst_pycon() -> None: "\n" "world\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( "hello\n" "\n" @@ -1133,7 +1133,7 @@ def test_format_src_rst_pycon_with_continuation() -> None: ' ... "c": 3,}\n' "\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1148,7 +1148,7 @@ def test_format_src_rst_pycon_with_continuation() -> None: def test_format_src_rst_pycon_adds_continuation() -> None: before = """.. code-block:: pycon\n\n >>> d = {"a": 1,"b": 2,"c": 3,}\n\n""" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1173,7 +1173,7 @@ def test_format_src_rst_pycon_preserves_trailing_whitespace() -> None: "\n" "world\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -1191,7 +1191,7 @@ def test_format_src_rst_pycon_indented() -> None: "\n" " world\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. versionadded:: 3.1\n" "\n" @@ -1215,7 +1215,7 @@ def test_format_src_rst_pycon_code_block_is_final_line1() -> None: " ... pass\n" " ...\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1227,7 +1227,7 @@ def test_format_src_rst_pycon_code_block_is_final_line1() -> None: def test_format_src_rst_pycon_code_block_is_final_line2() -> None: before = ".. code-block:: pycon\n\n >>> if True:\n ... pass\n" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1245,7 +1245,7 @@ def test_format_src_rst_pycon_nested_def1() -> None: " ... def f(): pass\n" " ...\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1263,7 +1263,7 @@ def test_format_src_rst_pycon_nested_def2() -> None: " >>> if True:\n" " ... def f(): pass\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1283,7 +1283,7 @@ def test_format_src_rst_pycon_empty_line() -> None: " ... 1,\n" " ... ]\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1302,7 +1302,7 @@ def test_format_src_rst_pycon_preserves_output_indentation() -> None: ' File "", line 1, in \n' " ZeroDivisionError: division by zero\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -1315,19 +1315,19 @@ def test_format_src_rst_pycon_elided_traceback() -> None: " ...\n" " ZeroDivisionError: division by zero\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_pycon_no_prompt() -> None: before = ".. code-block:: pycon\n\n pass\n" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_pycon_no_trailing_newline() -> None: before = ".. code-block:: pycon\n\n >>> pass" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == (".. code-block:: pycon\n\n >>> pass\n") @@ -1338,7 +1338,7 @@ def test_format_src_rst_pycon_comment_before_promopt() -> None: " # Comment about next line\n" " >>> pass\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == ( ".. code-block:: pycon\n" "\n" @@ -1356,11 +1356,11 @@ def test_format_src_rst_pycon_comments() -> None: "\n" ".. blacken-docs:on\n" ) - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_pycon_empty() -> None: before = "some text\n\n.. code-block:: pycon\n\n\nsome other text\n" - after, _ = format_file_contents(before, BLACK_MODE) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before From ea682ac67e81af99b4f76093f19e4b20dfd48fc1 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 01:51:00 +0000 Subject: [PATCH 25/35] Use consistent multi line strings --- tests/test_ruff_format_docs.py | 1542 +++++++++++++++++--------------- 1 file changed, 810 insertions(+), 732 deletions(-) diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index 406bc9f..e401d15 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -26,306 +26,287 @@ def test_format_src_trivial() -> None: def test_format_src_markdown_simple() -> None: - before = dedent( - """\ + before = dedent("""\ ```python f(1,2,3) ``` - """, - ) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - """\ + assert after == dedent("""\ ```python f(1, 2, 3) ``` - """, - ) + """) def test_format_src_markdown_leading_whitespace() -> None: - before = dedent( - """\ + before = dedent("""\ ``` python f(1,2,3) ``` - """, - ) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - """\ + assert after == dedent("""\ ``` python f(1, 2, 3) ``` - """, - ) + """) def test_format_src_markdown_python_after_newline() -> None: - before = dedent( - """\ + before = dedent("""\ ``` python --version echo "python" ``` - """, - ) + """) after, errors = format_file_contents(before, FORMATTER_CONFIG) assert errors == [] assert after == before def test_format_src_markdown_short_name() -> None: - before = dedent( - """\ + before = dedent("""\ ``` py f(1,2,3) ``` - """, - ) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - """\ + assert after == dedent("""\ ``` py f(1, 2, 3) ``` - """, - ) + """) def test_format_src_markdown_options() -> None: - before = dedent( - """\ + before = dedent("""\ ```python title='example.py' f(1,2,3) ``` - """, - ) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - """\ + assert after == dedent("""\ ```python title='example.py' f(1, 2, 3) ``` - """, - ) + """) def test_format_src_markdown_trailing_whitespace() -> None: - before = dedent( - """\ + before = dedent("""\ ```python f(1,2,3) - ``` \n""", - ) + ``` \n""") after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - """\ + assert after == dedent("""\ ```python f(1, 2, 3) - ``` \n""", - ) + ``` \n""") def test_format_src_indented_markdown() -> None: - before = dedent( - """\ + before = dedent("""\ - do this pls: ```python f(1,2,3) ``` - also this - """, - ) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - """\ + assert after == dedent("""\ - do this pls: ```python f(1, 2, 3) ``` - also this - """, - ) + """) def test_format_src_markdown_pycon() -> None: - before = ( - "hello\n" - "\n" - "```pycon\n" - "\n" - " >>> f(1,2,3)\n" - " output\n" - "```\n" - "world\n" - ) + before = dedent("""\ + hello + + ```pycon + + >>> f(1,2,3) + output + ``` + world + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ("hello\n\n```pycon\n\n>>> f(1, 2, 3)\noutput\n```\nworld\n") + assert after == dedent("""\ + hello + + ```pycon + + >>> f(1, 2, 3) + output + ``` + world + """) def test_format_src_markdown_pycon_after_newline() -> None: - before = dedent( - """\ + before = dedent("""\ ``` pycon is great >>> yes it is ``` - """, - ) + """) after, errors = format_file_contents(before, FORMATTER_CONFIG) assert errors == [] assert after == before def test_format_src_markdown_pycon_options() -> None: - before = ( - "hello\n" - "\n" - "```pycon title='Session 1'\n" - "\n" - " >>> f(1,2,3)\n" - " output\n" - "```\n" - "world\n" - ) + before = dedent("""\ + hello + + ```pycon title='Session 1' + + >>> f(1,2,3) + output + ``` + world + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "hello\n" - "\n" - "```pycon title='Session 1'\n" - "\n" - ">>> f(1, 2, 3)\n" - "output\n" - "```\n" - "world\n" - ) + assert after == dedent("""\ + hello + + ```pycon title='Session 1' + + >>> f(1, 2, 3) + output + ``` + world + """) def test_format_src_markdown_pycon_twice() -> None: - before = ( - "```pycon\n" - ">>> f(1,2,3)\n" - "output\n" - "```\n" - "example 2\n" - "```pycon\n" - ">>> f(1,2,3)\n" - "output\n" - "```\n" - ) + before = dedent("""\ + ```pycon + >>> f(1,2,3) + output + ``` + example 2 + ```pycon + >>> f(1,2,3) + output + ``` + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "```pycon\n" - ">>> f(1, 2, 3)\n" - "output\n" - "```\n" - "example 2\n" - "```pycon\n" - ">>> f(1, 2, 3)\n" - "output\n" - "```\n" - ) + assert after == dedent("""\ + ```pycon + >>> f(1, 2, 3) + output + ``` + example 2 + ```pycon + >>> f(1, 2, 3) + output + ``` + """) def test_format_src_markdown_comments_disable() -> None: - before = ( - "\n" - "```python\n" - "'single quotes rock'\n" - "```\n" - "\n" - ) + before = dedent("""\ + + ```python + 'single quotes rock' + ``` + + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_markdown_comments_disabled_enabled() -> None: - before = ( - "\n" - "```python\n" - "'single quotes rock'\n" - "```\n" - "\n" - "```python\n" - "'double quotes rock'\n" - "```\n" - ) + before = dedent("""\ + + ```python + 'single quotes rock' + ``` + + ```python + 'double quotes rock' + ``` + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "\n" - "```python\n" - "'single quotes rock'\n" - "```\n" - "\n" - "```python\n" - '"double quotes rock"\n' - "```\n" - ) + assert after == dedent("""\ + + ```python + 'single quotes rock' + ``` + + ```python + "double quotes rock" + ``` + """) def test_format_src_markdown_comments_before() -> None: - before = ( - "\n" - "\n" - "```python\n" - "'double quotes rock'\n" - "```\n" - ) + before = dedent("""\ + + + ```python + 'double quotes rock' + ``` + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "\n" - "\n" - "```python\n" - '"double quotes rock"\n' - "```\n" - ) + assert after == dedent("""\ + + + ```python + "double quotes rock" + ``` + """) def test_format_src_markdown_comments_after() -> None: - before = ( - "```python\n" - "'double quotes rock'\n" - "```\n" - "\n" - "\n" - ) + before = dedent("""\ + ```python + 'double quotes rock' + ``` + + + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "```python\n" - '"double quotes rock"\n' - "```\n" - "\n" - "\n" - ) + assert after == dedent("""\ + ```python + "double quotes rock" + ``` + + + """) def test_format_src_markdown_comments_only_on() -> None: # fmt: off - before = ( - "\n" - "```python\n" - "'double quotes rock'\n" - "```\n" - ) + before = dedent("""\ + + ```python + 'double quotes rock' + ``` + """ ) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "\n" - "```python\n" - '"double quotes rock"\n' - "```\n" - ) + assert after == dedent("""\ + + ```python + "double quotes rock" + ``` + """ ) # fmt: on def test_format_src_markdown_comments_only_off() -> None: # fmt: off - before = ( - "\n" - "```python\n" - "'single quotes rock'\n" - "```\n" - ) + before = dedent("""\ + + ```python + 'single quotes rock' + ``` + """ ) # fmt: on after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -348,232 +329,245 @@ def test_format_src_markdown_comments_multiple() -> None: def test_on_off_comments_in_code_blocks() -> None: - before = ( - "````md\n" - "\n" - "```python\n" - "f(1,2,3)\n" - "```\n" - "\n" - "````\n" - ) + before = dedent("""\ + ````md + + ```python + f(1,2,3) + ``` + + ```` + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_markdown_comments_disable_pycon() -> None: - before = ( - "\n" - "```pycon\n" - ">>> 'single quotes rock'\n" - "```\n" - "\n" - ) + before = dedent("""\ + + ```pycon + >>> 'single quotes rock' + ``` + + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_latex_minted() -> None: - before = "hello\n\\begin{minted}{python}\nf(1,2,3)\n\\end{minted}\nworld!" + before = dedent("""\ + hello + \\begin{minted}{python} + f(1,2,3) + \\end{minted} + world!""") after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "hello\n\\begin{minted}{python}\nf(1, 2, 3)\n\\end{minted}\nworld!" - ) + assert after == dedent("""\ + hello + \\begin{minted}{python} + f(1, 2, 3) + \\end{minted} + world!""") def test_format_src_latex_minted_opt() -> None: - before = ( - "maths!\n" - "\\begin{minted}[mathescape]{python}\n" - "# Returns $\\sum_{i=1}^{n}i$\n" - "def sum_from_one_to(n):\n" - " r = range(1, n+1)\n" - " return sum(r)\n" - "\\end{minted}\n" - "done" - ) + before = dedent("""\ + maths! + \\begin{minted}[mathescape]{python} + # Returns $\\sum_{i=1}^{n}i$ + def sum_from_one_to(n): + r = range(1, n+1) + return sum(r) + \\end{minted} + done""") after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "maths!\n" - "\\begin{minted}[mathescape]{python}\n" - "# Returns $\\sum_{i=1}^{n}i$\n" - "def sum_from_one_to(n):\n" - " r = range(1, n + 1)\n" - " return sum(r)\n" - "\\end{minted}\n" - "done" - ) + assert after == dedent("""\ + maths! + \\begin{minted}[mathescape]{python} + # Returns $\\sum_{i=1}^{n}i$ + def sum_from_one_to(n): + r = range(1, n + 1) + return sum(r) + \\end{minted} + done""") def test_format_src_latex_minted_indented() -> None: # Personally I would have minted python code all flush left, # with only the Python code's own four space indentation: - before = dedent( - """\ + before = dedent("""\ hello \\begin{minted}{python} if True: f(1,2,3) \\end{minted} world! - """, - ) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - """\ + assert after == dedent("""\ hello \\begin{minted}{python} if True: f(1, 2, 3) \\end{minted} world! - """, - ) + """) def test_format_src_latex_minted_pycon() -> None: - before = ( - "Preceding text\n" - "\\begin{minted}[gobble=2,showspaces]{pycon}\n" - ">>> print( 'Hello World' )\n" - "Hello World\n" - "\\end{minted}\n" - "Following text." - ) + before = dedent("""\ + Preceding text + \\begin{minted}[gobble=2,showspaces]{pycon} + >>> print( 'Hello World' ) + Hello World + \\end{minted} + Following text.""") after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "Preceding text\n" - "\\begin{minted}[gobble=2,showspaces]{pycon}\n" - '>>> print("Hello World")\n' - "Hello World\n" - "\\end{minted}\n" - "Following text." - ) + assert after == dedent("""\ + Preceding text + \\begin{minted}[gobble=2,showspaces]{pycon} + >>> print("Hello World") + Hello World + \\end{minted} + Following text.""") def test_format_src_latex_minted_pycon_indented() -> None: # Nicer style to put the \begin and \end on new lines, # but not actually required for the begin line - before = ( - "Preceding text\n" - " \\begin{minted}{pycon}\n" - " >>> print( 'Hello World' )\n" - " Hello World\n" - " \\end{minted}\n" - "Following text." - ) + before = dedent("""\ + Preceding text + \\begin{minted}{pycon} + >>> print( 'Hello World' ) + Hello World + \\end{minted} + Following text.""") after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "Preceding text\n" - " \\begin{minted}{pycon}\n" - ' >>> print("Hello World")\n' - " Hello World\n" - " \\end{minted}\n" - "Following text." - ) + assert after == dedent("""\ + Preceding text + \\begin{minted}{pycon} + >>> print("Hello World") + Hello World + \\end{minted} + Following text.""") def test_format_src_latex_minted_comments_off() -> None: - before = ( - "% blacken-docs:off\n" - "\\begin{minted}{python}\n" - "'single quotes rock'\n" - "\\end{minted}\n" - "% blacken-docs:on\n" - ) + before = dedent("""\ + % blacken-docs:off + \\begin{minted}{python} + 'single quotes rock' + \\end{minted} + % blacken-docs:on + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_latex_minted_comments_off_pycon() -> None: - before = ( - "% blacken-docs:off\n" - "\\begin{minted}{pycon}\n" - ">>> 'single quotes rock'\n" - "\\end{minted}\n" - "% blacken-docs:on\n" - ) + before = dedent("""\ + % blacken-docs:off + \\begin{minted}{pycon} + >>> 'single quotes rock' + \\end{minted} + % blacken-docs:on + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_pythontex() -> None: # fmt: off - before = ( - "hello\n" - "\\begin{pyblock}\n" - "f(1,2,3)\n" - "\\end{pyblock}\n" - "world!" - ) + before = dedent("""\ + hello + \\begin{pyblock} + f(1,2,3) + \\end{pyblock} + world!""" ) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "hello\n" - "\\begin{pyblock}\n" - "f(1, 2, 3)\n" - "\\end{pyblock}\n" - "world!" - ) + assert after == dedent("""\ + hello + \\begin{pyblock} + f(1, 2, 3) + \\end{pyblock} + world!""" ) # fmt: on def test_format_src_pythontex_comments_off() -> None: - before = ( - "% blacken-docs:off\n" - "\\begin{pyblock}\n" - "f(1,2,3)\n" - "\\end{pyblock}\n" - "% blacken-docs:on\n" - ) + before = dedent("""\ + % blacken-docs:off + \\begin{pyblock} + f(1,2,3) + \\end{pyblock} + % blacken-docs:on + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst() -> None: - before = "hello\n\n.. code-block:: python\n\n f(1,2,3)\n\nworld\n" + before = dedent("""\ + hello + + .. code-block:: python + + f(1,2,3) + + world + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ("hello\n\n.. code-block:: python\n\n f(1, 2, 3)\n\nworld\n") + assert after == dedent("""\ + hello + + .. code-block:: python + + f(1, 2, 3) + + world + """) def test_format_src_rst_empty() -> None: - before = "some text\n\n.. code-block:: python\n\n\nsome other text\n" + before = dedent("""\ + some text + + .. code-block:: python + + + some other text + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_literal_blocks() -> None: - before = dedent( - """\ + before = dedent("""\ hello:: f(1,2,3) world - """, - ) + """) after, _ = format_file_contents( before, FORMATTER_CONFIG, rst_literal_blocks=True, ) - assert after == dedent( - """\ + assert after == dedent("""\ hello:: f(1, 2, 3) world - """, - ) + """) def test_format_src_rst_literal_block_empty() -> None: - before = dedent( - """\ + before = dedent("""\ hello:: world - """, - ) + """) after, _ = format_file_contents( before, FORMATTER_CONFIG, @@ -583,15 +577,14 @@ def test_format_src_rst_literal_block_empty() -> None: def test_format_src_rst_literal_blocks_nested() -> None: - before = dedent( - """ + before = dedent("""\ + * hello .. warning:: don't hello too much - """, - ) + """) after, errors = format_file_contents( before, FORMATTER_CONFIG, @@ -602,15 +595,14 @@ def test_format_src_rst_literal_blocks_nested() -> None: def test_format_src_rst_literal_blocks_empty() -> None: - before = dedent( - """ + before = dedent("""\ + Example:: .. warning:: There was no example. - """, - ) + """) after, errors = format_file_contents( before, FORMATTER_CONFIG, @@ -621,66 +613,65 @@ def test_format_src_rst_literal_blocks_empty() -> None: def test_format_src_rst_literal_blocks_comments() -> None: - before = ( - ".. blacken-docs:off\n" - "Example::\n" - "\n" - " 'single quotes rock'\n" - "\n" - ".. blacken-docs:on\n" - ) + before = dedent("""\ + .. blacken-docs:off + Example:: + + 'single quotes rock' + + .. blacken-docs:on + """) after, _ = format_file_contents(before, FORMATTER_CONFIG, rst_literal_blocks=True) assert after == before def test_format_src_rst_sphinx_doctest() -> None: - before = ( - ".. testsetup:: group1\n" - "\n" - " import parrot \n" - " mock = SomeMock( )\n" - "\n" - ".. testcleanup:: group1\n" - "\n" - " mock.stop( )\n" - "\n" - ".. doctest:: group1\n" - "\n" - " >>> parrot.voom( 3000 )\n" - " This parrot wouldn't voom if you put 3000 volts through it!\n" - "\n" - ".. testcode::\n" - "\n" - " parrot.voom( 3000 )\n" - "\n" - ) + before = dedent("""\ + .. testsetup:: group1 + + import parrot + mock = SomeMock( ) + + .. testcleanup:: group1 + + mock.stop( ) + + .. doctest:: group1 + + >>> parrot.voom( 3000 ) + This parrot wouldn't voom if you put 3000 volts through it! + + .. testcode:: + + parrot.voom( 3000 ) + + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. testsetup:: group1\n" - "\n" - " import parrot\n" - "\n" - " mock = SomeMock()\n" - "\n" - ".. testcleanup:: group1\n" - "\n" - " mock.stop()\n" - "\n" - ".. doctest:: group1\n" - "\n" - " >>> parrot.voom(3000)\n" - " This parrot wouldn't voom if you put 3000 volts through it!\n" - "\n" - ".. testcode::\n" - "\n" - " parrot.voom(3000)\n" - "\n" - ) + assert after == dedent("""\ + .. testsetup:: group1 + + import parrot + + mock = SomeMock() + + .. testcleanup:: group1 + + mock.stop() + + .. doctest:: group1 + + >>> parrot.voom(3000) + This parrot wouldn't voom if you put 3000 volts through it! + + .. testcode:: + + parrot.voom(3000) + + """) def test_format_src_rst_indented() -> None: - before = dedent( - """\ + before = dedent("""\ .. versionadded:: 3.1 hello @@ -691,11 +682,9 @@ def hi() -> None: f(1,2,3) world - """, - ) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - """\ + assert after == dedent("""\ .. versionadded:: 3.1 hello @@ -706,60 +695,68 @@ def hi() -> None: f(1, 2, 3) world - """, - ) + """) def test_format_src_rst_code_block_indent() -> None: - before = ".. code-block:: python\n \n f(1,2,3)\n" + before = dedent("""\ + .. code-block:: python + + f(1,2,3) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ".. code-block:: python\n \n f(1, 2, 3)\n" + assert after == dedent("""\ + .. code-block:: python + + f(1, 2, 3) + """) def test_format_src_rst_with_highlight_directives() -> None: - before = ( - ".. code-block:: python\n" - " :lineno-start: 10\n" - " :emphasize-lines: 11\n" - "\n" - " def foo():\n" - " bar(1,2,3)\n" - ) + before = dedent("""\ + .. code-block:: python + :lineno-start: 10 + :emphasize-lines: 11 + + def foo(): + bar(1,2,3) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. code-block:: python\n" - " :lineno-start: 10\n" - " :emphasize-lines: 11\n" - "\n" - " def foo():\n" - " bar(1, 2, 3)\n" - ) + assert after == dedent("""\ + .. code-block:: python + :lineno-start: 10 + :emphasize-lines: 11 + + def foo(): + bar(1, 2, 3) + """) def test_format_src_rst_python_inside_non_python_code_block() -> None: - before = ( - "blacken-docs does changes like:\n" - "\n" - ".. code-block:: diff\n" - "\n" - " .. code-block:: python\n" - "\n" - " - 'Hello World'\n" - ' + "Hello World"\n' - ) + before = dedent("""\ + blacken-docs does changes like: + + .. code-block:: diff + + .. code-block:: python + + - 'Hello World' + + + "Hello World" + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_python_comments() -> None: - before = ( - ".. blacken-docs:off\n" - ".. code-block:: python\n" - "\n" - " 'single quotes rock'\n" - "\n" - ".. blacken-docs:on\n" - ) + before = dedent("""\ + .. blacken-docs:off + .. code-block:: python + + 'single quotes rock' + + .. blacken-docs:on + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -767,20 +764,32 @@ def test_format_src_rst_python_comments() -> None: def test_integration_ok(tmp_path: Path, capsys: CaptureFixture[str]) -> None: f = tmp_path / "f.md" f.write_text( - "```python\nf(1, 2, 3)\n```\n", + dedent("""\ + ```python + f(1, 2, 3) + ``` + """), ) result = main((str(f),)) assert result == 0 assert not capsys.readouterr()[1] - assert f.read_text() == ("```python\nf(1, 2, 3)\n```\n") + assert f.read_text() == dedent("""\ + ```python + f(1, 2, 3) + ``` + """) def test_integration_modifies(tmp_path: Path, capsys: CaptureFixture[str]) -> None: f = tmp_path / "f.md" f.write_text( - "```python\nf(1,2,3)\n```\n", + dedent("""\ + ```python + f(1,2,3) + ``` + """), ) result = main((str(f),)) @@ -788,15 +797,21 @@ def test_integration_modifies(tmp_path: Path, capsys: CaptureFixture[str]) -> No assert result == 1 out, _ = capsys.readouterr() assert out == f"{f}: Rewriting...\n" - assert f.read_text() == ("```python\nf(1, 2, 3)\n```\n") + assert f.read_text() == dedent("""\ + ```python + f(1, 2, 3) + ``` + """) def test_integration_line_length(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( - "```python\n" - "foo(very_very_very_very_very_very_very, long_long_long_long_long)\n" - "```\n", + dedent("""\ + ```python + foo(very_very_very_very_very_very_very, long_long_long_long_long) + ``` + """), ) result = main((str(f), "--line-length=80")) @@ -804,25 +819,23 @@ def test_integration_line_length(tmp_path: Path) -> None: result2 = main((str(f), "--line-length=50")) assert result2 == 1 - assert f.read_text() == ( - "```python\n" - "foo(\n" - " very_very_very_very_very_very_very,\n" - " long_long_long_long_long,\n" - ")\n" - "```\n" - ) + assert f.read_text() == dedent("""\ + ```python + foo( + very_very_very_very_very_very_very, + long_long_long_long_long, + ) + ``` + """) def test_integration_check(tmp_path: Path) -> None: f = tmp_path / "f.md" - text = dedent( - """\ + text = dedent("""\ ```python x = 'a' 'b' ``` - """, - ) + """) f.write_text(text) result = main((str(f), "--check")) @@ -834,66 +847,60 @@ def test_integration_check(tmp_path: Path) -> None: def test_integration_preview(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( - dedent( - """\ + dedent("""\ ```python x = 'a' 'b' ``` - """, - ), + """), ) result = main((str(f), "--preview")) assert result == 1 - assert f.read_text() == dedent( - """\ + assert f.read_text() == dedent("""\ ```python x = "a" "b" ``` - """, - ) + """) def test_integration_pyi(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( - dedent( - """\ + dedent("""\ ```python class Foo: ... class Bar: ... ``` - """, - ), + """), ) result = main((str(f), "--pyi")) assert result == 1 - assert f.read_text() == dedent( - """\ + assert f.read_text() == dedent("""\ ```python class Foo: ... class Bar: ... ``` - """, - ) + """) def test_integration_py36(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( - "```python\n" - "def very_very_long_function_name(\n" - " very_very_very_very_very_very,\n" - " very_very_very_very_very_very,\n" - " *long_long_long_long_long_long\n" - "):\n" - " pass\n" - "```\n", + dedent("""\ + ```python + def very_very_long_function_name( + very_very_very_very_very_very, + very_very_very_very_very_very, + *long_long_long_long_long_long + ): + pass + ``` + """), ) result = main((str(f),)) @@ -902,60 +909,61 @@ def test_integration_py36(tmp_path: Path) -> None: result2 = main((str(f), "--target-version=py36")) assert result2 == 1 - assert f.read_text() == ( - "```python\n" - "def very_very_long_function_name(\n" - " very_very_very_very_very_very,\n" - " very_very_very_very_very_very,\n" - " *long_long_long_long_long_long,\n" - "):\n" - " pass\n" - "```\n" - ) + assert f.read_text() == dedent("""\ + ```python + def very_very_long_function_name( + very_very_very_very_very_very, + very_very_very_very_very_very, + *long_long_long_long_long_long, + ): + pass + ``` + """) def test_integration_filename_last(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( - "```python\n" - "def very_very_long_function_name(\n" - " very_very_very_very_very_very,\n" - " very_very_very_very_very_very,\n" - " *long_long_long_long_long_long\n" - "):\n" - " pass\n" - "```\n", + dedent("""\ + ```python + def very_very_long_function_name( + very_very_very_very_very_very, + very_very_very_very_very_very, + *long_long_long_long_long_long + ): + pass + ``` + """), ) - result = main((str(f),)) - assert result == 0 - result2 = main(("--target-version", "py36", str(f))) assert result2 == 1 - assert f.read_text() == ( - "```python\n" - "def very_very_long_function_name(\n" - " very_very_very_very_very_very,\n" - " very_very_very_very_very_very,\n" - " *long_long_long_long_long_long,\n" - "):\n" - " pass\n" - "```\n" - ) + assert f.read_text() == dedent("""\ + ```python + def very_very_long_function_name( + very_very_very_very_very_very, + very_very_very_very_very_very, + *long_long_long_long_long_long, + ): + pass + ``` + """) def test_integration_multiple_target_version(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( - "```python\n" - "def very_very_long_function_name(\n" - " very_very_very_very_very_very,\n" - " very_very_very_very_very_very,\n" - " *long_long_long_long_long_long\n" - "):\n" - " pass\n" - "```\n", + dedent("""\ + ```python + def very_very_long_function_name( + very_very_very_very_very_very, + very_very_very_very_very_very, + *long_long_long_long_long_long + ): + pass + ``` + """), ) result = main((str(f),)) @@ -970,19 +978,31 @@ def test_integration_multiple_target_version(tmp_path: Path) -> None: def test_integration_skip_string_normalization(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( - "```python\nf('hi')\n```\n", + dedent("""\ + ```python + f('hi') + ``` + """), ) result = main((str(f), "--skip-string-normalization")) assert result == 0 - assert f.read_text() == ("```python\nf('hi')\n```\n") + assert f.read_text() == dedent("""\ + ```python + f('hi') + ``` + """) def test_integration_syntax_error(tmp_path: Path, capsys: CaptureFixture[str]) -> None: f = tmp_path / "f.md" f.write_text( - "```python\nf(\n```\n", + dedent("""\ + ```python + f( + ``` + """), ) result = main((str(f),)) @@ -990,7 +1010,11 @@ def test_integration_syntax_error(tmp_path: Path, capsys: CaptureFixture[str]) - assert result == 2 out, _ = capsys.readouterr() assert out.startswith(f"{f}:1: code block parse error") - assert f.read_text() == ("```python\nf(\n```\n") + assert f.read_text() == dedent("""\ + ```python + f( + ``` + """) def test_integration_ignored_syntax_error( @@ -999,49 +1023,80 @@ def test_integration_ignored_syntax_error( ) -> None: f = tmp_path / "f.md" f.write_text( - "```python\nf( )\n```\n\n```python\nf(\n```\n", + dedent("""\ + ```python + f( ) + ``` + + ```python + f( + ``` + """), ) result = main((str(f), "--skip-errors")) assert result == 1 out, _ = capsys.readouterr() - assert f.read_text() == ("```python\nf()\n```\n\n```python\nf(\n```\n") + assert f.read_text() == dedent("""\ + ```python + f() + ``` + + ```python + f( + ``` + """) def test_format_src_rst_jupyter_sphinx() -> None: - before = "hello\n\n.. jupyter-execute::\n\n f(1,2,3)\n\nworld\n" + before = dedent("""\ + hello + + .. jupyter-execute:: + + f(1,2,3) + + world + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ("hello\n\n.. jupyter-execute::\n\n f(1, 2, 3)\n\nworld\n") + assert after == dedent("""\ + hello + + .. jupyter-execute:: + + f(1, 2, 3) + + world + """) def test_format_src_rst_jupyter_sphinx_with_directive() -> None: - before = ( - "hello\n" - "\n" - ".. jupyter-execute::\n" - " :hide-code:\n" - "\n" - " f(1,2,3)\n" - "\n" - "world\n" - ) + before = dedent("""\ + hello + + .. jupyter-execute:: + :hide-code: + + f(1,2,3) + + world + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "hello\n" - "\n" - ".. jupyter-execute::\n" - " :hide-code:\n" - "\n" - " f(1, 2, 3)\n" - "\n" - "world\n" - ) + assert after == dedent("""\ + hello + + .. jupyter-execute:: + :hide-code: + + f(1, 2, 3) + + world + """) def test_format_src_python_docstring_markdown() -> None: - before = dedent( - '''\ + before = dedent('''\ def f(): """ hello world @@ -1051,11 +1106,9 @@ def f(): ``` """ pass - ''', - ) + ''') after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - '''\ + assert after == dedent('''\ def f(): """ hello world @@ -1065,13 +1118,11 @@ def f(): ``` """ pass - ''', - ) + ''') def test_format_src_python_docstring_rst() -> None: - before = dedent( - '''\ + before = dedent('''\ def f(): """ hello world @@ -1081,11 +1132,9 @@ def f(): f(1,2,3) """ pass - ''', - ) + ''') after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == dedent( - '''\ + assert after == dedent('''\ def f(): """ hello world @@ -1095,272 +1144,301 @@ def f(): f(1, 2, 3) """ pass - ''', - ) + ''') def test_format_src_rst_pycon() -> None: - before = ( - "hello\n" - "\n" - ".. code-block:: pycon\n" - "\n" - " >>> f(1,2,3)\n" - " output\n" - "\n" - "world\n" - ) + before = dedent("""\ + hello + + .. code-block:: pycon + + >>> f(1,2,3) + output + + world + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - "hello\n" - "\n" - ".. code-block:: pycon\n" - "\n" - " >>> f(1, 2, 3)\n" - " output\n" - "\n" - "world\n" - ) + assert after == dedent("""\ + hello + + .. code-block:: pycon + + >>> f(1, 2, 3) + output + + world + """) def test_format_src_rst_pycon_with_continuation() -> None: - before = ( - ".. code-block:: pycon\n" - "\n" - " >>> d = {\n" - ' ... "a": 1,\n' - ' ... "b": 2,\n' - ' ... "c": 3,}\n' - "\n" - ) + before = dedent("""\ + .. code-block:: pycon + + >>> d = { + ... "a": 1, + ... "b": 2, + ... "c": 3,} + + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. code-block:: pycon\n" - "\n" - " >>> d = {\n" - ' ... "a": 1,\n' - ' ... "b": 2,\n' - ' ... "c": 3,\n' - " ... }\n" - "\n" - ) + assert after == dedent("""\ + .. code-block:: pycon + + >>> d = { + ... "a": 1, + ... "b": 2, + ... "c": 3, + ... } + + """) def test_format_src_rst_pycon_adds_continuation() -> None: - before = """.. code-block:: pycon\n\n >>> d = {"a": 1,"b": 2,"c": 3,}\n\n""" + before = dedent("""\ + .. code-block:: pycon + + >>> d = {"a": 1,"b": 2,"c": 3,} + + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. code-block:: pycon\n" - "\n" - " >>> d = {\n" - ' ... "a": 1,\n' - ' ... "b": 2,\n' - ' ... "c": 3,\n' - " ... }\n" - "\n" - ) + assert after == dedent("""\ + .. code-block:: pycon + + >>> d = { + ... "a": 1, + ... "b": 2, + ... "c": 3, + ... } + + """) def test_format_src_rst_pycon_preserves_trailing_whitespace() -> None: - before = ( - "hello\n" - "\n" - ".. code-block:: pycon\n" - "\n" - ' >>> d = {"a": 1, "b": 2, "c": 3}\n' - "\n" - "\n" - "\n" - "world\n" - ) + before = dedent("""\ + hello + + .. code-block:: pycon + + >>> d = {"a": 1, "b": 2, "c": 3} + + + + world + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_pycon_indented() -> None: - before = ( - ".. versionadded:: 3.1\n" - "\n" - " hello\n" - "\n" - " .. code-block:: pycon\n" - "\n" - " >>> def hi():\n" - " ... f(1,2,3)\n" - " ...\n" - "\n" - " world\n" - ) + before = dedent("""\ + .. versionadded:: 3.1 + + hello + + .. code-block:: pycon + + >>> def hi(): + ... f(1,2,3) + ... + + world + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. versionadded:: 3.1\n" - "\n" - " hello\n" - "\n" - " .. code-block:: pycon\n" - "\n" - " >>> def hi():\n" - " ... f(1, 2, 3)\n" - " ...\n" - "\n" - " world\n" - ) + assert after == dedent("""\ + .. versionadded:: 3.1 + + hello + + .. code-block:: pycon + + >>> def hi(): + ... f(1, 2, 3) + ... + + world + """) def test_format_src_rst_pycon_code_block_is_final_line1() -> None: - before = ( - ".. code-block:: pycon\n" - "\n" - " >>> if True:\n" - " ... pass\n" - " ...\n" - ) + before = dedent("""\ + .. code-block:: pycon + + >>> if True: + ... pass + ... + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. code-block:: pycon\n" - "\n" - " >>> if True:\n" - " ... pass\n" - " ...\n" - ) + assert after == dedent("""\ + .. code-block:: pycon + + >>> if True: + ... pass + ... + """) def test_format_src_rst_pycon_code_block_is_final_line2() -> None: - before = ".. code-block:: pycon\n\n >>> if True:\n ... pass\n" + before = dedent("""\ + .. code-block:: pycon + + >>> if True: + ... pass + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. code-block:: pycon\n" - "\n" - " >>> if True:\n" - " ... pass\n" - " ...\n" - ) + assert after == dedent("""\ + .. code-block:: pycon + + >>> if True: + ... pass + ... + """) def test_format_src_rst_pycon_nested_def1() -> None: - before = ( - ".. code-block:: pycon\n" - "\n" - " >>> if True:\n" - " ... def f(): pass\n" - " ...\n" - ) + before = dedent("""\ + .. code-block:: pycon + + >>> if True: + ... def f(): pass + ... + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. code-block:: pycon\n" - "\n" - " >>> if True:\n" - " ... def f():\n" - " ... pass\n" - " ...\n" - ) + assert after == dedent("""\ + .. code-block:: pycon + + >>> if True: + ... def f(): + ... pass + ... + """) def test_format_src_rst_pycon_nested_def2() -> None: - before = ( - ".. code-block:: pycon\n" - "\n" - " >>> if True:\n" - " ... def f(): pass\n" - ) + before = dedent("""\ + .. code-block:: pycon + + >>> if True: + ... def f(): pass + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. code-block:: pycon\n" - "\n" - " >>> if True:\n" - " ... def f():\n" - " ... pass\n" - " ...\n" - ) + assert after == dedent("""\ + .. code-block:: pycon + + >>> if True: + ... def f(): + ... pass + ... + """) def test_format_src_rst_pycon_empty_line() -> None: - before = ( - ".. code-block:: pycon\n" - "\n" - " >>> l = [\n" - " ...\n" - " ... 1,\n" - " ... ]\n" - ) + before = dedent("""\ + .. code-block:: pycon + + >>> l = [ + ... + ... 1, + ... ] + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. code-block:: pycon\n" - "\n" - " >>> l = [\n" - " ... 1,\n" - " ... ]\n" - ) + assert after == dedent("""\ + .. code-block:: pycon + + >>> l = [ + ... 1, + ... ] + """) def test_format_src_rst_pycon_preserves_output_indentation() -> None: - before = ( - ".. code-block:: pycon\n" - "\n" - " >>> 1 / 0\n" - " Traceback (most recent call last):\n" - ' File "", line 1, in \n' - " ZeroDivisionError: division by zero\n" - ) + before = dedent("""\ + .. code-block:: pycon + + >>> 1 / 0 + Traceback (most recent call last): + + File "", line 1, in + + ZeroDivisionError: division by zero + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_pycon_elided_traceback() -> None: - before = ( - ".. code-block:: pycon\n" - "\n" - " >>> 1 / 0\n" - " Traceback (most recent call last):\n" - " ...\n" - " ZeroDivisionError: division by zero\n" - ) + before = dedent("""\ + .. code-block:: pycon + + >>> 1 / 0 + Traceback (most recent call last): + ... + ZeroDivisionError: division by zero + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_pycon_no_prompt() -> None: - before = ".. code-block:: pycon\n\n pass\n" + before = dedent("""\ + .. code-block:: pycon + + pass + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_pycon_no_trailing_newline() -> None: - before = ".. code-block:: pycon\n\n >>> pass" + before = dedent("""\ + .. code-block:: pycon + + >>> pass""") after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == (".. code-block:: pycon\n\n >>> pass\n") + assert after == dedent("""\ + .. code-block:: pycon + + >>> pass + """) def test_format_src_rst_pycon_comment_before_promopt() -> None: - before = ( - ".. code-block:: pycon\n" - "\n" - " # Comment about next line\n" - " >>> pass\n" - ) + before = dedent("""\ + .. code-block:: pycon + + # Comment about next line + >>> pass + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) - assert after == ( - ".. code-block:: pycon\n" - "\n" - " # Comment about next line\n" - " >>> pass\n" - ) + assert after == dedent("""\ + .. code-block:: pycon + + # Comment about next line + >>> pass + """) def test_format_src_rst_pycon_comments() -> None: - before = ( - ".. blacken-docs:off\n" - ".. code-block:: pycon\n" - "\n" - " >>> 'single quotes rock'\n" - "\n" - ".. blacken-docs:on\n" - ) + before = dedent("""\ + .. blacken-docs:off + .. code-block:: pycon + + >>> 'single quotes rock' + + .. blacken-docs:on + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before def test_format_src_rst_pycon_empty() -> None: - before = "some text\n\n.. code-block:: pycon\n\n\nsome other text\n" + before = dedent("""\ + some text + + .. code-block:: pycon + + + some other text + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before From 7d6b58d5f0fb45ebd352a099c875fe7a7e9bacba Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 01:56:51 +0000 Subject: [PATCH 26/35] Remove extra format on/off --- tests/test_ruff_format_docs.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index e401d15..add6e23 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -282,32 +282,29 @@ def test_format_src_markdown_comments_after() -> None: def test_format_src_markdown_comments_only_on() -> None: - # fmt: off before = dedent("""\ ```python 'double quotes rock' ``` - """ ) + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent("""\ ```python "double quotes rock" ``` - """ ) - # fmt: on + """) def test_format_src_markdown_comments_only_off() -> None: - # fmt: off before = dedent("""\ ```python 'single quotes rock' ``` - """ ) - # fmt: on + """) + after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -477,21 +474,19 @@ def test_format_src_latex_minted_comments_off_pycon() -> None: def test_format_src_pythontex() -> None: - # fmt: off before = dedent("""\ hello \\begin{pyblock} f(1,2,3) \\end{pyblock} - world!""" ) + world!""") after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent("""\ hello \\begin{pyblock} f(1, 2, 3) \\end{pyblock} - world!""" ) - # fmt: on + world!""") def test_format_src_pythontex_comments_off() -> None: From ba3051cf3f318a37b699d75987e9797a200565c3 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 01:57:47 +0000 Subject: [PATCH 27/35] Update to ruff format docs --- src/ruff_format_docs/__init__.py | 2 +- tests/test_ruff_format_docs.py | 80 ++++++++++++++++---------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py index 7e455c9..ffd2d19 100644 --- a/src/ruff_format_docs/__init__.py +++ b/src/ruff_format_docs/__init__.py @@ -91,7 +91,7 @@ ) INDENT_RE = re.compile("^ +(?=[^ ])", re.MULTILINE) TRAILING_NL_RE = re.compile(r"\n+\Z", re.MULTILINE) -ON_OFF = r"blacken-docs:(on|off)" +ON_OFF = r"ruff-format-docs:(on|off)" ON_OFF_COMMENT_RE = re.compile( # Markdown rf"(?:^\s*$)|" diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index add6e23..e8dcf72 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -211,11 +211,11 @@ def test_format_src_markdown_pycon_twice() -> None: def test_format_src_markdown_comments_disable() -> None: before = dedent("""\ - + ```python 'single quotes rock' ``` - + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -223,22 +223,22 @@ def test_format_src_markdown_comments_disable() -> None: def test_format_src_markdown_comments_disabled_enabled() -> None: before = dedent("""\ - + ```python 'single quotes rock' ``` - + ```python 'double quotes rock' ``` """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent("""\ - + ```python 'single quotes rock' ``` - + ```python "double quotes rock" ``` @@ -247,16 +247,16 @@ def test_format_src_markdown_comments_disabled_enabled() -> None: def test_format_src_markdown_comments_before() -> None: before = dedent("""\ - - + + ```python 'double quotes rock' ``` """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent("""\ - - + + ```python "double quotes rock" ``` @@ -268,29 +268,29 @@ def test_format_src_markdown_comments_after() -> None: ```python 'double quotes rock' ``` - - + + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent("""\ ```python "double quotes rock" ``` - - + + """) def test_format_src_markdown_comments_only_on() -> None: before = dedent("""\ - + ```python 'double quotes rock' ``` """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == dedent("""\ - + ```python "double quotes rock" ``` @@ -299,7 +299,7 @@ def test_format_src_markdown_comments_only_on() -> None: def test_format_src_markdown_comments_only_off() -> None: before = dedent("""\ - + ```python 'single quotes rock' ``` @@ -311,12 +311,12 @@ def test_format_src_markdown_comments_only_off() -> None: def test_format_src_markdown_comments_multiple() -> None: before = ( - "\n" # ignored - "\n" - "\n" - "\n" # ignored - "\n" - "\n" # ignored + "\n" # ignored + "\n" + "\n" + "\n" # ignored + "\n" + "\n" # ignored "```python\n" "'single quotes rock'\n" "```\n" # no on comment, off until the end @@ -328,11 +328,11 @@ def test_format_src_markdown_comments_multiple() -> None: def test_on_off_comments_in_code_blocks() -> None: before = dedent("""\ ````md - + ```python f(1,2,3) ``` - + ```` """) after, _ = format_file_contents(before, FORMATTER_CONFIG) @@ -341,11 +341,11 @@ def test_on_off_comments_in_code_blocks() -> None: def test_format_src_markdown_comments_disable_pycon() -> None: before = dedent("""\ - + ```pycon >>> 'single quotes rock' ``` - + """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -451,11 +451,11 @@ def test_format_src_latex_minted_pycon_indented() -> None: def test_format_src_latex_minted_comments_off() -> None: before = dedent("""\ - % blacken-docs:off + % ruff-format-docs:off \\begin{minted}{python} 'single quotes rock' \\end{minted} - % blacken-docs:on + % ruff-format-docs:on """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -463,11 +463,11 @@ def test_format_src_latex_minted_comments_off() -> None: def test_format_src_latex_minted_comments_off_pycon() -> None: before = dedent("""\ - % blacken-docs:off + % ruff-format-docs:off \\begin{minted}{pycon} >>> 'single quotes rock' \\end{minted} - % blacken-docs:on + % ruff-format-docs:on """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -491,11 +491,11 @@ def test_format_src_pythontex() -> None: def test_format_src_pythontex_comments_off() -> None: before = dedent("""\ - % blacken-docs:off + % ruff-format-docs:off \\begin{pyblock} f(1,2,3) \\end{pyblock} - % blacken-docs:on + % ruff-format-docs:on """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -609,12 +609,12 @@ def test_format_src_rst_literal_blocks_empty() -> None: def test_format_src_rst_literal_blocks_comments() -> None: before = dedent("""\ - .. blacken-docs:off + .. ruff-format-docs:off Example:: 'single quotes rock' - .. blacken-docs:on + .. ruff-format-docs:on """) after, _ = format_file_contents(before, FORMATTER_CONFIG, rst_literal_blocks=True) assert after == before @@ -729,7 +729,7 @@ def foo(): def test_format_src_rst_python_inside_non_python_code_block() -> None: before = dedent("""\ - blacken-docs does changes like: + ruff-format-docs does changes like: .. code-block:: diff @@ -745,12 +745,12 @@ def test_format_src_rst_python_inside_non_python_code_block() -> None: def test_format_src_rst_python_comments() -> None: before = dedent("""\ - .. blacken-docs:off + .. ruff-format-docs:off .. code-block:: python 'single quotes rock' - .. blacken-docs:on + .. ruff-format-docs:on """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before @@ -1415,12 +1415,12 @@ def test_format_src_rst_pycon_comment_before_promopt() -> None: def test_format_src_rst_pycon_comments() -> None: before = dedent("""\ - .. blacken-docs:off + .. ruff-format-docs:off .. code-block:: pycon >>> 'single quotes rock' - .. blacken-docs:on + .. ruff-format-docs:on """) after, _ = format_file_contents(before, FORMATTER_CONFIG) assert after == before From 9c5b24c66b594758e605cee9dbe5cfc211e0ec4d Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 01:59:41 +0000 Subject: [PATCH 28/35] Add flake8 clean block to improve readability --- .pre-commit-config.yaml | 8 ++++++++ src/ruff_format_docs/__init__.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d6b7df..da45028 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,6 +57,14 @@ repos: - urllib3==2.0.5 - black==24.10.0 + - repo: https://github.com/PyCQA/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + additional_dependencies: + - flake8-clean-block==0.1.2 + args: ["--ignore=E,F,W,C90"] + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.8.4 hooks: diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py index ffd2d19..9913f18 100644 --- a/src/ruff_format_docs/__init__.py +++ b/src/ruff_format_docs/__init__.py @@ -131,6 +131,7 @@ def format_file_contents( # noqa: PLR0915 elif off_start is not None: off_ranges.append((off_start, comment.end())) off_start = None + if off_start is not None: off_ranges.append((off_start, len(src))) @@ -140,6 +141,7 @@ def _within_off_range(code_range: tuple[int, int]) -> bool: off_start, off_end = off_ranges[index - 1] except IndexError: return False + code_start, code_end = code_range return code_start >= off_start and code_end <= off_end @@ -153,20 +155,25 @@ def _collect_error(match: Match[str]) -> Generator[None]: def _md_match(match: Match[str]) -> str: if _within_off_range(match.span()): return match[0] + code = textwrap.dedent(match["code"]) with _collect_error(match): code = format_str(code, mode=config) + code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' def _rst_match(match: Match[str]) -> str: if _within_off_range(match.span()): return match[0] + lang = match["lang"] if lang is not None and lang not in PYGMENTS_PY_LANGS: return match[0] + if not match["code"].strip(): return match[0] + min_indent = min(INDENT_RE.findall(match["code"])) trailing_ws_match = TRAILING_NL_RE.search(match["code"]) assert trailing_ws_match @@ -174,14 +181,17 @@ def _rst_match(match: Match[str]) -> str: code = textwrap.dedent(match["code"]) with _collect_error(match): code = format_str(code, mode=config) + code = textwrap.indent(code, min_indent) return f'{match["before"]}{code.rstrip()}{trailing_ws}' def _rst_literal_blocks_match(match: Match[str]) -> str: if _within_off_range(match.span()): return match[0] + if not match["code"].strip(): return match[0] + min_indent = min(INDENT_RE.findall(match["code"])) trailing_ws_match = TRAILING_NL_RE.search(match["code"]) assert trailing_ws_match @@ -189,6 +199,7 @@ def _rst_literal_blocks_match(match: Match[str]) -> str: code = textwrap.dedent(match["code"]) with _collect_error(match): code = format_str(code, mode=config) + code = textwrap.indent(code, min_indent) return f'{match["before"]}{code.rstrip()}{trailing_ws}' @@ -203,6 +214,7 @@ def finish_fragment() -> None: if fragment is not None: with _collect_error(match): fragment = format_str(fragment, mode=config) + fragment_lines = fragment.splitlines() code += f"{PYCON_PREFIX}{fragment_lines[0]}\n" for line in fragment_lines[1:]: @@ -216,8 +228,10 @@ def finish_fragment() -> None: # ... if line: code += f"{PYCON_CONTINUATION_PREFIX} {line}\n" + if fragment_lines[-1].startswith(" "): code += f"{PYCON_CONTINUATION_PREFIX}\n" + fragment = None indentation: int | None = None @@ -226,6 +240,7 @@ def finish_fragment() -> None: line = line.lstrip() # noqa: PLW2901 if indentation is None and line: indentation = len(orig_line) - len(line) + continuation_match = PYCON_CONTINUATION_RE.match(line) if continuation_match and fragment is not None: fragment += line[continuation_match.end() :] + "\n" @@ -235,12 +250,14 @@ def finish_fragment() -> None: fragment = line[len(PYCON_PREFIX) :] + "\n" else: code += orig_line[indentation:] + "\n" + finish_fragment() return code def _md_pycon_match(match: Match[str]) -> str: if _within_off_range(match.span()): return match[0] + code = _pycon_match(match) code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' @@ -248,9 +265,11 @@ def _md_pycon_match(match: Match[str]) -> str: def _rst_pycon_match(match: Match[str]) -> str: if _within_off_range(match.span()): return match[0] + code = _pycon_match(match) if not code.strip(): return match[0] + min_indent = min(INDENT_RE.findall(match["code"])) code = textwrap.indent(code, min_indent) return f'{match["before"]}{code}' @@ -258,15 +277,18 @@ def _rst_pycon_match(match: Match[str]) -> str: def _latex_match(match: Match[str]) -> str: if _within_off_range(match.span()): return match[0] + code = textwrap.dedent(match["code"]) with _collect_error(match): code = format_str(code, mode=config) + code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' def _latex_pycon_match(match: Match[str]) -> str: if _within_off_range(match.span()): return match[0] + code = _pycon_match(match) code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' @@ -280,6 +302,7 @@ def _latex_pycon_match(match: Match[str]) -> str: _rst_literal_blocks_match, src, ) + src = LATEX_RE.sub(_latex_match, src) src = LATEX_PYCON_RE.sub(_latex_pycon_match, src) src = PYTHONTEX_RE.sub(_latex_match, src) @@ -296,6 +319,7 @@ def format_file( """Format a file with ruff.""" with file.open(encoding="UTF-8") as f: contents = f.read() + new_contents, errors = format_file_contents( contents, black_mode, @@ -304,16 +328,21 @@ def format_file( for error in errors: lineno = contents[: error.offset].count("\n") + 1 print(f"{file}:{lineno}: code block parse error {error.exc}") + if errors and not skip_errors: return 2 + if contents == new_contents: return 0 + if check_only: print(f"{file}: Requires a rewrite.") return 1 + print(f"{file}: Rewriting...") with file.open("w", encoding="UTF-8") as f: f.write(new_contents) + return 1 @@ -368,4 +397,5 @@ def main(argv: Sequence[str] | None = None) -> int: rst_literal_blocks=args.rst_literal_blocks, check_only=args.check, ) + return retv From dacc38446419b8f641f38c5813c50b12f622b109 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Thu, 26 Dec 2024 00:30:30 +0000 Subject: [PATCH 29/35] Use ruff to format --- src/ruff_format_docs/__init__.py | 24 +++++++++++++++++++++++- tests/test_ruff_format_docs.py | 22 +++++++++++++++++----- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py index 9913f18..cf84c96 100644 --- a/src/ruff_format_docs/__init__.py +++ b/src/ruff_format_docs/__init__.py @@ -3,13 +3,14 @@ import argparse import contextlib import re +import subprocess import textwrap from bisect import bisect from pathlib import Path from re import Match from typing import TYPE_CHECKING -from black import FileMode, Mode, format_str +from black import FileMode, Mode from black.const import DEFAULT_LINE_LENGTH from black.mode import TargetVersion @@ -103,6 +104,27 @@ ) +def format_str(code: str, mode: FileMode) -> str: + """Format a code block with ruff.""" + subprocess_result = subprocess.run( + [ + "ruff", + "format", + "--stdin-filename", + "file.py", + "--config", + f"line-length={mode.line_length}", + "-", + ], + check=True, + capture_output=True, + text=True, + input=code, + ) + + return subprocess_result.stdout + + class CodeBlockError: """An error that occurred while formatting a code block.""" diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index e8dcf72..8a2d97b 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -7,6 +7,7 @@ from textwrap import dedent from typing import TYPE_CHECKING +import pytest from black import FileMode from black.const import DEFAULT_LINE_LENGTH @@ -859,7 +860,8 @@ def test_integration_preview(tmp_path: Path) -> None: """) -def test_integration_pyi(tmp_path: Path) -> None: +@pytest.mark.xfail(reason="Need to decide how to handle pyi files") +def test_integration_pyi(tmp_path: Path) -> None: # pragma: no cover f = tmp_path / "f.md" f.write_text( dedent("""\ @@ -883,7 +885,8 @@ class Bar: ... """) -def test_integration_py36(tmp_path: Path) -> None: +@pytest.mark.xfail(reason="Need to investigate odd integration with target version") +def test_integration_py36(tmp_path: Path) -> None: # pragma: no cover f = tmp_path / "f.md" f.write_text( dedent("""\ @@ -916,7 +919,8 @@ def very_very_long_function_name( """) -def test_integration_filename_last(tmp_path: Path) -> None: +@pytest.mark.xfail(reason="Need to investigate odd integration with target version") +def test_integration_filename_last(tmp_path: Path) -> None: # pragma: no cover f = tmp_path / "f.md" f.write_text( dedent("""\ @@ -946,7 +950,12 @@ def very_very_long_function_name( """) -def test_integration_multiple_target_version(tmp_path: Path) -> None: +@pytest.mark.xfail( + reason="Need to decide if we want to handle multiple target versions", +) +def test_integration_multiple_target_version( + tmp_path: Path, +) -> None: # pragma: no cover f = tmp_path / "f.md" f.write_text( dedent("""\ @@ -970,7 +979,10 @@ def very_very_long_function_name( assert result2 == 0 -def test_integration_skip_string_normalization(tmp_path: Path) -> None: +@pytest.mark.xfail(reason="Need to if we want to support this") +def test_integration_skip_string_normalization( + tmp_path: Path, +) -> None: # pragma: no cover f = tmp_path / "f.md" f.write_text( dedent("""\ From ddb31fd3adf27b7c660fa78dd2ba094a2e2de1fc Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 02:07:57 +0000 Subject: [PATCH 30/35] Remove mode kwargs --- src/ruff_format_docs/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py index cf84c96..595af30 100644 --- a/src/ruff_format_docs/__init__.py +++ b/src/ruff_format_docs/__init__.py @@ -180,7 +180,7 @@ def _md_match(match: Match[str]) -> str: code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, mode=config) + code = format_str(code, config) code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' @@ -202,7 +202,7 @@ def _rst_match(match: Match[str]) -> str: trailing_ws = trailing_ws_match.group() code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, mode=config) + code = format_str(code, config) code = textwrap.indent(code, min_indent) return f'{match["before"]}{code.rstrip()}{trailing_ws}' @@ -220,7 +220,7 @@ def _rst_literal_blocks_match(match: Match[str]) -> str: trailing_ws = trailing_ws_match.group() code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, mode=config) + code = format_str(code, config) code = textwrap.indent(code, min_indent) return f'{match["before"]}{code.rstrip()}{trailing_ws}' @@ -235,7 +235,7 @@ def finish_fragment() -> None: if fragment is not None: with _collect_error(match): - fragment = format_str(fragment, mode=config) + fragment = format_str(fragment, config) fragment_lines = fragment.splitlines() code += f"{PYCON_PREFIX}{fragment_lines[0]}\n" @@ -302,7 +302,7 @@ def _latex_match(match: Match[str]) -> str: code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, mode=config) + code = format_str(code, config) code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' From cedf6d15d589451752df443cafc7b62e6d82d21c Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 02:13:23 +0000 Subject: [PATCH 31/35] Replace black mode with formatter config --- .pre-commit-config.yaml | 1 - src/ruff_format_docs/__init__.py | 82 +++++++++++++++++--------------- tests/test_ruff_format_docs.py | 16 ++++--- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da45028..44f374f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,7 +55,6 @@ repos: additional_dependencies: - pytest==8.3.3 - urllib3==2.0.5 - - black==24.10.0 - repo: https://github.com/PyCQA/flake8 rev: 7.1.1 diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py index 595af30..11cefea 100644 --- a/src/ruff_format_docs/__init__.py +++ b/src/ruff_format_docs/__init__.py @@ -8,11 +8,7 @@ from bisect import bisect from pathlib import Path from re import Match -from typing import TYPE_CHECKING - -from black import FileMode, Mode -from black.const import DEFAULT_LINE_LENGTH -from black.mode import TargetVersion +from typing import TYPE_CHECKING, NamedTuple if TYPE_CHECKING: from collections.abc import Generator, Sequence @@ -104,7 +100,7 @@ ) -def format_str(code: str, mode: FileMode) -> str: +def format_str(code: str, config: FormatterConfig) -> str: """Format a code block with ruff.""" subprocess_result = subprocess.run( [ @@ -112,8 +108,7 @@ def format_str(code: str, mode: FileMode) -> str: "format", "--stdin-filename", "file.py", - "--config", - f"line-length={mode.line_length}", + *config.call_args, "-", ], check=True, @@ -136,8 +131,7 @@ def __init__(self, offset: int, exc: Exception) -> None: def format_file_contents( # noqa: PLR0915 src: str, - config: FileMode, - *, + config: FormatterConfig, rst_literal_blocks: bool = False, ) -> tuple[str, Sequence[CodeBlockError]]: """Format all code blocks in a file.""" @@ -333,20 +327,16 @@ def _latex_pycon_match(match: Match[str]) -> str: def format_file( file: Path, - black_mode: FileMode, skip_errors: bool, rst_literal_blocks: bool, check_only: bool, + config: FormatterConfig, ) -> int: """Format a file with ruff.""" with file.open(encoding="UTF-8") as f: contents = f.read() - new_contents, errors = format_file_contents( - contents, - black_mode, - rst_literal_blocks=rst_literal_blocks, - ) + new_contents, errors = format_file_contents(contents, config, rst_literal_blocks) for error in errors: lineno = contents[: error.offset].count("\n") + 1 print(f"{file}:{lineno}: code block parse error {error.exc}") @@ -368,29 +358,39 @@ def format_file( return 1 +class FormatterConfig(NamedTuple): + """Configuration for Ruff formatter.""" + + target_version: str + preview: bool + configs: list[str] + + @property + def call_args(self) -> list[str]: + """Construct the call arguments for ruff's formatter.""" + args = ["--target-version", self.target_version] + if self.preview: + args.append("--preview") + + for config in self.configs: + args.extend(["--config", config]) + + return args + + def main(argv: Sequence[str] | None = None) -> int: """CLI entry-point for ruff docs formatter.""" parser = argparse.ArgumentParser() - parser.add_argument( - "-l", - "--line-length", - type=int, - default=DEFAULT_LINE_LENGTH, - ) parser.add_argument("--preview", action="store_true") - parser.add_argument( - "-S", - "--skip-string-normalization", - action="store_true", - ) parser.add_argument( "-t", "--target-version", - action="append", - type=lambda v: TargetVersion[v.upper()], - default=[], - help=f"choices: {[v.name.lower() for v in TargetVersion]}", - dest="target_versions", + action="store", + help=( + "The minimum Python version that should be supported. Possible values: " + "py37, py38, py39, py310, py311, py312, py313. Default: py39" + ), + default="py39", ) parser.add_argument("--check", action="store_true") parser.add_argument("-E", "--skip-errors", action="store_true") @@ -398,26 +398,30 @@ def main(argv: Sequence[str] | None = None) -> int: "--rst-literal-blocks", action="store_true", ) - parser.add_argument("--pyi", action="store_true") + parser.add_argument( + "--config", + action="append", + default=[], + help="Config to pass to ruff", + dest="configs", + ) parser.add_argument("filenames", nargs="*") args = parser.parse_args(argv) - black_mode = Mode( - target_versions=set(args.target_versions), - line_length=args.line_length, - string_normalization=not args.skip_string_normalization, - is_pyi=args.pyi, + config = FormatterConfig( + target_version=args.target_version, preview=args.preview, + configs=args.configs, ) retv = 0 for filename in args.filenames: retv |= format_file( Path(filename), - black_mode, skip_errors=args.skip_errors, rst_literal_blocks=args.rst_literal_blocks, check_only=args.check, + config=config, ) return retv diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index 8a2d97b..e0a1b26 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -8,17 +8,19 @@ from typing import TYPE_CHECKING import pytest -from black import FileMode -from black.const import DEFAULT_LINE_LENGTH -from ruff_format_docs import format_file_contents, main +from ruff_format_docs import FormatterConfig, format_file_contents, main if TYPE_CHECKING: from pathlib import Path from _pytest.capture import CaptureFixture -FORMATTER_CONFIG = FileMode(line_length=DEFAULT_LINE_LENGTH) +FORMATTER_CONFIG = FormatterConfig( + target_version="py39", + preview=False, + configs=["line-length=88"], +) def test_format_src_trivial() -> None: @@ -810,10 +812,10 @@ def test_integration_line_length(tmp_path: Path) -> None: """), ) - result = main((str(f), "--line-length=80")) + result = main((str(f), "--config", "line-length=80")) assert result == 0 - result2 = main((str(f), "--line-length=50")) + result2 = main((str(f), "--config", "line-length=50")) assert result2 == 1 assert f.read_text() == dedent("""\ ```python @@ -855,7 +857,7 @@ def test_integration_preview(tmp_path: Path) -> None: assert result == 1 assert f.read_text() == dedent("""\ ```python - x = "a" "b" + x = "ab" ``` """) From 88bbbc97e7a35656d97ce2c415c20c5372963af0 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 02:14:57 +0000 Subject: [PATCH 32/35] Handle pyi code blocks --- src/ruff_format_docs/__init__.py | 22 ++++++++++++++-------- tests/test_ruff_format_docs.py | 9 ++++----- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/ruff_format_docs/__init__.py b/src/ruff_format_docs/__init__.py index 11cefea..20d3ef9 100644 --- a/src/ruff_format_docs/__init__.py +++ b/src/ruff_format_docs/__init__.py @@ -14,11 +14,8 @@ from collections.abc import Generator, Sequence PYGMENTS_PY_LANGS = frozenset(("python", "py", "sage", "python3", "py3", "numpy")) -PYGMENTS_PY_LANGS_RE_FRAGMENT = f"({'|'.join(PYGMENTS_PY_LANGS)})" MD_RE = re.compile( - r"(?P^(?P *)```[^\S\r\n]*" - + PYGMENTS_PY_LANGS_RE_FRAGMENT - + r"( .*?)?\n)" + r"(?P^(?P *)```[^\S\r\n]*(?P\w+)( .*?)?\n)" r"(?P.*?)" r"(?P^(?P=indent)```[^\S\r\n]*$)", re.DOTALL | re.MULTILINE, @@ -100,14 +97,19 @@ ) -def format_str(code: str, config: FormatterConfig) -> str: +def format_str( + code: str, + config: FormatterConfig, + is_pyi: bool = False, +) -> str: """Format a code block with ruff.""" + extension = "pyi" if is_pyi else "py" subprocess_result = subprocess.run( [ "ruff", "format", "--stdin-filename", - "file.py", + f"file.{extension}", *config.call_args, "-", ], @@ -172,9 +174,13 @@ def _md_match(match: Match[str]) -> str: if _within_off_range(match.span()): return match[0] + lang = match["lang"] + if lang is None or lang not in {*PYGMENTS_PY_LANGS, "pyi"}: + return match.group() + code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, config) + code = format_str(code, config, is_pyi=(lang == "pyi")) code = textwrap.indent(code, match["indent"]) return f'{match["before"]}{code}{match["after"]}' @@ -196,7 +202,7 @@ def _rst_match(match: Match[str]) -> str: trailing_ws = trailing_ws_match.group() code = textwrap.dedent(match["code"]) with _collect_error(match): - code = format_str(code, config) + code = format_str(code, config, is_pyi=(lang == "pyi")) code = textwrap.indent(code, min_indent) return f'{match["before"]}{code.rstrip()}{trailing_ws}' diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index e0a1b26..f327cc9 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -862,12 +862,11 @@ def test_integration_preview(tmp_path: Path) -> None: """) -@pytest.mark.xfail(reason="Need to decide how to handle pyi files") -def test_integration_pyi(tmp_path: Path) -> None: # pragma: no cover +def test_integration_pyi(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( dedent("""\ - ```python + ```pyi class Foo: ... @@ -876,11 +875,11 @@ class Bar: ... """), ) - result = main((str(f), "--pyi")) + result = main((str(f),)) assert result == 1 assert f.read_text() == dedent("""\ - ```python + ```pyi class Foo: ... class Bar: ... ``` From 45d7d64535dbb1dcbf82f93a6fcb52331610d695 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 02:16:00 +0000 Subject: [PATCH 33/35] Remove redundant test --- tests/test_ruff_format_docs.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index f327cc9..675d5c1 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -886,40 +886,6 @@ class Bar: ... """) -@pytest.mark.xfail(reason="Need to investigate odd integration with target version") -def test_integration_py36(tmp_path: Path) -> None: # pragma: no cover - f = tmp_path / "f.md" - f.write_text( - dedent("""\ - ```python - def very_very_long_function_name( - very_very_very_very_very_very, - very_very_very_very_very_very, - *long_long_long_long_long_long - ): - pass - ``` - """), - ) - - result = main((str(f),)) - assert result == 0 - - result2 = main((str(f), "--target-version=py36")) - - assert result2 == 1 - assert f.read_text() == dedent("""\ - ```python - def very_very_long_function_name( - very_very_very_very_very_very, - very_very_very_very_very_very, - *long_long_long_long_long_long, - ): - pass - ``` - """) - - @pytest.mark.xfail(reason="Need to investigate odd integration with target version") def test_integration_filename_last(tmp_path: Path) -> None: # pragma: no cover f = tmp_path / "f.md" From 42287518eb88cbc71d3fb46df1c6543f3d83a532 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 02:16:31 +0000 Subject: [PATCH 34/35] Update file name last test --- tests/test_ruff_format_docs.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index 675d5c1..31cccf5 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -886,15 +886,14 @@ class Bar: ... """) -@pytest.mark.xfail(reason="Need to investigate odd integration with target version") -def test_integration_filename_last(tmp_path: Path) -> None: # pragma: no cover +def test_integration_filename_last(tmp_path: Path) -> None: f = tmp_path / "f.md" f.write_text( dedent("""\ ```python def very_very_long_function_name( very_very_very_very_very_very, - very_very_very_very_very_very, + another_very_very_very_very_very_very, *long_long_long_long_long_long ): pass @@ -902,14 +901,14 @@ def very_very_long_function_name( """), ) - result2 = main(("--target-version", "py36", str(f))) + result2 = main(("--config", "line-length=88", str(f))) assert result2 == 1 assert f.read_text() == dedent("""\ ```python def very_very_long_function_name( very_very_very_very_very_very, - very_very_very_very_very_very, + another_very_very_very_very_very_very, *long_long_long_long_long_long, ): pass From 65048f1b388611eb122f5aa3c7da2a044bb7c2af Mon Sep 17 00:00:00 2001 From: Calum Young Date: Mon, 30 Dec 2024 02:17:07 +0000 Subject: [PATCH 35/35] Remove more extra tests --- tests/test_ruff_format_docs.py | 54 ---------------------------------- 1 file changed, 54 deletions(-) diff --git a/tests/test_ruff_format_docs.py b/tests/test_ruff_format_docs.py index 31cccf5..e314c2c 100644 --- a/tests/test_ruff_format_docs.py +++ b/tests/test_ruff_format_docs.py @@ -7,8 +7,6 @@ from textwrap import dedent from typing import TYPE_CHECKING -import pytest - from ruff_format_docs import FormatterConfig, format_file_contents, main if TYPE_CHECKING: @@ -916,58 +914,6 @@ def very_very_long_function_name( """) -@pytest.mark.xfail( - reason="Need to decide if we want to handle multiple target versions", -) -def test_integration_multiple_target_version( - tmp_path: Path, -) -> None: # pragma: no cover - f = tmp_path / "f.md" - f.write_text( - dedent("""\ - ```python - def very_very_long_function_name( - very_very_very_very_very_very, - very_very_very_very_very_very, - *long_long_long_long_long_long - ): - pass - ``` - """), - ) - - result = main((str(f),)) - assert result == 0 - - result2 = main( - ("--target-version", "py35", "--target-version", "py36", str(f)), - ) - assert result2 == 0 - - -@pytest.mark.xfail(reason="Need to if we want to support this") -def test_integration_skip_string_normalization( - tmp_path: Path, -) -> None: # pragma: no cover - f = tmp_path / "f.md" - f.write_text( - dedent("""\ - ```python - f('hi') - ``` - """), - ) - - result = main((str(f), "--skip-string-normalization")) - - assert result == 0 - assert f.read_text() == dedent("""\ - ```python - f('hi') - ``` - """) - - def test_integration_syntax_error(tmp_path: Path, capsys: CaptureFixture[str]) -> None: f = tmp_path / "f.md" f.write_text(