├── .gitattributes ├── src ├── lib.rs ├── main.rs ├── cmd_ctags.rs ├── cmd_git.rs └── bin.rs ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── periodic.yml │ ├── dependabot_merge.yml │ ├── regression.yml │ └── release.yml ├── .gitmodules ├── test └── lfs.txt ├── .cargo └── config ├── .gitignore ├── benches └── ptags_bench.rs ├── LICENSE ├── Makefile ├── Cargo.toml ├── README.md └── Cargo.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | test/lfs.txt filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bin; 2 | pub mod cmd_ctags; 3 | pub mod cmd_git; 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dalance 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/ptags_test"] 2 | path = test/ptags_test 3 | url = https://github.com/dalance/ptags_test.git 4 | -------------------------------------------------------------------------------- /test/lfs.txt: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:17e682f060b5f8e47ea04c5c4855908b0a5ad612022260fe50e11ecb0cc0ab76 3 | size 4 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.30.1", 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 | toml = "0.9" 29 | 30 | [dev-dependencies] 31 | bencher = "0.1" 32 | 33 | [lib] 34 | name = "ptagslib" 35 | path = "src/lib.rs" 36 | 37 | [[bin]] 38 | name = "ptags" 39 | path = "src/main.rs" 40 | 41 | [[bench]] 42 | name = "ptags_bench" 43 | harness = false 44 | 45 | [package.metadata.release] 46 | pre-release-commit-message = "Prepare to v{{version}}" 47 | post-release-commit-message = "Start next development iteration v{{version}}" 48 | tag-message = "Bump version to {{version}}" 49 | tag-prefix = "" 50 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | use std::path::PathBuf; 9 | use std::process::{ChildStdin, Command, Output, Stdio}; 10 | use std::str; 11 | use std::sync::mpsc; 12 | use std::thread; 13 | use tempfile::NamedTempFile; 14 | use thiserror::Error; 15 | 16 | // --------------------------------------------------------------------------------------------------------------------- 17 | // Error 18 | // --------------------------------------------------------------------------------------------------------------------- 19 | 20 | #[derive(Debug, Error)] 21 | enum CtagsError { 22 | #[error("failed to execute ctags command ({})\n{}", cmd, err)] 23 | ExecFailed { cmd: String, err: String }, 24 | 25 | #[error("failed to call ctags command ({})", cmd)] 26 | CallFailed { cmd: String }, 27 | 28 | #[error("failed to convert to UTF-8 ({:?})", s)] 29 | ConvFailed { s: Vec }, 30 | } 31 | 32 | // --------------------------------------------------------------------------------------------------------------------- 33 | // CmdCtags 34 | // --------------------------------------------------------------------------------------------------------------------- 35 | 36 | pub struct CmdCtags; 37 | 38 | impl CmdCtags { 39 | pub fn call(opt: &Opt, files: &[String]) -> Result, Error> { 40 | let mut args = Vec::new(); 41 | args.push(String::from("-L -")); 42 | args.push(String::from("-f -")); 43 | if opt.unsorted { 44 | args.push(String::from("--sort=no")); 45 | } 46 | for e in &opt.exclude { 47 | args.push(String::from(format!("--exclude={}", e))); 48 | } 49 | args.append(&mut opt.opt_ctags.clone()); 50 | 51 | let cmd = CmdCtags::get_cmd(&opt, &args); 52 | 53 | let (tx, rx) = mpsc::channel::>(); 54 | 55 | for i in 0..opt.thread { 56 | let tx = tx.clone(); 57 | let file = files[i].clone(); 58 | let dir = opt.dir.clone(); 59 | let bin_ctags = opt.bin_ctags.clone(); 60 | let args = args.clone(); 61 | let cmd = cmd.clone(); 62 | 63 | if opt.verbose { 64 | eprintln!("Call : {}", cmd); 65 | } 66 | 67 | thread::spawn(move || { 68 | let child = Command::new(bin_ctags.clone()) 69 | .args(args) 70 | .current_dir(dir) 71 | .stdin(Stdio::piped()) 72 | .stdout(Stdio::piped()) 73 | //.stderr(Stdio::piped()) // Stdio::piped is x2 slow to wait_with_output() completion 74 | .stderr(Stdio::null()) 75 | .spawn(); 76 | match child { 77 | Ok(mut x) => { 78 | { 79 | let stdin = x.stdin.as_mut().unwrap(); 80 | let pipe_size = std::cmp::min(file.len() as i32, 1048576); 81 | let _ = CmdCtags::set_pipe_size(&stdin, pipe_size) 82 | .or_else(|x| tx.send(Err(x.into()))); 83 | let _ = stdin.write_all(file.as_bytes()); 84 | } 85 | match x.wait_with_output() { 86 | Ok(x) => { 87 | let _ = tx.send(Ok(x)); 88 | } 89 | Err(x) => { 90 | let _ = tx.send(Err(x.into())); 91 | } 92 | } 93 | } 94 | Err(_) => { 95 | let _ = tx.send(Err(CtagsError::CallFailed { cmd }.into())); 96 | } 97 | } 98 | }); 99 | } 100 | 101 | let mut children = Vec::new(); 102 | for _ in 0..opt.thread { 103 | children.push(rx.recv()); 104 | } 105 | 106 | let mut outputs = Vec::new(); 107 | for child in children { 108 | let output = child??; 109 | 110 | if !output.status.success() { 111 | bail!(CtagsError::ExecFailed { 112 | cmd: cmd, 113 | err: String::from(str::from_utf8(&output.stderr).context( 114 | CtagsError::ConvFailed { 115 | s: output.stderr.to_vec(), 116 | } 117 | )?) 118 | }); 119 | } 120 | 121 | outputs.push(output); 122 | } 123 | 124 | Ok(outputs) 125 | } 126 | 127 | pub fn get_tags_header(opt: &Opt) -> Result { 128 | let tmp_empty = NamedTempFile::new()?; 129 | let tmp_tags = NamedTempFile::new()?; 130 | let tmp_tags_path: PathBuf = tmp_tags.path().into(); 131 | // In windiws environment, write access by ctags to the opened tmp_tags fails. 132 | // So the tmp_tags must be closed and deleted. 133 | tmp_tags.close()?; 134 | 135 | let _ = Command::new(&opt.bin_ctags) 136 | .arg(format!("-L {}", tmp_empty.path().to_string_lossy())) 137 | .arg(format!("-f {}", tmp_tags_path.to_string_lossy())) 138 | .args(&opt.opt_ctags) 139 | .current_dir(&opt.dir) 140 | .status(); 141 | let mut f = BufReader::new(File::open(&tmp_tags_path)?); 142 | let mut s = String::new(); 143 | f.read_to_string(&mut s)?; 144 | 145 | fs::remove_file(&tmp_tags_path)?; 146 | 147 | Ok(s) 148 | } 149 | 150 | fn get_cmd(opt: &Opt, args: &[String]) -> String { 151 | let mut cmd = format!( 152 | "cd {}; {}", 153 | opt.dir.to_string_lossy(), 154 | opt.bin_ctags.to_string_lossy() 155 | ); 156 | for arg in args { 157 | cmd = format!("{} {}", cmd, arg); 158 | } 159 | cmd 160 | } 161 | 162 | #[allow(dead_code)] 163 | fn is_exuberant_ctags(opt: &Opt) -> Result { 164 | let output = Command::new(&opt.bin_ctags) 165 | .arg("--version") 166 | .current_dir(&opt.dir) 167 | .output()?; 168 | Ok(str::from_utf8(&output.stdout)?.starts_with("Exuberant Ctags")) 169 | } 170 | 171 | #[cfg(target_os = "linux")] 172 | fn set_pipe_size(stdin: &ChildStdin, len: i32) -> Result<(), Error> { 173 | fcntl(stdin, FcntlArg::F_SETPIPE_SZ(len))?; 174 | Ok(()) 175 | } 176 | 177 | #[cfg(not(target_os = "linux"))] 178 | fn set_pipe_size(_stdin: &ChildStdin, _len: i32) -> Result<(), Error> { 179 | Ok(()) 180 | } 181 | } 182 | 183 | // --------------------------------------------------------------------------------------------------------------------- 184 | // Test 185 | // --------------------------------------------------------------------------------------------------------------------- 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | use super::super::bin::{git_files, Opt}; 190 | use super::CmdCtags; 191 | use std::str; 192 | use structopt::StructOpt; 193 | 194 | #[test] 195 | fn test_call() { 196 | let args = vec!["ptags", "-t", "1", "--exclude=README.md"]; 197 | let opt = Opt::from_iter(args.iter()); 198 | let files = git_files(&opt).unwrap(); 199 | let outputs = CmdCtags::call(&opt, &files).unwrap(); 200 | let mut iter = str::from_utf8(&outputs[0].stdout).unwrap().lines(); 201 | assert_eq!( 202 | iter.next().unwrap_or(""), 203 | "BIN_NAME\tMakefile\t/^BIN_NAME = ptags$/;\"\tm" 204 | ); 205 | } 206 | 207 | #[test] 208 | fn test_call_with_opt() { 209 | let args = vec!["ptags", "-t", "1", "--opt-ctags=-u"]; 210 | let opt = Opt::from_iter(args.iter()); 211 | let files = git_files(&opt).unwrap(); 212 | let outputs = CmdCtags::call(&opt, &files).unwrap(); 213 | let mut iter = str::from_utf8(&outputs[0].stdout).unwrap().lines(); 214 | assert_eq!( 215 | iter.next().unwrap_or(""), 216 | "VERSION\tMakefile\t/^VERSION = $(patsubst \"%\",%, $(word 3, $(shell grep version Cargo.toml)))$/;\"\tm" 217 | ); 218 | } 219 | 220 | #[test] 221 | fn test_call_exclude() { 222 | let args = vec![ 223 | "ptags", 224 | "-t", 225 | "1", 226 | "--exclude=Make*", 227 | "--exclude=README.md", 228 | "-v", 229 | ]; 230 | let opt = Opt::from_iter(args.iter()); 231 | let files = git_files(&opt).unwrap(); 232 | let outputs = CmdCtags::call(&opt, &files).unwrap(); 233 | let mut iter = str::from_utf8(&outputs[0].stdout).unwrap().lines(); 234 | 235 | // Exuberant Ctags doesn't support Rust ( *.rs ). 236 | // So the result becomes empty when 'Makefile' is excluded. 237 | if CmdCtags::is_exuberant_ctags(&opt).unwrap() { 238 | assert_eq!(iter.next().unwrap_or(""), ""); 239 | } else { 240 | assert_eq!( 241 | iter.next().unwrap_or(""), 242 | "CallFailed\tsrc/cmd_ctags.rs\t/^ CallFailed { cmd: String },$/;\"\te\tenum:CtagsError" 243 | ); 244 | } 245 | } 246 | 247 | #[test] 248 | fn test_command_fail() { 249 | let args = vec!["ptags", "--bin-ctags", "aaa"]; 250 | let opt = Opt::from_iter(args.iter()); 251 | let files = git_files(&opt).unwrap(); 252 | let outputs = CmdCtags::call(&opt, &files); 253 | assert_eq!( 254 | &format!("{:?}", outputs), 255 | "Err(failed to call ctags command (cd .; aaa -L - -f -))" 256 | ); 257 | } 258 | 259 | #[test] 260 | fn test_ctags_fail() { 261 | let args = vec!["ptags", "--opt-ctags=--u"]; 262 | let opt = Opt::from_iter(args.iter()); 263 | let files = git_files(&opt).unwrap(); 264 | let outputs = CmdCtags::call(&opt, &files); 265 | assert_eq!( 266 | &format!("{:?}", outputs)[0..60], 267 | "Err(failed to execute ctags command (cd .; ctags -L - -f - -" 268 | ); 269 | } 270 | 271 | #[test] 272 | fn test_get_tags_header() { 273 | let args = vec!["ptags"]; 274 | let opt = Opt::from_iter(args.iter()); 275 | let output = CmdCtags::get_tags_header(&opt).unwrap(); 276 | let output = output.lines().next(); 277 | assert_eq!(&output.unwrap_or("")[0..5], "!_TAG"); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /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/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 std::time::{Duration, Instant}; 13 | use structopt::{clap, StructOpt}; 14 | use structopt_toml::StructOptToml; 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::from_secs(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.as_millis()); 273 | eprintln!(" call_ctags: {}", time_call_ctags.as_millis()); 274 | eprintln!(" write_tags: {}", time_write_tags.as_millis()); 275 | } 276 | 277 | Ok(()) 278 | } 279 | 280 | pub fn run() -> Result<(), Error> { 281 | let cfg_path = match dirs::home_dir() { 282 | Some(mut path) => { 283 | path.push(".ptags.toml"); 284 | if path.exists() { 285 | Some(path) 286 | } else { 287 | None 288 | } 289 | } 290 | None => None, 291 | }; 292 | 293 | let opt = match cfg_path { 294 | Some(path) => { 295 | let mut f = 296 | fs::File::open(&path).context(format!("failed to open file ({:?})", path))?; 297 | let mut s = String::new(); 298 | let _ = f.read_to_string(&mut s); 299 | Opt::from_args_with_toml(&s).context(format!("failed to parse toml ({:?})", path))? 300 | } 301 | None => Opt::from_args(), 302 | }; 303 | run_opt(&opt) 304 | } 305 | 306 | // --------------------------------------------------------------------------------------------------------------------- 307 | // Test 308 | // --------------------------------------------------------------------------------------------------------------------- 309 | 310 | #[cfg(test)] 311 | mod tests { 312 | use super::*; 313 | use std::path::Path; 314 | 315 | #[test] 316 | fn test_run() { 317 | let args = vec!["ptags"]; 318 | let opt = Opt::from_iter(args.iter()); 319 | let ret = run_opt(&opt); 320 | assert!(ret.is_ok()); 321 | } 322 | 323 | #[test] 324 | fn test_run_opt() { 325 | let args = vec!["ptags", "-s", "-v", "--validate-utf8", "--unsorted"]; 326 | let opt = Opt::from_iter(args.iter()); 327 | let ret = run_opt(&opt); 328 | assert!(ret.is_ok()); 329 | } 330 | 331 | #[test] 332 | fn test_run_fail() { 333 | let args = vec!["ptags", "--bin-git", "aaa"]; 334 | let opt = Opt::from_iter(args.iter()); 335 | let ret = run_opt(&opt); 336 | assert_eq!( 337 | &format!("{:?}", ret)[0..42], 338 | "Err(failed to get file list\n\nCaused by:\n " 339 | ); 340 | } 341 | 342 | #[test] 343 | fn test_run_completion() { 344 | let args = vec!["ptags", "--completion", "bash"]; 345 | let opt = Opt::from_iter(args.iter()); 346 | let ret = run_opt(&opt); 347 | assert!(ret.is_ok()); 348 | let args = vec!["ptags", "--completion", "fish"]; 349 | let opt = Opt::from_iter(args.iter()); 350 | let ret = run_opt(&opt); 351 | assert!(ret.is_ok()); 352 | let args = vec!["ptags", "--completion", "zsh"]; 353 | let opt = Opt::from_iter(args.iter()); 354 | let ret = run_opt(&opt); 355 | assert!(ret.is_ok()); 356 | let args = vec!["ptags", "--completion", "powershell"]; 357 | let opt = Opt::from_iter(args.iter()); 358 | let ret = run_opt(&opt); 359 | assert!(ret.is_ok()); 360 | 361 | assert!(Path::new("ptags.bash").exists()); 362 | assert!(Path::new("ptags.fish").exists()); 363 | assert!(Path::new("_ptags").exists()); 364 | assert!(Path::new("_ptags.ps1").exists()); 365 | let _ = fs::remove_file("ptags.bash"); 366 | let _ = fs::remove_file("ptags.fish"); 367 | let _ = fs::remove_file("_ptags"); 368 | let _ = fs::remove_file("_ptags.ps1"); 369 | } 370 | 371 | #[test] 372 | fn test_run_config() { 373 | let args = vec!["ptags", "--config"]; 374 | let opt = Opt::from_iter(args.iter()); 375 | let ret = run_opt(&opt); 376 | assert!(ret.is_ok()); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /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.100" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 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 = "dirs" 115 | version = "6.0.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 118 | dependencies = [ 119 | "dirs-sys", 120 | ] 121 | 122 | [[package]] 123 | name = "dirs-sys" 124 | version = "0.5.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 127 | dependencies = [ 128 | "libc", 129 | "option-ext", 130 | "redox_users", 131 | "windows-sys", 132 | ] 133 | 134 | [[package]] 135 | name = "equivalent" 136 | version = "1.0.1" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 139 | 140 | [[package]] 141 | name = "errno" 142 | version = "0.3.10" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 145 | dependencies = [ 146 | "libc", 147 | "windows-sys", 148 | ] 149 | 150 | [[package]] 151 | name = "error-chain" 152 | version = "0.12.4" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 155 | dependencies = [ 156 | "version_check", 157 | ] 158 | 159 | [[package]] 160 | name = "fastrand" 161 | version = "2.1.1" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 164 | 165 | [[package]] 166 | name = "getrandom" 167 | version = "0.2.10" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 170 | dependencies = [ 171 | "cfg-if", 172 | "libc", 173 | "wasi 0.11.0+wasi-snapshot-preview1", 174 | ] 175 | 176 | [[package]] 177 | name = "getrandom" 178 | version = "0.3.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 181 | dependencies = [ 182 | "cfg-if", 183 | "libc", 184 | "wasi 0.13.3+wasi-0.2.2", 185 | "windows-targets", 186 | ] 187 | 188 | [[package]] 189 | name = "glob" 190 | version = "0.3.1" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 193 | 194 | [[package]] 195 | name = "hashbrown" 196 | version = "0.15.2" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 199 | 200 | [[package]] 201 | name = "heck" 202 | version = "0.3.3" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 205 | dependencies = [ 206 | "unicode-segmentation", 207 | ] 208 | 209 | [[package]] 210 | name = "heck" 211 | version = "0.4.1" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 214 | 215 | [[package]] 216 | name = "hermit-abi" 217 | version = "0.1.19" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 220 | dependencies = [ 221 | "libc", 222 | ] 223 | 224 | [[package]] 225 | name = "indexmap" 226 | version = "2.11.4" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 229 | dependencies = [ 230 | "equivalent", 231 | "hashbrown", 232 | ] 233 | 234 | [[package]] 235 | name = "itoa" 236 | version = "1.0.9" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 239 | 240 | [[package]] 241 | name = "lazy_static" 242 | version = "1.4.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 245 | 246 | [[package]] 247 | name = "libc" 248 | version = "0.2.175" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 251 | 252 | [[package]] 253 | name = "libredox" 254 | version = "0.1.3" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 257 | dependencies = [ 258 | "bitflags 2.4.1", 259 | "libc", 260 | ] 261 | 262 | [[package]] 263 | name = "linux-raw-sys" 264 | version = "0.9.2" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" 267 | 268 | [[package]] 269 | name = "memchr" 270 | version = "2.6.4" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 273 | 274 | [[package]] 275 | name = "nix" 276 | version = "0.30.1" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" 279 | dependencies = [ 280 | "bitflags 2.4.1", 281 | "cfg-if", 282 | "cfg_aliases", 283 | "libc", 284 | ] 285 | 286 | [[package]] 287 | name = "once_cell" 288 | version = "1.19.0" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 291 | 292 | [[package]] 293 | name = "option-ext" 294 | version = "0.2.0" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 297 | 298 | [[package]] 299 | name = "proc-macro-error" 300 | version = "1.0.4" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 303 | dependencies = [ 304 | "proc-macro-error-attr", 305 | "proc-macro2", 306 | "quote", 307 | "syn 1.0.109", 308 | "version_check", 309 | ] 310 | 311 | [[package]] 312 | name = "proc-macro-error-attr" 313 | version = "1.0.4" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 316 | dependencies = [ 317 | "proc-macro2", 318 | "quote", 319 | "version_check", 320 | ] 321 | 322 | [[package]] 323 | name = "proc-macro2" 324 | version = "1.0.89" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 327 | dependencies = [ 328 | "unicode-ident", 329 | ] 330 | 331 | [[package]] 332 | name = "ptags" 333 | version = "0.3.5" 334 | dependencies = [ 335 | "anyhow", 336 | "bencher", 337 | "dirs", 338 | "nix", 339 | "serde", 340 | "serde_derive", 341 | "structopt", 342 | "structopt-toml", 343 | "tempfile", 344 | "thiserror", 345 | "toml 0.9.8", 346 | ] 347 | 348 | [[package]] 349 | name = "pulldown-cmark" 350 | version = "0.9.3" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" 353 | dependencies = [ 354 | "bitflags 1.3.2", 355 | "memchr", 356 | "unicase", 357 | ] 358 | 359 | [[package]] 360 | name = "quote" 361 | version = "1.0.35" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 364 | dependencies = [ 365 | "proc-macro2", 366 | ] 367 | 368 | [[package]] 369 | name = "redox_users" 370 | version = "0.5.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" 373 | dependencies = [ 374 | "getrandom 0.2.10", 375 | "libredox", 376 | "thiserror", 377 | ] 378 | 379 | [[package]] 380 | name = "rustix" 381 | version = "1.0.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "17f8dcd64f141950290e45c99f7710ede1b600297c91818bb30b3667c0f45dc0" 384 | dependencies = [ 385 | "bitflags 2.4.1", 386 | "errno", 387 | "libc", 388 | "linux-raw-sys", 389 | "windows-sys", 390 | ] 391 | 392 | [[package]] 393 | name = "ryu" 394 | version = "1.0.15" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 397 | 398 | [[package]] 399 | name = "same-file" 400 | version = "1.0.6" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 403 | dependencies = [ 404 | "winapi-util", 405 | ] 406 | 407 | [[package]] 408 | name = "semver" 409 | version = "1.0.20" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" 412 | dependencies = [ 413 | "serde", 414 | ] 415 | 416 | [[package]] 417 | name = "serde" 418 | version = "1.0.228" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 421 | dependencies = [ 422 | "serde_core", 423 | "serde_derive", 424 | ] 425 | 426 | [[package]] 427 | name = "serde_core" 428 | version = "1.0.228" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 431 | dependencies = [ 432 | "serde_derive", 433 | ] 434 | 435 | [[package]] 436 | name = "serde_derive" 437 | version = "1.0.228" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 440 | dependencies = [ 441 | "proc-macro2", 442 | "quote", 443 | "syn 2.0.87", 444 | ] 445 | 446 | [[package]] 447 | name = "serde_json" 448 | version = "1.0.107" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 451 | dependencies = [ 452 | "itoa", 453 | "ryu", 454 | "serde", 455 | ] 456 | 457 | [[package]] 458 | name = "serde_spanned" 459 | version = "1.0.3" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" 462 | dependencies = [ 463 | "serde_core", 464 | ] 465 | 466 | [[package]] 467 | name = "skeptic" 468 | version = "0.13.7" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" 471 | dependencies = [ 472 | "bytecount", 473 | "cargo_metadata", 474 | "error-chain", 475 | "glob", 476 | "pulldown-cmark", 477 | "tempfile", 478 | "walkdir", 479 | ] 480 | 481 | [[package]] 482 | name = "strsim" 483 | version = "0.8.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 486 | 487 | [[package]] 488 | name = "structopt" 489 | version = "0.3.26" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 492 | dependencies = [ 493 | "clap", 494 | "lazy_static", 495 | "structopt-derive", 496 | ] 497 | 498 | [[package]] 499 | name = "structopt-derive" 500 | version = "0.4.18" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 503 | dependencies = [ 504 | "heck 0.3.3", 505 | "proc-macro-error", 506 | "proc-macro2", 507 | "quote", 508 | "syn 1.0.109", 509 | ] 510 | 511 | [[package]] 512 | name = "structopt-toml" 513 | version = "0.5.1" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "4d887e6156acb1f4e2d2968d61d1d3b6c5e102af5f23c3ca606723b5ac2c45cb" 516 | dependencies = [ 517 | "anyhow", 518 | "clap", 519 | "serde", 520 | "serde_derive", 521 | "skeptic", 522 | "structopt", 523 | "structopt-toml-derive", 524 | "toml 0.5.11", 525 | ] 526 | 527 | [[package]] 528 | name = "structopt-toml-derive" 529 | version = "0.5.1" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "216c57b49178d22a3ec2f0ce5e961219d11772f7ca70b00c08879d15827d6daf" 532 | dependencies = [ 533 | "heck 0.4.1", 534 | "proc-macro2", 535 | "quote", 536 | "syn 1.0.109", 537 | ] 538 | 539 | [[package]] 540 | name = "syn" 541 | version = "1.0.109" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 544 | dependencies = [ 545 | "proc-macro2", 546 | "quote", 547 | "unicode-ident", 548 | ] 549 | 550 | [[package]] 551 | name = "syn" 552 | version = "2.0.87" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 555 | dependencies = [ 556 | "proc-macro2", 557 | "quote", 558 | "unicode-ident", 559 | ] 560 | 561 | [[package]] 562 | name = "tempfile" 563 | version = "3.23.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 566 | dependencies = [ 567 | "fastrand", 568 | "getrandom 0.3.1", 569 | "once_cell", 570 | "rustix", 571 | "windows-sys", 572 | ] 573 | 574 | [[package]] 575 | name = "textwrap" 576 | version = "0.11.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 579 | dependencies = [ 580 | "unicode-width", 581 | ] 582 | 583 | [[package]] 584 | name = "thiserror" 585 | version = "2.0.17" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 588 | dependencies = [ 589 | "thiserror-impl", 590 | ] 591 | 592 | [[package]] 593 | name = "thiserror-impl" 594 | version = "2.0.17" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 597 | dependencies = [ 598 | "proc-macro2", 599 | "quote", 600 | "syn 2.0.87", 601 | ] 602 | 603 | [[package]] 604 | name = "toml" 605 | version = "0.5.11" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 608 | dependencies = [ 609 | "serde", 610 | ] 611 | 612 | [[package]] 613 | name = "toml" 614 | version = "0.9.8" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" 617 | dependencies = [ 618 | "indexmap", 619 | "serde_core", 620 | "serde_spanned", 621 | "toml_datetime", 622 | "toml_parser", 623 | "toml_writer", 624 | "winnow", 625 | ] 626 | 627 | [[package]] 628 | name = "toml_datetime" 629 | version = "0.7.3" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" 632 | dependencies = [ 633 | "serde_core", 634 | ] 635 | 636 | [[package]] 637 | name = "toml_parser" 638 | version = "1.0.4" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" 641 | dependencies = [ 642 | "winnow", 643 | ] 644 | 645 | [[package]] 646 | name = "toml_writer" 647 | version = "1.0.4" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" 650 | 651 | [[package]] 652 | name = "unicase" 653 | version = "2.7.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 656 | dependencies = [ 657 | "version_check", 658 | ] 659 | 660 | [[package]] 661 | name = "unicode-ident" 662 | version = "1.0.12" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 665 | 666 | [[package]] 667 | name = "unicode-segmentation" 668 | version = "1.10.1" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 671 | 672 | [[package]] 673 | name = "unicode-width" 674 | version = "0.1.11" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 677 | 678 | [[package]] 679 | name = "vec_map" 680 | version = "0.8.2" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 683 | 684 | [[package]] 685 | name = "version_check" 686 | version = "0.9.4" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 689 | 690 | [[package]] 691 | name = "walkdir" 692 | version = "2.4.0" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" 695 | dependencies = [ 696 | "same-file", 697 | "winapi-util", 698 | ] 699 | 700 | [[package]] 701 | name = "wasi" 702 | version = "0.11.0+wasi-snapshot-preview1" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 705 | 706 | [[package]] 707 | name = "wasi" 708 | version = "0.13.3+wasi-0.2.2" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 711 | dependencies = [ 712 | "wit-bindgen-rt", 713 | ] 714 | 715 | [[package]] 716 | name = "winapi" 717 | version = "0.3.9" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 720 | dependencies = [ 721 | "winapi-i686-pc-windows-gnu", 722 | "winapi-x86_64-pc-windows-gnu", 723 | ] 724 | 725 | [[package]] 726 | name = "winapi-i686-pc-windows-gnu" 727 | version = "0.4.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 730 | 731 | [[package]] 732 | name = "winapi-util" 733 | version = "0.1.6" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 736 | dependencies = [ 737 | "winapi", 738 | ] 739 | 740 | [[package]] 741 | name = "winapi-x86_64-pc-windows-gnu" 742 | version = "0.4.0" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 745 | 746 | [[package]] 747 | name = "windows-sys" 748 | version = "0.59.0" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 751 | dependencies = [ 752 | "windows-targets", 753 | ] 754 | 755 | [[package]] 756 | name = "windows-targets" 757 | version = "0.52.6" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 760 | dependencies = [ 761 | "windows_aarch64_gnullvm", 762 | "windows_aarch64_msvc", 763 | "windows_i686_gnu", 764 | "windows_i686_gnullvm", 765 | "windows_i686_msvc", 766 | "windows_x86_64_gnu", 767 | "windows_x86_64_gnullvm", 768 | "windows_x86_64_msvc", 769 | ] 770 | 771 | [[package]] 772 | name = "windows_aarch64_gnullvm" 773 | version = "0.52.6" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 776 | 777 | [[package]] 778 | name = "windows_aarch64_msvc" 779 | version = "0.52.6" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 782 | 783 | [[package]] 784 | name = "windows_i686_gnu" 785 | version = "0.52.6" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 788 | 789 | [[package]] 790 | name = "windows_i686_gnullvm" 791 | version = "0.52.6" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 794 | 795 | [[package]] 796 | name = "windows_i686_msvc" 797 | version = "0.52.6" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 800 | 801 | [[package]] 802 | name = "windows_x86_64_gnu" 803 | version = "0.52.6" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 806 | 807 | [[package]] 808 | name = "windows_x86_64_gnullvm" 809 | version = "0.52.6" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 812 | 813 | [[package]] 814 | name = "windows_x86_64_msvc" 815 | version = "0.52.6" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 818 | 819 | [[package]] 820 | name = "winnow" 821 | version = "0.7.13" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" 824 | 825 | [[package]] 826 | name = "wit-bindgen-rt" 827 | version = "0.33.0" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 830 | dependencies = [ 831 | "bitflags 2.4.1", 832 | ] 833 | --------------------------------------------------------------------------------