├── .cargo └── config ├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── dependabot_merge.yml │ ├── periodic.yml │ ├── regression.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── benches └── ptags_bench.rs ├── src ├── bin.rs ├── cmd_ctags.rs ├── cmd_git.rs ├── lib.rs └── main.rs └── test └── lfs.txt /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-gnu] 2 | linker = "x86_64-w64-mingw32-gcc" 3 | 4 | [target.i686-pc-windows-gnu] 5 | linker = "i686-w64-mingw32-gcc" 6 | 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | test/lfs.txt filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dalance 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "20:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/dependabot_merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request_target 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v2.2.0 16 | with: 17 | github-token: '${{ secrets.GITHUB_TOKEN }}' 18 | - name: Enable auto-merge for Dependabot PRs 19 | if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' || ( !startsWith( steps.metadata.outputs.new-version, '0.' ) && steps.metadata.outputs.update-type == 'version-update:semver-minor' ) }} 20 | run: gh pr merge --auto --merge "$PR_URL" 21 | env: 22 | PR_URL: ${{github.event.pull_request.html_url}} 23 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 24 | -------------------------------------------------------------------------------- /.github/workflows/periodic.yml: -------------------------------------------------------------------------------- 1 | name: Periodic 2 | 3 | on: 4 | schedule: 5 | - cron: 0 0 * * SUN 6 | 7 | jobs: 8 | build: 9 | 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest] 13 | rust: [stable, beta, nightly] 14 | 15 | runs-on: ${{ matrix.os }} 16 | 17 | steps: 18 | - name: Setup Rust 19 | uses: hecrj/setup-rust-action@v1 20 | with: 21 | rust-version: ${{ matrix.rust }} 22 | - name: Install ctags on Linux 23 | if: matrix.os == 'ubuntu-latest' 24 | run: | 25 | sudo apt-get update 26 | sudo apt-get install exuberant-ctags 27 | - name: Checkout 28 | uses: actions/checkout@v1 29 | - name: git submodule init 30 | run: | 31 | git submodule init 32 | git submodule update 33 | - name: Run tests 34 | run: cargo test -- --test-threads=1 35 | -------------------------------------------------------------------------------- /.github/workflows/regression.yml: -------------------------------------------------------------------------------- 1 | name: Regression 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macOS-latest] 15 | rust: [stable] 16 | 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - name: Setup Rust 21 | uses: hecrj/setup-rust-action@v1 22 | with: 23 | rust-version: ${{ matrix.rust }} 24 | - name: Install ctags on Linux 25 | if: matrix.os == 'ubuntu-latest' 26 | run: | 27 | sudo apt-get update 28 | sudo apt-get install universal-ctags 29 | - name: Install ctags on macOS 30 | if: matrix.os == 'macOS-latest' 31 | run: | 32 | brew update 33 | brew install universal-ctags 34 | brew install git-lfs 35 | - name: Checkout 36 | uses: actions/checkout@v1 37 | - name: git submodule init 38 | run: | 39 | git submodule init 40 | git submodule update 41 | - name: Run tests 42 | run: cargo test -- --test-threads=1 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v*.*.*' 8 | 9 | jobs: 10 | build: 11 | 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macOS-latest, windows-latest] 15 | rust: [stable] 16 | 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - name: Setup Rust 21 | uses: hecrj/setup-rust-action@v1 22 | with: 23 | rust-version: ${{ matrix.rust }} 24 | - name: Checkout 25 | uses: actions/checkout@v1 26 | - name: Setup MUSL 27 | if: matrix.os == 'ubuntu-latest' 28 | run: | 29 | rustup target add x86_64-unknown-linux-musl 30 | sudo apt-get -qq install musl-tools 31 | - name: Setup Target 32 | if: matrix.os == 'macOS-latest' 33 | run: | 34 | rustup target add aarch64-apple-darwin 35 | - name: Build for Linux 36 | if: matrix.os == 'ubuntu-latest' 37 | run: make release_lnx 38 | - name: Build for macOS 39 | if: matrix.os == 'macOS-latest' 40 | run: make release_mac 41 | - name: Build for Windows 42 | if: matrix.os == 'windows-latest' 43 | run: make release_win 44 | - name: Upload artifacts 45 | uses: actions/upload-artifact@v3 46 | with: 47 | name: ptags 48 | path: '*.zip' 49 | - name: Release 50 | if: github.event_name == 'push' && github.ref_type == 'tag' 51 | uses: softprops/action-gh-release@v1 52 | with: 53 | files: '*.zip' 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | #Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | data/ 13 | tags 14 | *.zip 15 | *.gz 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/ptags_test"] 2 | path = test/ptags_test 3 | url = https://github.com/dalance/ptags_test.git 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ansi_term" 7 | version = "0.12.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.98" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "bencher" 33 | version = "0.1.5" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5" 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "1.3.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 42 | 43 | [[package]] 44 | name = "bitflags" 45 | version = "2.4.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 48 | 49 | [[package]] 50 | name = "bytecount" 51 | version = "0.6.7" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" 54 | 55 | [[package]] 56 | name = "camino" 57 | version = "1.1.6" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" 60 | dependencies = [ 61 | "serde", 62 | ] 63 | 64 | [[package]] 65 | name = "cargo-platform" 66 | version = "0.1.4" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" 69 | dependencies = [ 70 | "serde", 71 | ] 72 | 73 | [[package]] 74 | name = "cargo_metadata" 75 | version = "0.14.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" 78 | dependencies = [ 79 | "camino", 80 | "cargo-platform", 81 | "semver", 82 | "serde", 83 | "serde_json", 84 | ] 85 | 86 | [[package]] 87 | name = "cfg-if" 88 | version = "1.0.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 91 | 92 | [[package]] 93 | name = "cfg_aliases" 94 | version = "0.2.1" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 97 | 98 | [[package]] 99 | name = "clap" 100 | version = "2.34.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 103 | dependencies = [ 104 | "ansi_term", 105 | "atty", 106 | "bitflags 1.3.2", 107 | "strsim", 108 | "textwrap", 109 | "unicode-width", 110 | "vec_map", 111 | ] 112 | 113 | [[package]] 114 | name = "deranged" 115 | version = "0.4.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 118 | dependencies = [ 119 | "powerfmt", 120 | ] 121 | 122 | [[package]] 123 | name = "dirs" 124 | version = "6.0.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 127 | dependencies = [ 128 | "dirs-sys", 129 | ] 130 | 131 | [[package]] 132 | name = "dirs-sys" 133 | version = "0.5.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 136 | dependencies = [ 137 | "libc", 138 | "option-ext", 139 | "redox_users", 140 | "windows-sys", 141 | ] 142 | 143 | [[package]] 144 | name = "equivalent" 145 | version = "1.0.1" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 148 | 149 | [[package]] 150 | name = "errno" 151 | version = "0.3.10" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 154 | dependencies = [ 155 | "libc", 156 | "windows-sys", 157 | ] 158 | 159 | [[package]] 160 | name = "error-chain" 161 | version = "0.12.4" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 164 | dependencies = [ 165 | "version_check", 166 | ] 167 | 168 | [[package]] 169 | name = "fastrand" 170 | version = "2.1.1" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 173 | 174 | [[package]] 175 | name = "getrandom" 176 | version = "0.2.10" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 179 | dependencies = [ 180 | "cfg-if", 181 | "libc", 182 | "wasi 0.11.0+wasi-snapshot-preview1", 183 | ] 184 | 185 | [[package]] 186 | name = "getrandom" 187 | version = "0.3.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 190 | dependencies = [ 191 | "cfg-if", 192 | "libc", 193 | "wasi 0.13.3+wasi-0.2.2", 194 | "windows-targets", 195 | ] 196 | 197 | [[package]] 198 | name = "glob" 199 | version = "0.3.1" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 202 | 203 | [[package]] 204 | name = "hashbrown" 205 | version = "0.15.2" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 208 | 209 | [[package]] 210 | name = "heck" 211 | version = "0.3.3" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 214 | dependencies = [ 215 | "unicode-segmentation", 216 | ] 217 | 218 | [[package]] 219 | name = "heck" 220 | version = "0.4.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 223 | 224 | [[package]] 225 | name = "hermit-abi" 226 | version = "0.1.19" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 229 | dependencies = [ 230 | "libc", 231 | ] 232 | 233 | [[package]] 234 | name = "indexmap" 235 | version = "2.7.1" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 238 | dependencies = [ 239 | "equivalent", 240 | "hashbrown", 241 | ] 242 | 243 | [[package]] 244 | name = "itoa" 245 | version = "1.0.9" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 248 | 249 | [[package]] 250 | name = "lazy_static" 251 | version = "1.4.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 254 | 255 | [[package]] 256 | name = "libc" 257 | version = "0.2.170" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" 260 | 261 | [[package]] 262 | name = "libredox" 263 | version = "0.1.3" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 266 | dependencies = [ 267 | "bitflags 2.4.1", 268 | "libc", 269 | ] 270 | 271 | [[package]] 272 | name = "linux-raw-sys" 273 | version = "0.9.2" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" 276 | 277 | [[package]] 278 | name = "memchr" 279 | version = "2.6.4" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 282 | 283 | [[package]] 284 | name = "nix" 285 | version = "0.29.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 288 | dependencies = [ 289 | "bitflags 2.4.1", 290 | "cfg-if", 291 | "cfg_aliases", 292 | "libc", 293 | ] 294 | 295 | [[package]] 296 | name = "num-conv" 297 | version = "0.1.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 300 | 301 | [[package]] 302 | name = "once_cell" 303 | version = "1.19.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 306 | 307 | [[package]] 308 | name = "option-ext" 309 | version = "0.2.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 312 | 313 | [[package]] 314 | name = "powerfmt" 315 | version = "0.2.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 318 | 319 | [[package]] 320 | name = "proc-macro-error" 321 | version = "1.0.4" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 324 | dependencies = [ 325 | "proc-macro-error-attr", 326 | "proc-macro2", 327 | "quote", 328 | "syn 1.0.109", 329 | "version_check", 330 | ] 331 | 332 | [[package]] 333 | name = "proc-macro-error-attr" 334 | version = "1.0.4" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 337 | dependencies = [ 338 | "proc-macro2", 339 | "quote", 340 | "version_check", 341 | ] 342 | 343 | [[package]] 344 | name = "proc-macro2" 345 | version = "1.0.89" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 348 | dependencies = [ 349 | "unicode-ident", 350 | ] 351 | 352 | [[package]] 353 | name = "ptags" 354 | version = "0.3.5" 355 | dependencies = [ 356 | "anyhow", 357 | "bencher", 358 | "dirs", 359 | "nix", 360 | "serde", 361 | "serde_derive", 362 | "structopt", 363 | "structopt-toml", 364 | "tempfile", 365 | "thiserror", 366 | "time", 367 | "toml 0.8.22", 368 | ] 369 | 370 | [[package]] 371 | name = "pulldown-cmark" 372 | version = "0.9.3" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" 375 | dependencies = [ 376 | "bitflags 1.3.2", 377 | "memchr", 378 | "unicase", 379 | ] 380 | 381 | [[package]] 382 | name = "quote" 383 | version = "1.0.35" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 386 | dependencies = [ 387 | "proc-macro2", 388 | ] 389 | 390 | [[package]] 391 | name = "redox_users" 392 | version = "0.5.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" 395 | dependencies = [ 396 | "getrandom 0.2.10", 397 | "libredox", 398 | "thiserror", 399 | ] 400 | 401 | [[package]] 402 | name = "rustix" 403 | version = "1.0.0" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "17f8dcd64f141950290e45c99f7710ede1b600297c91818bb30b3667c0f45dc0" 406 | dependencies = [ 407 | "bitflags 2.4.1", 408 | "errno", 409 | "libc", 410 | "linux-raw-sys", 411 | "windows-sys", 412 | ] 413 | 414 | [[package]] 415 | name = "ryu" 416 | version = "1.0.15" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 419 | 420 | [[package]] 421 | name = "same-file" 422 | version = "1.0.6" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 425 | dependencies = [ 426 | "winapi-util", 427 | ] 428 | 429 | [[package]] 430 | name = "semver" 431 | version = "1.0.20" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" 434 | dependencies = [ 435 | "serde", 436 | ] 437 | 438 | [[package]] 439 | name = "serde" 440 | version = "1.0.219" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 443 | dependencies = [ 444 | "serde_derive", 445 | ] 446 | 447 | [[package]] 448 | name = "serde_derive" 449 | version = "1.0.219" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 452 | dependencies = [ 453 | "proc-macro2", 454 | "quote", 455 | "syn 2.0.87", 456 | ] 457 | 458 | [[package]] 459 | name = "serde_json" 460 | version = "1.0.107" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 463 | dependencies = [ 464 | "itoa", 465 | "ryu", 466 | "serde", 467 | ] 468 | 469 | [[package]] 470 | name = "serde_spanned" 471 | version = "0.6.8" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 474 | dependencies = [ 475 | "serde", 476 | ] 477 | 478 | [[package]] 479 | name = "skeptic" 480 | version = "0.13.7" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" 483 | dependencies = [ 484 | "bytecount", 485 | "cargo_metadata", 486 | "error-chain", 487 | "glob", 488 | "pulldown-cmark", 489 | "tempfile", 490 | "walkdir", 491 | ] 492 | 493 | [[package]] 494 | name = "strsim" 495 | version = "0.8.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 498 | 499 | [[package]] 500 | name = "structopt" 501 | version = "0.3.26" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 504 | dependencies = [ 505 | "clap", 506 | "lazy_static", 507 | "structopt-derive", 508 | ] 509 | 510 | [[package]] 511 | name = "structopt-derive" 512 | version = "0.4.18" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 515 | dependencies = [ 516 | "heck 0.3.3", 517 | "proc-macro-error", 518 | "proc-macro2", 519 | "quote", 520 | "syn 1.0.109", 521 | ] 522 | 523 | [[package]] 524 | name = "structopt-toml" 525 | version = "0.5.1" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "4d887e6156acb1f4e2d2968d61d1d3b6c5e102af5f23c3ca606723b5ac2c45cb" 528 | dependencies = [ 529 | "anyhow", 530 | "clap", 531 | "serde", 532 | "serde_derive", 533 | "skeptic", 534 | "structopt", 535 | "structopt-toml-derive", 536 | "toml 0.5.11", 537 | ] 538 | 539 | [[package]] 540 | name = "structopt-toml-derive" 541 | version = "0.5.1" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "216c57b49178d22a3ec2f0ce5e961219d11772f7ca70b00c08879d15827d6daf" 544 | dependencies = [ 545 | "heck 0.4.1", 546 | "proc-macro2", 547 | "quote", 548 | "syn 1.0.109", 549 | ] 550 | 551 | [[package]] 552 | name = "syn" 553 | version = "1.0.109" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 556 | dependencies = [ 557 | "proc-macro2", 558 | "quote", 559 | "unicode-ident", 560 | ] 561 | 562 | [[package]] 563 | name = "syn" 564 | version = "2.0.87" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 567 | dependencies = [ 568 | "proc-macro2", 569 | "quote", 570 | "unicode-ident", 571 | ] 572 | 573 | [[package]] 574 | name = "tempfile" 575 | version = "3.20.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" 578 | dependencies = [ 579 | "fastrand", 580 | "getrandom 0.3.1", 581 | "once_cell", 582 | "rustix", 583 | "windows-sys", 584 | ] 585 | 586 | [[package]] 587 | name = "textwrap" 588 | version = "0.11.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 591 | dependencies = [ 592 | "unicode-width", 593 | ] 594 | 595 | [[package]] 596 | name = "thiserror" 597 | version = "2.0.12" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 600 | dependencies = [ 601 | "thiserror-impl", 602 | ] 603 | 604 | [[package]] 605 | name = "thiserror-impl" 606 | version = "2.0.12" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 609 | dependencies = [ 610 | "proc-macro2", 611 | "quote", 612 | "syn 2.0.87", 613 | ] 614 | 615 | [[package]] 616 | name = "time" 617 | version = "0.3.41" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 620 | dependencies = [ 621 | "deranged", 622 | "num-conv", 623 | "powerfmt", 624 | "serde", 625 | "time-core", 626 | ] 627 | 628 | [[package]] 629 | name = "time-core" 630 | version = "0.1.4" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 633 | 634 | [[package]] 635 | name = "toml" 636 | version = "0.5.11" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 639 | dependencies = [ 640 | "serde", 641 | ] 642 | 643 | [[package]] 644 | name = "toml" 645 | version = "0.8.22" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" 648 | dependencies = [ 649 | "serde", 650 | "serde_spanned", 651 | "toml_datetime", 652 | "toml_edit", 653 | ] 654 | 655 | [[package]] 656 | name = "toml_datetime" 657 | version = "0.6.9" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" 660 | dependencies = [ 661 | "serde", 662 | ] 663 | 664 | [[package]] 665 | name = "toml_edit" 666 | version = "0.22.26" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" 669 | dependencies = [ 670 | "indexmap", 671 | "serde", 672 | "serde_spanned", 673 | "toml_datetime", 674 | "toml_write", 675 | "winnow", 676 | ] 677 | 678 | [[package]] 679 | name = "toml_write" 680 | version = "0.1.1" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" 683 | 684 | [[package]] 685 | name = "unicase" 686 | version = "2.7.0" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 689 | dependencies = [ 690 | "version_check", 691 | ] 692 | 693 | [[package]] 694 | name = "unicode-ident" 695 | version = "1.0.12" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 698 | 699 | [[package]] 700 | name = "unicode-segmentation" 701 | version = "1.10.1" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 704 | 705 | [[package]] 706 | name = "unicode-width" 707 | version = "0.1.11" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 710 | 711 | [[package]] 712 | name = "vec_map" 713 | version = "0.8.2" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 716 | 717 | [[package]] 718 | name = "version_check" 719 | version = "0.9.4" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 722 | 723 | [[package]] 724 | name = "walkdir" 725 | version = "2.4.0" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" 728 | dependencies = [ 729 | "same-file", 730 | "winapi-util", 731 | ] 732 | 733 | [[package]] 734 | name = "wasi" 735 | version = "0.11.0+wasi-snapshot-preview1" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 738 | 739 | [[package]] 740 | name = "wasi" 741 | version = "0.13.3+wasi-0.2.2" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 744 | dependencies = [ 745 | "wit-bindgen-rt", 746 | ] 747 | 748 | [[package]] 749 | name = "winapi" 750 | version = "0.3.9" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 753 | dependencies = [ 754 | "winapi-i686-pc-windows-gnu", 755 | "winapi-x86_64-pc-windows-gnu", 756 | ] 757 | 758 | [[package]] 759 | name = "winapi-i686-pc-windows-gnu" 760 | version = "0.4.0" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 763 | 764 | [[package]] 765 | name = "winapi-util" 766 | version = "0.1.6" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 769 | dependencies = [ 770 | "winapi", 771 | ] 772 | 773 | [[package]] 774 | name = "winapi-x86_64-pc-windows-gnu" 775 | version = "0.4.0" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 778 | 779 | [[package]] 780 | name = "windows-sys" 781 | version = "0.59.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 784 | dependencies = [ 785 | "windows-targets", 786 | ] 787 | 788 | [[package]] 789 | name = "windows-targets" 790 | version = "0.52.6" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 793 | dependencies = [ 794 | "windows_aarch64_gnullvm", 795 | "windows_aarch64_msvc", 796 | "windows_i686_gnu", 797 | "windows_i686_gnullvm", 798 | "windows_i686_msvc", 799 | "windows_x86_64_gnu", 800 | "windows_x86_64_gnullvm", 801 | "windows_x86_64_msvc", 802 | ] 803 | 804 | [[package]] 805 | name = "windows_aarch64_gnullvm" 806 | version = "0.52.6" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 809 | 810 | [[package]] 811 | name = "windows_aarch64_msvc" 812 | version = "0.52.6" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 815 | 816 | [[package]] 817 | name = "windows_i686_gnu" 818 | version = "0.52.6" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 821 | 822 | [[package]] 823 | name = "windows_i686_gnullvm" 824 | version = "0.52.6" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 827 | 828 | [[package]] 829 | name = "windows_i686_msvc" 830 | version = "0.52.6" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 833 | 834 | [[package]] 835 | name = "windows_x86_64_gnu" 836 | version = "0.52.6" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 839 | 840 | [[package]] 841 | name = "windows_x86_64_gnullvm" 842 | version = "0.52.6" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 845 | 846 | [[package]] 847 | name = "windows_x86_64_msvc" 848 | version = "0.52.6" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 851 | 852 | [[package]] 853 | name = "winnow" 854 | version = "0.7.7" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" 857 | dependencies = [ 858 | "memchr", 859 | ] 860 | 861 | [[package]] 862 | name = "wit-bindgen-rt" 863 | version = "0.33.0" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 866 | dependencies = [ 867 | "bitflags 2.4.1", 868 | ] 869 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ptags" 3 | version = "0.3.5" 4 | authors = ["dalance@gmail.com"] 5 | repository = "https://github.com/dalance/ptags" 6 | keywords = ["ctags", "universal-ctags"] 7 | categories = ["command-line-utilities", "development-tools"] 8 | license = "MIT" 9 | readme = "README.md" 10 | description = "A parallel universal-ctags wrapper for git repository" 11 | edition = "2018" 12 | 13 | [badges] 14 | travis-ci = { repository = "dalance/ptags" } 15 | appveyor = { repository = "dalance/ptags", branch = "master", service = "github" } 16 | codecov = { repository = "dalance/ptags", branch = "master", service = "github" } 17 | 18 | [dependencies] 19 | anyhow = "1.0" 20 | dirs = "6" 21 | nix = { version = "0.29.0", features = ["fs"] } 22 | serde = "1" 23 | serde_derive = "1" 24 | structopt = "0.3" 25 | structopt-toml = "0.5" 26 | tempfile = "3" 27 | thiserror = "2.0" 28 | time = "0.3" 29 | toml = "0.8" 30 | 31 | [dev-dependencies] 32 | bencher = "0.1" 33 | 34 | [lib] 35 | name = "ptagslib" 36 | path = "src/lib.rs" 37 | 38 | [[bin]] 39 | name = "ptags" 40 | path = "src/main.rs" 41 | 42 | [[bench]] 43 | name = "ptags_bench" 44 | harness = false 45 | 46 | [package.metadata.release] 47 | pre-release-commit-message = "Prepare to v{{version}}" 48 | post-release-commit-message = "Start next development iteration v{{version}}" 49 | tag-message = "Bump version to {{version}}" 50 | tag-prefix = "" 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 dalance 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = $(patsubst "%",%, $(word 3, $(shell grep version Cargo.toml))) 2 | BUILD_TIME = $(shell date +"%Y/%m/%d %H:%M:%S") 3 | GIT_REVISION = $(shell git log -1 --format="%h") 4 | RUST_VERSION = $(word 2, $(shell rustc -V)) 5 | LONG_VERSION = "$(VERSION) ( rev: $(GIT_REVISION), rustc: $(RUST_VERSION), build at: $(BUILD_TIME) )" 6 | BIN_NAME = ptags 7 | 8 | export LONG_VERSION 9 | 10 | .PHONY: all test clean release_lnx release_win release_mac 11 | 12 | all: test 13 | 14 | test: 15 | cargo test -- --test-threads=1 16 | 17 | watch: 18 | cargo watch "test -- --test-threads=1" 19 | 20 | clean: 21 | cargo clean 22 | 23 | release_lnx: 24 | cargo build --release --target=x86_64-unknown-linux-musl 25 | zip -j ${BIN_NAME}-v${VERSION}-x86_64-lnx.zip target/x86_64-unknown-linux-musl/release/${BIN_NAME} 26 | 27 | release_win: 28 | cargo build --release --target=x86_64-pc-windows-msvc 29 | 7z a ${BIN_NAME}-v${VERSION}-x86_64-win.zip target/x86_64-pc-windows-msvc/release/${BIN_NAME}.exe 30 | 31 | release_mac: 32 | cargo build --release --target=x86_64-apple-darwin 33 | zip -j ${BIN_NAME}-v${VERSION}-x86_64-mac.zip target/x86_64-apple-darwin/release/${BIN_NAME} 34 | cargo build --release --target=aarch64-apple-darwin 35 | zip -j ${BIN_NAME}-v${VERSION}-aarch64-mac.zip target/aarch64-apple-darwin/release/${BIN_NAME} 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ptags 2 | A parallel [universal-ctags](https://ctags.io) wrapper for git repository 3 | 4 | [![Actions Status](https://github.com/dalance/ptags/workflows/Regression/badge.svg)](https://github.com/dalance/ptags/actions) 5 | [![Crates.io](https://img.shields.io/crates/v/ptags.svg)](https://crates.io/crates/ptags) 6 | [![codecov](https://codecov.io/gh/dalance/ptags/branch/master/graph/badge.svg)](https://codecov.io/gh/dalance/ptags) 7 | 8 | ## Description 9 | 10 | **ptags** is a [universal-ctags](https://ctags.io) wrapper to have the following features. 11 | - Search git tracked files only ( `.gitignore` support ) 12 | - Call `ctags` command in parallel for acceleration 13 | - Up to x5 faster than universal-ctags 14 | 15 | ## Install 16 | 17 | ### Download binary 18 | 19 | Download from [release page](https://github.com/dalance/ptags/releases/latest), and extract to the directory in PATH. 20 | 21 | ### Arch Linux 22 | 23 | You can install from AUR. 24 | 25 | - https://aur.archlinux.org/packages/ptags/ 26 | - https://aur.archlinux.org/packages/ptags-git/ 27 | 28 | If you use `yay`, you can install like below: 29 | 30 | ``` 31 | yay -S ptags // latest tagged version 32 | yay -S ptags-git // current master of git repo 33 | ``` 34 | 35 | ### Cargo 36 | 37 | You can install by [cargo](https://crates.io). 38 | 39 | ``` 40 | cargo install ptags 41 | ``` 42 | 43 | ## Requirement 44 | 45 | **ptags** uses `ctags` and `git` command internally. 46 | The tested version is below. 47 | 48 | | Command | Version | 49 | | --------- | ----------------------------------------------------- | 50 | | `ctags` | Universal Ctags 0.0.0(f9e6e3c1) / Exuberant Ctags 5.8 | 51 | | `git` | git version 2.14.2 | 52 | | `git-lfs` | git-lfs/2.3.3 | 53 | 54 | ## Usage 55 | 56 | ``` 57 | ptags 0.1.12-pre 58 | dalance@gmail.com 59 | A parallel universal-ctags wrapper for git repository 60 | 61 | USAGE: 62 | ptags [FLAGS] [OPTIONS] [--] [DIR] 63 | 64 | FLAGS: 65 | --config Generate configuration sample file 66 | --exclude-lfs Exclude git-lfs tracked files 67 | -h, --help Prints help information 68 | --include-ignored Include ignored files 69 | --include-submodule Include submodule files 70 | --include-untracked Include untracked files 71 | -s, --stat Show statistics 72 | --unsorted Disable tags sort 73 | --validate-utf8 Validate UTF8 sequence of tag file 74 | -V, --version Prints version information 75 | -v, --verbose Verbose mode 76 | 77 | OPTIONS: 78 | --bin-ctags Path to ctags binary [default: ctags] 79 | --bin-git Path to git binary [default: git] 80 | --completion Generate shell completion file [possible values: bash, fish, 81 | zsh, powershell] 82 | -e, --exclude ... Glob pattern of exclude file ( ex. --exclude '*.rs' ) 83 | -c, --opt-ctags ... Options passed to ctags 84 | -g, --opt-git ... Options passed to git 85 | --opt-git-lfs ... Options passed to git-lfs 86 | -f, --file Output filename ( filename '-' means output to stdout ) [default: tags] 87 | -t, --thread Number of threads [default: 8] 88 | 89 | ARGS: 90 | Search directory [default: .] 91 | ``` 92 | 93 | You can pass options to `ctags` by`-c`/`--ctags_opt` option like below. 94 | 95 | ``` 96 | ptags -c --links=no -c --languages=Rust 97 | ``` 98 | 99 | Searched file types per options are below. 100 | `--include-submodule` and `--include_untracked` are exclusive. 101 | This is the restriction of `git ls-files`. 102 | Any include/exclude options without the above combination can be used simultaneously. 103 | 104 | | File type | Default | --exclude-lfs | --include-ignored | --include-submodule | --include-untracked | 105 | | ------------- | -------- | ------------- | ----------------- | ------------------- | ------------------- | 106 | | tracked | o | o | o | o | o | 107 | | untracked | x | x | x | x | o | 108 | | ignored | x | x | o | x | x | 109 | | lfs tracked | o | x | o | o | o | 110 | | in submodules | x | x | x | o | x | 111 | 112 | You can override any default option by `~/.ptags.toml` like below. 113 | The complete example of `~/.ptags.toml` can be generated by `--config` option. 114 | 115 | ```toml 116 | thread = 16 117 | bin_ctags = "ctags2" 118 | bin_git = "git2" 119 | ``` 120 | 121 | ## Benchmark 122 | 123 | ### Environment 124 | - CPU: Ryzen Threadripper 1950X 125 | - MEM: 128GB 126 | - OS : CentOS 7.4.1708 127 | 128 | ### Data 129 | 130 | | Name | Repository | Revision | Files | Size[GB] | 131 | | ------- | ------------------------------------ | ------------ | ------ | -------- | 132 | | source0 | https://github.com/neovim/neovim | f5b0f5e17 | 2370 | 0.1 | 133 | | source1 | https://github.com/llvm-mirror/llvm | ddf9edb4020 | 29670 | 1.2 | 134 | | source2 | https://github.com/torvalds/linux | 071e31e254e0 | 52998 | 2.2 | 135 | | source3 | https://github.com/chromium/chromium | d79c68510b7e | 293205 | 13 | 136 | 137 | ### Result 138 | 139 | **ptags** is up to x5 faster than universal-ctags. 140 | 141 | | Command | Version | source0 | source1 | source2 | source3 | 142 | | ------------- | ------------------------------- | --------------- | --------------- | ---------------- | --------------- | 143 | | `ctags -R` | Universal Ctags 0.0.0(f9e6e3c1) | 0.41s ( x1 ) | 3.42s ( x1 ) | 23.64s ( x1 ) | 32.23 ( x1 ) | 144 | | `ptags -t 16` | ptags 0.1.4 | 0.13s ( x3.15 ) | 0.58s ( x5.90 ) | 4.24s ( x5.58 ) | 7.27s ( x4.43 ) | 145 | 146 | -------------------------------------------------------------------------------- /benches/ptags_bench.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate bencher; 3 | extern crate ptagslib; 4 | extern crate structopt; 5 | 6 | use bencher::Bencher; 7 | use ptagslib::bin::{run_opt, Opt}; 8 | use structopt::StructOpt; 9 | 10 | fn bench_default(bench: &mut Bencher) { 11 | bench.iter(|| { 12 | let args = vec!["ptags"]; 13 | let opt = Opt::from_iter(args.iter()); 14 | let _ = run_opt(&opt); 15 | }) 16 | } 17 | 18 | fn bench_unsorted(bench: &mut Bencher) { 19 | bench.iter(|| { 20 | let args = vec!["ptags", "--unsorted"]; 21 | let opt = Opt::from_iter(args.iter()); 22 | let _ = run_opt(&opt); 23 | }) 24 | } 25 | 26 | benchmark_group!(benches, bench_default, bench_unsorted); 27 | benchmark_main!(benches); 28 | -------------------------------------------------------------------------------- /src/bin.rs: -------------------------------------------------------------------------------- 1 | use crate::cmd_ctags::CmdCtags; 2 | use crate::cmd_git::CmdGit; 3 | use anyhow::{Context, Error}; 4 | use dirs; 5 | use serde_derive::{Deserialize, Serialize}; 6 | use std::fs; 7 | use std::io::BufRead; 8 | use std::io::{stdout, BufWriter, Read, Write}; 9 | use std::path::PathBuf; 10 | use std::process::Output; 11 | use std::str; 12 | use structopt::{clap, StructOpt}; 13 | use structopt_toml::StructOptToml; 14 | use time::{Duration, Instant}; 15 | use toml; 16 | 17 | // --------------------------------------------------------------------------------------------------------------------- 18 | // Options 19 | // --------------------------------------------------------------------------------------------------------------------- 20 | 21 | #[derive(Debug, Deserialize, Serialize, StructOpt, StructOptToml)] 22 | #[serde(default)] 23 | #[structopt(name = "ptags")] 24 | #[structopt(long_version = option_env!("LONG_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")))] 25 | #[structopt(setting = clap::AppSettings::AllowLeadingHyphen)] 26 | #[structopt(setting = clap::AppSettings::ColoredHelp)] 27 | pub struct Opt { 28 | /// Number of threads 29 | #[structopt(short = "t", long = "thread", default_value = "8")] 30 | pub thread: usize, 31 | 32 | /// Output filename ( filename '-' means output to stdout ) 33 | #[structopt(short = "f", long = "file", default_value = "tags", parse(from_os_str))] 34 | pub output: PathBuf, 35 | 36 | /// Search directory 37 | #[structopt(name = "DIR", default_value = ".", parse(from_os_str))] 38 | pub dir: PathBuf, 39 | 40 | /// Show statistics 41 | #[structopt(short = "s", long = "stat")] 42 | pub stat: bool, 43 | 44 | /// Filename of input file list 45 | #[structopt(short = "L", long = "list")] 46 | pub list: Option, 47 | 48 | /// Path to ctags binary 49 | #[structopt(long = "bin-ctags", default_value = "ctags", parse(from_os_str))] 50 | pub bin_ctags: PathBuf, 51 | 52 | /// Path to git binary 53 | #[structopt(long = "bin-git", default_value = "git", parse(from_os_str))] 54 | pub bin_git: PathBuf, 55 | 56 | /// Options passed to ctags 57 | #[structopt(short = "c", long = "opt-ctags", number_of_values = 1)] 58 | pub opt_ctags: Vec, 59 | 60 | /// Options passed to git 61 | #[structopt(short = "g", long = "opt-git", number_of_values = 1)] 62 | pub opt_git: Vec, 63 | 64 | /// Options passed to git-lfs 65 | #[structopt(long = "opt-git-lfs", number_of_values = 1)] 66 | pub opt_git_lfs: Vec, 67 | 68 | /// Verbose mode 69 | #[structopt(short = "v", long = "verbose")] 70 | pub verbose: bool, 71 | 72 | /// Exclude git-lfs tracked files 73 | #[structopt(long = "exclude-lfs")] 74 | pub exclude_lfs: bool, 75 | 76 | /// Include untracked files 77 | #[structopt(long = "include-untracked")] 78 | pub include_untracked: bool, 79 | 80 | /// Include ignored files 81 | #[structopt(long = "include-ignored")] 82 | pub include_ignored: bool, 83 | 84 | /// Include submodule files 85 | #[structopt(long = "include-submodule")] 86 | pub include_submodule: bool, 87 | 88 | /// Validate UTF8 sequence of tag file 89 | #[structopt(long = "validate-utf8")] 90 | pub validate_utf8: bool, 91 | 92 | /// Disable tags sort 93 | #[structopt(long = "unsorted")] 94 | pub unsorted: bool, 95 | 96 | /// Glob pattern of exclude file ( ex. --exclude '*.rs' ) 97 | #[structopt(short = "e", long = "exclude", number_of_values = 1)] 98 | pub exclude: Vec, 99 | 100 | /// Generate shell completion file 101 | #[structopt( 102 | long = "completion", 103 | possible_values = &["bash", "fish", "zsh", "powershell"] 104 | )] 105 | pub completion: Option, 106 | 107 | /// Generate configuration sample file 108 | #[structopt(long = "config")] 109 | pub config: bool, 110 | } 111 | 112 | // --------------------------------------------------------------------------------------------------------------------- 113 | // Functions 114 | // --------------------------------------------------------------------------------------------------------------------- 115 | 116 | macro_rules! watch_time ( 117 | ( $func:block ) => ( 118 | { 119 | let beg = Instant::now(); 120 | $func; 121 | Instant::now() - beg 122 | } 123 | ); 124 | ); 125 | 126 | pub fn git_files(opt: &Opt) -> Result, Error> { 127 | let list = CmdGit::get_files(&opt)?; 128 | let mut files = vec![String::from(""); opt.thread]; 129 | 130 | for (i, f) in list.iter().enumerate() { 131 | files[i % opt.thread].push_str(f); 132 | files[i % opt.thread].push_str("\n"); 133 | } 134 | 135 | Ok(files) 136 | } 137 | 138 | pub fn input_files(file: &String, opt: &Opt) -> Result, Error> { 139 | let mut list = Vec::new(); 140 | if file == &String::from("-") { 141 | let stdin = std::io::stdin(); 142 | for line in stdin.lock().lines() { 143 | list.push(String::from(line?)); 144 | } 145 | } else { 146 | for line in fs::read_to_string(file)?.lines() { 147 | list.push(String::from(line)); 148 | } 149 | } 150 | 151 | let mut files = vec![String::from(""); opt.thread]; 152 | 153 | for (i, f) in list.iter().enumerate() { 154 | files[i % opt.thread].push_str(f); 155 | files[i % opt.thread].push_str("\n"); 156 | } 157 | 158 | Ok(files) 159 | } 160 | 161 | fn call_ctags(opt: &Opt, files: &[String]) -> Result, Error> { 162 | Ok(CmdCtags::call(&opt, &files)?) 163 | } 164 | 165 | fn get_tags_header(opt: &Opt) -> Result { 166 | Ok(CmdCtags::get_tags_header(&opt).context("failed to get ctags header")?) 167 | } 168 | 169 | fn write_tags(opt: &Opt, outputs: &[Output]) -> Result<(), Error> { 170 | let mut iters = Vec::new(); 171 | let mut lines = Vec::new(); 172 | for o in outputs { 173 | let mut iter = if opt.validate_utf8 { 174 | str::from_utf8(&o.stdout)?.lines() 175 | } else { 176 | unsafe { str::from_utf8_unchecked(&o.stdout).lines() } 177 | }; 178 | lines.push(iter.next()); 179 | iters.push(iter); 180 | } 181 | 182 | let mut f = if opt.output.to_str().unwrap_or("") == "-" { 183 | BufWriter::new(Box::new(stdout()) as Box) 184 | } else { 185 | let f = fs::File::create(&opt.output)?; 186 | BufWriter::new(Box::new(f) as Box) 187 | }; 188 | 189 | f.write(get_tags_header(&opt)?.as_bytes())?; 190 | 191 | while lines.iter().any(|x| x.is_some()) { 192 | let mut min = 0; 193 | for i in 1..lines.len() { 194 | if opt.unsorted { 195 | if !lines[i].is_none() && lines[min].is_none() { 196 | min = i; 197 | } 198 | } else { 199 | if !lines[i].is_none() 200 | && (lines[min].is_none() || lines[i].unwrap() < lines[min].unwrap()) 201 | { 202 | min = i; 203 | } 204 | } 205 | } 206 | f.write(lines[min].unwrap().as_bytes())?; 207 | f.write("\n".as_bytes())?; 208 | lines[min] = iters[min].next(); 209 | } 210 | 211 | Ok(()) 212 | } 213 | 214 | // --------------------------------------------------------------------------------------------------------------------- 215 | // Run 216 | // --------------------------------------------------------------------------------------------------------------------- 217 | 218 | pub fn run_opt(opt: &Opt) -> Result<(), Error> { 219 | if opt.config { 220 | let toml = toml::to_string(&opt)?; 221 | println!("{}", toml); 222 | return Ok(()); 223 | } 224 | 225 | match opt.completion { 226 | Some(ref x) => { 227 | let shell = match x.as_str() { 228 | "bash" => clap::Shell::Bash, 229 | "fish" => clap::Shell::Fish, 230 | "zsh" => clap::Shell::Zsh, 231 | "powershell" => clap::Shell::PowerShell, 232 | _ => clap::Shell::Bash, 233 | }; 234 | Opt::clap().gen_completions("ptags", shell, "./"); 235 | return Ok(()); 236 | } 237 | None => {} 238 | } 239 | 240 | let files; 241 | let time_git_files; 242 | if let Some(ref list) = opt.list { 243 | files = input_files(list, &opt).context("failed to get file list")?; 244 | time_git_files = Duration::seconds(0); 245 | } else { 246 | time_git_files = watch_time!({ 247 | files = git_files(&opt).context("failed to get file list")?; 248 | }); 249 | } 250 | 251 | let outputs; 252 | let time_call_ctags = watch_time!({ 253 | outputs = call_ctags(&opt, &files).context("failed to call ctags")?; 254 | }); 255 | 256 | let time_write_tags = watch_time!({ 257 | let _ = write_tags(&opt, &outputs) 258 | .context(format!("failed to write file ({:?})", &opt.output))?; 259 | }); 260 | 261 | if opt.stat { 262 | let sum: usize = files.iter().map(|x| x.lines().count()).sum(); 263 | 264 | eprintln!("\nStatistics"); 265 | eprintln!("- Options"); 266 | eprintln!(" thread : {}\n", opt.thread); 267 | 268 | eprintln!("- Searched files"); 269 | eprintln!(" total : {}\n", sum); 270 | 271 | eprintln!("- Elapsed time[ms]"); 272 | eprintln!(" git_files : {}", time_git_files.whole_milliseconds()); 273 | eprintln!(" call_ctags: {}", time_call_ctags.whole_milliseconds()); 274 | eprintln!(" write_tags: {}", time_write_tags.whole_milliseconds()); 275 | } 276 | 277 | Ok(()) 278 | } 279 | 280 | #[cfg_attr(tarpaulin, skip)] 281 | pub fn run() -> Result<(), Error> { 282 | let cfg_path = match dirs::home_dir() { 283 | Some(mut path) => { 284 | path.push(".ptags.toml"); 285 | if path.exists() { 286 | Some(path) 287 | } else { 288 | None 289 | } 290 | } 291 | None => None, 292 | }; 293 | 294 | let opt = match cfg_path { 295 | Some(path) => { 296 | let mut f = 297 | fs::File::open(&path).context(format!("failed to open file ({:?})", path))?; 298 | let mut s = String::new(); 299 | let _ = f.read_to_string(&mut s); 300 | Opt::from_args_with_toml(&s).context(format!("failed to parse toml ({:?})", path))? 301 | } 302 | None => Opt::from_args(), 303 | }; 304 | run_opt(&opt) 305 | } 306 | 307 | // --------------------------------------------------------------------------------------------------------------------- 308 | // Test 309 | // --------------------------------------------------------------------------------------------------------------------- 310 | 311 | #[cfg(test)] 312 | mod tests { 313 | use super::*; 314 | use std::path::Path; 315 | 316 | #[test] 317 | fn test_run() { 318 | let args = vec!["ptags"]; 319 | let opt = Opt::from_iter(args.iter()); 320 | let ret = run_opt(&opt); 321 | assert!(ret.is_ok()); 322 | } 323 | 324 | #[test] 325 | fn test_run_opt() { 326 | let args = vec!["ptags", "-s", "-v", "--validate-utf8", "--unsorted"]; 327 | let opt = Opt::from_iter(args.iter()); 328 | let ret = run_opt(&opt); 329 | assert!(ret.is_ok()); 330 | } 331 | 332 | #[test] 333 | fn test_run_fail() { 334 | let args = vec!["ptags", "--bin-git", "aaa"]; 335 | let opt = Opt::from_iter(args.iter()); 336 | let ret = run_opt(&opt); 337 | assert_eq!( 338 | &format!("{:?}", ret)[0..42], 339 | "Err(failed to get file list\n\nCaused by:\n " 340 | ); 341 | } 342 | 343 | #[test] 344 | fn test_run_completion() { 345 | let args = vec!["ptags", "--completion", "bash"]; 346 | let opt = Opt::from_iter(args.iter()); 347 | let ret = run_opt(&opt); 348 | assert!(ret.is_ok()); 349 | let args = vec!["ptags", "--completion", "fish"]; 350 | let opt = Opt::from_iter(args.iter()); 351 | let ret = run_opt(&opt); 352 | assert!(ret.is_ok()); 353 | let args = vec!["ptags", "--completion", "zsh"]; 354 | let opt = Opt::from_iter(args.iter()); 355 | let ret = run_opt(&opt); 356 | assert!(ret.is_ok()); 357 | let args = vec!["ptags", "--completion", "powershell"]; 358 | let opt = Opt::from_iter(args.iter()); 359 | let ret = run_opt(&opt); 360 | assert!(ret.is_ok()); 361 | 362 | assert!(Path::new("ptags.bash").exists()); 363 | assert!(Path::new("ptags.fish").exists()); 364 | assert!(Path::new("_ptags").exists()); 365 | assert!(Path::new("_ptags.ps1").exists()); 366 | let _ = fs::remove_file("ptags.bash"); 367 | let _ = fs::remove_file("ptags.fish"); 368 | let _ = fs::remove_file("_ptags"); 369 | let _ = fs::remove_file("_ptags.ps1"); 370 | } 371 | 372 | #[test] 373 | fn test_run_config() { 374 | let args = vec!["ptags", "--config"]; 375 | let opt = Opt::from_iter(args.iter()); 376 | let ret = run_opt(&opt); 377 | assert!(ret.is_ok()); 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/cmd_ctags.rs: -------------------------------------------------------------------------------- 1 | use crate::bin::Opt; 2 | use anyhow::{bail, Context, Error}; 3 | #[cfg(target_os = "linux")] 4 | use nix::fcntl::{fcntl, FcntlArg}; 5 | use std::fs; 6 | use std::fs::File; 7 | use std::io::{BufReader, Read, Write}; 8 | #[cfg(target_os = "linux")] 9 | use std::os::unix::io::AsRawFd; 10 | use std::path::PathBuf; 11 | use std::process::{ChildStdin, Command, Output, Stdio}; 12 | use std::str; 13 | use std::sync::mpsc; 14 | use std::thread; 15 | use tempfile::NamedTempFile; 16 | use thiserror::Error; 17 | 18 | // --------------------------------------------------------------------------------------------------------------------- 19 | // Error 20 | // --------------------------------------------------------------------------------------------------------------------- 21 | 22 | #[derive(Debug, Error)] 23 | enum CtagsError { 24 | #[error("failed to execute ctags command ({})\n{}", cmd, err)] 25 | ExecFailed { cmd: String, err: String }, 26 | 27 | #[error("failed to call ctags command ({})", cmd)] 28 | CallFailed { cmd: String }, 29 | 30 | #[error("failed to convert to UTF-8 ({:?})", s)] 31 | ConvFailed { s: Vec }, 32 | } 33 | 34 | // --------------------------------------------------------------------------------------------------------------------- 35 | // CmdCtags 36 | // --------------------------------------------------------------------------------------------------------------------- 37 | 38 | pub struct CmdCtags; 39 | 40 | impl CmdCtags { 41 | pub fn call(opt: &Opt, files: &[String]) -> Result, Error> { 42 | let mut args = Vec::new(); 43 | args.push(String::from("-L -")); 44 | args.push(String::from("-f -")); 45 | if opt.unsorted { 46 | args.push(String::from("--sort=no")); 47 | } 48 | for e in &opt.exclude { 49 | args.push(String::from(format!("--exclude={}", e))); 50 | } 51 | args.append(&mut opt.opt_ctags.clone()); 52 | 53 | let cmd = CmdCtags::get_cmd(&opt, &args); 54 | 55 | let (tx, rx) = mpsc::channel::>(); 56 | 57 | for i in 0..opt.thread { 58 | let tx = tx.clone(); 59 | let file = files[i].clone(); 60 | let dir = opt.dir.clone(); 61 | let bin_ctags = opt.bin_ctags.clone(); 62 | let args = args.clone(); 63 | let cmd = cmd.clone(); 64 | 65 | if opt.verbose { 66 | eprintln!("Call : {}", cmd); 67 | } 68 | 69 | thread::spawn(move || { 70 | let child = Command::new(bin_ctags.clone()) 71 | .args(args) 72 | .current_dir(dir) 73 | .stdin(Stdio::piped()) 74 | .stdout(Stdio::piped()) 75 | //.stderr(Stdio::piped()) // Stdio::piped is x2 slow to wait_with_output() completion 76 | .stderr(Stdio::null()) 77 | .spawn(); 78 | match child { 79 | Ok(mut x) => { 80 | { 81 | let stdin = x.stdin.as_mut().unwrap(); 82 | let pipe_size = std::cmp::min(file.len() as i32, 1048576); 83 | let _ = CmdCtags::set_pipe_size(&stdin, pipe_size) 84 | .or_else(|x| tx.send(Err(x.into()))); 85 | let _ = stdin.write_all(file.as_bytes()); 86 | } 87 | match x.wait_with_output() { 88 | Ok(x) => { 89 | let _ = tx.send(Ok(x)); 90 | } 91 | Err(x) => { 92 | let _ = tx.send(Err(x.into())); 93 | } 94 | } 95 | } 96 | Err(_) => { 97 | let _ = tx.send(Err(CtagsError::CallFailed { cmd }.into())); 98 | } 99 | } 100 | }); 101 | } 102 | 103 | let mut children = Vec::new(); 104 | for _ in 0..opt.thread { 105 | children.push(rx.recv()); 106 | } 107 | 108 | let mut outputs = Vec::new(); 109 | for child in children { 110 | let output = child??; 111 | 112 | if !output.status.success() { 113 | bail!(CtagsError::ExecFailed { 114 | cmd: cmd, 115 | err: String::from(str::from_utf8(&output.stderr).context( 116 | CtagsError::ConvFailed { 117 | s: output.stderr.to_vec(), 118 | } 119 | )?) 120 | }); 121 | } 122 | 123 | outputs.push(output); 124 | } 125 | 126 | Ok(outputs) 127 | } 128 | 129 | pub fn get_tags_header(opt: &Opt) -> Result { 130 | let tmp_empty = NamedTempFile::new()?; 131 | let tmp_tags = NamedTempFile::new()?; 132 | let tmp_tags_path: PathBuf = tmp_tags.path().into(); 133 | // In windiws environment, write access by ctags to the opened tmp_tags fails. 134 | // So the tmp_tags must be closed and deleted. 135 | tmp_tags.close()?; 136 | 137 | let _ = Command::new(&opt.bin_ctags) 138 | .arg(format!("-L {}", tmp_empty.path().to_string_lossy())) 139 | .arg(format!("-f {}", tmp_tags_path.to_string_lossy())) 140 | .args(&opt.opt_ctags) 141 | .current_dir(&opt.dir) 142 | .status(); 143 | let mut f = BufReader::new(File::open(&tmp_tags_path)?); 144 | let mut s = String::new(); 145 | f.read_to_string(&mut s)?; 146 | 147 | fs::remove_file(&tmp_tags_path)?; 148 | 149 | Ok(s) 150 | } 151 | 152 | fn get_cmd(opt: &Opt, args: &[String]) -> String { 153 | let mut cmd = format!( 154 | "cd {}; {}", 155 | opt.dir.to_string_lossy(), 156 | opt.bin_ctags.to_string_lossy() 157 | ); 158 | for arg in args { 159 | cmd = format!("{} {}", cmd, arg); 160 | } 161 | cmd 162 | } 163 | 164 | #[allow(dead_code)] 165 | fn is_exuberant_ctags(opt: &Opt) -> Result { 166 | let output = Command::new(&opt.bin_ctags) 167 | .arg("--version") 168 | .current_dir(&opt.dir) 169 | .output()?; 170 | Ok(str::from_utf8(&output.stdout)?.starts_with("Exuberant Ctags")) 171 | } 172 | 173 | #[cfg(target_os = "linux")] 174 | fn set_pipe_size(stdin: &ChildStdin, len: i32) -> Result<(), Error> { 175 | fcntl(stdin.as_raw_fd(), FcntlArg::F_SETPIPE_SZ(len))?; 176 | Ok(()) 177 | } 178 | 179 | #[cfg(not(target_os = "linux"))] 180 | fn set_pipe_size(_stdin: &ChildStdin, _len: i32) -> Result<(), Error> { 181 | Ok(()) 182 | } 183 | } 184 | 185 | // --------------------------------------------------------------------------------------------------------------------- 186 | // Test 187 | // --------------------------------------------------------------------------------------------------------------------- 188 | 189 | #[cfg(test)] 190 | mod tests { 191 | use super::super::bin::{git_files, Opt}; 192 | use super::CmdCtags; 193 | use std::str; 194 | use structopt::StructOpt; 195 | 196 | #[test] 197 | fn test_call() { 198 | let args = vec!["ptags", "-t", "1", "--exclude=README.md"]; 199 | let opt = Opt::from_iter(args.iter()); 200 | let files = git_files(&opt).unwrap(); 201 | let outputs = CmdCtags::call(&opt, &files).unwrap(); 202 | let mut iter = str::from_utf8(&outputs[0].stdout).unwrap().lines(); 203 | assert_eq!( 204 | iter.next().unwrap_or(""), 205 | "BIN_NAME\tMakefile\t/^BIN_NAME = ptags$/;\"\tm" 206 | ); 207 | } 208 | 209 | #[test] 210 | fn test_call_with_opt() { 211 | let args = vec!["ptags", "-t", "1", "--opt-ctags=-u"]; 212 | let opt = Opt::from_iter(args.iter()); 213 | let files = git_files(&opt).unwrap(); 214 | let outputs = CmdCtags::call(&opt, &files).unwrap(); 215 | let mut iter = str::from_utf8(&outputs[0].stdout).unwrap().lines(); 216 | if cfg!(target_os = "linux") { 217 | assert_eq!( 218 | iter.next().unwrap_or(""), 219 | "VERSION\tMakefile\t/^VERSION = $(patsubst \"%\",%, $(word 3, $(shell grep version Cargo.toml)))$/;\"\tm" 220 | ); 221 | } 222 | if cfg!(target_os = "macos") { 223 | assert_eq!( 224 | iter.next().unwrap_or(""), 225 | "package\tCargo.toml\t/^[package]$/;\"\tt" 226 | ); 227 | } 228 | } 229 | 230 | #[test] 231 | fn test_call_exclude() { 232 | let args = vec![ 233 | "ptags", 234 | "-t", 235 | "1", 236 | "--exclude=Make*", 237 | "--exclude=README.md", 238 | "-v", 239 | ]; 240 | let opt = Opt::from_iter(args.iter()); 241 | let files = git_files(&opt).unwrap(); 242 | let outputs = CmdCtags::call(&opt, &files).unwrap(); 243 | let mut iter = str::from_utf8(&outputs[0].stdout).unwrap().lines(); 244 | 245 | // Exuberant Ctags doesn't support Rust ( *.rs ). 246 | // So the result becomes empty when 'Makefile' is excluded. 247 | if CmdCtags::is_exuberant_ctags(&opt).unwrap() { 248 | assert_eq!(iter.next().unwrap_or(""), ""); 249 | } else { 250 | assert_eq!( 251 | iter.next().unwrap_or(""), 252 | "CallFailed\tsrc/cmd_ctags.rs\t/^ CallFailed { cmd: String },$/;\"\te\tenum:CtagsError" 253 | ); 254 | } 255 | } 256 | 257 | #[test] 258 | fn test_command_fail() { 259 | let args = vec!["ptags", "--bin-ctags", "aaa"]; 260 | let opt = Opt::from_iter(args.iter()); 261 | let files = git_files(&opt).unwrap(); 262 | let outputs = CmdCtags::call(&opt, &files); 263 | assert_eq!( 264 | &format!("{:?}", outputs), 265 | "Err(failed to call ctags command (cd .; aaa -L - -f -))" 266 | ); 267 | } 268 | 269 | #[test] 270 | fn test_ctags_fail() { 271 | let args = vec!["ptags", "--opt-ctags=--u"]; 272 | let opt = Opt::from_iter(args.iter()); 273 | let files = git_files(&opt).unwrap(); 274 | let outputs = CmdCtags::call(&opt, &files); 275 | assert_eq!( 276 | &format!("{:?}", outputs)[0..60], 277 | "Err(failed to execute ctags command (cd .; ctags -L - -f - -" 278 | ); 279 | } 280 | 281 | #[test] 282 | fn test_get_tags_header() { 283 | let args = vec!["ptags"]; 284 | let opt = Opt::from_iter(args.iter()); 285 | let output = CmdCtags::get_tags_header(&opt).unwrap(); 286 | let output = output.lines().next(); 287 | assert_eq!(&output.unwrap_or("")[0..5], "!_TAG"); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/cmd_git.rs: -------------------------------------------------------------------------------- 1 | use crate::bin::Opt; 2 | use anyhow::{bail, Context, Error}; 3 | use std::process::{Command, Output}; 4 | use std::str; 5 | use thiserror::Error; 6 | 7 | // --------------------------------------------------------------------------------------------------------------------- 8 | // Error 9 | // --------------------------------------------------------------------------------------------------------------------- 10 | 11 | #[derive(Debug, Error)] 12 | enum GitError { 13 | #[error("failed to execute git command ({})\n{}", cmd, err)] 14 | ExecFailed { cmd: String, err: String }, 15 | 16 | #[error("failed to call git command ({})", cmd)] 17 | CallFailed { cmd: String }, 18 | 19 | #[error("failed to convert to UTF-8 ({:?})", s)] 20 | ConvFailed { s: Vec }, 21 | } 22 | 23 | // --------------------------------------------------------------------------------------------------------------------- 24 | // CmdGit 25 | // --------------------------------------------------------------------------------------------------------------------- 26 | 27 | pub struct CmdGit; 28 | 29 | impl CmdGit { 30 | pub fn get_files(opt: &Opt) -> Result, Error> { 31 | let mut list = CmdGit::ls_files(&opt)?; 32 | if opt.exclude_lfs { 33 | let lfs_list = CmdGit::lfs_ls_files(&opt)?; 34 | let mut new_list = Vec::new(); 35 | for l in list { 36 | if !lfs_list.contains(&l) { 37 | new_list.push(l); 38 | } 39 | } 40 | list = new_list; 41 | } 42 | Ok(list) 43 | } 44 | 45 | fn call(opt: &Opt, args: &[String]) -> Result { 46 | let cmd = CmdGit::get_cmd(&opt, &args); 47 | if opt.verbose { 48 | eprintln!("Call : {}", cmd); 49 | } 50 | 51 | let output = Command::new(&opt.bin_git) 52 | .args(args) 53 | .current_dir(&opt.dir) 54 | .output() 55 | .context(GitError::CallFailed { cmd: cmd.clone() })?; 56 | 57 | if !output.status.success() { 58 | bail!(GitError::ExecFailed { 59 | cmd: cmd, 60 | err: String::from(str::from_utf8(&output.stderr).context( 61 | GitError::ConvFailed { 62 | s: output.stderr.to_vec(), 63 | } 64 | )?) 65 | }); 66 | } 67 | 68 | Ok(output) 69 | } 70 | 71 | fn ls_files(opt: &Opt) -> Result, Error> { 72 | let mut args = vec![String::from("ls-files")]; 73 | args.push(String::from("--cached")); 74 | args.push(String::from("--exclude-standard")); 75 | if opt.include_submodule { 76 | args.push(String::from("--recurse-submodules")); 77 | } else if opt.include_untracked { 78 | args.push(String::from("--other")); 79 | } else if opt.include_ignored { 80 | args.push(String::from("--ignored")); 81 | args.push(String::from("--other")); 82 | } 83 | args.append(&mut opt.opt_git.clone()); 84 | 85 | let output = CmdGit::call(&opt, &args)?; 86 | 87 | let list = str::from_utf8(&output.stdout) 88 | .context(GitError::ConvFailed { 89 | s: output.stdout.to_vec(), 90 | })? 91 | .lines(); 92 | let mut ret = Vec::new(); 93 | for l in list { 94 | ret.push(String::from(l)); 95 | } 96 | ret.sort(); 97 | 98 | if opt.verbose { 99 | eprintln!("Files: {}", ret.len()); 100 | } 101 | 102 | Ok(ret) 103 | } 104 | 105 | fn lfs_ls_files(opt: &Opt) -> Result, Error> { 106 | let mut args = vec![String::from("lfs"), String::from("ls-files")]; 107 | args.append(&mut opt.opt_git_lfs.clone()); 108 | 109 | let output = CmdGit::call(&opt, &args)?; 110 | 111 | let cdup = CmdGit::show_cdup(&opt)?; 112 | let prefix = CmdGit::show_prefix(&opt)?; 113 | 114 | let list = str::from_utf8(&output.stdout) 115 | .context(GitError::ConvFailed { 116 | s: output.stdout.to_vec(), 117 | })? 118 | .lines(); 119 | let mut ret = Vec::new(); 120 | for l in list { 121 | let mut path = String::from(l.split(' ').nth(2).unwrap_or("")); 122 | if path.starts_with(&prefix) { 123 | path = path.replace(&prefix, ""); 124 | } else { 125 | path = format!("{}{}", cdup, path); 126 | } 127 | ret.push(path); 128 | } 129 | ret.sort(); 130 | Ok(ret) 131 | } 132 | 133 | fn show_cdup(opt: &Opt) -> Result { 134 | let args = vec![String::from("rev-parse"), String::from("--show-cdup")]; 135 | 136 | let output = CmdGit::call(&opt, &args)?; 137 | 138 | let mut list = str::from_utf8(&output.stdout) 139 | .context(GitError::ConvFailed { 140 | s: output.stdout.to_vec(), 141 | })? 142 | .lines(); 143 | Ok(String::from(list.next().unwrap_or(""))) 144 | } 145 | 146 | fn show_prefix(opt: &Opt) -> Result { 147 | let args = vec![String::from("rev-parse"), String::from("--show-prefix")]; 148 | 149 | let output = CmdGit::call(&opt, &args)?; 150 | 151 | let mut list = str::from_utf8(&output.stdout) 152 | .context(GitError::ConvFailed { 153 | s: output.stdout.to_vec(), 154 | })? 155 | .lines(); 156 | Ok(String::from(list.next().unwrap_or(""))) 157 | } 158 | 159 | fn get_cmd(opt: &Opt, args: &[String]) -> String { 160 | let mut cmd = format!( 161 | "cd {}; {}", 162 | opt.dir.to_string_lossy(), 163 | opt.bin_git.to_string_lossy() 164 | ); 165 | for arg in args { 166 | cmd = format!("{} {}", cmd, arg); 167 | } 168 | cmd 169 | } 170 | } 171 | 172 | // --------------------------------------------------------------------------------------------------------------------- 173 | // Test 174 | // --------------------------------------------------------------------------------------------------------------------- 175 | 176 | #[cfg(test)] 177 | mod tests { 178 | use super::CmdGit; 179 | use crate::bin::Opt; 180 | use std::fs; 181 | use std::io::{BufWriter, Write}; 182 | use structopt::StructOpt; 183 | 184 | static TRACKED_FILES: [&'static str; 23] = [ 185 | ".cargo/config", 186 | ".gitattributes", 187 | ".github/FUNDING.yml", 188 | ".github/dependabot.yml", 189 | ".github/workflows/dependabot_merge.yml", 190 | ".github/workflows/periodic.yml", 191 | ".github/workflows/regression.yml", 192 | ".github/workflows/release.yml", 193 | ".gitignore", 194 | ".gitmodules", 195 | "Cargo.lock", 196 | "Cargo.toml", 197 | "LICENSE", 198 | "Makefile", 199 | "README.md", 200 | "benches/ptags_bench.rs", 201 | "src/bin.rs", 202 | "src/cmd_ctags.rs", 203 | "src/cmd_git.rs", 204 | "src/lib.rs", 205 | "src/main.rs", 206 | "test/lfs.txt", 207 | "test/ptags_test", 208 | ]; 209 | 210 | #[test] 211 | fn test_get_files() { 212 | let args = vec!["ptags"]; 213 | let opt = Opt::from_iter(args.iter()); 214 | let files = CmdGit::get_files(&opt).unwrap(); 215 | assert_eq!(files, TRACKED_FILES,); 216 | } 217 | 218 | #[test] 219 | fn test_get_files_exclude_lfs() { 220 | let args = vec!["ptags", "--exclude-lfs"]; 221 | let opt = Opt::from_iter(args.iter()); 222 | let files = CmdGit::get_files(&opt).unwrap(); 223 | 224 | let mut expect_files = Vec::new(); 225 | expect_files.extend_from_slice(&TRACKED_FILES); 226 | let idx = expect_files.binary_search(&"test/lfs.txt").unwrap(); 227 | expect_files.remove(idx); 228 | 229 | assert_eq!(files, expect_files,); 230 | } 231 | 232 | #[test] 233 | fn test_get_files_exclude_lfs_cd() { 234 | let args = vec!["ptags", "--exclude-lfs", "src"]; 235 | let opt = Opt::from_iter(args.iter()); 236 | let files = CmdGit::get_files(&opt).unwrap(); 237 | assert_eq!( 238 | files, 239 | vec!["bin.rs", "cmd_ctags.rs", "cmd_git.rs", "lib.rs", "main.rs"] 240 | ); 241 | } 242 | 243 | #[test] 244 | fn test_get_files_include_ignored() { 245 | { 246 | let mut f = BufWriter::new(fs::File::create("ignored.gz").unwrap()); 247 | let _ = f.write(b""); 248 | } 249 | let args = vec!["ptags", "--include-ignored"]; 250 | let opt = Opt::from_iter(args.iter()); 251 | let files: Vec = CmdGit::get_files(&opt) 252 | .unwrap() 253 | .into_iter() 254 | .filter(|f| !f.starts_with("target/")) 255 | .collect(); 256 | let _ = fs::remove_file("ignored.gz"); 257 | 258 | let mut expect_files = Vec::new(); 259 | expect_files.push("ignored.gz"); 260 | expect_files.push("tags"); 261 | 262 | assert_eq!(files, expect_files,); 263 | } 264 | 265 | #[test] 266 | fn test_get_files_include_submodule() { 267 | let args = vec!["ptags", "--include-submodule"]; 268 | let opt = Opt::from_iter(args.iter()); 269 | let files = CmdGit::get_files(&opt).unwrap(); 270 | 271 | let mut expect_files = Vec::new(); 272 | expect_files.extend_from_slice(&TRACKED_FILES); 273 | let idx = expect_files.binary_search(&"test/ptags_test").unwrap(); 274 | expect_files.remove(idx); 275 | expect_files.push("test/ptags_test/README.md"); 276 | 277 | assert_eq!(files, expect_files,); 278 | } 279 | 280 | #[test] 281 | fn test_get_files_include_untracked() { 282 | { 283 | let mut f = BufWriter::new(fs::File::create("tmp").unwrap()); 284 | let _ = f.write(b""); 285 | } 286 | let args = vec!["ptags", "--include-untracked"]; 287 | let opt = Opt::from_iter(args.iter()); 288 | let files = CmdGit::get_files(&opt).unwrap(); 289 | let _ = fs::remove_file("tmp"); 290 | 291 | let mut expect_files = Vec::new(); 292 | expect_files.extend_from_slice(&TRACKED_FILES); 293 | expect_files.push("tmp"); 294 | 295 | assert_eq!(files, expect_files,); 296 | } 297 | 298 | #[test] 299 | fn test_command_fail() { 300 | let args = vec!["ptags", "--bin-git", "aaa"]; 301 | let opt = Opt::from_iter(args.iter()); 302 | let files = CmdGit::ls_files(&opt); 303 | assert_eq!( 304 | &format!("{:?}", files)[0..42], 305 | "Err(failed to call git command (cd .; aaa " 306 | ); 307 | } 308 | 309 | #[test] 310 | fn test_git_fail() { 311 | let args = vec!["ptags", "--opt-git=-aaa"]; 312 | let opt = Opt::from_iter(args.iter()); 313 | let files = CmdGit::ls_files(&opt); 314 | assert_eq!( 315 | &format!("{:?}", files)[0..83], 316 | "Err(failed to execute git command (cd .; git ls-files --cached --exclude-standard -" 317 | ); 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bin; 2 | pub mod cmd_ctags; 3 | pub mod cmd_git; 4 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use ptagslib::bin::run; 2 | 3 | // --------------------------------------------------------------------------------------------------------------------- 4 | // Main 5 | // --------------------------------------------------------------------------------------------------------------------- 6 | 7 | fn main() { 8 | match run() { 9 | Err(x) => { 10 | println!("{}", x); 11 | for x in x.chain() { 12 | println!("{}", x); 13 | } 14 | } 15 | _ => (), 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/lfs.txt: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:17e682f060b5f8e47ea04c5c4855908b0a5ad612022260fe50e11ecb0cc0ab76 3 | size 4 4 | --------------------------------------------------------------------------------