Compare commits

...

222 Commits

Author SHA1 Message Date
sharkdp 11b3e96d2d Install fakeroot when deploying 2018-02-10 17:23:26 +01:00
sharkdp a6086abe7f Updates for v6.3.0 2018-02-10 16:46:42 +01:00
David Peter 7f65e7a0d6 Update benchmark results 2018-02-10 16:43:36 +01:00
sharkdp 814d37030e Implement -F/--fixed-strings/--literal 2018-02-10 15:55:06 +01:00
Lewis Belcher 089c183d30 Update README.md
The suggested use of `--ignore` no longer exists (renamed to `--no-ignore`)
2018-02-10 14:19:54 +01:00
Martin Larralde 370d9f081f Use RegexSet instead of hand-written parser 2018-02-10 14:18:37 +01:00
Martin Larralde 86fe9977e8 Implement multiple suffixes extension support with tests (#214) 2018-02-10 14:18:37 +01:00
Martin Larralde b1bb60ebb9 Install rustfmt-nightly in debug mode in Travis (#231) 2018-02-07 19:50:10 +01:00
Steve Pentland b4d39270d6 Allow -exec to work as --exec (#226)
* Replace usage of `-exec` by `--exec`, except when it is used as a value that is given to `--exec/-exec/-x`.
* Limit usage of --exec option to single instance.
2018-01-29 20:32:46 +01:00
sharkdp 6d83eea32b Small screencast updates 2018-01-28 15:44:07 +01:00
Mario Nebl 26beaf97a6 Update screencast 2018-01-28 12:59:27 +01:00
Mario Nebl 33d37d7623 Avoid glitches on new prompt lines 2018-01-28 12:59:27 +01:00
Mario Nebl dce004afef Use updated screencast 2018-01-28 12:59:27 +01:00
Mario Nebl c1941cc7ed Change to working char to signify carriage returns 2018-01-28 12:59:27 +01:00
Mario Nebl 0a1c65b9ce Delay command with output before them 2018-01-28 12:59:27 +01:00
Mario Nebl 4fb9d84ffc Use new screencast
* Might not require rawgit as per https://github.com/marionebl/svg-term-cli/issues/5#issuecomment-360037548
2018-01-28 12:59:27 +01:00
Mario Nebl 2a84eb5cea Update screencast 2018-01-28 12:59:27 +01:00
Mario Nebl 6dd59b33b1 Document svg-terminal command 2018-01-28 12:59:27 +01:00
Mario Nebl 4fea5f3a3c Ensure -h output can be read at end of cast 2018-01-28 12:59:27 +01:00
Mario Nebl b269f4902e Demonstrate -e and --exec flags 2018-01-28 12:59:27 +01:00
Mario Nebl 33791d8400 Use ▶ prompt 2018-01-28 12:59:27 +01:00
Mario Nebl 4e737b4b44 Use upstream src 2018-01-28 12:59:27 +01:00
Mario Nebl c985ba2e6f Use local svg demo 2018-01-28 12:59:27 +01:00
Mario Nebl 658a97a94a Add svg screencast 2018-01-28 12:59:27 +01:00
Dock O'Neal bc2b7a33ae Fixing exec hang by disabling ctrl-c handling for exec 2018-01-25 07:47:04 +01:00
Vinaya 1045930dd9 Fix typos (#218)
This fixes few typos: "distributions", "want to" instead of "want so".
2018-01-10 13:02:25 +01:00
David Peter cc36543da2 Add GA
I'm curious :-)
2018-01-09 17:41:02 +01:00
sharkdp 050fa9c3b1 Fix file owner in deb package, closes #213
Also, fix a few lintian warnings / errors.
2018-01-04 19:48:59 +01:00
sharkdp 6be33fbf33 Update dependencies 2018-01-03 10:40:28 +01:00
sharkdp 61a065dd92 Update version information 2018-01-03 10:40:28 +01:00
sharkdp 941caeb053 Update documentation 2018-01-03 10:40:28 +01:00
David Peter 32a34cf8c9 Introduce maximum output buffer size (#212)
Closes #191
2018-01-03 10:40:08 +01:00
sharkdp 39fb41f05a Move exit codes to 'internal' module 2018-01-03 10:00:22 +01:00
Dock 26ad7da347 Using wants_to_quit to exit the sender as well as the reciever (#211)
Closes #210
2018-01-03 09:26:11 +01:00
sharkdp c5b920b913 Fix shell completion directories for Ubuntu 2018-01-03 09:10:33 +01:00
sharkdp 37613d6989 Fix rustftm check 2018-01-02 23:05:16 +01:00
David Peter b4ae0dca7e Add instructions on how to use fd with fzf 2018-01-02 22:26:38 +01:00
sharkdp 8dde7c3365 Re-enable help-text wrapping 2018-01-01 16:01:29 +01:00
sharkdp a5f5ad6254 Re-enable code style check 2018-01-01 16:00:32 +01:00
sharkdp aeb3722b69 Update installation instructions, see #174 2018-01-01 15:27:05 +01:00
Thejaswi Kadur faf934da4b Added support for filtering by multiple filetypes and extensions (#205)
Closes #177 
Closes #199
2018-01-01 12:09:33 +01:00
Dock 4d66c84109 Correct exit codes (#202)
* Changed exit code on ctrl-c to 130 to match specification
* Changed exit code when stdout errors. 1 for general error.
2017-12-18 06:53:34 -08:00
Thayne McCombs 2bc72a5eca Fix bash_completion path in deb. 2017-12-14 19:26:24 -08:00
sharkdp 8702133ee8 Temporarily disable the cargo fmt check 2017-12-13 19:23:38 -08:00
sharkdp 9192bc153f Fix path of bash completion 2017-12-09 23:01:32 -08:00
sharkdp b2797c0c45 Bump minimum rust version to 1.20 2017-12-09 22:14:53 -08:00
sharkdp aaf9e024d5 Updates for 6.1.0 2017-12-09 21:40:13 -08:00
Matthias Reitinger 5784823aca Unify execute_command across platforms 2017-12-09 21:10:48 -08:00
Dock 51aea57a6a Add multiple path support (#182)
* Adding support for multiple paths. (panic)

- Started adding multiple file support
- fd panics with multiple files right now

* Moved the ctrlc handler to main.

- Moved the ctrlc handler to main so we can search multiple files

* Tests now allow custom directory setup

- TestEnv::new() now takes two arguments, the directories to create and
the files to create inside those directories.

* rust-fmt changes

* rust-fmt changes

* Moving code around, no need to do everything in one big loop

- PathDisplay was never actually used for anything, removed it during refactor of main
- Removed redundant logic for absolute paths
- Moved code placed needlessly inside a loop in the last commit outside of that loop.

* Moving code around, no need to do everything in one big loop

- PathDisplay was never actually used for anything, removed it during refactor of main
- Removed redundant logic for absolute paths
- Moved code placed needlessly inside a loop in the last commit outside of that loop.

* Removed commented code in testenv

* Refactored walk::scan to accept the path buffer vector. Using the ParallelWalker allows for multithreaded searching of multiple directories

* Moved ctrlc handler back into walker, it is only called once from main now.

* Moved the colored output check back to it's original place

* Removing shell-escape, not sure how it got added...

* Added test for `fd 'a.foo' test1` to show that a.foo is only found in the test1 and not the test2 direcotry

* Removing side effect from walk::scan, `dir_vec` is no longer a mutable reference and an iterator is being used instead.

* Running rustfmt to format code correctly
2017-12-06 14:52:23 -08:00
Thayne McCombs 8914b43845 Fix deb build 2017-12-05 12:11:01 -08:00
Thayne McCombs 59ea560d37 Create deb file as part of CI build. WIP 2017-12-05 12:11:01 -08:00
Ubuntu c165ff0546 Refactorying main.rs : remove unused variable (empty_pattern) 2017-12-03 12:21:09 +01:00
doxterpepper e2b8a3cce2 The -i flag allows a package to be installed without updating. It seems cargo-update is checking only for installed packages to be updated. Since rustfmt is not installed initially in the travis build then it does not need updating and so nothing happens. The -i flag fixes that by telling cargo-update to install the package. 2017-12-03 11:22:52 +01:00
ptzz 9bd1d12c00 Add --no-ignore-vcs option
When passed, .gitignore files will not be respected.
2017-11-22 23:18:22 +01:00
Dock c0bfc65d88 Handle terminal signals (#128)
fixes #87
2017-11-22 23:05:09 +01:00
maxice8 44ee5a0b85 Add instructions on how to install under Void Linux 2017-11-18 11:15:31 +01:00
sharkdp 941a1e3d21 Update Cargo.lock 2017-11-16 08:26:40 +01:00
David Peter 0999f8e328 Updated installation instructions 2017-11-16 08:19:20 +01:00
sharkdp 9be32fa6a7 Bump version number 2017-11-15 23:39:55 +01:00
sharkdp 5ed7514096 Update error message if command could not be found 2017-11-15 23:24:11 +01:00
Matthias Reitinger 3c5d8a1e86 Fix typos 2017-11-15 23:18:36 +01:00
Matthias Reitinger ccdbc76f5a Fix execute_command on Unix 2017-11-15 23:18:36 +01:00
Matthias Reitinger 355a171557 Fix manpage formatting 2017-11-15 23:18:36 +01:00
Matthias Reitinger 5f4a1203b4 Update documentation for --exec syntax change 2017-11-15 23:18:36 +01:00
Matthias Reitinger 0f82fe7ec0 Rename TokenizedCommand to CommandTemplate 2017-11-15 23:18:36 +01:00
Matthias Reitinger c18caaa6f6 Remove CommandTicket to avoid Vec allocation 2017-11-15 23:18:36 +01:00
Matthias Reitinger bc88b8f80e Fix exec tests 2017-11-15 23:18:36 +01:00
Matthias Reitinger 18709b1ede Remove shell with --exec 2017-11-15 23:18:36 +01:00
Andrey Cherkashin 1a6c92c475 Add instructions on how to install under FreeBSD
As of this morning 5.0.0 is in ports tree.
2017-11-15 21:54:34 +01:00
Andrey Cherkashin 3104729f29 Create shell completions OUT_DIR 2017-11-15 19:54:26 +01:00
Adit Cahya Ramadhan 673392045f Check minimal rustc version on the build process 2017-11-13 19:27:51 +01:00
sharkdp 26f71dd21a Remove superfluous path.replace
See #153
2017-11-11 08:58:55 +01:00
sharkdp c022528f54 Bump version for crates.io
[ci skip]
2017-10-26 22:24:07 +02:00
David Peter 46db4a3860 Fix Windows module 2017-10-26 22:20:12 +02:00
sharkdp b3f1e1844d Move 'win' crate back into local module
This is necessary to publish on crates.io
2017-10-26 21:59:12 +02:00
sharkdp bba5c4f607 Fix some clippy warnings 2017-10-26 21:13:56 +02:00
J.W 614f576c47 Add TODOs and FIXMEs about Windows 2017-10-26 20:56:32 +02:00
J.W dc29ef70c4 Fix Input::remove_extension and Input::dirname 2017-10-26 20:56:32 +02:00
J.W d702d849ea Test --exec on Unix 2017-10-26 20:56:32 +02:00
J.W 6b232a82ee Fix --exec 2017-10-26 20:56:32 +02:00
J.W 87ba4e3bb0 Fix root directory for Windows 2017-10-26 20:56:32 +02:00
sharkdp 9d26b74c2a Workaround for the unsafe block.
See #147
2017-10-25 22:45:00 +02:00
sharkdp 7b8d9322ba README and man page updates. 2017-10-25 22:43:36 +02:00
sharkdp 5ad69fb2fb Add support for exclude-patterns
* Add `--exclude`/`-E` option.
* Support for multiple exclude patterns

Example:
``` bash
> fd --exclude 'tests/**/*.rs' mod
src/exec/mod.rs
src/fshelper/mod.rs
src/lscolors/mod.rs
```

Closes #89
2017-10-25 22:22:12 +02:00
sharkdp 19afb15a98 Fix --exec examples in README
[ci skip]
2017-10-25 21:44:13 +02:00
sharkdp f4ed2ba74d Man page updates
[skip ci]
2017-10-25 21:16:35 +02:00
J.W e38b7d7bff Fix --type: skip if file type is unknown 2017-10-25 18:29:40 +02:00
sharkdp 1ec204caa8 Add correct secret key for Travis 2017-10-25 18:28:27 +02:00
Adit Cahya Ramadhan dbf1ba8b82 Set up Travis to upload binary for every release
- Add deploy script
- Set deployment option on `.travis.yml`

fix #76
2017-10-25 18:27:13 +02:00
John L. Villalovos 4bf7ab67d0 Typo fix: Change 'descent' to 'descend'
The correct word is 'descend'

Change-Id: I6833bd28e84841799f3f060239a36876949d890e
2017-10-25 18:18:33 +02:00
sharkdp 7ecb623950 Updates for the parallel command execution part
[skip ci]
2017-10-22 21:59:03 +02:00
sharkdp 5863a5e706 Updates for the tutorial
[skip ci]
2017-10-22 21:59:03 +02:00
sharkdp d32fe0241e Updated tutorial with real-world examples
Initial work by @TitiAlone

Closes #73

[skip ci]
2017-10-22 21:59:03 +02:00
sharkdp e9cf8af911 Updates and preparations for v5.0 2017-10-22 12:10:51 +02:00
Adit Cahya Ramadhan 15a3ca0c80 Using cargo-update to update the rustfmt on travis 2017-10-22 10:38:20 +02:00
Antti Keränen 701b8f209b Relicense under MIT/Apache-2.0 2017-10-22 10:36:42 +02:00
J.W 570ea78d24 jakwings/fd@a44e677 -> subdirectory "win" 2017-10-21 00:33:49 +02:00
J.W ce73c086b6 Auto disable colored output for unsupported Windows shells 2017-10-21 00:33:49 +02:00
Michael Aaron Murphy 6a01faf6e1 Escape exec Inputs 2017-10-21 00:29:46 +02:00
Ben Morgan 200dfaa20a README: Arch Linux has package fd-rs in official repos 2017-10-21 00:23:15 +02:00
Jose Narvaez 6dc704a18e Removed unnecessary exclusion of Redox in cond-compilation and narrowed what we import from . 2017-10-20 12:59:36 +02:00
Jose Narvaez 715b07599b RedoxOS support. 2017-10-20 12:59:36 +02:00
Adit Cahya Ramadhan d2d2c31307 Only test x86_64-unknown-linux-gnu on beta and nightly
Reducing total CI time
2017-10-20 09:01:40 +02:00
Adit Cahya Ramadhan f7f5192139 Incorporate TARGET in the install and build step
- Tidying up the build matrix
  - All channels has TARGET env var
  - Build against this targets:
    - x86_64-unknown-linux-gnu
    - x86_64-unknown-linux-musl
    - i686-unknown-linux-gnu
    - i686-unknown-linux-musl
    - x86_64-apple-darwin
  - Code formating check has it's own item in the build matrix
- Prevent target re-add error from rustup
- Incorporate the TARGET env var in the `install` and `script` stage
2017-10-20 09:01:40 +02:00
Ivan Tham ddfa0873fb Shorten man page with some fixes
Maybe in the future we need to redirect `--help` to the man pages? Is there a way for cargo to install the man pages?
2017-10-19 20:10:34 +02:00
Ivan Tham b6c95a4800 Add fd.1 man page 2017-10-19 20:10:34 +02:00
J.W 8d85debc12 Refactor path handling (fixes #113)
* Fix path check
* Fix full path matching
* Allow more simple driver names in Windows tests
* Factor out special is_dir() check for "." and ".."
2017-10-18 20:04:34 +02:00
Florian Hofmair 37def9bb83 Allow overriding of OUT_DIR in build.rs (#124) 2017-10-17 08:11:42 +02:00
David Peter e0eab07881 Merge pull request #116 from mmstick/exec
Implement the --exec flag
2017-10-15 21:36:51 +02:00
Michael Aaron Murphy 16eed82ca1 Handle OS-specific path separators when manipulating paths 2017-10-15 11:20:55 -04:00
Michael Aaron Murphy 2a23905af5 Fix merge conflicts 2017-10-15 09:37:48 -04:00
J.W 54d9dde0d4 Use .canonicalize() to check paths & Reword comments 2017-10-15 15:01:24 +02:00
J.W e649c8fa79 Use a uniform output format for searching ../
closes #107, fixes #82 by the way
2017-10-15 15:01:24 +02:00
sharkdp fe5d8aa29e Disable rust-beta and nightly on AppVeyor 2017-10-15 12:46:06 +02:00
J.W ecfd5568cf AppVeyor: fix path of cache direcotry 2017-10-15 10:12:55 +02:00
Matt 08aa06a0ad Added another way of installation
Added option using Nix package manager
2017-10-15 10:12:02 +02:00
Michael Aaron Murphy ad1b4f3b2b Optimize grouping for *nix archs 2017-10-14 21:45:31 -04:00
Michael Aaron Murphy 1bc58b2fbb Group outputs from exec 2017-10-14 18:06:49 -04:00
Michael Aaron Murphy 718f723d31 Enable absolute paths w/ exec 2017-10-14 16:42:47 -04:00
Michael Aaron Murphy 884bd41cae Apply cargo fmt 2017-10-14 14:04:04 -04:00
Michael Aaron Murphy fb1cd3a322 Fix for older builds
Rust 1.19 will be a requirement, however.
2017-10-14 13:38:54 -04:00
sharkdp db5f2fc3ac Enable caching on appveyor 2017-10-14 18:55:26 +02:00
Michael Aaron Murphy 137fe2e697 Merge Conflicts 2017-10-14 12:35:16 -04:00
Michael Aaron Murphy 4a5a5faf4d Implement --exec feature
Closes #84
2017-10-14 12:24:17 -04:00
J.W f2632d5fce Dot "." also matches newline by default (closes #111) 2017-10-14 16:12:11 +02:00
sharkdp c38ef0e9b2 Fix description of smart-case in help text 2017-10-14 12:09:34 +02:00
sharkdp be2238ddf4 Fix Cargo warnings 2017-10-14 12:04:57 +02:00
Matthias Reitinger 7150c9a3a9 Refactor output.rs 2017-10-14 11:11:06 +02:00
Matthias Reitinger 0b04f39398 Split up print_entry in colorized and uncolorized helper functions. 2017-10-14 11:11:06 +02:00
Matthias Reitinger 0677e6331d Fix too many path separators on Windows (#93). 2017-10-14 11:11:06 +02:00
Matthias Reitinger b441528067 Improve smart case to only consider literal uppercase chars (#103) 2017-10-14 11:03:21 +02:00
sharkdp 8fc3a83d92 Use absolute path to perform search, fixes #81
Previously, we were using the relative path to extract the search
string. For the current directory, the returned relative path was equal
to `""`. This is why the current directory did not show in the search
results (see #81).

This commit also changes the way that `--full-path` works, which was
previously working on relative paths. It seems more useful to search the
absolute path, though. Otherwise, search results could change just by
calling fd (with a given search path) from a different directory.
2017-10-13 18:16:26 +02:00
Antti Keränen 97e3110333 Make Travis check that the code is correctly formatted
Closes #99.
2017-10-12 21:53:20 +02:00
Antti Keränen 049b9ec06b Format the source code using rustfmt 2017-10-12 19:33:14 +02:00
J.W e075e1cf7c Add -i, --ignore-case 2017-10-12 07:28:05 +02:00
Rav Chandra 35e5ecd492 liberally accept >2 -u options as per ripgrep 2017-10-11 23:15:21 +02:00
Rav Chandra 4f2d9e77b3 Add ripgrep aliases for no-ignore and hidden 2017-10-11 23:15:21 +02:00
Garrett Squire ccb899a511 break main into separate modules 2017-10-11 08:21:09 +02:00
Jean-Christophe Petkovich affe41949d Switch symlink arg from 's' to 'l' (#83)
closes #80
2017-10-09 21:25:30 +02:00
Oli Lalonde 68a85c7e5b Update README.md 2017-10-08 22:52:43 +02:00
sharkdp b9b6964f30 Small update for the tutorial 2017-10-08 21:46:29 +02:00
David Peter 0cd9d8c3a6 Link to benchmark section 2017-10-08 20:54:54 +02:00
David Peter 108fce3396 Add first version of CONTRIBUTING.md 2017-10-07 22:01:52 +02:00
sharkdp a7f4a0a581 Re-enable macOS on Travis, both nightly and stable
fixes #69
2017-10-07 21:59:06 +02:00
David Peter b98e4c9931 Update development instructions 2017-10-07 16:19:47 +02:00
David Peter 43b124cf19 Add Windows install instructions 2017-10-07 16:18:19 +02:00
sharkdp 20e179c91d Fix appveyor deploy script 2017-10-07 16:03:07 +02:00
sharkdp d8da6ec83c Update long help text 2017-10-07 15:15:30 +02:00
David Peter 4731dc670c Enhanced Windows support, see #70
* Use easier way to convert path components
* Fix failing tests on Windows
2017-10-07 09:40:44 +02:00
Alejandro Baez a84536a173 app: refactor to ripgrep style for long_help 2017-10-07 09:13:39 +02:00
sharkdp 080e8a9b6e First update for tutorial 2017-10-06 17:07:26 +02:00
Ricky 3b55edf5dd Add examples to README (#68)
closes #57
2017-10-06 16:31:04 +02:00
sharkdp 0aae4a3d46 Update help text 2017-10-05 21:35:22 +02:00
sharkdp 34e4620e26 Code style udpates 2017-10-05 21:31:44 +02:00
sharkdp 2403ac350a cargo update 2017-10-05 00:01:56 +02:00
Matthias Reitinger 6f22957cdc Re-write integration tests in Rust (#67)
* Rewrite integration tests in Rust.
* Improve failure output for integration tests.
* Move TestEnv to separate module.
* Remove old integration tests script.
* Re-add integration test for subdirectory.
2017-10-04 23:19:30 +02:00
sharkdp 00b57b50ae Include PowerShell completion script in release
See #64
2017-10-04 20:48:43 +02:00
Florian Hofmair 396b9baaa5 Use clap to generate shell completions 2017-10-04 18:00:25 +02:00
David Peter 083b1cfd9b Update USAGE. 2017-10-03 20:23:53 +02:00
Antti Keränen 4ec33d064c Add a comment about the default value of --threads 2017-10-03 20:21:49 +02:00
sharkdp 7399558a06 Re-structure '--help' text 2017-10-03 19:30:39 +02:00
Antti Keränen 4b61beaa75 Add flag to force color
Color can now be forced on even when piping the output. Changed the
--no-color flag to --color which uses the same options that ripgrep
uses for --color (excluding ansi).

Closes #49.
2017-10-03 18:32:24 +02:00
Matthias Reitinger 2256affd8f Fix extension comparison logic 2017-10-03 18:28:54 +02:00
Matthias Reitinger b0fb774c0e Fix test suite name 2017-10-03 18:28:54 +02:00
Matthias Reitinger 32a321aafd Extract entry extension only when there is an extension filter 2017-10-03 18:28:54 +02:00
Matthias Reitinger 83db8673cf Add extension filtering 2017-10-03 18:28:54 +02:00
sharkdp 8073635177 Add badges 2017-09-27 21:47:41 +02:00
sharkdp 60df14f0a3 Fix build on Windows 2017-09-27 21:25:50 +02:00
sharkdp 042959a4af Add appveyor.yml file 2017-09-27 21:21:26 +02:00
David Peter 89124b317e Remove link to fd-rs-git 2017-09-27 20:53:25 +02:00
sharkdp adaf4f6d73 Reduce number of 'stat' syscalls from 3 to 1, see #36 2017-09-18 19:27:26 +02:00
David Peter d8c588d805 Add crates.io badge 2017-09-17 13:17:40 +02:00
David Peter df44407b84 Formatting of help text 2017-09-17 09:45:54 +02:00
sharkdp c1b8d1eae1 Minor style changes 2017-09-17 09:37:39 +02:00
exitium 2a3dd5b631 Add file type filtering 2017-09-17 09:25:01 +02:00
sharkdp fbce0d4f39 Fix clippy warnings 2017-09-15 21:03:47 +02:00
sharkdp 1397dbcc6f Fix warning 2017-09-15 20:56:07 +02:00
David Peter 2def50bddd Add stable AUR package 2017-09-13 19:30:42 +02:00
David Peter 6c69b98b49 Add link to Homebrew formula 2017-09-10 09:49:53 +02:00
sharkdp fe26f71582 temporarily disable osx 2017-09-09 20:42:30 +02:00
David Peter 3afd216008 Update demo video 2017-09-09 20:39:41 +02:00
sharkdp f1f537b83a Add new cargo install instructions 2017-09-09 19:35:03 +02:00
sharkdp a2349848c0 Add command-line options 2017-09-09 19:35:03 +02:00
sharkdp 916b55fae1 Update Cargo.toml, see #42 2017-09-09 19:15:35 +02:00
sharkdp f41c7a4344 Update version 2017-09-09 19:07:03 +02:00
sharkdp 4884c23283 Update development instructions 2017-09-09 19:04:17 +02:00
David Peter d67b1bba61 Update benchmark results 2017-09-09 18:46:05 +02:00
David Peter b34716051a Add note about regex and ignore crates 2017-09-09 17:07:19 +02:00
sharkdp fde8f2e8e3 Try to fix test on macOS 2017-09-09 16:42:10 +02:00
sharkdp a790f9bd62 Remove unicode test 2017-09-09 16:26:33 +02:00
sharkdp b4c8a8ba56 Use N-1 search threads, where N = # cores 2017-09-09 16:13:27 +02:00
sharkdp 1268e982f6 Add --dereference as an alias for --follow 2017-09-09 15:40:32 +02:00
sharkdp 31c76989ad Change short flag for --follow to -L, see #33 2017-09-09 15:34:25 +02:00
sharkdp f92dfb86bb Update help text 2017-09-09 15:25:48 +02:00
sharkdp c55b255a3e Update dependencies 2017-09-09 15:25:48 +02:00
sharkdp 977e0ac694 Spawn a separate thread for the receiver 2017-09-09 15:25:48 +02:00
sharkdp 550e3b3572 Implement short-time buffering for sorted output 2017-09-09 15:25:48 +02:00
sharkdp e06189e654 Initial version of parallel directory traversal
see #32
2017-09-09 15:25:48 +02:00
sharkdp 2d1d24c38c Rename config parameter for print0 and fix sorting in tests 2017-08-01 22:36:38 +02:00
David Peter 10ea476e31 Merge pull request #50 from michaelmior/print0
Add --print0 option
2017-08-01 21:43:30 +02:00
David Peter 1586d653ae Add link to AUR package 2017-08-01 18:43:45 +02:00
David Peter fb5ba2c068 Merge pull request #51 from wezm/patch-1
Add Arch Linux installation instructions
2017-08-01 18:40:49 +02:00
Wesley Moore f8b02ac5ae Add Arch Linux installation instructions 2017-08-01 11:44:29 +10:00
Michael Mior 1b0c8f349a Add --print0 option 2017-07-19 17:10:06 -04:00
sharkdp 918e4a014a Fix warning in tests on Linux 2017-07-02 16:01:08 +02:00
sharkdp f5261595a7 Stabilize sort results 2017-06-15 16:12:11 +02:00
sharkdp d8d0cf5d6e Modify test.sh for macOS, fixes #44 2017-06-15 16:12:11 +02:00
David Peter eb728756ed Update install instructions 2017-06-15 12:25:16 +02:00
sharkdp 3f69c0fa1a Update test.sh script, see #44 2017-06-13 21:08:02 +02:00
sharkdp dfd7433347 Bump to v2.0.0 2017-06-13 20:29:29 +02:00
sharkdp d3d7460707 Update .travis.yml, see #43 2017-06-12 22:12:10 +02:00
sharkdp 54f33e5227 Use absolute paths if root dir is an absolute path, fixes #40 2017-06-11 20:55:01 +02:00
sharkdp 1b1f644398 Throw an error if root directory is non-existent, fixes #39 2017-06-11 20:41:32 +02:00
sharkdp 24070b0be0 Handle invalid UTF-8, fixes #34 2017-06-11 15:38:56 +02:00
sharkdp 087e709acd Use clap, change --sensitive to --case-sensitive
see #33
2017-06-11 14:25:58 +02:00
sharkdp 1adcf76f1b Use atty instead of isatty 2017-06-10 17:30:48 +02:00
sharkdp c74c2ca39b Update benchmark results, see #31 2017-06-09 15:06:31 +02:00
sharkdp c8d4ec89a3 Use references instead of values 2017-06-09 14:39:57 +02:00
Sebastiaan Vermeulen f7910c125d windows compatibility (#35)
windows compatibility
2017-06-09 12:18:12 +02:00
sharkdp 2ea23c0000 Exit gracefully on broken pipe, fixes #24 2017-06-07 23:58:08 +02:00
sharkdp 641ca69b49 Add note about rust 1.16 2017-06-06 20:47:11 +02:00
sharkdp 8c6a9c8e32 Set static lifetime, fixes #23 2017-06-06 20:36:17 +02:00
31 changed files with 4155 additions and 691 deletions
+139 -6
View File
@@ -1,8 +1,141 @@
language: rust
rust:
- stable
- beta
- nightly
cache: cargo
matrix:
allow_failures:
- rust: nightly
# allow_failures:
# - rust: nightly
include:
# Stable channel.
- os: linux
rust: stable
env: TARGET=x86_64-unknown-linux-gnu
- os: linux
rust: stable
env: TARGET=x86_64-unknown-linux-musl
- os: linux
rust: stable
env: TARGET=i686-unknown-linux-gnu
- os: linux
rust: stable
env: TARGET=i686-unknown-linux-musl
- os: osx
rust: stable
env: TARGET=x86_64-apple-darwin
# Beta channel.
- os: linux
rust: beta
env: TARGET=x86_64-unknown-linux-gnu
# Disabled to reduce total CI time
# - os: linux
# rust: beta
# env: TARGET=x86_64-unknown-linux-musl
# - os: linux
# rust: beta
# env: TARGET=i686-unknown-linux-gnu
# - os: linux
# rust: beta
# env: TARGET=i686-unknown-linux-musl
# - os: osx
# rust: beta
# env: TARGET=x86_64-apple-darwin
# Nightly channel.
- os: linux
rust: nightly
env: TARGET=x86_64-unknown-linux-gnu
# Disabled to reduce total CI time
# - os: linux
# rust: nightly
# env: TARGET=x86_64-unknown-linux-musl
# - os: linux
# rust: nightly
# env: TARGET=i686-unknown-linux-gnu
# - os: linux
# rust: nightly
# env: TARGET=i686-unknown-linux-musl
# - os: osx
# rust: nightly
# env: TARGET=x86_64-apple-darwin
# Minimum Rust supported channel.
- os: linux
rust: 1.20.0
env: TARGET=x86_64-unknown-linux-gnu
- os: linux
rust: 1.20.0
env: TARGET=x86_64-unknown-linux-musl
- os: linux
rust: 1.20.0
env: TARGET=i686-unknown-linux-gnu
- os: linux
rust: 1.20.0
env: TARGET=i686-unknown-linux-musl
- os: osx
rust: 1.20.0
env: TARGET=x86_64-apple-darwin
# Code formatting check
- os: linux
rust: nightly
# skip the global install step
install:
- cargo install --debug --force rustfmt-nightly
script: cargo fmt -- --write-mode=diff
addons:
apt:
packages:
# needed for i686-unknown-linux-gnu target
- gcc-multilib
# needed to build deb packages
- fakeroot
env:
global:
# Default target on travis-ci.
# Used as conditional check in the install stage
- HOST=x86_64-unknown-linux-gnu
# Used on the deployment script
- PROJECT_NAME=fd
install:
# prevent target re-add error from rustup
- if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi
script:
# Incorporate TARGET env var to the build and test process
- cargo build --target $TARGET --verbose
- cargo test --target $TARGET --verbose
before_deploy:
- bash ci/before_deploy.bash
deploy:
provider: releases
# NOTE updating the `api_key.secure`
# - go to: https://github.com/settings/tokens/new
# - generate new token using `public_repo` scope
# - encrypt it using: `travis encrypt API_KEY_HERE`
# - paste the output below
api_key:
secure: "RyFdh2lpDmaNhPar7ezsb18Xz+6XFM40y7cZCDRML+Sk+eYK1xtDNfEhDRJU5Qo1ReVsByds/QJTSXr2KmZPk3lXwG3SiN7UtrLUxCxFr6qrcM/iujlKTf5UxeRklkzPXxnH95DEyEgxvgbVhWTGVDWoyMnrVQXZKDy6z1iAiYB5h2Zl1rs+MRb/Enlt5q6XIKAlG0ppGtl8CfYudq5ZiqfJaMWTt9SWm2YskC8FeMc0S3IM6/EhTvaNYLdaarFqVWQEVql+6oCuL3ayPzmGyxLdxM37tIMNQ0f97zxqWodacXTG5ULdRD8if1l/SmTujrtjbZ0KWRjsjOq4vBtxBJKGdprcSiB0xH/hToqqtTSO0z5FPXi5cB8UlK6YLDDHcP3kXNer8CYMLI1VPaUDLTF57/0/RPi2DZiiGfZsIAS6PsICbHdTQVzxQckM4lN1vnAGgkhXIMbztml21pv+QrGy98OZJ0ubf5ztgQhpT0WPH4JXT8M6htsoo8dZf8lQ5aLfmW9RKePJDqixQwPqmimPIkrlxRDTDGII0ZAZws7l779eOLmEcM2tH2HbsUKUCZIG/pRHLSlP45Jn2bULGzuXZ2daq70z6zvIbom0CUzSXIvdTXEZI2AM5RBvPYGGaKI8YlxgRdQvJp3h0BzPdFOXI3RAxscCY7PJpa/RdIg="
# for uploading multiple files
file_glob: true
# NOTE explanation on each env variable
# - PROJECT_NAME: name of the project, set on the `env.global` above
# - TRAVIS_TAG: tag name that the build is being deployed for, usually the version number
# - TARGET: target triple of the build
file:
- $PROJECT_NAME-$TRAVIS_TAG-$TARGET.*
- $PROJECT_NAME*.deb
# don't delete artifacts from previous stage
skip_cleanup: true
on:
# deploy only if we push a tag
tags: true
# deploy only on stable channel that has TARGET env variable sets
condition: $TRAVIS_RUST_VERSION = stable && $TARGET != ""
notifications:
email:
on_success: never
+20
View File
@@ -0,0 +1,20 @@
## Contributing to *fd*
**Thank you very much for considering to contribute to this project!**
We welcome any form of contribution:
* New issues (feature requests, bug reports, questions, ideas, ...)
* Pull requests (documentation improvements, code improvements, new features, ...)
**Note**: Before you take the time to open a pull request, please open a ticket first. This will
give us the chance to discuss any potential changes first.
## Important links
* [Open issues](https://github.com/sharkdp/fd/issues)
* [Open pull requests](https://github.com/sharkdp/fd/pulls)
* [Development section in the README](https://github.com/sharkdp/fd#development)
* [fd on crates.io](https://crates.io/crates/fd-find)
* [LICENSE](https://github.com/sharkdp/fd/blob/master/LICENSE)
Generated
+300 -73
View File
@@ -1,20 +1,9 @@
[root]
name = "fd"
version = "1.0.0"
dependencies = [
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"isatty 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "aho-corasick"
version = "0.6.3"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -22,58 +11,138 @@ name = "ansi_term"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ansi_term"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "atty"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fnv"
version = "1.0.5"
name = "ctrlc"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "diff"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "getopts"
version = "0.2.14"
name = "fd-find"
version = "6.3.0"
dependencies = [
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fnv"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-zircon"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "globset"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ignore"
version = "0.2.0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "isatty"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@@ -85,42 +154,107 @@ dependencies = [
[[package]]
name = "lazy_static"
version = "0.2.8"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.22"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.3.7"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "log"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num_cpus"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "0.2.1"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.4.0"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -133,26 +267,64 @@ dependencies = [
]
[[package]]
name = "thread-id"
version = "3.0.0"
name = "strsim"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "tempdir"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "term_size"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.3"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-width"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unreachable"
version = "0.1.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -163,6 +335,16 @@ name = "utf8-ranges"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
@@ -183,33 +365,78 @@ name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f"
"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685"
"checksum globset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "feeb1b6840809ef5efcf7a4a990bc4e1b7ee3df8cf9e2379a75aeb2ba42ac9c3"
"checksum ignore 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9bda0eaa44a060e21581315efb449e7fabceb93cf38c8376dd1b7e0623e07844"
"checksum isatty 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fa500db770a99afe2a0f2229be2a3d09c7ed9d7e4e8440bf71253141994e240f"
"checksum ctrlc 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "df391ea008fca636e41e40863a0b39a850e2ab26b0cdeed0c3657fd05a66d44c"
"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd510087c325af53ba24f3be8f1c081b0982319adcb8b03cad764512923ccc19"
"checksum fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "08b3a6f13ad6b96572b53ce7af74543132f1a7055ccceb6d073dd36c54481859"
"checksum globset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "464627f948c3190ae3d04b1bc6d7dca2f785bda0ac01278e6db129ad383dbeb6"
"checksum ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3fcaf2365eb14b28ec7603c98c06cc531f19de9eb283d89a3dff8417c8c99f5"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf"
"checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502"
"checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad"
"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01"
"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457"
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
"checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
"checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "9e7944d95d25ace8f377da3ac7068ce517e4c646754c43a1b1849177bbf72e59"
"checksum redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "07b8f011e3254d5a9b318fde596d409a0001c9ae4c6e7907520c2eaa4d988c99"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa"
"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a"
"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7"
"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "890b38836c01d72fdb636d15c9cfc52ec7fd783b330abc93cd1686f4308dfccc"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc"
"checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668"
+51 -5
View File
@@ -1,11 +1,57 @@
[package]
name = "fd"
version = "1.0.0"
authors = ["David Peter <mail@david-peter.de>"]
build = "build.rs"
categories = ["command-line-utilities"]
description = "fd is a simple, fast and user-friendly alternative to find."
exclude = ["/benchmarks/*"]
homepage = "https://github.com/sharkdp/fd"
keywords = [
"search",
"find",
"file",
"filesystem",
"tool",
]
license = "MIT/Apache-2.0"
name = "fd-find"
readme = "README.md"
repository = "https://github.com/sharkdp/fd"
version = "6.3.0"
[[bin]]
name = "fd"
path = "src/main.rs"
[badges.appveyor]
repository = "sharkdp/fd"
[badges.travis-ci]
repository = "sharkdp/fd"
[build-dependencies]
clap = "2.26.0"
version_check = "0.1.3"
[dependencies]
ansi_term = "0.9"
getopts = "0.2"
isatty = "0.1"
regex = "0.2"
atty = "0.2"
ignore = "0.2"
lazy_static = "1"
num_cpus = "1.6.2"
regex = "0.2"
regex-syntax = "0.4"
ctrlc = "3.0"
[dependencies.clap]
version = "2.26.0"
features = ["suggestions", "color", "wrap_help"]
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
kernel32-sys = "0.2"
winapi = "0.2"
[dev-dependencies]
diff = "0.1"
tempdir = "0.3"
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 David Peter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 fd developers
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+313 -18
View File
@@ -1,5 +1,8 @@
# fd
[![Build Status](https://travis-ci.org/sharkdp/fd.svg?branch=master)](https://travis-ci.org/sharkdp/fd)
[![Build status](https://ci.appveyor.com/api/projects/status/21c4p5fwggc5gy3j?svg=true)](https://ci.appveyor.com/project/sharkdp/fd)
[![Version info](https://img.shields.io/crates/v/fd-find.svg)](https://crates.io/crates/fd-find)
![Analytics](https://ga-beacon.appspot.com/UA-39945208-8/sharkdp/fd/README?pixel)
*fd* is a simple, fast and user-friendly alternative to
[*find*](https://www.gnu.org/software/findutils/).
@@ -9,20 +12,80 @@ While it does not seek to mirror all of *find*'s powerful functionality, it prov
## Features
* Convenient syntax: `fd PATTERN` instead of `find -iname '*PATTERN*'`.
* Colorized terminal output (similar to *ls*).
* It's *fast* (see [benchmarks](#benchmark) below).
* Smart case: the search is case-insensitive by default. It switches to
case-sensitive if the pattern contains an uppercase
character[\*](http://vimdoc.sourceforge.net/htmldoc/options.html#'smartcase').
* Colorized terminal output (similar to *ls*).
* Ignores hidden directories and files, by default.
* Ignores patterns from your `.gitignore`, by default.
* Regular expressions.
* Unicode-awareness.
* The command name is *50%* shorter[\*](https://github.com/ggreer/the_silver_searcher) than
`find` :-).
* Parallel command execution with a syntax similar to GNU Parallel.
## Demo
![Demo](http://i.imgur.com/iU6qkQj.gif)
![Demo](doc/screencast.svg)
## Benchmark
Let's search my home folder for files that end in `[0-9].jpg`. It contains ~190.000
subdirectories and about a million files. For averaging and statistical analysis, I'm using
[hyperfine](https://github.com/sharkdp/hyperfine). The following benchmarks are performed
with a "warm"/pre-filled disk-cache (results for a "cold" disk-cache show the same trends).
Let's start with `find`:
```
Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
Time (mean ± σ): 7.236 s ± 0.090 s
Range (min … max): 7.133 s … 7.385 s
```
`find` is much faster if it does not need to perform a regular-expression search:
```
Benchmark #2: find ~ -iname '*[0-9].jpg'
Time (mean ± σ): 3.914 s ± 0.027 s
Range (min … max): 3.876 s … 3.964 s
```
Now let's try the same for `fd`. Note that `fd` *always* performs a regular expression
search. The options `--hidden` and `--no-ignore` are needed for a fair comparison,
otherwise `fd` does not have to traverse hidden folders and ignored paths (see below):
```
Benchmark #3: fd -HI '.*[0-9]\.jpg$' ~
Time (mean ± σ): 811.6 ms ± 26.9 ms
Range (min … max): 786.0 ms … 870.7 ms
```
For this particular example, `fd` is approximately nine times faster than `find -iregex`
and about five times faster than `find -iname`. By the way, both tools found the exact
same 20880 files :smile:.
Finally, let's run `fd` without `--hidden` and `--no-ignore` (this can lead to different
search results, of course). If *fd* does not have to traverse the hidden and git-ignored
folders, it is almost an order of magnitude faster:
```
Benchmark #4: fd '[0-9]\.jpg$' ~
Time (mean ± σ): 123.7 ms ± 6.0 ms
Range (min … max): 118.8 ms … 140.0 ms
```
**Note**: This is *one particular* benchmark on *one particular* machine. While I have
performed quite a lot of different tests (and found consistent results), things might
be different for you! I encourage everyone to try it out on their own. See
[this repository](https://github.com/sharkdp/fd-benchmarks) for all necessary scripts.
Concerning *fd*'s speed, the main credit goes to the `regex` and `ignore` crates that are also used
in [ripgrep](https://github.com/BurntSushi/ripgrep) (check it out!).
## Colorized output
`fd` can colorize files by extension, just like `ls`. In order for this to work, the environment
@@ -34,28 +97,260 @@ complete (and more colorful) variants, see
[here](https://github.com/seebi/dircolors-solarized) or
[here](https://github.com/trapd00r/LS_COLORS).
## Benchmark
A search in my home folder with ~150.000 subdirectories and ~1M files. The given options for
`fd` are needed for a fair comparison (otherwise `fd` is even faster by a factor of 4 because it
does not have to search hidden and ignored paths):
## Parallel command execution
If the `-x`/`--exec` option is specified alongside a command template, a job pool will be created
for executing commands in parallel for each discovered path as the input. The syntax for generating
commands is similar to that of GNU Parallel:
- `{}`: A placeholder token that will be replaced with the path of the search result
(`documents/images/party.jpg`).
- `{.}`: Like `{}`, but without the file extension (`documents/images/party`).
- `{/}`: A placeholder that will be replaced by the basename of the search result (`party.jpg`).
- `{//}`: Uses the parent of the discovered path (`documents/images`).
- `{/.}`: Uses the basename, with the extension removed (`party`).
``` bash
> time fd --hidden --no-ignore --full-path '.*[0-9]\.jpg$' > /dev/null
1,03s user 0,92s system 99% cpu 1,961 total
# Convert all jpg files to png files:
fd -e jpg -x convert {} {.}.png
> time find -iregex '.*[0-9]\.jpg$' > /dev/null
3,98s user 0,84s system 99% cpu 4,832 total
```
Both tools found the exact same 14030 files and the results are comparable when averaged over
multiple runs.
# Unpack all zip files (if no placeholder is given, the path is appended):
fd -e zip -x unzip
## Install
With [cargo](https://github.com/rust-lang/cargo), you can clone, build and install *fd* with a single command:
# Convert all flac files into opus files:
fd -e flac -x ffmpeg -i {} -c:a libopus {.}.opus
# Count the number of lines in Rust files (the command template can be terminated with ';'):
fd -x wc -l \; -e rs
```
cargo install --git https://github.com/sharkdp/fd
## Installation
### On Ubuntu
*... and other Debian-based Linux distributions.*
Download the latest `.deb` package from the [release page](https://github.com/sharkdp/fd/releases) and install it via:
``` bash
sudo dpkg -i fd_6.3.0_amd64.deb # adapt version number and architecture
```
The release page of this repository also includes precompiled binaries for Linux.
### On Arch Linux
You can install [the fd-rs package](https://www.archlinux.org/packages/community/x86_64/fd-rs/) from the official repos:
```
pacman -S fd-rs
```
### On Void Linux
You can install `fd` via xbps-install:
```
xbps-install -S fd
```
### On macOS
You can install [this Homebrew package](http://braumeister.org/formula/fd):
```
brew install fd
```
### On Windows
You can download pre-built binaries from the [release page](https://github.com/sharkdp/fd/releases).
### On NixOS / via Nix
You can use the [Nix package manager](https://nixos.org/nix/) to install `fd`:
```
nix-env -i fd
```
### On FreeBSD
You can install `sysutils/fd` via portmaster:
```
portmaster sysutils/fd
```
### From source
With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can install *fd* via:
```
cargo install fd-find
```
Note that rust version *1.20.0* or later is required.
### From binaries
The [release page](https://github.com/sharkdp/fd/releases) includes precompiled binaries for Linux, macOS and Windows.
## Development
```bash
cargo build --release
git clone https://github.com/sharkdp/fd
# Build
cd fd
cargo build
# Run unit tests and integration tests
cargo test
# Install
cargo install
```
## Command-line options
```
USAGE:
fd [FLAGS/OPTIONS] [<pattern>] [<path>...]
FLAGS:
-H, --hidden Search hidden files and directories
-I, --no-ignore Do not respect .(git)ignore files
--no-ignore-vcs Do not respect .gitignore files
-s, --case-sensitive Case-sensitive search (default: smart case)
-i, --ignore-case Case-insensitive search (default: smart case)
-F, --fixed-strings Treat the pattern as a literal string
-a, --absolute-path Show absolute instead of relative paths
-L, --follow Follow symbolic links
-p, --full-path Search full path (default: file-/dirname only)
-0, --print0 Separate results by the null character
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-d, --max-depth <depth> Set maximum search depth (default: none)
-t, --type <filetype>... Filter by type: f(ile), d(irectory), (sym)l(ink)
-e, --extension <ext>... Filter by file extension
-x, --exec <cmd> Execute a command for each search result
-E, --exclude <pattern>... Exclude entries that match the given glob pattern
-c, --color <when> When to use colors: never, *auto*, always
-j, --threads <num> Set number of threads to use for searching & executing
ARGS:
<pattern> the search pattern, a regular expression (optional)
<path>... the root directory for the filesystem search (optional)
```
## Tutorial
First, to get an overview of all available command line options, you can either run
`fd -h` for a concise help message (see above) or `fd --help` for a more detailed
version.
### Simple search
*fd* is designed to find entries in your filesystem. The most basic search you can perform is to
run *fd* with a single argument: the search pattern. For example, assume that you want to find an
old script of yours (the name included `netflix`):
``` bash
> fd netfl
Software/python/imdb-ratings/netflix-details.py
```
If called with just a single argument like this, *fd* searches the current directory recursively
for any entries that *contain* the pattern `netfl`.
### Regular expression search
The search pattern is treated as a regular expression. Here, we search for entries that start
with `x` and end with `rc`:
``` bash
> cd /etc
> fd '^x.*rc$'
X11/xinit/xinitrc
X11/xinit/xserverrc
```
### Specifying the root directory
If we want to search a specific directory, it can be given as a second argument to *fd*:
``` bash
> fd passwd /etc
/etc/default/passwd
/etc/pam.d/passwd
/etc/passwd
```
### Running *fd* without any arguments
*fd* can be called with no arguments. This is very useful to get a quick overview of all entries
in the current directory, recursively (similar to `ls -R`):
``` bash
> cd fd/tests
> fd
testenv
testenv/mod.rs
tests.rs
```
### Searching for a particular file extension
Often, we are interested in all files of a particular type. This can be done with the `-e` (or
`--extension`) option. Here, we search for all Markdown files in the fd repository:
``` bash
> cd fd
> fd -e md
CONTRIBUTING.md
README.md
```
The `-e` option can be used in combination with a search pattern:
``` bash
> fd -e rs mod
src/fshelper/mod.rs
src/lscolors/mod.rs
tests/testenv/mod.rs
```
### Hidden and ignored files
By default, *fd* does not search hidden directories and does not show hidden files in the
search results. To disable this behavior, we can use the `-H` (or `--hidden`) option:
``` bash
> fd pre-commit
> fd -H pre-commit
.git/hooks/pre-commit.sample
```
If we work in a directory that is a Git repository (or includes Git repositories), *fd* does not
search folders (and does not show files) that match one of the `.gitignore` patterns. To disable
this behavior, we can use the `-I` (or `--no-ignore`) option:
``` bash
> fd num_cpu
> fd -I num_cpu
target/debug/deps/libnum_cpus-f5ce7ef99006aa05.rlib
```
To really search *all* files and directories, simply combine the hidden and ignore features to show
everything (`-HI`).
### Using fd with `xargs` or `parallel`
If we want to run a command on all search results, we can pipe the output to `xargs`:
``` bash
> fd -0 -e rs | xargs -0 wc -l
```
Here, the `-0` option tells *fd* to separate search results by the NULL character (instead of .
newlines) In the same way, the `-0` option of `xargs` tells it to read the input in this way .
### Using fd with `fzf`
You can use *fd* to generate input for the command-line fuzzy finder [fzf](https://github.com/junegunn/fzf):
``` bash
export FZF_DEFAULT_COMMAND='fd --type file'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
```
Then, you can type `vim <Ctrl-T>` on your terminal to open fzf and search through the fd-results.
Alternatively, you might like to follow symbolic links and include hidden files (but exclude `.git` folders):
``` bash
export FZF_DEFAULT_COMMAND='fd --type file --follow --hidden --exclude .git'
```
You can even use fd's colored output inside fzf by setting:
``` bash
export FZF_DEFAULT_COMMAND="fd --type file --color=always"
export FZF_DEFAULT_OPTS="--ansi"
```
For more details, see the [Tips section](https://github.com/junegunn/fzf#tips) of the fzf README.
+94
View File
@@ -0,0 +1,94 @@
# Source: https://github.com/starkat99/appveyor-rust/
environment:
global:
PROJECT_NAME: fd
matrix:
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Beta channel (disabled for speed reasons)
#- TARGET: i686-pc-windows-gnu
# CHANNEL: beta
#- TARGET: i686-pc-windows-msvc
# CHANNEL: beta
#- TARGET: x86_64-pc-windows-gnu
# CHANNEL: beta
#- TARGET: x86_64-pc-windows-msvc
# CHANNEL: beta
# Nightly channel (disabled for speed reasons)
#- TARGET: i686-pc-windows-gnu
# CHANNEL: nightly
#- TARGET: i686-pc-windows-msvc
# CHANNEL: nightly
#- TARGET: x86_64-pc-windows-gnu
# CHANNEL: nightly
#- TARGET: x86_64-pc-windows-msvc
# CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
install:
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents
# the "directory does not contain a project or solution file" error.
# source: https://github.com/starkat99/appveyor-rust/blob/master/appveyor.yml#L113
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo build --verbose
- cargo run
- cargo test
before_deploy:
# Generate artifacts for release
- cargo build --release
- mkdir staging
- copy target\release\fd.exe staging
- ps: copy target\release\build\fd-find*\out\_fd.ps1 staging
- cd staging
# release zipfile will look like 'rust-everywhere-v1.2.3-x86_64-pc-windows-msvc'
- 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
- appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
deploy:
description: 'Windows release'
# All the zipped artifacts will be deployed
artifact: /.*\.zip/
# Here's how:
# - Go to 'https://github.com/settings/tokens/new' and generate a Token with only the
# `public_repo` scope enabled
# - Then go to 'https://ci.appveyor.com/tools/encrypt' and enter the newly generated token.
# - Enter the "encrypted value" below
auth_token:
secure: mWJ8ieZdGEgHf232fdMyzb9T1rKbkJivYbp/REMo8ax3X5vkQJDHhFjWeuWA3wIC
provider: GitHub
# deploy when a new tag is pushed and only on the stable channel
on:
# channel to use to produce the release artifacts
CHANNEL: stable
appveyor_repo_tag: true
branches:
only:
- master
# IMPORTANT Regex to match tags. Required, or appveyor may not trigger deploys when a new tag
# is pushed. This regex matches semantic versions like v1.2.3-rc4+2016.02.22
- /^v\d+\.\d+\.\d+.*$/
# Disable caching, for now
#cache:
# - '%USERPROFILE%\.cargo'
# - 'target -> Cargo.lock'
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
#[macro_use]
extern crate clap;
extern crate version_check;
use clap::Shell;
use std::io::{self, Write};
use std::process::exit;
use std::fs;
include!("src/app.rs");
fn main() {
match version_check::is_min_version("1.20") {
// rustc >= 1.20
Some((true, _)) => {}
// rustc < 1.20 or can't figure it out
_ => {
writeln!(&mut io::stderr(), "This crate requires rustc >= 1.20").unwrap();
exit(1);
}
}
let var = std::env::var_os("SHELL_COMPLETIONS_DIR").or(std::env::var_os("OUT_DIR"));
let outdir = match var {
None => return,
Some(outdir) => outdir,
};
fs::create_dir_all(&outdir).unwrap();
let mut app = build_app();
app.gen_completions("fd", Shell::Bash, &outdir);
app.gen_completions("fd", Shell::Fish, &outdir);
app.gen_completions("fd", Shell::Zsh, &outdir);
app.gen_completions("fd", Shell::PowerShell, &outdir);
}
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
# Building and packaging for release
set -ex
build() {
cargo build --target "$TARGET" --release --verbose
}
pack() {
local tempdir
local out_dir
local package_name
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
out_dir=$(pwd)
package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET"
# create a "staging" directory
mkdir "$tempdir/$package_name"
mkdir "$tempdir/$package_name/autocomplete"
# copying the main binary
cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/"
strip "$tempdir/$package_name/$PROJECT_NAME"
# manpage, readme and license
cp "doc/$PROJECT_NAME.1" "$tempdir/$package_name"
cp README.md "$tempdir/$package_name"
cp LICENSE-MIT "$tempdir/$package_name"
cp LICENSE-APACHE "$tempdir/$package_name"
# various autocomplete
cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".bash "$tempdir/$package_name/autocomplete/${PROJECT_NAME}.bash-completion"
cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".fish "$tempdir/$package_name/autocomplete"
cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/_"$PROJECT_NAME" "$tempdir/$package_name/autocomplete"
# archiving
pushd "$tempdir"
tar czf "$out_dir/$package_name.tar.gz" "$package_name"/*
popd
rm -r "$tempdir"
}
make_deb() {
local tempdir
local architecture
local version
local dpkgname
local conflictname
case $TARGET in
x86_64*)
architecture=amd64
;;
i686*)
architecture=i386
;;
*)
echo "ERROR: unknown target" >&2
return 1
;;
esac
version=${TRAVIS_TAG#v}
if [[ $TARGET = *musl* ]]; then
dpkgname=$PROJECT_NAME-musl
conflictname=$PROJECT_NAME
else
dpkgname=$PROJECT_NAME
conflictname=$PROJECT_NAME-musl
fi
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
# copy the main binary
install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME"
strip "$tempdir/usr/bin/$PROJECT_NAME"
# manpage
install -Dm644 "doc/$PROJECT_NAME.1" "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
gzip --best "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
# readme and license
install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
install -Dm644 LICENSE-MIT "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-MIT"
install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE"
# completions
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.fish "$tempdir/usr/share/fish/completions/$PROJECT_NAME.fish"
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/_$PROJECT_NAME "$tempdir/usr/share/zsh/vendor-completions/_$PROJECT_NAME"
# Control file
mkdir "$tempdir/DEBIAN"
cat > "$tempdir/DEBIAN/control" <<EOF
Package: $dpkgname
Version: $version
Section: utils
Priority: optional
Maintainer: David Peter <mail@david-peter.de>
Architecture: $architecture
Provides: $PROJECT_NAME
Conflicts: $conflictname
Description: Simple, fast and user-friendly alternative to find
While fd does not seek to mirror all of find's powerful functionality, it
provides sensible (opinionated) defaults for 80% of the use cases.
EOF
fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
}
main() {
build
pack
if [[ $TARGET = *linux* ]]; then
make_deb
fi
}
main
+151
View File
@@ -0,0 +1,151 @@
.TH FD 1
.SH NAME
fd \- find entries in the filesystem
.SH SYNOPSIS
.B fd
.RB [ \-HIEsiaLp0hV ]
.RB [ \-d
.IR depth ]
.RB [ \-t
.IR filetype ]
.RB [ \-e
.IR ext ]
.RB [ \-E
.IR exclude ]
.RB [ \-c
.IR when ]
.RB [ \-j
.IR num ]
.RB [ \-x
.IR cmd ]
.RI [ pattern ]
.RI [ path... ]
.SH DESCRIPTION
.B fd
is a simple, fast and user-friendly alternative to
.BR find (1).
.SH OPTIONS
.TP
.B \-H, \-\-hidden
Include hidden files and directories in the search results.
.TP
.B \-I, \-\-no\-ignore
Do not respect files like
.I .gitignore
and
.I .ignore
and include ignored files in the search results.
.TP
.B \-\-no\-ignore\-vcs
Do not respect version control ignore files like
.I ".gitignore"
and include the respective entries in the search results.
.TP
.B \-s, \-\-case\-sensitive
Perform a case-sensitive search (default: smart case).
.TP
.B \-i, \-\-ignore\-case
Perform a case-insensitive search (default: smart case).
.TP
.B \-F, \-\-fixed\-strings
Treat the pattern as a literal string instead of a regular expression.
.TP
.B \-a, \-\-absolute\-path
Show absolute instead of relative paths.
.TP
.B \-L, \-\-follow
Dereference all symbolic links encountered.
.TP
.B \-p, \-\-full\-path
Match the
.I pattern
against the full path instead of just the file or directory name.
.TP
.B \-0, \-\-print0
Separate search results by null characters instead of newlines.
.TP
.B \-h, \-\-help
Print help information.
.TP
.B \-V, \-\-version
Print version information.
.TP
.BI "\-d, \-\-max\-depth " d
Limit directory traversal to at most
.I d
levels of depth. By default, there is no limit on the search depth.
.TP
.BI "\-t, \-\-type " filetype
Filter search by type:
.RS
.IP "f, file"
regular files
.IP "d, directories"
directories
.IP "s, symlink"
symbolic links
.RE
.RS
This option can be used repeatedly to allow for multiple file types.
.RE
.TP
.BI "\-e, \-\-extension " ext
Filter search results by file extension
.IR ext .
This option can be used repeatedly to allow for multiple possible file extensions.
.TP
.BI "\-E, \-\-exclude " pattern
Exclude files/directories that match the given glob pattern.
This overrides any other ignore logic.
Multiple exclude patterns can be specified.
.TP
.BI "\-c, \-\-color " when
Declare
.I when
to colorize search results:
.RS
.IP auto
Colorize output when standard output is connected to terminal (default).
.IP never
Do not colorize output.
.IP always
Always colorize output.
.RE
.TP
.BI "\-j, \-\-threads " num
Number of threads to use for searching (default: number of available CPUs).
.TP
.BI "\-x, \-\-exec " command "\fR [args...] ;"
Execute
.I command
for each search result. The following placeholders are substituted by a path derived from the current search result:
.RS
.IP {}
path
.IP {/}
basename
.IP {//}
parent directory
.IP {.}
path without file extension
.IP {/.}
basename without file extension
.RE
.SH ENVIRONMENT
.TP
.B LS_COLORS
Determines how to colorize search results, see
.BR dircolors (1) .
.SH EXAMPLES
.TP
.RI "Find files and directories that match the pattern '" needle "':"
$ fd needle
.TP
.RI "Start a search in a given directory (" /var/log "):"
$ fd nginx /var/log
.TP
.RI "Find all Python files (all files with the extention " .py ") in the current directory:"
$ fd -e py
.SH SEE ALSO
.BR find (1)
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# Designed to be executed via svg-term from the fd root directory:
# svg-term --command="bash doc/screencast.sh" --out doc/screencast.svg --padding=10
set -e
set -u
PROMPT="▶"
enter() {
INPUT=$1
DELAY=1
prompt
sleep "$DELAY"
type "$INPUT"
sleep 0.5
printf '%b' "\\n"
eval "$INPUT"
type "\\n"
}
prompt() {
printf '%b ' $PROMPT | pv -q
}
type() {
printf '%b' "$1" | pv -qL $((10+(-2 + RANDOM%5)))
}
main() {
IFS='%'
enter "fd"
enter "fd app"
enter "fd sh"
enter "fd sh --type f"
enter "fd -e md"
enter "fd -e md --exec wc -l"
enter "fd '^[A-Z]'"
enter "fd --exclude src"
enter "fd --hidden sample"
prompt
sleep 3
echo ""
unset IFS
}
main
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 115 KiB

+235
View File
@@ -0,0 +1,235 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::collections::HashMap;
use clap::{App, AppSettings, Arg};
struct Help {
short: &'static str,
long: &'static str,
}
macro_rules! doc {
($map:expr, $name:expr, $short:expr) => {
doc!($map, $name, $short, $short)
};
($map:expr, $name:expr, $short:expr, $long:expr) => {
$map.insert($name, Help {
short: $short,
long: concat!($long, "\n ")
});
};
}
pub fn build_app() -> App<'static, 'static> {
let helps = usage();
let arg = |name| {
Arg::with_name(name)
.help(helps[name].short)
.long_help(helps[name].long)
};
App::new("fd")
.version(crate_version!())
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
.setting(AppSettings::ColoredHelp)
.setting(AppSettings::DeriveDisplayOrder)
.arg(arg("hidden").long("hidden").short("H"))
.arg(arg("no-ignore").long("no-ignore").short("I"))
.arg(arg("no-ignore-vcs").long("no-ignore-vcs"))
.arg(
arg("rg-alias-hidden-ignore")
.short("u")
.multiple(true)
.hidden(true),
)
.arg(
arg("case-sensitive")
.long("case-sensitive")
.short("s")
.overrides_with("ignore-case"),
)
.arg(
arg("ignore-case")
.long("ignore-case")
.short("i")
.overrides_with("case-sensitive"),
)
.arg(
arg("fixed-strings")
.long("fixed-strings")
.short("F")
.alias("literal"),
)
.arg(arg("absolute-path").long("absolute-path").short("a"))
.arg(arg("follow").long("follow").short("L").alias("dereference"))
.arg(arg("full-path").long("full-path").short("p"))
.arg(arg("null_separator").long("print0").short("0"))
.arg(arg("depth").long("max-depth").short("d").takes_value(true))
.arg(
arg("file-type")
.long("type")
.short("t")
.multiple(true)
.number_of_values(1)
.takes_value(true)
.value_name("filetype")
.possible_values(&["f", "file", "d", "directory", "l", "symlink"])
.hide_possible_values(true),
)
.arg(
arg("extension")
.long("extension")
.short("e")
.multiple(true)
.number_of_values(1)
.takes_value(true)
.value_name("ext"),
)
.arg(
arg("exec")
.long("exec")
.short("x")
.min_values(1)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd"),
)
.arg(
arg("exclude")
.long("exclude")
.short("E")
.takes_value(true)
.value_name("pattern")
.number_of_values(1)
.multiple(true),
)
.arg(
arg("color")
.long("color")
.short("c")
.takes_value(true)
.value_name("when")
.possible_values(&["never", "auto", "always"])
.hide_possible_values(true),
)
.arg(
arg("threads")
.long("threads")
.short("j")
.takes_value(true)
.value_name("num"),
)
.arg(
arg("max-buffer-time")
.long("max-buffer-time")
.takes_value(true)
.hidden(true),
)
.arg(arg("pattern"))
.arg(arg("path").multiple(true))
}
#[cfg_attr(rustfmt, rustfmt_skip)]
fn usage() -> HashMap<&'static str, Help> {
let mut h = HashMap::new();
doc!(h, "hidden"
, "Search hidden files and directories"
, "Include hidden directories and files in the search results (default: hidden files \
and directories are skipped).");
doc!(h, "no-ignore"
, "Do not respect .(git)ignore files"
, "Show search results from files and directories that would otherwise be ignored by \
'.*ignore' files.");
doc!(h, "no-ignore-vcs"
, "Do not respect .gitignore files"
, "Show search results from files and directories that would otherwise be ignored by \
'.gitignore' files.");
doc!(h, "case-sensitive"
, "Case-sensitive search (default: smart case)"
, "Perform a case-sensitive search. By default, fd uses case-insensitive searches, \
unless the pattern contains an uppercase character (smart case).");
doc!(h, "ignore-case"
, "Case-insensitive search (default: smart case)"
, "Perform a case-insensitive search. By default, fd uses case-insensitive searches, \
unless the pattern contains an uppercase character (smart case).");
doc!(h, "fixed-strings"
, "Treat the pattern as a literal string"
, "Treat the pattern as a literal string instead of a regular expression.");
doc!(h, "absolute-path"
, "Show absolute instead of relative paths"
, "Shows the full path starting from the root as opposed to relative paths.");
doc!(h, "follow"
, "Follow symbolic links"
, "By default, fd does not descend into symlinked directories. Using this flag, symbolic \
links are also traversed.");
doc!(h, "full-path"
, "Search full path (default: file-/dirname only)"
, "By default, the search pattern is only matched against the filename (or directory \
name). Using this flag, the pattern is matched against the full path.");
doc!(h, "null_separator"
, "Separate results by the null character"
, "Separate search results by the null character (instead of newlines). Useful for \
piping results to 'xargs'.");
doc!(h, "depth"
, "Set maximum search depth (default: none)"
, "Limit the directory traversal to a given depth. By default, there is no limit \
on the search depth.");
doc!(h, "file-type"
, "Filter by type: f(ile), d(irectory), (sym)l(ink)"
, "Filter the search by type (multiple allowable filetypes can be specified):\n \
'f' or 'file': regular files\n \
'd' or 'directory': directories\n \
'l' or 'symlink': symbolic links");
doc!(h, "extension"
, "Filter by file extension"
, "(Additionally) filter search results by their file extension. Multiple allowable file \
extensions can be specified.");
doc!(h, "exec"
, "Execute a command for each search result"
, "Execute a command for each search result.\n\
All arguments following --exec are taken to be arguments to the command until the \
argument ';' is encountered.\n\
Each occurrence of the following placeholders is substituted by a path derived from the \
current search result before the command is executed:\n \
'{}': path\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension");
doc!(h, "exclude"
, "Exclude entries that match the given glob pattern"
, "Exclude files/directories that match the given glob pattern. This overrides any \
other ignore logic. Multiple exclude patterns can be specified.");
doc!(h, "color"
, "When to use colors: never, *auto*, always"
, "Declare when to use color for the pattern match output:\n \
'auto': show colors if the output goes to an interactive console (default)\n \
'never': do not use colorized output\n \
'always': always use colorized output");
doc!(h, "threads"
, "Set number of threads to use for searching & executing"
, "Set number of threads to use for searching & executing (default: number of available \
CPU cores)");
doc!(h, "max-buffer-time"
, "the time (in ms) to buffer, before streaming to the console"
, "Amount of time in milliseconds to buffer, before streaming the search results to\
the console.");
doc!(h, "pattern"
, "the search pattern, a regular expression (optional)");
doc!(h, "path"
, "the root directory for the filesystem search (optional)"
, "The directory where the filesystem search is rooted (optional). \
If omitted, search the current working directory.");
doc!(h, "rg-alias-hidden-ignore"
, "Alias for no-ignore and/or hidden"
, "Alias for no-ignore ('u') and no-ignore and hidden ('uu')");
h
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::io;
use std::io::Write;
/// Executes a command.
pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
// Spawn the supplied command.
let output = cmd.output();
// Then wait for the command to exit, if it was spawned.
match output {
Ok(output) => {
// While this lock is active, this thread will be the only thread allowed
// to write its outputs.
let _lock = out_perm.lock().unwrap();
let stdout = io::stdout();
let stderr = io::stderr();
let _ = stdout.lock().write_all(&output.stdout);
let _ = stderr.lock().write_all(&output.stderr);
}
Err(why) => {
if why.kind() == io::ErrorKind::NotFound {
eprintln!("fd: execution error: command not found");
} else {
eprintln!("fd: execution error: {}", why);
}
}
}
}
+165
View File
@@ -0,0 +1,165 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::path::MAIN_SEPARATOR;
/// Removes the parent component of the path
pub fn basename(path: &str) -> &str {
let mut index = 0;
for (id, character) in path.char_indices() {
if character == MAIN_SEPARATOR {
index = id;
}
}
// FIXME: On Windows, should return what for C:file.txt D:file.txt and \\server\share ?
if index != 0 {
return &path[index + 1..];
}
path
}
/// Removes the extension from the path
pub fn remove_extension(path: &str) -> &str {
let mut has_dir = false;
let mut dir_index = 0;
let mut ext_index = 0;
for (id, character) in path.char_indices() {
if character == MAIN_SEPARATOR {
has_dir = true;
dir_index = id;
}
if character == '.' {
ext_index = id;
}
}
// Account for hidden files and directories
if ext_index != 0 && (!has_dir || dir_index + 2 <= ext_index) {
return &path[0..ext_index];
}
path
}
/// Removes the basename from the path.
pub fn dirname(path: &str) -> &str {
let mut has_dir = false;
let mut index = 0;
for (id, character) in path.char_indices() {
if character == MAIN_SEPARATOR {
has_dir = true;
index = id;
}
}
// FIXME: On Windows, return what for C:file.txt D:file.txt and \\server\share ?
if !has_dir {
"."
} else if index == 0 {
&path[..1]
} else {
&path[0..index]
}
}
#[cfg(test)]
mod tests {
use super::{basename, dirname, remove_extension, MAIN_SEPARATOR};
fn correct(input: &str) -> String {
input.replace('/', &MAIN_SEPARATOR.to_string())
}
#[test]
fn path_remove_ext_simple() {
assert_eq!(remove_extension("foo.txt"), "foo");
}
#[test]
fn path_remove_ext_dir() {
assert_eq!(
remove_extension(&correct("dir/foo.txt")),
correct("dir/foo")
);
}
#[test]
fn path_hidden() {
assert_eq!(remove_extension(".foo"), ".foo")
}
#[test]
fn path_remove_ext_utf8() {
assert_eq!(remove_extension("💖.txt"), "💖");
}
#[test]
fn path_remove_ext_empty() {
assert_eq!(remove_extension(""), "");
}
#[test]
fn path_basename_simple() {
assert_eq!(basename("foo.txt"), "foo.txt");
}
#[test]
fn path_basename_no_ext() {
assert_eq!(remove_extension(basename("foo.txt")), "foo");
}
#[test]
fn path_basename_dir() {
assert_eq!(basename(&correct("dir/foo.txt")), "foo.txt");
}
#[test]
fn path_basename_empty() {
assert_eq!(basename(""), "");
}
#[test]
fn path_basename_utf8() {
assert_eq!(basename(&correct("💖/foo.txt")), "foo.txt");
assert_eq!(basename(&correct("dir/💖.txt")), "💖.txt");
}
#[test]
fn path_dirname_simple() {
assert_eq!(dirname("foo.txt"), ".");
}
#[test]
fn path_dirname_dir() {
assert_eq!(dirname(&correct("dir/foo.txt")), "dir");
}
#[test]
fn path_dirname_utf8() {
assert_eq!(dirname(&correct("💖/foo.txt")), "💖");
assert_eq!(dirname(&correct("dir/💖.txt")), "dir");
}
#[test]
fn path_dirname_empty() {
assert_eq!(dirname(""), ".");
}
#[test]
fn path_dirname_root() {
#[cfg(windows)]
assert_eq!(dirname("C:\\"), "C:");
#[cfg(windows)]
assert_eq!(dirname("\\"), "\\");
#[cfg(not(windows))]
assert_eq!(dirname("/"), "/");
}
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Receiver;
use super::CommandTemplate;
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
/// generate a command with the supplied command template. The generated command will then
/// be executed, and this process will continue until the receiver's sender has closed.
pub fn job(rx: Arc<Mutex<Receiver<PathBuf>>>, cmd: Arc<CommandTemplate>, out_perm: Arc<Mutex<()>>) {
loop {
// Create a lock on the shared receiver for this thread.
let lock = rx.lock().unwrap();
// Obtain the next path from the receiver, else if the channel
// has closed, exit from the loop
let value: PathBuf = match lock.recv() {
Ok(value) => value,
Err(_) => break,
};
// Drop the lock so that other threads can read from the the receiver.
drop(lock);
// Generate a command and execute it.
cmd.generate_and_execute(&value, Arc::clone(&out_perm));
}
}
+177
View File
@@ -0,0 +1,177 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
// TODO: Possible optimization could avoid pushing characters on a buffer.
mod command;
mod token;
mod job;
mod input;
use std::borrow::Cow;
use std::path::Path;
use std::process::Command;
use std::sync::{Arc, Mutex};
use regex::Regex;
use self::input::{basename, dirname, remove_extension};
use self::command::execute_command;
use self::token::Token;
pub use self::job::job;
/// Represents a template that is utilized to generate command strings.
///
/// The template is meant to be coupled with an input in order to generate a command. The
/// `generate_and_execute()` method will be used to generate a command and execute it.
#[derive(Debug, Clone, PartialEq)]
pub struct CommandTemplate {
args: Vec<ArgumentTemplate>,
}
impl CommandTemplate {
pub fn new<I, S>(input: I) -> CommandTemplate
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
lazy_static! {
static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
}
let mut args = Vec::new();
let mut has_placeholder = false;
for arg in input {
let arg = arg.as_ref();
let mut tokens = Vec::new();
let mut start = 0;
for placeholder in PLACEHOLDER_PATTERN.find_iter(arg) {
// Leading text before the placeholder.
if placeholder.start() > start {
tokens.push(Token::Text(arg[start..placeholder.start()].to_owned()));
}
start = placeholder.end();
match placeholder.as_str() {
"{}" => tokens.push(Token::Placeholder),
"{.}" => tokens.push(Token::NoExt),
"{/}" => tokens.push(Token::Basename),
"{//}" => tokens.push(Token::Parent),
"{/.}" => tokens.push(Token::BasenameNoExt),
_ => panic!("Unhandled placeholder"),
}
has_placeholder = true;
}
// Without a placeholder, the argument is just fixed text.
if tokens.is_empty() {
args.push(ArgumentTemplate::Text(arg.to_owned()));
continue;
}
if start < arg.len() {
// Trailing text after last placeholder.
tokens.push(Token::Text(arg[start..].to_owned()));
}
args.push(ArgumentTemplate::Tokens(tokens));
}
// If a placeholder token was not supplied, append one at the end of the command.
if !has_placeholder {
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
}
CommandTemplate { args: args }
}
/// Generates and executes a command.
///
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
/// build. Once all arguments have been processed, the command is executed.
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) {
let input = input
.strip_prefix(".")
.unwrap_or(input)
.to_string_lossy()
.into_owned();
let mut cmd = Command::new(self.args[0].generate(&input).as_ref());
for arg in &self.args[1..] {
cmd.arg(arg.generate(&input).as_ref());
}
execute_command(cmd, out_perm)
}
}
/// Represents a template for a single command argument.
///
/// The argument is either a collection of `Token`s including at least one placeholder variant, or
/// a fixed text.
#[derive(Clone, Debug, PartialEq)]
enum ArgumentTemplate {
Tokens(Vec<Token>),
Text(String),
}
impl ArgumentTemplate {
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
use self::Token::*;
match *self {
ArgumentTemplate::Tokens(ref tokens) => {
let mut s = String::new();
for token in tokens {
match *token {
Basename => s += basename(path),
BasenameNoExt => s += remove_extension(basename(path)),
NoExt => s += remove_extension(path),
Parent => s += dirname(path),
Placeholder => s += path,
Text(ref string) => s += string,
}
}
Cow::Owned(s)
}
ArgumentTemplate::Text(ref text) => Cow::Borrowed(text),
}
}
}
#[cfg(test)]
mod tests {
use super::{ArgumentTemplate, CommandTemplate, Token};
#[test]
fn tokens() {
let expected = CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Text("${SHELL}:".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
],
};
assert_eq!(CommandTemplate::new(&[&"echo", &"${SHELL}:"]), expected);
assert_eq!(
CommandTemplate::new(&["echo", "{.}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
}
);
}
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::fmt::{self, Display, Formatter};
/// Designates what should be written to a buffer
///
/// Each `Token` contains either text, or a placeholder variant, which will be used to generate
/// commands after all tokens for a given command template have been collected.
#[derive(Clone, Debug, PartialEq)]
pub enum Token {
Placeholder,
Basename,
Parent,
NoExt,
BasenameNoExt,
Text(String),
}
impl Display for Token {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
Token::Placeholder => f.write_str("{}")?,
Token::Basename => f.write_str("{/}")?,
Token::Parent => f.write_str("{//}")?,
Token::NoExt => f.write_str("{.}")?,
Token::BasenameNoExt => f.write_str("{/.}")?,
Token::Text(ref string) => f.write_str(string)?,
}
Ok(())
}
}
+4
View File
@@ -0,0 +1,4 @@
/// exit code 1 represents a general error
pub const ERROR: i32 = 1;
/// exit code 130 represents a process killed by signal SIGINT
pub const SIGINT: i32 = 130;
+39 -38
View File
@@ -1,43 +1,44 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::env::current_dir;
use std::path::{Path, PathBuf};
use std::io;
/// Get a relative path with respect to a certain base path.
/// See: https://stackoverflow.com/a/39343127/704831
pub fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
use std::path::Component;
if path.is_absolute() != base.is_absolute() {
if path.is_absolute() {
Some(PathBuf::from(path))
} else {
None
}
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
if path.is_absolute() {
Ok(path.to_path_buf())
} else {
let mut ita = path.components();
let mut itb = base.components();
let mut comps: Vec<Component> = vec![];
loop {
match (ita.next(), itb.next()) {
(None, None) => break,
(Some(a), None) => {
comps.push(a);
comps.extend(ita.by_ref());
break;
}
(None, _) => comps.push(Component::ParentDir),
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
(Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
(Some(_), Some(b)) if b == Component::ParentDir => return None,
(Some(a), Some(_)) => {
comps.push(Component::ParentDir);
for _ in itb {
comps.push(Component::ParentDir);
}
comps.push(a);
comps.extend(ita.by_ref());
break;
}
}
}
Some(comps.iter().map(|c| c.as_os_str()).collect())
let path = path.strip_prefix(".").unwrap_or(path);
current_dir().map(|path_buf| path_buf.join(path))
}
}
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
let path_buf = path_absolute_form(path)?;
#[cfg(windows)]
let path_buf = Path::new(
path_buf
.as_path()
.to_string_lossy()
.trim_left_matches(r"\\?\"),
).to_path_buf();
Ok(path_buf)
}
// Path::is_dir() is not guarandteed to be intuitively correct for "." and ".."
// See: https://github.com/rust-lang/rust/issues/45302
pub fn is_dir(path: &Path) -> bool {
if path.file_name().is_some() {
path.is_dir()
} else {
path.is_dir() && path.canonicalize().is_ok()
}
}
+229
View File
@@ -0,0 +1,229 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::collections::HashSet;
use std::ffi::OsString;
use std::process;
use std::time;
use std::io::Write;
use exec::CommandTemplate;
use lscolors::LsColors;
use walk::FileType;
use regex_syntax::{Expr, ExprBuilder};
use regex::RegexSet;
/// Configuration options for *fd*.
pub struct FdOptions {
/// Whether the search is case-sensitive or case-insensitive.
pub case_sensitive: bool,
/// Whether to search within the full file path or just the base name (filename or directory
/// name).
pub search_full_path: bool,
/// Whether to ignore hidden files and directories (or not).
pub ignore_hidden: bool,
/// Whether to respect ignore files (`.gitignore`, `.ignore`, ..) or not.
pub read_ignore: bool,
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
pub read_gitignore: bool,
/// Whether to follow symlinks or not.
pub follow_links: bool,
/// Whether elements of output should be separated by a null character
pub null_separator: bool,
/// The maximum search depth, or `None` if no maximum search depth should be set.
///
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
/// all files under subdirectories of the current directory, etc.
pub max_depth: Option<usize>,
/// The number of threads to use.
pub threads: usize,
/// Time to buffer results internally before streaming to the console. This is useful to
/// provide a sorted output, in case the total execution time is shorter than
/// `max_buffer_time`.
pub max_buffer_time: Option<time::Duration>,
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
/// how to style different filetypes.
pub ls_colors: Option<LsColors>,
/// The type of file to search for. All files other than the specified type will be ignored.
pub file_types: HashSet<FileType>,
/// The extension to search for. Only entries matching the extension will be included.
///
/// The value (if present) will be a lowercase string without leading dots.
pub extensions: Option<RegexSet>,
/// If a value is supplied, each item found will be used to generate and execute commands.
pub command: Option<CommandTemplate>,
/// A list of glob patterns that should be excluded from the search.
pub exclude_patterns: Vec<String>,
}
/// Print error message to stderr and exit with status `1`.
pub fn error(message: &str) -> ! {
writeln!(&mut ::std::io::stderr(), "{}", message).expect("Failed writing to stderr");
process::exit(1);
}
/// Determine if a regex pattern contains a literal uppercase character.
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
ExprBuilder::new()
.parse(pattern)
.map(|expr| expr_has_uppercase_char(&expr))
.unwrap_or(false)
}
/// Determine if a regex expression contains a literal uppercase character.
fn expr_has_uppercase_char(expr: &Expr) -> bool {
match *expr {
Expr::Literal { ref chars, .. } => chars.iter().any(|c| c.is_uppercase()),
Expr::Class(ref ranges) => ranges
.iter()
.any(|r| r.start.is_uppercase() || r.end.is_uppercase()),
Expr::Group { ref e, .. } | Expr::Repeat { ref e, .. } => expr_has_uppercase_char(e),
Expr::Concat(ref es) => es.iter().any(expr_has_uppercase_char),
Expr::Alternate(ref es) => es.iter().any(expr_has_uppercase_char),
_ => false,
}
}
/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;
/// Exit code representing a general error
pub const EXITCODE_ERROR: i32 = 1;
/// Exit code representing that the process was killed by SIGINT
pub const EXITCODE_SIGINT: i32 = 130;
/// Traverse args_os, looking for -exec and replacing it with --exec.
///
/// # Returns
///
/// * The args, with substitution if required
pub fn transform_args_with_exec<I>(original: I) -> Vec<OsString>
where
I: Iterator<Item = OsString>,
{
let mut in_exec_opt = false;
let target = OsString::from("-exec");
let long_start = OsString::from("--exec");
let short_start = OsString::from("-x");
let exec_end = OsString::from(";");
original.fold(vec![], |mut args, curr| {
if in_exec_opt {
if curr == exec_end {
in_exec_opt = false;
}
args.push(curr);
return args;
}
if curr == target || curr == long_start || curr == short_start {
args.push(if curr == target {
OsString::from("--exec")
} else {
curr
});
in_exec_opt = true;
} else {
args.push(curr);
}
args
})
}
#[cfg(test)]
fn oss(v: &str) -> OsString {
OsString::from(v)
}
/// Ensure that -exec gets transformed into --exec
#[test]
fn normal_exec_substitution() {
let original = vec![oss("fd"), oss("foo"), oss("-exec"), oss("cmd")];
let expected = vec![oss("fd"), oss("foo"), oss("--exec"), oss("cmd")];
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
/// Ensure that --exec is not touched
#[test]
fn passthru_of_original_exec() {
let original = vec![oss("fd"), oss("foo"), oss("--exec"), oss("cmd")];
let expected = vec![oss("fd"), oss("foo"), oss("--exec"), oss("cmd")];
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
#[test]
fn temp_check_that_exec_context_observed() {
let original = vec![
oss("fd"),
oss("foo"),
oss("-exec"),
oss("cmd"),
oss("-exec"),
oss("ls"),
oss(";"),
oss("-exec"),
oss("rm"),
oss(";"),
oss("--exec"),
oss("find"),
oss("-exec"),
oss("rm"),
oss(";"),
oss("-x"),
oss("foo"),
oss("-exec"),
oss("something"),
oss(";"),
oss("-exec"),
];
let expected = vec![
oss("fd"),
oss("foo"),
oss("--exec"),
oss("cmd"),
oss("-exec"),
oss("ls"),
oss(";"),
oss("--exec"),
oss("rm"),
oss(";"),
oss("--exec"),
oss("find"),
oss("-exec"),
oss("rm"),
oss(";"),
oss("-x"),
oss("foo"),
oss("-exec"),
oss("something"),
oss(";"),
oss("--exec"),
];
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
+75 -62
View File
@@ -1,7 +1,15 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
/// A parser for the `LS_COLORS` environment variable.
use std::collections::HashMap;
use ansi_term::{Style, Colour};
use ansi_term::{Colour, Style};
/// Maps file extensions to ANSI colors / styles.
pub type ExtensionStyles = HashMap<String, Style>;
@@ -9,11 +17,11 @@ pub type ExtensionStyles = HashMap<String, Style>;
/// Maps filenames to ANSI colors / styles.
pub type FilenameStyles = HashMap<String, Style>;
const LS_CODES: &'static [&'static str] =
&["no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
"so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec",
"ec", "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh",
"cl"];
const LS_CODES: &[&str] = &[
"no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi", "so", "bd", "bd", "cd",
"cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec", "su", "su", "sg", "sg", "st", "ow", "ow",
"tw", "tw", "ca", "mh", "cl",
];
/// Defines how different file system entries should be colorized / styled.
#[derive(Debug, PartialEq)]
@@ -42,7 +50,7 @@ impl Default for LsColors {
symlink: Colour::Cyan.normal(),
executable: Colour::Red.bold(),
extensions: HashMap::new(),
filenames: HashMap::new()
filenames: HashMap::new(),
}
}
}
@@ -55,7 +63,7 @@ impl LsColors {
"1" | "01" => Some(Colour::bold),
"3" | "03" => Some(Colour::italic),
"4" | "04" => Some(Colour::underline),
_ => None
_ => None,
}
}
@@ -67,40 +75,41 @@ impl LsColors {
// Try to match the first part as a text-decoration argument
let mut decoration = LsColors::parse_decoration(first);
let c1 = if decoration.is_none() { Some(first) } else { split.next() };
let c1 = if decoration.is_none() {
Some(first)
} else {
split.next()
};
let c2 = split.next();
let c3 = split.next();
let color =
if c1 == Some("38") && c2 == Some("5") {
let n_white = 7;
let n = if let Some(num) = c3 {
u8::from_str_radix(num, 10).unwrap_or(n_white)
} else {
n_white
};
Colour::Fixed(n)
} else if let Some(color_s) = c1 {
match color_s {
"30" => Colour::Black,
"31" => Colour::Red,
"32" => Colour::Green,
"33" => Colour::Yellow,
"34" => Colour::Blue,
"35" => Colour::Purple,
"36" => Colour::Cyan,
_ => Colour::White
}
let color = if c1 == Some("38") && c2 == Some("5") {
let n_white = 7;
let n = if let Some(num) = c3 {
u8::from_str_radix(num, 10).unwrap_or(n_white)
} else {
Colour::White
n_white
};
Colour::Fixed(n)
} else if let Some(color_s) = c1 {
match color_s {
"30" => Colour::Black,
"31" => Colour::Red,
"32" => Colour::Green,
"33" => Colour::Yellow,
"34" => Colour::Blue,
"35" => Colour::Purple,
"36" => Colour::Cyan,
_ => Colour::White,
}
} else {
Colour::White
};
if decoration.is_none() {
// Try to find a decoration somewhere in the sequence
decoration = code.split(';')
.flat_map(LsColors::parse_decoration)
.next();
decoration = code.split(';').flat_map(LsColors::parse_decoration).next();
}
let ansi_style = decoration.unwrap_or(Colour::normal)(color);
@@ -130,13 +139,12 @@ impl LsColors {
"di" => self.directory = style,
"ln" => self.symlink = style,
"ex" => self.executable = style,
_ => return
_ => return,
}
} else if pattern.starts_with("*.") {
let extension = String::from(pattern).split_off(2);
self.extensions.insert(extension, style);
}
else if pattern.starts_with('*') {
} else if pattern.starts_with('*') {
let filename = String::from(pattern).split_off(1);
self.filenames.insert(filename, style);
} else {
@@ -162,58 +170,63 @@ impl LsColors {
#[test]
fn test_parse_simple() {
assert_eq!(Some(Colour::Red.normal()),
LsColors::parse_style("31"));
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31"));
}
#[test]
fn test_parse_decoration() {
assert_eq!(Some(Colour::Red.normal()),
LsColors::parse_style("00;31"));
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("00;31"));
assert_eq!(Some(Colour::Blue.italic()),
LsColors::parse_style("03;34"));
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("03;34"));
assert_eq!(Some(Colour::Cyan.bold()),
LsColors::parse_style("01;36"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("01;36"));
}
#[test]
fn test_parse_decoration_backwards() {
assert_eq!(Some(Colour::Blue.italic()),
LsColors::parse_style("34;03"));
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("34;03"));
assert_eq!(Some(Colour::Cyan.bold()),
LsColors::parse_style("36;01"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("36;01"));
assert_eq!(Some(Colour::Red.normal()),
LsColors::parse_style("31;00"));
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31;00"));
}
#[test]
fn test_parse_256() {
assert_eq!(Some(Colour::Fixed(115).normal()),
LsColors::parse_style("38;5;115"));
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("38;5;115")
);
assert_eq!(Some(Colour::Fixed(115).normal()),
LsColors::parse_style("00;38;5;115"));
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("00;38;5;115")
);
assert_eq!(Some(Colour::Fixed(119).bold()),
LsColors::parse_style("01;38;5;119"));
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("01;38;5;119")
);
assert_eq!(Some(Colour::Fixed(119).bold()),
LsColors::parse_style("38;5;119;01"));
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("38;5;119;01")
);
}
#[test]
fn test_from_string() {
assert_eq!(LsColors::default(), LsColors::from_string(&String::new()));
let result = LsColors::from_string(
&String::from("rs=0:di=03;34:ln=01;36:*.foo=01;35:*README=33"));
let result = LsColors::from_string(&String::from(
"rs=0:di=03;34:ln=01;36:*.foo=01;35:*README=33",
));
assert_eq!(Colour::Blue.italic(), result.directory);
assert_eq!(Colour::Cyan.bold(), result.symlink);
assert_eq!(Some(&Colour::Purple.bold()), result.extensions.get("foo"));
assert_eq!(Some(&Colour::Yellow.normal()), result.filenames.get("README"));
assert_eq!(
Some(&Colour::Yellow.normal()),
result.filenames.get("README")
);
}
+161 -266
View File
@@ -1,292 +1,187 @@
extern crate ansi_term;
extern crate getopts;
extern crate isatty;
extern crate regex;
extern crate ignore;
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
extern crate ansi_term;
extern crate atty;
#[macro_use]
extern crate clap;
extern crate ignore;
#[macro_use]
extern crate lazy_static;
#[cfg(all(unix, not(target_os = "redox")))]
extern crate libc;
extern crate num_cpus;
extern crate regex;
extern crate regex_syntax;
pub mod lscolors;
pub mod fshelper;
pub mod lscolors;
mod app;
mod exec;
mod internal;
mod output;
mod walk;
#[cfg(windows)]
mod windows;
use std::env;
use std::error::Error;
use std::ffi::OsStr;
use std::fs;
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, Component};
use std::process;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time;
use getopts::Options;
use isatty::stdout_isatty;
use regex::{Regex, RegexBuilder};
use ignore::WalkBuilder;
use atty::Stream;
use regex::{RegexBuilder, RegexSetBuilder};
use exec::CommandTemplate;
use internal::{error, pattern_has_uppercase_char, transform_args_with_exec, FdOptions};
use lscolors::LsColors;
/// Defines how to display search result paths.
#[derive(PartialEq)]
enum PathDisplay {
/// As an absolute path
Absolute,
/// As a relative path
Relative
}
/// Configuration options for *fd*.
struct FdOptions {
/// Determines whether the regex search is case-sensitive or case-insensitive.
case_sensitive: bool,
/// Whether to search within the full file path or just the base name (filename or directory
/// name).
search_full_path: bool,
/// Whether to ignore hidden files and directories (or not).
ignore_hidden: bool,
/// Whether to respect VCS ignore files (`.gitignore`, `.ignore`, ..) or not.
read_ignore: bool,
/// Whether to follow symlinks or not.
follow_links: bool,
/// The maximum search depth, or `None` if no maximum search depth should be set.
///
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
/// all files under subdirectories of the current directory, etc.
max_depth: Option<usize>,
/// Display results as relative or absolute path.
path_display: PathDisplay,
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
/// how to style different filetypes.
ls_colors: Option<LsColors>
}
/// Root directory
static ROOT_DIR : &str = "/";
/// Print a search result to the console.
fn print_entry(base: &Path, entry: &Path, config: &FdOptions) {
let path_full = base.join(entry);
let path_str = match entry.to_str() {
Some(p) => p,
None => return
};
let is_executable = |p: &std::path::PathBuf| {
p.metadata()
.ok()
.map(|f| f.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
};
if let Some(ref ls_colors) = config.ls_colors {
let mut component_path = base.to_path_buf();
if config.path_display == PathDisplay::Absolute {
print!("{}", ls_colors.directory.paint(ROOT_DIR));
}
// Traverse the path and colorize each component
for component in entry.components() {
let comp_str = match component {
Component::Normal(p) => p.to_str().unwrap(),
Component::ParentDir => "..",
_ => error("Unexpected path component")
};
component_path.push(Path::new(comp_str));
let style =
if component_path.symlink_metadata()
.map(|md| md.file_type().is_symlink())
.unwrap_or(false) {
ls_colors.symlink
} else if component_path.is_dir() {
ls_colors.directory
} else if is_executable(&component_path) {
ls_colors.executable
} else {
// Look up file name
let o_style =
component_path.file_name()
.and_then(|n| n.to_str())
.and_then(|n| ls_colors.filenames.get(n));
match o_style {
Some(s) => *s,
None =>
// Look up file extension
component_path.extension()
.and_then(|e| e.to_str())
.and_then(|e| ls_colors.extensions.get(e))
.cloned()
.unwrap_or_default()
}
};
print!("{}", style.paint(comp_str));
if component_path.is_dir() && component_path != path_full {
let sep = std::path::MAIN_SEPARATOR.to_string();
print!("{}", style.paint(sep));
}
}
println!();
} else {
// Uncolorized output:
if config.path_display == PathDisplay::Absolute {
print!("{}", ROOT_DIR);
}
println!("{}", path_str);
}
}
/// Recursively scan the given search path and search for files / pathnames matching the pattern.
fn scan(root: &Path, pattern: &Regex, base: &Path, config: &FdOptions) {
let walker = WalkBuilder::new(root)
.hidden(config.ignore_hidden)
.ignore(config.read_ignore)
.git_ignore(config.read_ignore)
.parents(config.read_ignore)
.git_global(config.read_ignore)
.git_exclude(config.read_ignore)
.follow_links(config.follow_links)
.max_depth(config.max_depth)
.build()
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path() != root);
for entry in walker {
let path_rel_buf = match fshelper::path_relative_from(entry.path(), base) {
Some(p) => p,
None => error("Could not get relative path for directory entry.")
};
let path_rel = path_rel_buf.as_path();
let search_str =
if config.search_full_path {
path_rel.to_str()
} else {
path_rel.file_name()
.and_then(OsStr::to_str)
};
search_str.and_then(|s| pattern.find(s))
.map(|_| print_entry(base, path_rel, config));
}
}
/// Print error message to stderr and exit with status `1`.
fn error(message: &str) -> ! {
writeln!(&mut std::io::stderr(), "{}", message)
.expect("Failed writing to stderr");
process::exit(1);
}
use walk::FileType;
fn main() {
let args: Vec<String> = env::args().collect();
let mut opts = Options::new();
opts.optflag("h", "help",
"print this help message");
opts.optflag("s", "sensitive",
"case-sensitive search (default: smart case)");
opts.optflag("p", "full-path",
"search full path (default: file-/dirname only)");
opts.optflag("H", "hidden",
"search hidden files/directories");
opts.optflag("I", "no-ignore",
"do not respect .(git)ignore files");
opts.optflag("f", "follow",
"follow symlinks");
opts.optflag("n", "no-color",
"do not colorize output");
opts.optopt("d", "max-depth",
"maximum search depth (default: none)", "D");
opts.optflag("a", "absolute",
"show absolute instead of relative paths");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(e) => error(e.description())
};
if matches.opt_present("h") {
let brief = "Usage: fd [options] [PATTERN] [PATH]";
print!("{}", opts.usage(brief));
process::exit(1);
}
let checked_args = transform_args_with_exec(env::args_os());
let matches = app::build_app().get_matches_from(checked_args);
// Get the search pattern
let empty_pattern = String::new();
let pattern = matches.free.get(0).unwrap_or(&empty_pattern);
let pattern = matches.value_of("pattern").unwrap_or("");
// Get the current working directory
let current_dir_buf = match env::current_dir() {
Ok(cd) => cd,
Err(_) => error("Could not get current directory!")
};
let current_dir = current_dir_buf.as_path();
let current_dir = Path::new(".");
if !fshelper::is_dir(current_dir) {
error("Error: could not get current directory.");
}
// Get the root directory for the search
let root_dir_buf = matches.free.get(1)
.and_then(|r| fs::canonicalize(r).ok())
.unwrap_or_else(|| current_dir_buf.clone());
let root_dir = root_dir_buf.as_path();
//Get one or more root directories to search.
let mut dir_vec: Vec<_> = match matches.values_of("path") {
Some(paths) => paths
.map(|path| {
let path_buffer = PathBuf::from(path);
if !fshelper::is_dir(&path_buffer) {
error(&format!(
"Error: '{}' is not a directory.",
path_buffer.to_string_lossy()
));
}
path_buffer
})
.collect::<Vec<_>>(),
None => vec![current_dir.to_path_buf()],
};
if matches.is_present("absolute-path") {
dir_vec = dir_vec
.iter()
.map(|path_buffer| fshelper::absolute_path(path_buffer).unwrap())
.collect();
}
// Treat pattern as literal string if '--fixed-strings' is used
let pattern_regex = if matches.is_present("fixed-strings") {
regex::escape(pattern)
} else {
String::from(pattern)
};
// The search will be case-sensitive if the command line flag is set or
// if the pattern has an uppercase character (smart case).
let case_sensitive = matches.opt_present("sensitive") ||
pattern.chars().any(char::is_uppercase);
let case_sensitive = !matches.is_present("ignore-case")
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
let colored_output = !matches.opt_present("no-color") &&
stdout_isatty();
let colored_output = match matches.value_of("color") {
Some("always") => true,
Some("never") => false,
_ => atty::is(Stream::Stdout),
};
let ls_colors =
if colored_output {
Some(
env::var("LS_COLORS")
.ok()
.map(|val| LsColors::from_string(&val))
.unwrap_or_default()
)
} else {
None
};
#[cfg(windows)]
let colored_output = colored_output && windows::enable_colored_output();
let ls_colors = if colored_output {
Some(
env::var("LS_COLORS")
.ok()
.map(|val| LsColors::from_string(&val))
.unwrap_or_default(),
)
} else {
None
};
let command = matches.values_of("exec").map(CommandTemplate::new);
let config = FdOptions {
case_sensitive: case_sensitive,
search_full_path: matches.opt_present("full-path"),
ignore_hidden: !matches.opt_present("hidden"),
read_ignore: !matches.opt_present("no-ignore"),
follow_links: matches.opt_present("follow"),
max_depth: matches.opt_str("max-depth")
.and_then(|ds| usize::from_str_radix(&ds, 10).ok()),
path_display: if matches.opt_present("absolute") {
PathDisplay::Absolute
} else {
PathDisplay::Relative
},
ls_colors: ls_colors
case_sensitive,
search_full_path: matches.is_present("full-path"),
ignore_hidden: !(matches.is_present("hidden")
|| matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
read_ignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")),
read_gitignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-ignore-vcs")),
follow_links: matches.is_present("follow"),
null_separator: matches.is_present("null_separator"),
max_depth: matches
.value_of("depth")
.and_then(|n| usize::from_str_radix(n, 10).ok()),
threads: std::cmp::max(
matches
.value_of("threads")
.and_then(|n| usize::from_str_radix(n, 10).ok())
.unwrap_or_else(num_cpus::get),
1,
),
max_buffer_time: matches
.value_of("max-buffer-time")
.and_then(|n| u64::from_str_radix(n, 10).ok())
.map(time::Duration::from_millis),
ls_colors,
file_types: match matches.values_of("file-type") {
None => vec![
FileType::RegularFile,
FileType::Directory,
FileType::SymLink,
].into_iter()
.collect(),
Some(values) => values
.map(|value| match value {
"f" | "file" => FileType::RegularFile,
"d" | "directory" => FileType::Directory,
"l" | "symlink" => FileType::SymLink,
_ => FileType::RegularFile,
})
.collect(),
},
extensions: matches.values_of("extension").map(|exts| {
let patterns = exts.map(|e| e.trim_left_matches('.'))
.map(|e| format!(r".\.{}$", regex::escape(e)));
match RegexSetBuilder::new(patterns)
.case_insensitive(true)
.build()
{
Ok(re) => re,
Err(err) => error(err.description()),
}
}),
command,
exclude_patterns: matches
.values_of("exclude")
.map(|v| v.map(|p| String::from("!") + p).collect())
.unwrap_or_else(|| vec![]),
};
let root = Path::new(ROOT_DIR);
let base = match config.path_display {
PathDisplay::Relative => current_dir,
PathDisplay::Absolute => root
};
match RegexBuilder::new(pattern)
.case_insensitive(!config.case_sensitive)
.build() {
Ok(re) => scan(root_dir, &re, base, &config),
Err(err) => error(err.description())
match RegexBuilder::new(&pattern_regex)
.case_insensitive(!config.case_sensitive)
.dot_matches_new_line(true)
.build()
{
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
Err(err) => error(err.description()),
}
}
+131
View File
@@ -0,0 +1,131 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use internal::{FdOptions, EXITCODE_ERROR, EXITCODE_SIGINT};
use lscolors::LsColors;
use std::{fs, process};
use std::io::{self, Write};
use std::ops::Deref;
use std::path::{self, Component, Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::PermissionsExt;
use ansi_term;
pub fn print_entry(entry: &PathBuf, config: &FdOptions, wants_to_quit: &Arc<AtomicBool>) {
let path = entry.strip_prefix(".").unwrap_or(entry);
let r = if let Some(ref ls_colors) = config.ls_colors {
print_entry_colorized(path, config, ls_colors, &wants_to_quit)
} else {
print_entry_uncolorized(path, config)
};
if r.is_err() {
// Probably a broken pipe. Exit gracefully.
process::exit(EXITCODE_ERROR);
}
}
fn print_entry_colorized(
path: &Path,
config: &FdOptions,
ls_colors: &LsColors,
wants_to_quit: &Arc<AtomicBool>,
) -> io::Result<()> {
let default_style = ansi_term::Style::default();
let stdout = io::stdout();
let mut handle = stdout.lock();
// Separator to use before the current component.
let mut separator = String::new();
// Full path to the current component.
let mut component_path = PathBuf::new();
// Traverse the path and colorize each component
for component in path.components() {
let comp_str = component.as_os_str().to_string_lossy();
component_path.push(Path::new(comp_str.deref()));
let style = get_path_style(&component_path, ls_colors).unwrap_or(&default_style);
write!(handle, "{}{}", separator, style.paint(comp_str))?;
// Determine separator to print before next component.
separator = match component {
// Prefix needs no separator, as it is always followed by RootDir.
Component::Prefix(_) => String::new(),
// RootDir is already a separator.
Component::RootDir => String::new(),
// Everything else uses a separator that is painted the same way as the component.
_ => style.paint(path::MAIN_SEPARATOR.to_string()).to_string(),
};
if wants_to_quit.load(Ordering::Relaxed) {
write!(handle, "\n")?;
process::exit(EXITCODE_SIGINT);
}
}
if config.null_separator {
write!(handle, "\0")
} else {
writeln!(handle, "")
}
}
fn print_entry_uncolorized(path: &Path, config: &FdOptions) -> io::Result<()> {
let separator = if config.null_separator { "\0" } else { "\n" };
let path_str = path.to_string_lossy();
write!(&mut io::stdout(), "{}{}", path_str, separator)
}
fn get_path_style<'a>(path: &Path, ls_colors: &'a LsColors) -> Option<&'a ansi_term::Style> {
if path.symlink_metadata()
.map(|md| md.file_type().is_symlink())
.unwrap_or(false)
{
return Some(&ls_colors.symlink);
}
let metadata = path.metadata();
if metadata.as_ref().map(|md| md.is_dir()).unwrap_or(false) {
Some(&ls_colors.directory)
} else if metadata.map(|md| is_executable(&md)).unwrap_or(false) {
Some(&ls_colors.executable)
} else if let Some(filename_style) = path.file_name()
.and_then(|n| n.to_str())
.and_then(|n| ls_colors.filenames.get(n))
{
Some(filename_style)
} else if let Some(extension_style) = path.extension()
.and_then(|e| e.to_str())
.and_then(|e| ls_colors.extensions.get(e))
{
Some(extension_style)
} else {
None
}
}
#[cfg(any(unix, target_os = "redox"))]
fn is_executable(md: &fs::Metadata) -> bool {
md.permissions().mode() & 0o111 != 0
}
#[cfg(windows)]
fn is_executable(_: &fs::Metadata) -> bool {
false
}
+254
View File
@@ -0,0 +1,254 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
extern crate ctrlc;
use exec;
use fshelper;
use internal::{error, FdOptions, EXITCODE_SIGINT, MAX_BUFFER_LENGTH};
use output;
use std::process;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
use std::thread;
use std::time;
use ignore::{self, WalkBuilder};
use ignore::overrides::OverrideBuilder;
use regex::Regex;
/// The receiver thread can either be buffering results or directly streaming to the console.
enum ReceiverMode {
/// Receiver is still buffering in order to sort the results, if the search finishes fast
/// enough.
Buffering,
/// Receiver is directly printing results to the output.
Streaming,
}
/// The types of file to search for.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum FileType {
RegularFile,
Directory,
SymLink,
}
/// Recursively scan the given search path for files / pathnames matching the pattern.
///
/// If the `--exec` argument was supplied, this will create a thread pool for executing
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
/// path will simply be written to standard output.
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let mut path_iter = path_vec.iter();
let first_path_buf = path_iter
.next()
.expect("Error: Path vector can not be empty");
let (tx, rx) = channel();
let threads = config.threads;
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
for pattern in &config.exclude_patterns {
let res = override_builder.add(pattern);
if res.is_err() {
error(&format!("Error: malformed exclude pattern '{}'", pattern));
}
}
let overrides = override_builder.build().unwrap_or_else(|_| {
error("Mismatch in exclude patterns");
});
let mut walker = WalkBuilder::new(first_path_buf.as_path());
walker
.hidden(config.ignore_hidden)
.ignore(config.read_ignore)
.git_ignore(config.read_gitignore)
.parents(config.read_ignore || config.read_gitignore)
.git_global(config.read_gitignore)
.git_exclude(config.read_gitignore)
.overrides(overrides)
.follow_links(config.follow_links)
.max_depth(config.max_depth);
for path_entry in path_iter {
walker.add(path_entry.as_path());
}
let parallel_walker = walker.threads(threads).build_parallel();
let wants_to_quit = Arc::new(AtomicBool::new(false));
let receiver_wtq = Arc::clone(&wants_to_quit);
let sender_wtq = Arc::clone(&wants_to_quit);
if config.ls_colors.is_some() && config.command.is_none() {
let wq = Arc::clone(&receiver_wtq);
ctrlc::set_handler(move || {
wq.store(true, Ordering::Relaxed);
}).unwrap();
}
// Spawn the thread that receives all results through the channel.
let rx_config = Arc::clone(&config);
let receiver_thread = thread::spawn(move || {
// This will be set to `Some` if the `--exec` argument was supplied.
if let Some(ref cmd) = rx_config.command {
let shared_rx = Arc::new(Mutex::new(rx));
let out_perm = Arc::new(Mutex::new(()));
// TODO: the following line is a workaround to replace the `unsafe` block that was
// previously used here to avoid the (unnecessary?) cloning of the command. The
// `unsafe` block caused problems on some platforms (SIGILL instructions on Linux) and
// therefore had to be removed.
let cmd = Arc::new(cmd.clone());
// Each spawned job will store it's thread handle in here.
let mut handles = Vec::with_capacity(threads);
for _ in 0..threads {
let rx = Arc::clone(&shared_rx);
let cmd = Arc::clone(&cmd);
let out_perm = Arc::clone(&out_perm);
// Spawn a job thread that will listen for and execute inputs.
let handle = thread::spawn(move || exec::job(rx, cmd, out_perm));
// Push the handle of the spawned thread into the vector for later joining.
handles.push(handle);
}
// Wait for all threads to exit before exiting the program.
for h in handles {
h.join().unwrap();
}
} else {
let start = time::Instant::now();
let mut buffer = vec![];
// Start in buffering mode
let mut mode = ReceiverMode::Buffering;
// Maximum time to wait before we start streaming to the console.
let max_buffer_time = rx_config
.max_buffer_time
.unwrap_or_else(|| time::Duration::from_millis(100));
for value in rx {
match mode {
ReceiverMode::Buffering => {
buffer.push(value);
// Have we reached the maximum buffer size or maximum buffering time?
if buffer.len() > MAX_BUFFER_LENGTH
|| time::Instant::now() - start > max_buffer_time
{
// Flush the buffer
for v in &buffer {
output::print_entry(v, &rx_config, &receiver_wtq);
}
buffer.clear();
// Start streaming
mode = ReceiverMode::Streaming;
}
}
ReceiverMode::Streaming => {
output::print_entry(&value, &rx_config, &receiver_wtq);
}
}
}
// If we have finished fast enough (faster than max_buffer_time), we haven't streamed
// anything to the console, yet. In this case, sort the results and print them:
if !buffer.is_empty() {
buffer.sort();
for value in buffer {
output::print_entry(&value, &rx_config, &receiver_wtq);
}
}
}
});
// Spawn the sender threads.
parallel_walker.run(|| {
let config = Arc::clone(&config);
let pattern = Arc::clone(&pattern);
let tx_thread = tx.clone();
let wants_to_quit = Arc::clone(&sender_wtq);
Box::new(move |entry_o| {
if wants_to_quit.load(Ordering::Relaxed) {
return ignore::WalkState::Quit;
}
let entry = match entry_o {
Ok(e) => e,
Err(_) => return ignore::WalkState::Continue,
};
let entry_path = entry.path();
if entry.depth() == 0 {
return ignore::WalkState::Continue;
}
// Filter out unwanted file types.
if (entry.file_type().map_or(false, |ft| ft.is_file())
&& !config.file_types.contains(&FileType::RegularFile))
|| (entry.file_type().map_or(false, |ft| ft.is_dir())
&& !config.file_types.contains(&FileType::Directory))
|| (entry.file_type().map_or(false, |ft| ft.is_symlink())
&& !config.file_types.contains(&FileType::SymLink))
{
return ignore::WalkState::Continue;
}
// Filter out unwanted extensions.
if let Some(ref exts_regex) = config.extensions {
if let Some(path_str) = entry_path.file_name().map_or(None, |s| s.to_str()) {
if !exts_regex.is_match(path_str) {
return ignore::WalkState::Continue;
}
}
}
let search_str_o = if config.search_full_path {
match fshelper::path_absolute_form(entry_path) {
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
Err(_) => error("Error: unable to get full path."),
}
} else {
entry_path.file_name().map(|f| f.to_string_lossy())
};
if let Some(search_str) = search_str_o {
if pattern.is_match(&*search_str) {
// TODO: take care of the unwrap call
tx_thread.send(entry_path.to_owned()).unwrap()
}
}
ignore::WalkState::Continue
})
});
// Drop the initial sender. If we don't do this, the receiver will block even
// if all threads have finished, since there is still one sender around.
drop(tx);
// Wait for the receiver thread to print out all results.
receiver_thread.join().unwrap();
if wants_to_quit.load(Ordering::Relaxed) {
process::exit(EXITCODE_SIGINT);
}
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
extern crate kernel32;
extern crate winapi;
use self::kernel32::{GetConsoleMode, GetStdHandle, SetConsoleMode};
use self::winapi::{INVALID_HANDLE_VALUE, STD_OUTPUT_HANDLE};
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example
pub fn enable_colored_output() -> bool {
unsafe {
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
if handle == INVALID_HANDLE_VALUE {
return false;
}
// https://docs.microsoft.com/en-us/windows/console/getconsolemode
let mut mode = 0;
if GetConsoleMode(handle, &mut mode) == 0 {
return false;
}
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
//
// A console consists of an input buffer and one or more screen buffers. ... Setting the
// output modes of one screen buffer does not affect the output modes of other screen
// buffers.
SetConsoleMode(handle, mode) != 0
}
}
-202
View File
@@ -1,202 +0,0 @@
#!/bin/bash
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
fd="${SCRIPT_DIR}/../target/debug/fd"
export reset='\x1b[0m'
export bold='\x1b[01m'
export green='\x1b[32;01m'
export red='\x1b[31;01m'
set -eou pipefail
suite() {
echo
echo -e "${bold}$1${reset}"
echo
}
expect() {
expected_output="$1"
shift
tmp_expected="$(mktemp)"
tmp_output="$(mktemp)"
echo "$expected_output" > "$tmp_expected"
"$fd" "$@" | sort > "$tmp_output"
echo -ne " ${bold}${reset} Testing 'fd $*' ... "
if diff -q "$tmp_expected" "$tmp_output" > /dev/null; then
echo -e "${green}✓ okay${reset}"
else
echo -e "${red}❌FAILED${reset}"
echo -ne "\nShowing diff between ${red}expected${reset} and "
echo -e "${green}actual${reset} output:\n"
diff -C3 --label expected --label actual --color \
"$tmp_expected" "$tmp_output" || true
exit 1
fi
}
root=$(mktemp --directory)
cd "$root"
# Setup test environment
mkdir -p one/two/three
touch a.foo
touch one/b.foo
touch one/two/c.foo
touch one/two/C.Foo
touch one/two/three/d.foo
mkdir one/two/three/directory_foo
touch ignored.foo
touch .hidden.foo
ln -s one/two symlink
echo "ignored.foo" > .ignore
# Run the tests
suite "Simple tests"
expect "a.foo" a.foo
expect "one/b.foo" b.foo
expect "one/two/three/d.foo" d.foo
expect "a.foo
one/b.foo
one/two/c.foo
one/two/C.Foo
one/two/three/d.foo
one/two/three/directory_foo" foo
expect "a.foo
one
one/b.foo
one/two
one/two/c.foo
one/two/C.Foo
one/two/three
one/two/three/d.foo
one/two/three/directory_foo
symlink" # run 'fd' without arguments
suite "Explicit root path"
expect "one/b.foo
one/two/c.foo
one/two/C.Foo
one/two/three/d.foo
one/two/three/directory_foo" foo one
expect "one/two/three/d.foo
one/two/three/directory_foo" foo one/two/three
(
cd one/two
expect "../../a.foo
../b.foo
c.foo
C.Foo
three/d.foo
three/directory_foo" foo ../../
)
suite "Regex searches"
expect "a.foo
one/b.foo
one/two/c.foo
one/two/C.Foo" '[a-c].foo'
expect "a.foo
one/b.foo
one/two/c.foo" --sensitive '[a-c].foo'
suite "Smart case"
expect "one/two/c.foo
one/two/C.Foo" c.foo
expect "one/two/C.Foo" C.Foo
expect "one/two/C.Foo" Foo
suite "Case-sensitivity (--sensitive)"
expect "one/two/c.foo" --sensitive c.foo
expect "one/two/C.Foo" --sensitive C.Foo
suite "Full path search (--full-path)"
expect "one/two/three/d.foo
one/two/three/directory_foo" --full-path 'three.*foo'
expect "a.foo" --full-path '^a\.foo$'
suite "Hidden files (--hidden)"
expect "a.foo
.hidden.foo
one/b.foo
one/two/c.foo
one/two/C.Foo
one/two/three/d.foo
one/two/three/directory_foo" --hidden foo
suite "Ignored files (--no-ignore)"
expect "a.foo
ignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo
one/two/three/d.foo
one/two/three/directory_foo" --no-ignore foo
expect "a.foo
.hidden.foo
ignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo
one/two/three/d.foo
one/two/three/directory_foo" --hidden --no-ignore foo
suite "Symlinks (--follow)"
expect "one/two/c.foo
one/two/C.Foo
symlink/c.foo
symlink/C.Foo" --follow c.foo
suite "Maximum depth (--max-depth)"
expect "a.foo
one
one/b.foo
one/two
one/two/c.foo
one/two/C.Foo
one/two/three
symlink" --max-depth 3
expect "a.foo
one
one/b.foo
one/two
symlink" --max-depth 2
expect "a.foo
one
symlink" --max-depth 1
suite "Absolute paths (--absolute)"
expect "$root/a.foo
$root/one/b.foo
$root/one/two/c.foo
$root/one/two/C.Foo
$root/one/two/three/d.foo
$root/one/two/three/directory_foo" --absolute foo
# All done
echo
+194
View File
@@ -0,0 +1,194 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std;
use std::env;
use std::fs;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process;
#[cfg(unix)]
use std::os::unix;
#[cfg(windows)]
use std::os::windows;
extern crate diff;
extern crate tempdir;
use self::tempdir::TempDir;
/// Environment for the integration tests.
pub struct TestEnv {
/// Temporary working directory.
temp_dir: TempDir,
/// Path to the *fd* executable.
fd_exe: PathBuf,
}
/// Create the working directory and the test files.
fn create_working_directory(
directories: &[&'static str],
files: &[&'static str],
) -> Result<TempDir, io::Error> {
let temp_dir = TempDir::new("fd-tests")?;
{
let root = temp_dir.path();
for directory in directories {
fs::create_dir_all(root.join(directory))?;
}
for file in files {
fs::File::create(root.join(file))?;
}
#[cfg(unix)]
unix::fs::symlink(root.join("one/two"), root.join("symlink"))?;
// Note: creating symlinks on Windows requires the `SeCreateSymbolicLinkPrivilege` which
// is by default only granted for administrators.
#[cfg(windows)]
windows::fs::symlink_dir(root.join("one/two"), root.join("symlink"))?;
fs::File::create(root.join(".ignore"))?.write_all(b"ignored.foo")?;
fs::File::create(root.join(".gitignore"))?.write_all(b"gitignored.foo")?;
}
Ok(temp_dir)
}
/// Find the *fd* executable.
fn find_fd_exe() -> PathBuf {
// Tests exe is in target/debug/deps, the *fd* exe is in target/debug
let root = env::current_exe()
.expect("tests executable")
.parent()
.expect("tests executable directory")
.parent()
.expect("fd executable directory")
.to_path_buf();
let exe_name = if cfg!(windows) { "fd.exe" } else { "fd" };
root.join(exe_name)
}
/// Format an error message for when *fd* did not exit successfully.
fn format_exit_error(args: &[&str], output: &process::Output) -> String {
format!(
"`fd {}` did not exit successfully.\nstdout:\n---\n{}---\nstderr:\n---\n{}---",
args.join(" "),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)
}
/// Format an error message for when the output of *fd* did not match the expected output.
fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
// Generate diff text.
let diff_text = diff::lines(expected, actual)
.into_iter()
.map(|diff| match diff {
diff::Result::Left(l) => format!("-{}", l),
diff::Result::Both(l, _) => format!(" {}", l),
diff::Result::Right(r) => format!("+{}", r),
})
.collect::<Vec<_>>()
.join("\n");
format!(
concat!(
"`fd {}` did not produce the expected output.\n",
"Showing diff between expected and actual:\n{}\n"
),
args.join(" "),
diff_text
)
}
/// Normalize the output for comparison.
fn normalize_output(s: &str, trim_left: bool) -> String {
// Split into lines and normalize separators.
let mut lines = s.replace('\0', "NULL\n")
.lines()
.map(|line| {
let line =
if trim_left { line.trim_left() } else { line };
line.replace('/', &std::path::MAIN_SEPARATOR.to_string())
})
.collect::<Vec<_>>();
lines.sort_by_key(|s| s.clone());
lines.join("\n")
}
impl TestEnv {
pub fn new(directories: &[&'static str], files: &[&'static str]) -> TestEnv {
let temp_dir = create_working_directory(directories, files).expect("working directory");
let fd_exe = find_fd_exe();
TestEnv {
temp_dir: temp_dir,
fd_exe: fd_exe,
}
}
/// Get the root directory for the tests.
pub fn test_root(&self) -> PathBuf {
self.temp_dir.path().to_path_buf()
}
/// Get the root directory of the file system.
pub fn system_root(&self) -> PathBuf {
let mut components = self.temp_dir.path().components();
PathBuf::from(components.next().expect("root directory").as_os_str())
}
/// Assert that calling *fd* with the specified arguments produces the expected output.
pub fn assert_output(&self, args: &[&str], expected: &str) {
self.assert_output_subdirectory(".", args, expected)
}
/// Assert that calling *fd* in the specified path under the root working directory,
/// and with the specified arguments produces the expected output.
pub fn assert_output_subdirectory<P: AsRef<Path>>(
&self,
path: P,
args: &[&str],
expected: &str,
) {
// Setup *fd* command.
let mut cmd = process::Command::new(&self.fd_exe);
cmd.current_dir(self.temp_dir.path().join(path));
cmd.args(args);
// Run *fd*.
let output = cmd.output().expect("fd output");
// Check for exit status.
if !output.status.success() {
panic!(format_exit_error(args, &output));
}
// Normalize both expected and actual output.
let expected = normalize_output(expected, true);
let actual = normalize_output(&String::from_utf8_lossy(&output.stdout), false);
// Compare actual output to expected output.
if expected != actual {
panic!(format_output_error(args, &expected, &actual));
}
}
}
+823
View File
@@ -0,0 +1,823 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
//! Integration tests for the CLI interface of fd.
extern crate regex;
mod testenv;
use testenv::TestEnv;
use regex::escape;
static DEFAULT_DIRS: &'static [&'static str] = &["one/two/three", "one/two/three/directory_foo"];
static DEFAULT_FILES: &'static [&'static str] = &[
"a.foo",
"one/b.foo",
"one/two/c.foo",
"one/two/C.Foo2",
"one/two/three/d.foo",
"ignored.foo",
"gitignored.foo",
".hidden.foo",
"e1 e2",
];
fn get_absolute_root_path(env: &TestEnv) -> String {
let path = env.test_root()
.canonicalize()
.expect("absolute path")
.to_str()
.expect("string")
.to_string();
#[cfg(windows)]
let path = path.trim_left_matches(r"\\?\").to_string();
path
}
#[cfg(test)]
fn get_test_env_with_abs_path(dirs: &[&'static str], files: &[&'static str]) -> (TestEnv, String) {
let env = TestEnv::new(dirs, files);
let root_path = get_absolute_root_path(&env);
(env, root_path)
}
/// Simple tests
#[test]
fn test_simple() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(&["a.foo"], "a.foo");
te.assert_output(&["b.foo"], "one/b.foo");
te.assert_output(&["d.foo"], "one/two/three/d.foo");
te.assert_output(
&["foo"],
"a.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&[],
"a.foo
e1 e2
one
one/b.foo
one/two
one/two/c.foo
one/two/C.Foo2
one/two/three
one/two/three/d.foo
one/two/three/directory_foo
symlink",
);
}
/// Test multiple directory searches
#[test]
fn test_multi_file() {
let dirs = &["test1", "test2"];
let files = &["test1/a.foo", "test1/b.foo", "test2/a.foo"];
let te = TestEnv::new(dirs, files);
te.assert_output(
&["a.foo", "test1", "test2"],
"test1/a.foo
test2/a.foo",
);
te.assert_output(
&["", "test1", "test2"],
"test1/a.foo
test2/a.foo
test1/b.foo",
);
te.assert_output(&["a.foo", "test1"], "test1/a.foo");
te.assert_output(&["b.foo", "test1", "test2"], "test1/b.foo");
}
/// Explicit root path
#[test]
fn test_explicit_root_path() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["foo", "one"],
"one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&["foo", "one/two/three"],
"one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output_subdirectory(
"one/two",
&["foo", "../../"],
"../../a.foo
../../one/b.foo
../../one/two/c.foo
../../one/two/C.Foo2
../../one/two/three/d.foo
../../one/two/three/directory_foo",
);
te.assert_output_subdirectory(
"one/two/three",
&["", ".."],
"../c.foo
../C.Foo2
../three
../three/d.foo
../three/directory_foo",
);
}
/// Regex searches
#[test]
fn test_regex_searches() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["[a-c].foo"],
"a.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2",
);
te.assert_output(
&["--case-sensitive", "[a-c].foo"],
"a.foo
one/b.foo
one/two/c.foo",
);
}
/// Smart case
#[test]
fn test_smart_case() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["c.foo"],
"one/two/c.foo
one/two/C.Foo2",
);
te.assert_output(&["C.Foo"], "one/two/C.Foo2");
te.assert_output(&["Foo"], "one/two/C.Foo2");
// Only literal uppercase chars should trigger case sensitivity.
te.assert_output(
&["\\Ac"],
"one/two/c.foo
one/two/C.Foo2",
);
te.assert_output(&["\\AC"], "one/two/C.Foo2");
}
/// Case sensitivity (--case-sensitive)
#[test]
fn test_case_sensitive() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(&["--case-sensitive", "c.foo"], "one/two/c.foo");
te.assert_output(&["--case-sensitive", "C.Foo"], "one/two/C.Foo2");
te.assert_output(
&["--ignore-case", "--case-sensitive", "C.Foo"],
"one/two/C.Foo2",
);
}
/// Case insensitivity (--ignore-case)
#[test]
fn test_case_insensitive() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--ignore-case", "C.Foo"],
"one/two/c.foo
one/two/C.Foo2",
);
te.assert_output(
&["--case-sensitive", "--ignore-case", "C.Foo"],
"one/two/c.foo
one/two/C.Foo2",
);
}
/// Full path search (--full-path)
#[test]
fn test_full_path() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
te.assert_output(
&[
"--full-path",
&format!("^{prefix}.*three.*foo$", prefix = prefix),
],
"one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// Hidden files (--hidden)
#[test]
fn test_hidden() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--hidden", "foo"],
".hidden.foo
a.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// Ignored files (--no-ignore)
#[test]
fn test_no_ignore() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--no-ignore", "foo"],
"a.foo
ignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&["--hidden", "--no-ignore", "foo"],
".hidden.foo
a.foo
ignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// VCS ignored files (--no-ignore-vcs)
#[test]
fn test_no_ignore_vcs() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--no-ignore-vcs", "foo"],
"a.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// Ignored files with ripgrep aliases (-u / -uu)
#[test]
fn test_no_ignore_aliases() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["-u", "foo"],
"a.foo
ignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&["-uu", "foo"],
".hidden.foo
a.foo
ignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// Symlinks (--follow)
#[test]
fn test_follow() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--follow", "c.foo"],
"one/two/c.foo
one/two/C.Foo2
symlink/c.foo
symlink/C.Foo2",
);
}
/// Null separator (--print0)
#[test]
fn test_print0() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--print0", "foo"],
"a.fooNULL
one/b.fooNULL
one/two/C.Foo2NULL
one/two/c.fooNULL
one/two/three/d.fooNULL
one/two/three/directory_fooNULL",
);
}
/// Maximum depth (--max-depth)
#[test]
fn test_max_depth() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--max-depth", "3"],
"a.foo
e1 e2
one
one/b.foo
one/two
one/two/c.foo
one/two/C.Foo2
one/two/three
symlink",
);
te.assert_output(
&["--max-depth", "2"],
"a.foo
e1 e2
one
one/b.foo
one/two
symlink",
);
te.assert_output(
&["--max-depth", "1"],
"a.foo
e1 e2
one
symlink",
);
}
/// Absolute paths (--absolute-path)
#[test]
fn test_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--absolute-path"],
&format!(
"{abs_path}/a.foo
{abs_path}/e1 e2
{abs_path}/one
{abs_path}/one/b.foo
{abs_path}/one/two
{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo
{abs_path}/symlink",
abs_path = &abs_path
),
);
te.assert_output(
&["--absolute-path", "foo"],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
te.assert_output(
&["foo", &abs_path],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
/// File type filter (--type)
#[test]
fn test_type() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--type", "f"],
"a.foo
e1 e2
one/b.foo
one/two/c.foo
one/two/C.Foo2
one/two/three/d.foo",
);
te.assert_output(&["--type", "f", "e1"], "e1 e2");
te.assert_output(
&["--type", "d"],
"one
one/two
one/two/three
one/two/three/directory_foo",
);
te.assert_output(
&["--type", "d", "--type", "l"],
"one
one/two
one/two/three
one/two/three/directory_foo
symlink",
);
te.assert_output(&["--type", "l"], "symlink");
}
/// File extension (--extension)
#[test]
fn test_extension() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--extension", "foo"],
"a.foo
one/b.foo
one/two/c.foo
one/two/three/d.foo",
);
te.assert_output(
&["--extension", ".foo"],
"a.foo
one/b.foo
one/two/c.foo
one/two/three/d.foo",
);
te.assert_output(
&["--extension", ".foo", "--extension", "foo2"],
"a.foo
one/b.foo
one/two/c.foo
one/two/three/d.foo
one/two/C.Foo2",
);
te.assert_output(&["--extension", ".foo", "a"], "a.foo");
te.assert_output(&["--extension", "foo2"], "one/two/C.Foo2");
let te2 = TestEnv::new(&[], &["spam.bar.baz", "egg.bar.baz", "yolk.bar.baz.sig"]);
te2.assert_output(
&["--extension", ".bar.baz"],
"spam.bar.baz
egg.bar.baz",
);
te2.assert_output(&["--extension", "sig"], "yolk.bar.baz.sig");
te2.assert_output(&["--extension", "bar.baz.sig"], "yolk.bar.baz.sig");
let te3 = TestEnv::new(&[], &["latin1.e\u{301}xt", "smiley.☻"]);
te3.assert_output(&["--extension", ""], "smiley.☻");
te3.assert_output(&["--extension", ".e\u{301}xt"], "latin1.e\u{301}xt");
let te4 = TestEnv::new(&[], &[".hidden", "test.hidden"]);
te4.assert_output(&["--hidden", "--extension", ".hidden"], "test.hidden");
}
/// Symlinks misc
#[test]
fn test_symlink() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
// From: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html
// The getcwd() function shall place an absolute pathname of the current working directory in
// the array pointed to by buf, and return buf. The pathname shall contain no components that
// are dot or dot-dot, or are symbolic links.
//
// Key points:
// 1. The path of the current working directory of a Unix process cannot contain symlinks.
// 2. The path of the current working directory of a Windows process can contain symlinks.
//
// More:
// 1. On Windows, symlinks are resolved after the ".." component.
// 2. On Unix, symlinks are resolved immediately as encountered.
let parent_parent = if cfg!(windows) { ".." } else { "../.." };
te.assert_output_subdirectory(
"symlink",
&["", parent_parent],
&format!(
"{dir}/a.foo
{dir}/e1 e2
{dir}/one
{dir}/one/b.foo
{dir}/one/two
{dir}/one/two/c.foo
{dir}/one/two/C.Foo2
{dir}/one/two/three
{dir}/one/two/three/d.foo
{dir}/one/two/three/directory_foo
{dir}/symlink",
dir = &parent_parent
),
);
te.assert_output_subdirectory(
"symlink",
&["--absolute-path"],
&format!(
"{abs_path}/{dir}/c.foo
{abs_path}/{dir}/C.Foo2
{abs_path}/{dir}/three
{abs_path}/{dir}/three/d.foo
{abs_path}/{dir}/three/directory_foo",
dir = if cfg!(windows) { "symlink" } else { "one/two" },
abs_path = &abs_path
),
);
te.assert_output(
&["", &format!("{abs_path}/symlink", abs_path = abs_path)],
&format!(
"{abs_path}/symlink/c.foo
{abs_path}/symlink/C.Foo2
{abs_path}/symlink/three
{abs_path}/symlink/three/d.foo
{abs_path}/symlink/three/directory_foo",
abs_path = &abs_path
),
);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
te.assert_output_subdirectory(
"symlink",
&[
"--absolute-path",
"--full-path",
&format!("^{prefix}.*three", prefix = prefix),
],
&format!(
"{abs_path}/{dir}/three
{abs_path}/{dir}/three/d.foo
{abs_path}/{dir}/three/directory_foo",
dir = if cfg!(windows) { "symlink" } else { "one/two" },
abs_path = &abs_path
),
);
te.assert_output(
&[
"--full-path",
&format!("^{prefix}.*symlink.*three", prefix = prefix),
&format!("{abs_path}/symlink", abs_path = abs_path),
],
&format!(
"{abs_path}/symlink/three
{abs_path}/symlink/three/d.foo
{abs_path}/symlink/three/directory_foo",
abs_path = &abs_path
),
);
}
/// Exclude patterns (--exclude)
#[test]
fn test_excludes() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--exclude", "*.foo"],
"one
one/two
one/two/C.Foo2
one/two/three
one/two/three/directory_foo
e1 e2
symlink",
);
te.assert_output(
&["--exclude", "*.foo", "--exclude", "*.Foo2"],
"one
one/two
one/two/three
one/two/three/directory_foo
e1 e2
symlink",
);
te.assert_output(
&["--exclude", "*.foo", "--exclude", "*.Foo2", "foo"],
"one/two/three/directory_foo",
);
te.assert_output(
&["--exclude", "one/two", "foo"],
"a.foo
one/b.foo",
);
te.assert_output(
&["--exclude", "one/**/*.foo"],
"a.foo
e1 e2
one
one/two
one/two/C.Foo2
one/two/three
one/two/three/directory_foo
symlink",
);
}
/// Shell script execution (--exec)
#[test]
fn test_exec() {
assert_exec_output("--exec");
}
/// Shell script execution using -exec
#[test]
fn test_exec_substitution() {
assert_exec_output("-exec");
}
// Shell script execution using -x
#[test]
fn test_exec_short_arg() {
assert_exec_output("-x");
}
#[cfg(test)]
fn assert_exec_output(exec_style: &str) {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
// TODO Windows tests: D:file.txt \file.txt \\server\share\file.txt ...
if !cfg!(windows) {
te.assert_output(
&["--absolute-path", "foo", exec_style, "echo"],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/c.foo
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
te.assert_output(
&["foo", exec_style, "echo", "{}"],
"a.foo
one/b.foo
one/two/C.Foo2
one/two/c.foo
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&["foo", exec_style, "echo", "{.}"],
"a
one/b
one/two/C
one/two/c
one/two/three/d
one/two/three/directory_foo",
);
te.assert_output(
&["foo", exec_style, "echo", "{/}"],
"a.foo
b.foo
C.Foo2
c.foo
d.foo
directory_foo",
);
te.assert_output(
&["foo", exec_style, "echo", "{/.}"],
"a
b
C
c
d
directory_foo",
);
te.assert_output(
&["foo", exec_style, "echo", "{//}"],
".
one
one/two
one/two
one/two/three
one/two/three",
);
te.assert_output(&["e1", exec_style, "printf", "%s.%s\n"], "e1 e2.");
}
}
/// Literal search (--fixed-strings)
#[test]
fn test_fixed_strings() {
let dirs = &["test1", "test2"];
let files = &["test1/a.foo", "test1/a_foo", "test2/Download (1).tar.gz"];
let te = TestEnv::new(dirs, files);
// Regex search, dot is treated as "any character"
te.assert_output(
&["a.foo"],
"test1/a.foo
test1/a_foo",
);
// Literal search, dot is treated as character
te.assert_output(&["--fixed-strings", "a.foo"], "test1/a.foo");
// Regex search, parens are treated as group
te.assert_output(&["download (1)"], "");
// Literal search, parens are treated as characters
te.assert_output(
&["--fixed-strings", "download (1)"],
"test2/Download (1).tar.gz",
);
// Combine with --case-sensitive
te.assert_output(&["--fixed-strings", "--case-sensitive", "download (1)"], "");
}