diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8466208..0c0d5d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,11 @@ on: permissions: contents: read +env: + # Python versions to build wheels for + # Include both standard and free-threaded (t suffix) versions + PYTHON_VERSIONS: "3.10 3.11 3.12 3.13 3.13t 3.14 3.14t" + jobs: linux: runs-on: ubuntu-latest @@ -16,15 +21,30 @@ jobs: target: [x86_64, aarch64] steps: - uses: actions/checkout@v4 - + + - name: Set up Python versions + uses: actions/setup-python@v5 + with: + python-version: | + 3.10 + 3.11 + 3.12 + 3.13 + 3.14 + + - name: Install free-threaded Python + uses: astral-sh/setup-uv@v6 + + - run: uv python install 3.13t 3.14t + - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --find-interpreter - sccache: 'true' + args: --release --out dist -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.13t -i python3.14 -i python3.14t + sccache: "true" manylinux: auto - + - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -35,17 +55,27 @@ jobs: runs-on: windows-latest strategy: matrix: - target: [x64, x86] + target: [x64] steps: - uses: actions/checkout@v4 - + + - name: Set up Python versions + uses: actions/setup-python@v5 + with: + python-version: | + 3.10 + 3.11 + 3.12 + 3.13 + 3.14 + - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --find-interpreter - sccache: 'true' - + args: --release --out dist -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.14 + sccache: "true" + - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -59,14 +89,29 @@ jobs: target: [x86_64, aarch64] steps: - uses: actions/checkout@v4 - + + - name: Set up Python versions + uses: actions/setup-python@v5 + with: + python-version: | + 3.10 + 3.11 + 3.12 + 3.13 + 3.14 + + - name: Install free-threaded Python + uses: astral-sh/setup-uv@v6 + + - run: uv python install 3.13t 3.14t + - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --find-interpreter - sccache: 'true' - + args: --release --out dist -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.13t -i python3.14 -i python3.14t + sccache: "true" + - name: Upload wheels uses: actions/upload-artifact@v4 with: @@ -77,13 +122,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - name: Build sdist uses: PyO3/maturin-action@v1 with: command: sdist args: --out dist - + - name: Upload sdist uses: actions/upload-artifact@v4 with: @@ -100,7 +145,7 @@ jobs: pattern: wheels-* path: dist merge-multiple: true - + - name: Publish to PyPI uses: PyO3/maturin-action@v1 env: diff --git a/Cargo.lock b/Cargo.lock index d5765c8..c049576 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,33 +1,33 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bstr" -version = "1.10.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", @@ -36,24 +36,25 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.28" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -70,21 +71,21 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -98,17 +99,23 @@ dependencies = [ "encoding_rs", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + [[package]] name = "glob" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" -version = "0.4.15" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ "aho-corasick", "bstr", @@ -132,9 +139,9 @@ dependencies = [ [[package]] name = "grep-cli" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47f1288f0e06f279f84926fa4c17e3fcd2a22b357927a82f2777f7be26e4cec0" +checksum = "cf32d263c5d5cc2a23ce587097f5ddafdb188492ba2e6fb638eaccdc22453631" dependencies = [ "bstr", "globset", @@ -146,9 +153,9 @@ dependencies = [ [[package]] name = "grep-matcher" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02" +checksum = "36d7b71093325ab22d780b40d7df3066ae4aebb518ba719d38c697a8228a8023" dependencies = [ "memchr", ] @@ -170,9 +177,9 @@ dependencies = [ [[package]] name = "grep-regex" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edd147c7e3296e7a26bd3a81345ce849557d5a8e48ed88f736074e760f91f7e" +checksum = "0ce0c256c3ad82bcc07b812c15a45ec1d398122e8e15124f96695234db7112ef" dependencies = [ "bstr", "grep-matcher", @@ -183,9 +190,9 @@ dependencies = [ [[package]] name = "grep-searcher" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b6c14b3fc2e0a107d6604d3231dec0509e691e62447104bc385a46a7892cda" +checksum = "ac63295322dc48ebb20a25348147905d816318888e64f531bfc2a2bc0577dc34" dependencies = [ "bstr", "encoding_rs", @@ -204,15 +211,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "ignore" -version = "0.4.23" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" dependencies = [ "crossbeam-deque", "globset", @@ -226,39 +233,42 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.5" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -274,9 +284,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", @@ -284,32 +294,31 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.22.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225" +checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" dependencies = [ - "cfg-if", "indoc", "libc", "memoffset", @@ -323,20 +332,19 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3" +checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" dependencies = [ - "once_cell", "python3-dll-a", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.22.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c" +checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" dependencies = [ "libc", "pyo3-build-config", @@ -344,9 +352,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28" +checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -356,9 +364,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1" +checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" dependencies = [ "heck", "proc-macro2", @@ -369,51 +377,27 @@ dependencies = [ [[package]] name = "python3-dll-a" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0b78171a90d808b319acfad166c4790d9e9759bbc14ac8273fe133673dd41b" +checksum = "d381ef313ae70b4da5f95f8a4de773c6aa5cd28f73adec4b4a31df70b66780d8" dependencies = [ "cc", ] -[[package]] -name = "python_ripgrep" -version = "0.0.9" -dependencies = [ - "anyhow", - "bstr", - "glob", - "grep", - "grep-cli", - "grep-matcher", - "grep-printer", - "grep-regex", - "grep-searcher", - "ignore", - "log", - "num_cpus", - "pyo3", - "rayon", - "regex", - "same-file", - "termcolor", - "walkdir", -] - [[package]] name = "quote" -version = "1.0.37" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -421,9 +405,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -431,9 +415,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -443,9 +427,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -454,15 +438,39 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] -name = "ryu" -version = "1.0.18" +name = "ripgrep_rs" +version = "0.3.0" +dependencies = [ + "anyhow", + "bstr", + "glob", + "grep", + "grep-cli", + "grep-matcher", + "grep-printer", + "grep-regex", + "grep-searcher", + "ignore", + "log", + "num_cpus", + "pyo3", + "rayon", + "regex", + "same-file", + "termcolor", + "walkdir", +] + +[[package]] +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -475,18 +483,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -495,14 +512,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -513,9 +531,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -524,9 +542,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" [[package]] name = "termcolor" @@ -539,15 +557,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unindent" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" [[package]] name = "walkdir" @@ -561,82 +579,30 @@ dependencies = [ [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys", ] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-link", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "zmij" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml index 1014b6a..633a9b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,17 @@ [package] -name = "python_ripgrep" -version = "0.1.0" +name = "ripgrep_rs" +version = "0.3.0" edition = "2021" [lib] -name = "python_ripgrep" crate-type = ["cdylib"] +name = "ripgrep_rs" [dependencies] -pyo3 = { version = "0.22.3", features = ["extension-module", "abi3-py38", "generate-import-lib"] } - +anyhow = "1.0" +bstr = "1.7.0" # Dependencies for ripgrep_core glob = "0.3" -anyhow = "1.0" -rayon = "1.5" grep = "0.3.2" grep-cli = "0.1" grep-matcher = "0.1" @@ -23,12 +21,15 @@ grep-searcher = "0.1" ignore = "0.4" log = "0.4" num_cpus = "1.0" +pyo3 = { version = "0.27", features = [ + "extension-module", + "generate-import-lib" +] } +rayon = "1.5" regex = "1.5" same-file = "1.0" -walkdir = "2.3" termcolor = "1.3.0" -bstr = "1.7.0" - +walkdir = "2.3" [lints.rust] dead_code = "allow" diff --git a/README.md b/README.md index d81e5b4..e1eba89 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,10 @@ As of now, the library implements a subset of ripgrep's functionality. The main 3. `globs`: File patterns to include or exclude 4. `sort`: Sort mode for search results 5. `max_count`: Maximum number of matches to show +6. `case_sensitive`: Control case sensitivity +7. `smart_case`: Enable smart case matching +8. `no_ignore`: Disable gitignore/ignore file handling +9. `hidden`: Search hidden files and directories ## Implemented Flags @@ -89,6 +93,12 @@ The following is a checklist of ripgrep flags that have been implemented in this - [x] `separator_field_match`: (Optional) Separator between fields in matching lines - [x] `separator_context`: (Optional) Separator between context lines - [x] `-U, --multiline`: Enable matching across multiple lines +- [x] `-i, --ignore-case`: Case insensitive search (via `case_sensitive=False`) +- [x] `-s, --case-sensitive`: Case sensitive search (via `case_sensitive=True`) +- [x] `-S, --smart-case`: Smart case search (via `smart_case=True`) +- [x] `--no-ignore`: Don't respect ignore files (via `no_ignore=True`) +- [x] `--hidden`: Search hidden files and directories (via `hidden=True`) +- [x] `--json`: Output results in JSON Lines format (via `json=True`) The following flags from ripgrep are not yet implemented in this wrapper: @@ -99,13 +109,11 @@ The following flags from ripgrep are not yet implemented in this wrapper: - [ ] `--dfa-size-limit`: Limit for regex DFA size - [ ] `-E, --encoding`: Specify the text encoding of files to search - [ ] `-F, --fixed-strings`: Treat patterns as literal strings -- [ ] `-i, --ignore-case`: Case insensitive search - [ ] `-v, --invert-match`: Invert matching - [ ] `-n, --line-number`: Show line numbers - [ ] `-x, --line-regexp`: Only show matches surrounded by line boundaries - [ ] `-M, --max-columns`: Don't print lines longer than this limit - [ ] `--mmap`: Memory map searched files when possible -- [ ] `--no-ignore`: Don't respect ignore files - [ ] `--no-unicode`: Disable Unicode-aware search - [ ] `-0, --null`: Print NUL byte after file names - [ ] `-o, --only-matching`: Print only matched parts of a line @@ -113,8 +121,6 @@ The following flags from ripgrep are not yet implemented in this wrapper: - [ ] `-P, --pcre2`: Use the PCRE2 regex engine - [ ] `-p, --pretty`: Alias for --color=always --heading -n - [ ] `-r, --replace`: Replace matches with the given text -- [ ] `-S, --smart-case`: Smart case search -- [ ] `-s, --case-sensitive`: Case sensitive search - [ ] `--stats`: Print statistics about the search - [ ] `-a, --text`: Search binary files as if they were text - [ ] `-t, --type`: Only search files matching TYPE diff --git a/benchmark.py b/benchmark.py new file mode 100644 index 0000000..9143392 --- /dev/null +++ b/benchmark.py @@ -0,0 +1,164 @@ +"""Benchmark script for ripgrep_rs vs subprocess ripgrep.""" + +import subprocess +import time +from pathlib import Path + +import ripgrep_rs + + +def count_lines_in_result(result_list: list[str]) -> int: + """Count actual match lines from grouped file results.""" + total = 0 + for item in result_list: + lines = [l for l in item.strip().split("\n") if l] + total += len(lines) + return total + + +def bench_subprocess_grep( + path: str, pattern: str, n: int = 5, max_count: int | None = None +) -> tuple[float, int]: + times = [] + match_count = 0 + for _ in range(n): + start = time.perf_counter() + cmd = ["rg", "--no-heading", "-n", pattern, path] + if max_count: + cmd.extend(["--max-count", str(max_count)]) + result = subprocess.run(cmd, capture_output=True) + times.append(time.perf_counter() - start) + try: + stdout = result.stdout.decode("utf-8", errors="replace") + match_count = len([l for l in stdout.strip().split("\n") if l]) + except Exception: + match_count = -1 + return min(times) * 1000, match_count + + +def bench_lib_grep( + path: str, pattern: str, n: int = 5, max_count: int | None = None +) -> tuple[float, int]: + times = [] + match_count = 0 + for _ in range(n): + start = time.perf_counter() + result = ripgrep_rs.search( + [pattern], paths=[path], line_number=True, max_count=max_count + ) + times.append(time.perf_counter() - start) + match_count = count_lines_in_result(result) + return min(times) * 1000, match_count + + +def bench_subprocess_files(path: str, n: int = 5) -> tuple[float, int]: + times = [] + file_count = 0 + for _ in range(n): + start = time.perf_counter() + result = subprocess.run( + ["rg", "--files", path], + capture_output=True, + text=True, + ) + times.append(time.perf_counter() - start) + file_count = len([l for l in result.stdout.strip().split("\n") if l]) + return min(times) * 1000, file_count + + +def bench_lib_files(path: str, n: int = 5) -> tuple[float, int]: + times = [] + file_count = 0 + for _ in range(n): + start = time.perf_counter() + result = ripgrep_rs.files([""], paths=[path]) + times.append(time.perf_counter() - start) + file_count = len(result) + return min(times) * 1000, file_count + + +def run_benchmark( + name: str, + path: str, + pattern: str = "import", + iterations: int = 5, + max_count: int | None = None, +): + """Run a complete benchmark suite for a given path.""" + print(f"\n{'=' * 60}") + print(f"BENCHMARK: {name}") + print(f"Path: {path}") + print(f"{'=' * 60}") + + # File listing + print("\n--- File Listing (rg --files) ---") + t_sub, c_sub = bench_subprocess_files(path, iterations) + t_lib, c_lib = bench_lib_files(path, iterations) + ratio = t_sub / t_lib if t_lib > 0 else float("inf") + winner = "library" if t_lib < t_sub else "subprocess" + print(f" subprocess: {t_sub:8.2f}ms ({c_sub:,} files)") + print(f" library: {t_lib:8.2f}ms ({c_lib:,} files)") + print(f" Ratio: {ratio:.2f}x ({winner} faster)") + + # Grep + limit_info = f" (max_count={max_count})" if max_count else "" + print(f"\n--- Grep for '{pattern}'{limit_info} ---") + t_sub, c_sub = bench_subprocess_grep(path, pattern, iterations, max_count) + t_lib, c_lib = bench_lib_grep(path, pattern, iterations, max_count) + ratio = t_sub / t_lib if t_lib > 0 else float("inf") + winner = "library" if t_lib < t_sub else "subprocess" + print(f" subprocess: {t_sub:8.2f}ms ({c_sub:,} matches)") + print(f" library: {t_lib:8.2f}ms ({c_lib:,} matches)") + print(f" Ratio: {ratio:.2f}x ({winner} faster)") + + +def main(): + print("ripgrep_rs Benchmark Suite") + print("Comparing library bindings vs subprocess calls") + + # Find test directories + script_dir = Path(__file__).parent + + # Small: the ripgrep project itself + small_path = str(script_dir) + + # Medium: parent oss directory if it exists + medium_path = str(script_dir.parent) + + # Large: go up further if possible + large_path = ( + str(script_dir.parent.parent) + if script_dir.parent.parent.exists() + else medium_path + ) + + # Run benchmarks + run_benchmark("Small (this project)", small_path, pattern="fn ", iterations=10) + run_benchmark("Medium (parent dir)", medium_path, pattern="import", iterations=5) + + # Only run large if it's different from medium + # Skip grep for large dirs (too many matches cause OOM) + if large_path != medium_path: + print(f"\n{'=' * 60}") + print("BENCHMARK: Large (grandparent) - FILES ONLY") + print(f"Path: {large_path}") + print(f"{'=' * 60}") + print("\n--- File Listing (rg --files) ---") + t_sub, c_sub = bench_subprocess_files(large_path, 3) + t_lib, c_lib = bench_lib_files(large_path, 3) + ratio = t_sub / t_lib if t_lib > 0 else float("inf") + winner = "library" if t_lib < t_sub else "subprocess" + print(f" subprocess: {t_sub:8.2f}ms ({c_sub:,} files)") + print(f" library: {t_lib:8.2f}ms ({c_lib:,} files)") + print(f" Ratio: {ratio:.2f}x ({winner} faster)") + print("\n (Skipping grep for large dir - too many matches)") + + print("\n" + "=" * 60) + print("SUMMARY") + print("=" * 60) + print("- Library now uses parallel execution for both files and search") + print("- Should be faster than subprocess across all sizes") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index 2b1b774..c302f77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,37 +1,38 @@ -[build-system] -requires = ["maturin>=1.0,<2.0"] -build-backend = "maturin" - [project] -requires-python = ">=3.10,<3.13" +name = "ripgrep-rs" +version = "0.3.0" +description = "A Python wrapper for ripgrep" +readme = "README.md" +requires-python = ">=3.10" +license = { file = "LICENSE" } authors = [ - { name = "Josh Learn", email = "josh@exponent.run" }, - { name = "Indent", email = "noreply@indent.com" }, + { name = "Josh Learn", email = "josh@exponent.run" }, + { name = "Indent", email = "noreply@indent.com" }, ] +keywords = ["grep", "regex", "rg", "ripgrep", "rust", "search"] classifiers = [ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Rust", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Text Processing", + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Rust", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Text Processing", ] -description = "A Python wrapper for ripgrep" -name = "python-ripgrep" -version = "0.1.0" dependencies = [] -readme = "README.md" -license = { file = "LICENSE" } -keywords = ["ripgrep", "rg", "search", "grep", "regex", "rust"] [project.urls] -Homepage = "https://github.com/indent-com/python-ripgrep" -Repository = "https://github.com/indent-com/python-ripgrep" -Issues = "https://github.com/indent-com/python-ripgrep/issues" +Homepage = "https://github.com/phil65/python-ripgrep" +Issues = "https://github.com/phil65/python-ripgrep/issues" +Repository = "https://github.com/phil65/python-ripgrep" + +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" [tool.maturin] -features = ["pyo3/extension-module"] \ No newline at end of file +features = ["pyo3/extension-module"] diff --git a/python_ripgrep/__init__.py b/python_ripgrep/__init__.py deleted file mode 100644 index 458e6fc..0000000 --- a/python_ripgrep/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .python_ripgrep import PySortMode, PySortModeKind, files, search - -__all__ = ["PySortMode", "PySortModeKind", "files", "search"] diff --git a/ripgrep_rs/__init__.py b/ripgrep_rs/__init__.py new file mode 100644 index 0000000..23b4a5e --- /dev/null +++ b/ripgrep_rs/__init__.py @@ -0,0 +1,3 @@ +from .ripgrep_rs import PySortMode, PySortModeKind, files, search + +__all__ = ["PySortMode", "PySortModeKind", "files", "search"] diff --git a/python_ripgrep/__init__.pyi b/ripgrep_rs/__init__.pyi similarity index 77% rename from python_ripgrep/__init__.pyi rename to ripgrep_rs/__init__.pyi index e026063..91d4b5a 100644 --- a/python_ripgrep/__init__.pyi +++ b/ripgrep_rs/__init__.pyi @@ -26,6 +26,11 @@ def search( max_count: int | None = None, line_number: bool | None = None, multiline: bool | None = None, + case_sensitive: bool | None = None, + smart_case: bool | None = None, + no_ignore: bool | None = None, + hidden: bool | None = None, + json: bool | None = None, ) -> list[str]: ... def files( patterns: list[str], @@ -41,4 +46,10 @@ def files( max_count: int | None = None, line_number: bool | None = None, multiline: bool | None = None, + case_sensitive: bool | None = None, + smart_case: bool | None = None, + no_ignore: bool | None = None, + hidden: bool | None = None, + json: bool | None = None, + include_dirs: bool | None = None, ) -> list[str]: ... diff --git a/python_ripgrep/py.typed b/ripgrep_rs/py.typed similarity index 100% rename from python_ripgrep/py.typed rename to ripgrep_rs/py.typed diff --git a/src/lib.rs b/src/lib.rs index b103136..cefb181 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,13 @@ use pyo3::prelude::*; mod ripgrep_core; -use ripgrep_core::{py_search, py_files, PySortMode, PySortModeKind}; +use ripgrep_core::{py_files, py_search, PySortMode, PySortModeKind}; -#[pymodule] -fn python_ripgrep(m: &Bound<'_, PyModule>) -> PyResult<()> { +#[pymodule(gil_used = false)] +fn ripgrep_rs(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_function(wrap_pyfunction!(py_search, m)?)?; m.add_function(wrap_pyfunction!(py_files, m)?)?; Ok(()) -} \ No newline at end of file +} diff --git a/src/ripgrep_core.rs b/src/ripgrep_core.rs index 5b421ff..979ea5f 100644 --- a/src/ripgrep_core.rs +++ b/src/ripgrep_core.rs @@ -1,16 +1,21 @@ -use std::{borrow::BorrowMut, ffi::{OsStr, OsString}}; use hiargs::HiArgs; -use pyo3::prelude::*; +use ignore::WalkState; use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; +use std::{ + borrow::BorrowMut, + ffi::{OsStr, OsString}, + sync::mpsc, + thread, +}; -mod search; +mod haystack; mod hiargs; mod lowargs; -mod haystack; +mod search; #[macro_use] mod messages; - #[pyclass] pub struct PyArgs { pub patterns: Vec, @@ -26,29 +31,41 @@ pub struct PyArgs { pub max_count: Option, pub line_number: Option, pub multiline: Option, + pub case_sensitive: Option, + pub smart_case: Option, + pub no_ignore: Option, + pub hidden: Option, + pub json: Option, + pub include_dirs: Option, } #[pymethods] impl PyArgs { #[new] #[pyo3(signature = ( - patterns, - paths=None, - globs=None, - heading=None, + patterns, + paths=None, + globs=None, + heading=None, after_context=None, before_context=None, - separator_field_context=None, - separator_field_match=None, + separator_field_context=None, + separator_field_match=None, separator_context=None, sort=None, max_count=None, line_number=None, multiline=None, + case_sensitive=None, + smart_case=None, + no_ignore=None, + hidden=None, + json=None, + include_dirs=None, ))] fn new( - patterns: Vec, - paths: Option>, + patterns: Vec, + paths: Option>, globs: Option>, heading: Option, after_context: Option, @@ -60,6 +77,12 @@ impl PyArgs { max_count: Option, line_number: Option, multiline: Option, + case_sensitive: Option, + smart_case: Option, + no_ignore: Option, + hidden: Option, + json: Option, + include_dirs: Option, ) -> Self { PyArgs { patterns, @@ -75,11 +98,16 @@ impl PyArgs { max_count, line_number, multiline, + case_sensitive, + smart_case, + no_ignore, + hidden, + json, + include_dirs, } } } - #[pyclass(eq)] #[derive(PartialEq, Clone)] #[pyo3(get_all)] @@ -92,18 +120,11 @@ pub struct PySortMode { impl PySortMode { #[new] #[pyo3(signature = (kind, reverse=false))] - fn new( - kind: PySortModeKind, - reverse: bool, - ) -> Self { - PySortMode { - kind, - reverse, - } + fn new(kind: PySortModeKind, reverse: bool) -> Self { + PySortMode { kind, reverse } } } - #[pyclass(eq)] #[derive(PartialEq, Clone)] #[pyo3(get_all)] @@ -115,14 +136,16 @@ pub enum PySortModeKind { } fn build_patterns(patterns: Vec) -> Vec { - patterns.into_iter().map(|pattern| lowargs::PatternSource::Regexp(pattern)).collect() + patterns + .into_iter() + .map(|pattern| lowargs::PatternSource::Regexp(pattern)) + .collect() } fn build_paths(paths: Vec) -> Vec { paths.into_iter().map(|path| OsString::from(path)).collect() } - fn build_sort_mode_kind(kind: PySortModeKind) -> lowargs::SortModeKind { match kind { PySortModeKind::Path => lowargs::SortModeKind::Path, @@ -134,13 +157,19 @@ fn build_sort_mode_kind(kind: PySortModeKind) -> lowargs::SortModeKind { fn build_sort_mode(sort: Option) -> Option { if let Some(sort_mode) = sort { - Some(lowargs::SortMode { kind: build_sort_mode_kind(sort_mode.kind), reverse: sort_mode.reverse }) + Some(lowargs::SortMode { + kind: build_sort_mode_kind(sort_mode.kind), + reverse: sort_mode.reverse, + }) } else { None } } -fn build_context_mode(after_context: Option, before_context: Option) -> lowargs::ContextMode { +fn build_context_mode( + after_context: Option, + before_context: Option, +) -> lowargs::ContextMode { let mut context_mode = lowargs::ContextMode::default(); if let Some(after) = after_context { @@ -198,10 +227,37 @@ fn pyargs_to_hiargs(py_args: &PyArgs, mode: lowargs::Mode) -> anyhow::Result