From 464449237f8bb3bc4fe9634f1de2aabafaad61e1 Mon Sep 17 00:00:00 2001 From: Michael The Date: Mon, 27 May 2024 12:41:22 +0200 Subject: [PATCH 1/7] Format SQL code blocks in .md files --- poetry.lock | 199 +++++++++--------- pyproject.toml | 2 + src/sqlfmt/api.py | 38 +++- src/sqlfmt/cli.py | 9 + src/sqlfmt/mode.py | 5 +- .../fast/preformatted/007_markdown_file.md | 5 + .../008_markdown_file_without_sql_blocks.md | 9 + .../fast/unformatted/107_markdown_file.md | 11 + tests/data/preformatted/008_markdown_file.md | 19 ++ .../data/preformatted/009_markdown_fmt_off.md | 6 + tests/data/unformatted/500_markdown_file.md | 11 + .../nested_directory/another_markdown_file.md | 0 .../a_directory/one_markdown_file.md | 0 .../top_level_markdown_file.md | 0 .../test_general_formatting.py | 20 +- tests/unit_tests/test_api.py | 32 ++- tests/unit_tests/test_cli.py | 8 +- tests/util.py | 2 +- 18 files changed, 260 insertions(+), 116 deletions(-) create mode 100644 tests/data/fast/preformatted/007_markdown_file.md create mode 100644 tests/data/fast/preformatted/008_markdown_file_without_sql_blocks.md create mode 100644 tests/data/fast/unformatted/107_markdown_file.md create mode 100644 tests/data/preformatted/008_markdown_file.md create mode 100644 tests/data/preformatted/009_markdown_fmt_off.md create mode 100644 tests/data/unformatted/500_markdown_file.md create mode 100644 tests/data/unit_tests/test_api/test_file_discovery/a_directory/nested_directory/another_markdown_file.md create mode 100644 tests/data/unit_tests/test_api/test_file_discovery/a_directory/one_markdown_file.md create mode 100644 tests/data/unit_tests/test_api/test_file_discovery/top_level_markdown_file.md diff --git a/poetry.lock b/poetry.lock index 0b76331e..8831f802 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "black" @@ -84,63 +84,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.4" +version = "7.5.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, + {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, + {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, + {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, + {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, + {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, + {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, + {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, + {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, + {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, + {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, + {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, + {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, ] [package.dependencies] @@ -162,13 +162,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -176,13 +176,13 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.13.4" +version = "3.14.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, - {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, + {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, + {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] [package.extras] @@ -240,13 +240,13 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", [[package]] name = "identify" -version = "2.5.35" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -374,6 +374,17 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mistletoe" +version = "1.3.0" +description = "A fast, extensible Markdown parser in pure Python." +optional = true +python-versions = "~=3.5" +files = [ + {file = "mistletoe-1.3.0-py3-none-any.whl", hash = "sha256:a0710d0cdb7d2458cef91cf6b9b897db442927f8778dae890cbbfb1853a51700"}, + {file = "mistletoe-1.3.0.tar.gz", hash = "sha256:bd0e788add9b4478d19ec792f6d5fb908a20e70abfe61193f29178c55d3adbe9"}, +] + [[package]] name = "mypy" version = "1.10.0" @@ -434,18 +445,15 @@ files = [ [[package]] name = "nodeenv" -version = "1.8.0" +version = "1.9.0" description = "Node.js virtual environment builder" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "nodeenv-1.9.0-py2.py3-none-any.whl", hash = "sha256:508ecec98f9f3330b636d4448c0f1a56fc68017c68f1e7857ebc52acf0eb879a"}, + {file = "nodeenv-1.9.0.tar.gz", hash = "sha256:07f144e90dae547bf0d4ee8da0ee42664a42a04e02ed68e06324348dafe4bdb1"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "packaging" version = "24.0" @@ -470,13 +478,13 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.1" +version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] @@ -541,13 +549,13 @@ files = [ [[package]] name = "pytest" -version = "8.2.0" +version = "8.2.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, - {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, + {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, + {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, ] [package.dependencies] @@ -639,22 +647,6 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] -[[package]] -name = "setuptools" -version = "69.2.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "smmap" version = "5.0.1" @@ -713,13 +705,13 @@ files = [ [[package]] name = "tqdm" -version = "4.66.3" +version = "4.66.4" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53"}, - {file = "tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5"}, + {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, + {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, ] [package.dependencies] @@ -733,24 +725,24 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, + {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.2" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, + {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, ] [package.dependencies] @@ -759,14 +751,15 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [extras] jinjafmt = ["black"] +markdownfmt = ["mistletoe"] sqlfmt-primer = ["gitpython"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "b05d623fd0140c735a028c006167f9e6ccd2e4bb4b6a299e7ba5a741df835722" +content-hash = "00aaf8d31cec71f7e7a110cf23bd75c25266fee7d5af1eb25fe3d4fcf741beb7" diff --git a/pyproject.toml b/pyproject.toml index 6b5f5d2e..0143d920 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ packages = [ [tool.poetry.extras] jinjafmt = ["black"] +markdownfmt = ["mistletoe"] sqlfmt_primer = ["gitpython"] [tool.poetry.dependencies] @@ -40,6 +41,7 @@ jinja2 = "^3.0" black = { version = "*", optional = true } gitpython = { version = "^3.1.24", optional = true } +mistletoe = { version = '*', optional = true} [tool.poetry.group.dev.dependencies] pre-commit = ">=2.20,<4.0" diff --git a/src/sqlfmt/api.py b/src/sqlfmt/api.py index 24d44196..9560462b 100755 --- a/src/sqlfmt/api.py +++ b/src/sqlfmt/api.py @@ -54,6 +54,35 @@ def format_string(source_string: str, mode: Mode) -> str: return result +def format_markdown_string(source_string: str, mode: Mode) -> str: + """ + Takes a Markdown string and a mode as input, returns the the Markdown string with all of its SQL code blocks formatted. + """ + if mode.no_markdownfmt: + return source_string + + from mistletoe import Document + from mistletoe.block_token import BlockToken, CodeFence + from mistletoe.markdown_renderer import MarkdownRenderer + + def format_sql_code_blocks(token: BlockToken): + """Walk through the AST and replace SQL code blocks with its formatted version.""" + if isinstance(token, CodeFence) and token.language == "sql": + raw_text = token.children[0] + raw_text.content = format_string(raw_text.content, mode) + + for child in token.children: + if isinstance(child, BlockToken): + format_sql_code_blocks(child) + + with MarkdownRenderer() as renderer: + doc = Document(source_string) + format_sql_code_blocks(doc) + formatted_markdown_string = renderer.render(doc) + + return formatted_markdown_string + + def run( files: Collection[Path], mode: Mode, @@ -154,7 +183,7 @@ def _get_included_paths(paths: Iterable[Path], mode: Mode) -> Set[Path]: for p in paths: if p == STDIN_PATH: include_set.add(p) - elif p.is_file() and str(p).endswith(tuple(mode.SQL_EXTENSIONS)): + elif p.is_file() and str(p).endswith(tuple(mode.INCLUDED_FILE_EXTENSIONS)): include_set.add(p) elif p.is_dir(): include_set |= _get_included_paths(p.iterdir(), mode) @@ -233,10 +262,15 @@ def _format_one(path: Path, mode: Mode) -> SqlFormatResult: """ Runs format_string on the contents of a single file (found at path). Handles potential user errors in formatted code, and returns a SqlfmtResult + + If the file is a Markdown file, only format its SQL code blocks. """ source, encoding, utf_bom = _read_path_or_stdin(path, mode) try: - formatted = format_string(source, mode) + if path.is_file() and path.suffix == ".md": + formatted = format_markdown_string(source, mode) + else: + formatted = format_string(source, mode) return SqlFormatResult( source_path=path, source_string=source, diff --git a/src/sqlfmt/cli.py b/src/sqlfmt/cli.py index 06b50fa8..6c9d213e 100755 --- a/src/sqlfmt/cli.py +++ b/src/sqlfmt/cli.py @@ -91,6 +91,15 @@ "or if black was already available in this environment." ), ) +@click.option( + "--no-markdownfmt", + envvar="SQLFMT_NO_MARKDOWNFMT", + is_flag=True, + help=( + "Do not format sql code blocks in markdown files. Only necessary " + "to specify this flag if sqlfmt was installed with the markdownfmt extra." + ), +) @click.option( "-l", "--line-length", diff --git a/src/sqlfmt/mode.py b/src/sqlfmt/mode.py index 5370c86a..d80ebdaa 100644 --- a/src/sqlfmt/mode.py +++ b/src/sqlfmt/mode.py @@ -14,7 +14,9 @@ class Mode: report config. For more info on each option, see cli.py """ - SQL_EXTENSIONS: List[str] = field(default_factory=lambda: [".sql", ".sql.jinja"]) + INCLUDED_FILE_EXTENSIONS: List[str] = field( + default_factory=lambda: [".sql", ".sql.jinja", ".md"] + ) dialect_name: str = "polyglot" line_length: int = 88 check: bool = False @@ -25,6 +27,7 @@ class Mode: fast: bool = False single_process: bool = False no_jinjafmt: bool = False + no_markdownfmt: bool = False reset_cache: bool = False verbose: bool = False quiet: bool = False diff --git a/tests/data/fast/preformatted/007_markdown_file.md b/tests/data/fast/preformatted/007_markdown_file.md new file mode 100644 index 00000000..dfabc8a5 --- /dev/null +++ b/tests/data/fast/preformatted/007_markdown_file.md @@ -0,0 +1,5 @@ +# Hello + +```sql +select 1 +``` diff --git a/tests/data/fast/preformatted/008_markdown_file_without_sql_blocks.md b/tests/data/fast/preformatted/008_markdown_file_without_sql_blocks.md new file mode 100644 index 00000000..5aee2757 --- /dev/null +++ b/tests/data/fast/preformatted/008_markdown_file_without_sql_blocks.md @@ -0,0 +1,9 @@ +# Hello again + +```python +import antigravity +``` + +``` +SELECT 1 +``` diff --git a/tests/data/fast/unformatted/107_markdown_file.md b/tests/data/fast/unformatted/107_markdown_file.md new file mode 100644 index 00000000..308e2c73 --- /dev/null +++ b/tests/data/fast/unformatted/107_markdown_file.md @@ -0,0 +1,11 @@ +# Hello + +```sql +SELECT 1 +``` +)))))__SQLFMT_OUTPUT__((((( +# Hello + +```sql +select 1 +``` diff --git a/tests/data/preformatted/008_markdown_file.md b/tests/data/preformatted/008_markdown_file.md new file mode 100644 index 00000000..e532f72a --- /dev/null +++ b/tests/data/preformatted/008_markdown_file.md @@ -0,0 +1,19 @@ +# Hello + +This is some SQL code in a `sql` code block: + +```sql +select 1 +``` + +SQL code, but it's not a `sql` code block so it shouldn't get formatted: + +``` +SELECT 2 +``` + +And finally, some bash code + +```bash +echo "Hello, world!" +``` diff --git a/tests/data/preformatted/009_markdown_fmt_off.md b/tests/data/preformatted/009_markdown_fmt_off.md new file mode 100644 index 00000000..3ec48204 --- /dev/null +++ b/tests/data/preformatted/009_markdown_fmt_off.md @@ -0,0 +1,6 @@ +# Hello + +```sql +-- fmt: off +SELECT 1 +``` diff --git a/tests/data/unformatted/500_markdown_file.md b/tests/data/unformatted/500_markdown_file.md new file mode 100644 index 00000000..308e2c73 --- /dev/null +++ b/tests/data/unformatted/500_markdown_file.md @@ -0,0 +1,11 @@ +# Hello + +```sql +SELECT 1 +``` +)))))__SQLFMT_OUTPUT__((((( +# Hello + +```sql +select 1 +``` diff --git a/tests/data/unit_tests/test_api/test_file_discovery/a_directory/nested_directory/another_markdown_file.md b/tests/data/unit_tests/test_api/test_file_discovery/a_directory/nested_directory/another_markdown_file.md new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/unit_tests/test_api/test_file_discovery/a_directory/one_markdown_file.md b/tests/data/unit_tests/test_api/test_file_discovery/a_directory/one_markdown_file.md new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/unit_tests/test_api/test_file_discovery/top_level_markdown_file.md b/tests/data/unit_tests/test_api/test_file_discovery/top_level_markdown_file.md new file mode 100644 index 00000000..e69de29b diff --git a/tests/functional_tests/test_general_formatting.py b/tests/functional_tests/test_general_formatting.py index d0bfae9f..6d544bee 100644 --- a/tests/functional_tests/test_general_formatting.py +++ b/tests/functional_tests/test_general_formatting.py @@ -1,6 +1,6 @@ import pytest -from sqlfmt.api import format_string +from sqlfmt.api import format_markdown_string, format_string from sqlfmt.mode import Mode from tests.util import check_formatting, read_test_data @@ -97,3 +97,21 @@ def test_formatting(p: str) -> None: second_pass = format_string(actual, mode) check_formatting(expected, second_pass, ctx=f"2nd-{p}") + + +@pytest.mark.parametrize( + "p", + [ + "unformatted/500_markdown_file.md", + ], +) +def test_markdown_formatting(p: str) -> None: + mode = Mode() + + source, expected = read_test_data(p) + actual = format_markdown_string(source, mode) + + check_formatting(expected, actual, ctx=p) + + second_pass = format_markdown_string(actual, mode) + check_formatting(expected, second_pass, ctx=f"2nd-{p}") diff --git a/tests/unit_tests/test_api.py b/tests/unit_tests/test_api.py index f1361491..610062b2 100644 --- a/tests/unit_tests/test_api.py +++ b/tests/unit_tests/test_api.py @@ -12,6 +12,7 @@ _perform_safety_check, _read_path_or_stdin, _update_source_files, + format_markdown_string, format_string, get_matching_paths, initialize_progress_bar, @@ -47,8 +48,11 @@ def all_files(file_discovery_dir: Path) -> Set[Path]: files = { p / "top_level_file.sql", p / "top_level_file.two.sql", + p / "top_level_markdown_file.md", p / "a_directory/one_file.sql", + p / "a_directory/one_markdown_file.md", p / "a_directory/nested_directory/another_file.sql", + p / "a_directory/nested_directory/another_markdown_file.md", p / "a_directory/nested_directory/j2_extension.sql.jinja", p / "a_directory/symlink_source_directory/symlink_file.sql", p / "a_directory/symlink_target_directory/symlink_file.sql", @@ -75,11 +79,12 @@ def test_file_discovery( "exclude", [ ["**/*_file*"], - ["**/*.sql"], + ["**/*.sql", "**/*.md"], [ "**/top*", "**/a_directory/*", "**/a_directory/**/another_file.sql", + "**/a_directory/**/another_markdown_file.md", "**/a_directory/**/symlink_file.sql", ], ], @@ -95,7 +100,10 @@ def test_file_discovery_with_excludes( def test_file_discovery_with_abs_excludes( file_discovery_dir: Path, sql_jinja_files: Set[Path] ) -> None: - exclude = [str(file_discovery_dir / "**/*.sql")] + exclude = [ + str(file_discovery_dir / "**/*.sql"), + str(file_discovery_dir / "**/*.md"), + ] mode = Mode(exclude=exclude, exclude_root=None) res = get_matching_paths(file_discovery_dir.iterdir(), mode) assert res == sql_jinja_files @@ -114,7 +122,7 @@ def test_file_discovery_with_invalid_excludes( def test_file_discovery_with_excludes_no_root( file_discovery_dir: Path, all_files: Set[Path], sql_jinja_files: Set[Path] ) -> None: - mode = Mode(exclude=["**/*.sql"], exclude_root=None) + mode = Mode(exclude=["**/*.sql", "**/*.md"], exclude_root=None) # relative to here, excludes shouldn't do anything. cwd = os.getcwd() @@ -142,6 +150,12 @@ def test_format_empty_string(all_output_modes: Mode) -> None: assert expected == actual +def test_format_markdown_empty_string(all_output_modes: Mode) -> None: + source = expected = "" + actual = format_markdown_string(source, all_output_modes) + assert expected == actual + + @pytest.mark.parametrize( "source,exception", [ @@ -356,6 +370,18 @@ def print_dot(_: Any) -> None: assert "." * expected_dots in captured.out +def test_run_no_markdownfmt_mode(unformatted_files: List[Path]) -> None: + unformatted_markdown_files = [ + file for file in unformatted_files if file.suffix == ".md" + ] + mode = Mode(no_markdownfmt=True) + report = run(files=unformatted_markdown_files, mode=mode) + assert report.number_changed == 0 + assert report.number_unchanged == len(unformatted_markdown_files) + assert report.number_errored == 0 + assert not any([res.from_cache for res in report.results]) + + def test_initialize_progress_bar(default_mode: Mode) -> None: total = 100 progress_bar, progress_callback = initialize_progress_bar( diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py index 797afde1..b661ca2d 100755 --- a/tests/unit_tests/test_cli.py +++ b/tests/unit_tests/test_cli.py @@ -96,7 +96,7 @@ def test_preformatted_short_lines_env( ) assert results.exit_code == 0 print(results.stderr) - assert "5 files formatted" in results.stderr + assert "6 files formatted" in results.stderr # test that CLI flag overrides ENV VAR args = f"{preformatted_dir.as_posix()} -l 88 --check" @@ -105,7 +105,7 @@ def test_preformatted_short_lines_env( ) assert results.exit_code == 0 print(results.stderr) - assert "6 files passed formatting check" in results.stderr + assert "8 files passed formatting check" in results.stderr def test_unformatted_check(sqlfmt_runner: CliRunner, unformatted_dir: Path) -> None: @@ -153,9 +153,7 @@ def test_preformatted_config_file( def test_preformatted_exclude_all( sqlfmt_runner: CliRunner, preformatted_dir: Path ) -> None: - args = ( - f"{preformatted_dir.as_posix()} --exclude {preformatted_dir.as_posix()}/*.sql" - ) + args = f"{preformatted_dir.as_posix()} --exclude {preformatted_dir.as_posix()}/*.sql --exclude {preformatted_dir.as_posix()}/*.md" results = sqlfmt_runner.invoke(sqlfmt_main, args=args) assert results.exit_code == 0 assert results.stderr.startswith("0 files left unchanged") diff --git a/tests/util.py b/tests/util.py index de13f217..aa2bb154 100644 --- a/tests/util.py +++ b/tests/util.py @@ -75,7 +75,7 @@ def check_formatting(expected: str, actual: str, ctx: str = "") -> None: def discover_test_files(relpaths: Iterable[Union[str, Path]]) -> Iterator[Path]: for p in [BASE_DIR / p for p in relpaths]: - if p.is_file() and p.suffix == ".sql": + if p.is_file() and p.suffix in [".sql", ".md"]: yield p elif p.is_dir(): yield from (discover_test_files(p.iterdir())) From 855760cc9481bc48c248516b10d1168b931cbcaa Mon Sep 17 00:00:00 2001 From: Michael The Date: Wed, 29 May 2024 17:08:18 +0200 Subject: [PATCH 2/7] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a025983..c58ade5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Features + +- Add support for formatting SQL code blocks in Markdown files. Introduces a new extra install (`pipx install shandy-sqlfmt[markdownfmt]`) and CLI option (`--no-markdownfmt`) ([#593](https://github.com/tconbeer/sqlfmt/issues/593) - thank you, [@michael-the1](https://github.com/michael-the1)). + ## [0.21.3] - 2024-04-25 ### Bug Fixes From 68d51bafb20cb3cf0d7dfadfe9bf251c0f354988 Mon Sep 17 00:00:00 2001 From: Michael The Date: Mon, 3 Jun 2024 16:56:56 +0200 Subject: [PATCH 3/7] Only parse Markdown files if mistletoe is installed --- src/sqlfmt/api.py | 2 +- src/sqlfmt/mode.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/sqlfmt/api.py b/src/sqlfmt/api.py index 9560462b..c1828570 100755 --- a/src/sqlfmt/api.py +++ b/src/sqlfmt/api.py @@ -183,7 +183,7 @@ def _get_included_paths(paths: Iterable[Path], mode: Mode) -> Set[Path]: for p in paths: if p == STDIN_PATH: include_set.add(p) - elif p.is_file() and str(p).endswith(tuple(mode.INCLUDED_FILE_EXTENSIONS)): + elif p.is_file() and str(p).endswith(tuple(mode.included_file_extensions)): include_set.add(p) elif p.is_dir(): include_set |= _get_included_paths(p.iterdir(), mode) diff --git a/src/sqlfmt/mode.py b/src/sqlfmt/mode.py index d80ebdaa..deb58884 100644 --- a/src/sqlfmt/mode.py +++ b/src/sqlfmt/mode.py @@ -2,6 +2,7 @@ from dataclasses import dataclass, field from pathlib import Path from typing import List, Optional +from importlib import import_module from sqlfmt.dialect import ClickHouse, Polyglot from sqlfmt.exception import SqlfmtConfigError @@ -14,9 +15,6 @@ class Mode: report config. For more info on each option, see cli.py """ - INCLUDED_FILE_EXTENSIONS: List[str] = field( - default_factory=lambda: [".sql", ".sql.jinja", ".md"] - ) dialect_name: str = "polyglot" line_length: int = 88 check: bool = False @@ -49,6 +47,17 @@ def __post_init__(self) -> None: "which is not supported. Did you mean 'polyglot'?" ) + @property + def included_file_extensions(self) -> List[str]: + """List of file extensions to parse.""" + if self.no_markdownfmt: + return [".sql", ".sql.jinja"] + try: + import_module("mistletoe") + return [".sql", ".sql.jinja", ".md"] + except ImportError: + return [".sql", ".sql.jinja"] + @property def color(self) -> bool: """ From 814b668e3125602c7bae5e1f295a270a09879a87 Mon Sep 17 00:00:00 2001 From: Michael The Date: Wed, 5 Jun 2024 11:12:58 +0200 Subject: [PATCH 4/7] Use importlib.util.find_spec to check for mistletoe --- src/sqlfmt/mode.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/sqlfmt/mode.py b/src/sqlfmt/mode.py index deb58884..8f54c1a3 100644 --- a/src/sqlfmt/mode.py +++ b/src/sqlfmt/mode.py @@ -1,8 +1,8 @@ import os from dataclasses import dataclass, field +from importlib.util import find_spec from pathlib import Path from typing import List, Optional -from importlib import import_module from sqlfmt.dialect import ClickHouse, Polyglot from sqlfmt.exception import SqlfmtConfigError @@ -49,14 +49,13 @@ def __post_init__(self) -> None: @property def included_file_extensions(self) -> List[str]: - """List of file extensions to parse.""" - if self.no_markdownfmt: - return [".sql", ".sql.jinja"] - try: - import_module("mistletoe") + """List of file extensions to parse. + + Only parses Markdown files if mistletoe is installed and no_markdownfmt is not set. + """ + if not self.no_markdownfmt and find_spec("mistletoe"): return [".sql", ".sql.jinja", ".md"] - except ImportError: - return [".sql", ".sql.jinja"] + return [".sql", ".sql.jinja"] @property def color(self) -> bool: From cb7d2181b208baa4290b1f7a0a0eaaf690b68609 Mon Sep 17 00:00:00 2001 From: Michael The Date: Mon, 10 Jun 2024 10:01:19 +0200 Subject: [PATCH 5/7] Return tuple from included_file_extensions --- src/sqlfmt/api.py | 2 +- src/sqlfmt/mode.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sqlfmt/api.py b/src/sqlfmt/api.py index c1828570..ea10287b 100755 --- a/src/sqlfmt/api.py +++ b/src/sqlfmt/api.py @@ -183,7 +183,7 @@ def _get_included_paths(paths: Iterable[Path], mode: Mode) -> Set[Path]: for p in paths: if p == STDIN_PATH: include_set.add(p) - elif p.is_file() and str(p).endswith(tuple(mode.included_file_extensions)): + elif p.is_file() and str(p).endswith(mode.included_file_extensions): include_set.add(p) elif p.is_dir(): include_set |= _get_included_paths(p.iterdir(), mode) diff --git a/src/sqlfmt/mode.py b/src/sqlfmt/mode.py index 8f54c1a3..c2f87af1 100644 --- a/src/sqlfmt/mode.py +++ b/src/sqlfmt/mode.py @@ -2,7 +2,7 @@ from dataclasses import dataclass, field from importlib.util import find_spec from pathlib import Path -from typing import List, Optional +from typing import List, Optional, Tuple from sqlfmt.dialect import ClickHouse, Polyglot from sqlfmt.exception import SqlfmtConfigError @@ -48,14 +48,14 @@ def __post_init__(self) -> None: ) @property - def included_file_extensions(self) -> List[str]: + def included_file_extensions(self) -> Tuple[str, ...]: """List of file extensions to parse. Only parses Markdown files if mistletoe is installed and no_markdownfmt is not set. """ if not self.no_markdownfmt and find_spec("mistletoe"): - return [".sql", ".sql.jinja", ".md"] - return [".sql", ".sql.jinja"] + return (".sql", ".sql.jinja", ".md") + return (".sql", ".sql.jinja") @property def color(self) -> bool: From 9ed1fcf6135bb3777efb23ae682c381e320c3db1 Mon Sep 17 00:00:00 2001 From: Michael The Date: Wed, 12 Jun 2024 10:58:02 +0200 Subject: [PATCH 6/7] Add missing test files --- tests/functional_tests/test_general_formatting.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/functional_tests/test_general_formatting.py b/tests/functional_tests/test_general_formatting.py index 6d544bee..bf4fea07 100644 --- a/tests/functional_tests/test_general_formatting.py +++ b/tests/functional_tests/test_general_formatting.py @@ -102,6 +102,8 @@ def test_formatting(p: str) -> None: @pytest.mark.parametrize( "p", [ + "preformatted/008_markdown_file.md", + "preformatted/009_markdown_fmt_off.md", "unformatted/500_markdown_file.md", ], ) From b622a658d269eca90a175abc7e15e677bca4bd96 Mon Sep 17 00:00:00 2001 From: Michael The Date: Wed, 12 Jun 2024 11:14:51 +0200 Subject: [PATCH 7/7] Guard against formatting markdown files while mistletoe is not installed --- src/sqlfmt/api.py | 18 ++++++++++++++---- src/sqlfmt/exception.py | 8 ++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/sqlfmt/api.py b/src/sqlfmt/api.py index ea10287b..d5385e8c 100755 --- a/src/sqlfmt/api.py +++ b/src/sqlfmt/api.py @@ -24,7 +24,12 @@ from sqlfmt.analyzer import Analyzer from sqlfmt.cache import Cache, check_cache, clear_cache, load_cache, write_cache -from sqlfmt.exception import SqlfmtEquivalenceError, SqlfmtError, SqlfmtUnicodeError +from sqlfmt.exception import ( + SqlfmtEquivalenceError, + SqlfmtError, + SqlfmtImportError, + SqlfmtUnicodeError, +) from sqlfmt.formatter import QueryFormatter from sqlfmt.mode import Mode as Mode from sqlfmt.query import Query @@ -61,9 +66,14 @@ def format_markdown_string(source_string: str, mode: Mode) -> str: if mode.no_markdownfmt: return source_string - from mistletoe import Document - from mistletoe.block_token import BlockToken, CodeFence - from mistletoe.markdown_renderer import MarkdownRenderer + try: + from mistletoe import Document + from mistletoe.block_token import BlockToken, CodeFence + from mistletoe.markdown_renderer import MarkdownRenderer + except ImportError: + raise SqlfmtImportError( + "Tried to format a Markdown file but markdownfmt extras are not installed." + ) def format_sql_code_blocks(token: BlockToken): """Walk through the AST and replace SQL code blocks with its formatted version.""" diff --git a/src/sqlfmt/exception.py b/src/sqlfmt/exception.py index 7e350333..49e6dedb 100644 --- a/src/sqlfmt/exception.py +++ b/src/sqlfmt/exception.py @@ -63,6 +63,14 @@ class SqlfmtEquivalenceError(SqlfmtError): pass +class SqlfmtImportError(SqlfmtError): + """ + Raised when an extra library is imported while not installed + """ + + pass + + class SqlfmtControlFlowException(Exception): """ Generic exception for exceptions used to manage control