diff --git a/Makefile b/Makefile index 459a4065..c10814a0 100644 --- a/Makefile +++ b/Makefile @@ -280,7 +280,7 @@ install: $(MKDIR) $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m755 bfs $(DESTDIR)$(PREFIX)/bin/bfs $(MKDIR) $(DESTDIR)$(MANDIR)/man1 - $(INSTALL) -m644 bfs.1 $(DESTDIR)$(MANDIR)/man1/bfs.1 + $(INSTALL) -m644 docs/bfs.1 $(DESTDIR)$(MANDIR)/man1/bfs.1 $(MKDIR) $(DESTDIR)$(PREFIX)/share/bash-completion/completions $(INSTALL) -m644 completions/bfs.bash $(DESTDIR)$(PREFIX)/share/bash-completion/completions/bfs $(MKDIR) $(DESTDIR)$(PREFIX)/share/zsh/site-functions diff --git a/README.md b/README.md index 0ebac8f3..bec73c8f 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,39 @@ +
+ `bfs` ===== -[![License](http://img.shields.io/badge/license-0BSD-blue.svg)](https://github.com/tavianator/bfs/blob/main/LICENSE) -[![Version](https://img.shields.io/github/v/tag/tavianator/bfs?label=version)](https://github.com/tavianator/bfs/releases) -[![CI Status](https://github.com/tavianator/bfs/actions/workflows/ci.yml/badge.svg)](https://github.com/tavianator/bfs/actions/workflows/ci.yml) +Version +License +CI Status +Code coverage + +***Breadth-first search for your files.*** -Breadth-first search for your files. +[ **[Features](#features)** ]  +[ **[Installation](#installation)** ]  +[ **[Usage](/docs/USAGE.md)** ]  +[ **[Building](/docs/BUILDING.md)** ]  +[ **[Hacking](/docs/HACKING.md)** ]  +[ **[Changelog](/docs/CHANGELOG.md)** ] -Screenshot +Screenshot +

+
-`bfs` is a variant of the UNIX `find` command that operates [breadth-first](https://en.wikipedia.org/wiki/Breadth-first_search) rather than [depth-first](https://en.wikipedia.org/wiki/Depth-first_search). +`bfs` is a variant of the UNIX `find` command that operates [**breadth-first**](https://en.wikipedia.org/wiki/Breadth-first_search) rather than [**depth-first**](https://en.wikipedia.org/wiki/Depth-first_search). It is otherwise compatible with many versions of `find`, including -- [POSIX `find`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html) -- [GNU `find`](https://www.gnu.org/software/findutils/) -- {[Free](https://www.freebsd.org/cgi/man.cgi?find(1)),[Open](https://man.openbsd.org/find.1),[Net](https://man.netbsd.org/find.1)}BSD `find` -- [macOS `find`](https://ss64.com/osx/find.html) +
+ +[ **[POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html)** ]  +[ **[GNU](https://www.gnu.org/software/findutils/)** ]  +[ **[FreeBSD](https://www.freebsd.org/cgi/man.cgi?find(1))** ]  +[ **[OpenBSD](https://man.openbsd.org/find.1)** ]  +[ **[NetBSD](https://man.netbsd.org/find.1)** ]  +[ **[macOS](https://ss64.com/osx/find.html)** ] + +
If you're not familiar with `find`, the [GNU find manual](https://www.gnu.org/software/findutils/manual/html_mono/find.html) provides a good introduction. @@ -24,7 +42,10 @@ Features --------
-bfs operates breadth-first, which typically finds the file(s) you're looking for faster. + +bfs operates breadth-first, which typically finds the file(s) you're looking for faster. +

+
Imagine the following directory tree: @@ -72,24 +93,55 @@ haystack/deep/1/2/3/4
-bfs tries to be easier to use than find, while remaining compatible. + +bfs tries to be easier to use than find, while remaining compatible. +

+
For example, `bfs` is less picky about where you put its arguments: -
-$ bfs -L -name 'needle' haystack    │ $ find -L -name 'needle' haystack
-haystack/needle                     │ find: paths must precede expression: haystack
-                                    │
-$ bfs haystack -L -name 'needle'    │ $ find haystack -L -name 'needle'
-haystack/needle                     │ find: unknown predicate `-L'
-                                    │
-$ bfs -L haystack -name 'needle'    │ $ find -L haystack -name 'needle'
-haystack/needlehaystack/needle
-
+ + + + + + + + +
+ +```console +$ bfs -L -name 'needle' haystack +haystack/needle + +$ bfs haystack -L -name 'needle' +haystack/needle + +$ bfs -L haystack -name 'needle' +haystack/needle +``` + + + +```console +$ find -L -name 'needle' haystack +find: paths must precede expression: haystack + +$ find haystack -L -name 'needle' +find: unknown predicate `-L' + +$ find -L haystack -name 'needle' +haystack/needle +``` + +
-bfs gives helpful errors and warnings. + +bfs gives helpful errors and warnings. +

+
For example, `bfs` will detect and suggest corrections for typos: @@ -100,7 +152,7 @@ $ bfs -nam needle bfs: error: Unknown argument; did you mean -name? -`bfs` also includes a powerful static analysis to identify likely mistakes: +`bfs` also includes a powerful static analysis to help catch mistakes:
 $ bfs -print -name 'needle'
@@ -111,63 +163,45 @@ $ bfs -print -name 'needle'
 
-bfs adds some options that make common tasks easier. + +bfs adds some options that make common tasks easier. +

+
-### `-exclude` +For example, the `-exclude` operator skips over entire subtrees whenever an expression matches. +`-exclude` is both more powerful and easier to use than the standard `-prune` action; compare -The `-exclude` operator skips an entire subtree whenever an expression matches. -For example, `-exclude -name .git` will exclude any files or directories named `.git` from the search results. -`-exclude` is easier to use than the standard `-prune` action; compare - - bfs -name config -exclude -name .git +
+$ bfs -name config -exclude -name .git
+
to the equivalent - find ! \( -name .git -prune \) -name config - -Unlike `-prune`, `-exclude` even works in combination with `-depth`/`-delete`. - ---- - -### `-hidden`/`-nohidden` - -`-hidden` matches "hidden" files (dotfiles). -`bfs -hidden` is effectively shorthand for - - find \( -name '.*' -not -name . -not -name .. \) - -`-nohidden` is equivalent to `-exclude -hidden`. - ---- - -### `-unique` - -This option ensures that `bfs` only visits each file once, even if it's reachable through multiple hard or symbolic links. -It's particularly useful when following symbolic links (`-L`). - ---- - -### `-color`/`-nocolor` - -When printing to a terminal, `bfs` automatically colors paths like GNU `ls`, according to the `LS_COLORS` environment variable. -The `-color` and `-nocolor` options override the automatic behavior, which may be handy when you want to preserve colors through a pipe: - - bfs -color | less -R +
+$ find ! \( -name .git -prune \) -name config
+
-If the [`NO_COLOR`](https://no-color.org/) environment variable is set, colors will be disabled by default. +As an additional shorthand, `-nohidden` skips over all hidden files and directories. +See the [usage documentation](/docs/USAGE.md#extensions) for more about the extensions provided by `bfs`.
Installation ------------ -
-bfs may already be packaged for your operating system. +
+ +bfs may already be packaged for your operating system. +

+
 Alpine Linux
 # apk add bfs
 
+Arch Linux
+Available in the AUR
+
 Debian/Ubuntu
 # apt install bfs
 
@@ -189,22 +223,16 @@ $ brew install tavianator/tap/bfs
 
-To build bfs from source, you may need to install some dependencies. + +To build bfs from source, you may need to install some dependencies. +

+
The only absolute requirements for building `bfs` are a C compiler, [GNU make](https://www.gnu.org/software/make/), and [Bash](https://www.gnu.org/software/bash/). These are installed by default on many systems, and easy to install on most others. Refer to your operating system's documentation on building software. `bfs` also depends on some system libraries for some of its features. -These dependencies are optional, and can be turned off at build time if necessary by setting the appropriate variable to the empty string (e.g. `make WITH_ONIGURUMA=`). - -| Dependency | Platforms | `make` flag | -|-------------------------------------------------------|------------|------------------| -| [acl](https://savannah.nongnu.org/projects/acl) | Linux only | `WITH_ACL` | -| [attr](https://savannah.nongnu.org/projects/attr) | Linux only | `WITH_ATTR` | -| [libcap](https://sites.google.com/site/fullycapable/) | Linux only | `WITH_LIBCAP` | -| [Oniguruma](https://github.com/kkos/oniguruma) | All | `WITH_ONIGURUMA` | - Here's how to install them on some common platforms:
@@ -235,9 +263,18 @@ Here's how to install them on some common platforms:
 Homebrew
 $ brew install oniguruma
 
+ +These dependencies are technically optional, though strongly recommended. +See the [build documentation](/docs/BUILDING.md#dependencies) for how to disable them.
-Once the dependencies are installed, download one of the [releases](https://github.com/tavianator/bfs/releases) or clone the [git repo](https://github.com/tavianator/bfs). +
+ +Once you have the dependencies, you can build bfs. +

+
+ +Download one of the [releases](https://github.com/tavianator/bfs/releases) or clone the [git repo](https://github.com/tavianator/bfs). Then run $ make @@ -254,3 +291,5 @@ If you're interested in speed, you may want to build the release version instead Finally, if you want to install it globally, run # make install + +
diff --git a/CONTRIBUTING.md b/docs/BUILDING.md similarity index 73% rename from CONTRIBUTING.md rename to docs/BUILDING.md index 28bfac2f..ebab60bc 100644 --- a/CONTRIBUTING.md +++ b/docs/BUILDING.md @@ -1,15 +1,8 @@ -Contributing -============ - -License -------- - -`bfs` is licensed under the [Zero-Clause BSD License](https://opensource.org/licenses/0BSD), a maximally permissive license. -Contributions must use the same license. +Building `bfs` +============== - -Building --------- +Compiling +--------- `bfs` uses [GNU Make](https://www.gnu.org/software/make/) as its build system. A simple invocation of @@ -63,15 +56,34 @@ Here are some of the common ones; check the [`Makefile`](/Makefile) for more. | `CC` | The C compiler to use, e.g. `make CC=clang` | | `CFLAGS`
`EXTRA_CFLAGS` | Override/add to the default compiler flags | | `LDFLAGS`
`EXTRA_LDFLAGS` | Override/add to the linker flags | -| `WITH_ACL`
`WITH_ATTR`
... | Enable/disable optional dependencies | +| `WITH_ACL`
`WITH_ATTR`
... | Enable/disable [optional dependencies] | | `TEST_FLAGS` | `tests.sh` flags for `make check` | | `DESTDIR` | The root directory for `make install` | | `PREFIX` | The installation prefix (default: `/usr`) | | `MANDIR` | The man page installation directory | +[optional dependencies]: #dependencies + +### Dependencies + +`bfs` depends on some system libraries for some of its features. +These dependencies are optional, and can be turned off at build time if necessary by setting the appropriate variable to the empty string (e.g. `make WITH_ONIGURUMA=`). + +| Dependency | Platforms | `make` flag | +|-------------|------------|------------------| +| [acl] | Linux only | `WITH_ACL` | +| [attr] | Linux only | `WITH_ATTR` | +| [libcap] | Linux only | `WITH_LIBCAP` | +| [Oniguruma] | All | `WITH_ONIGURUMA` | + +[acl]: https://savannah.nongnu.org/projects/acl +[attr]: https://savannah.nongnu.org/projects/attr +[libcap]: https://sites.google.com/site/fullycapable/ +[Oniguruma]: https://github.com/kkos/oniguruma + ### Dependency tracking -The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the `Makefile`). +The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the [`Makefile`](/Makefile)). So if you edit a header file, `make` will rebuild the necessary object files ensuring they don't go out of sync. We go one step further than most build systems by tracking the flags that were used for the previous compilation. @@ -90,7 +102,7 @@ To test a different configuration, you'll have to repeat it (e.g. `make release Testing ------- -`bfs` comes with an extensive testsuite which can be run with +`bfs` comes with an extensive test suite which can be run with $ make check @@ -131,37 +143,3 @@ You can run it yourself with $ make distcheck Some of these tests require `sudo`, and will prompt for your password if necessary. - - -Hacking -------- - -`bfs` is written in [C](https://en.wikipedia.org/wiki/C_(programming_language)), specifically [C11](https://en.wikipedia.org/wiki/C11_(C_standard_revision)). -You can get a feel for the coding style by skimming the source code. -[`main.c`](src/main.c) contains an overview of the rest of source files. -A quick summary: - -- Tabs for indentation, spaces for alignment. -- Most types and functions should be namespaced with `bfs_`. - Exceptions are made for things that could be generally useful outside of `bfs`. -- Error handling follows the C standard library conventions: return a nonzero `int` or a `NULL` pointer, with the error code in `errno`. - All failure cases should be handled, including `malloc()` failures. -- `goto` is not harmful for cleaning up in error paths. - -### Adding tests - -Both new features and bug fixes should have associated tests. -To add a test, create a new function in `tests.sh` called `test_`. -Snapshot tests use the `bfs_diff` function to automatically compare the generated and expected outputs. -For example, - -```bash -function test_something() { - bfs_diff basic -name something -} -``` - -`basic` is one of the directory trees generated for test cases; others include `links`, `loops`, `deep`, and `rainbow`. - -Run `./tests.sh test_something --update` to generate the reference snapshot (and don't forget to `git add` it). -Finally, add the test case to one of the arrays `posix_tests`, `bsd_tests`, `gnu_tests`, or `bfs_tests` depending on which `find` implementations it should be compatible with. diff --git a/RELEASES.md b/docs/CHANGELOG.md similarity index 100% rename from RELEASES.md rename to docs/CHANGELOG.md diff --git a/docs/HACKING.md b/docs/HACKING.md new file mode 100644 index 00000000..7dfc4973 --- /dev/null +++ b/docs/HACKING.md @@ -0,0 +1,47 @@ +Hacking on `bfs` +================ + +License +------- + +`bfs` is licensed under the [Zero-Clause BSD License](https://opensource.org/licenses/0BSD), a maximally permissive license. +Contributions must use the same license. + + +Implementation +-------------- + +`bfs` is written in [C](https://en.wikipedia.org/wiki/C_(programming_language)), specifically [C11](https://en.wikipedia.org/wiki/C11_(C_standard_revision)). +You can get a feel for the coding style by skimming the source code. +[`main.c`](/src/main.c) contains an overview of the rest of source files. +A quick summary: + +- Tabs for indentation, spaces for alignment. +- Most types and functions should be namespaced with `bfs_`. + Exceptions are made for things that could be generally useful outside of `bfs`. +- Error handling follows the C standard library conventions: return a nonzero `int` or a `NULL` pointer, with the error code in `errno`. + All failure cases should be handled, including `malloc()` failures. +- `goto` is not considered harmful for cleaning up in error paths. + + +Tests +----- + +`bfs` includes an extensive test suite. +See the [build documentation](BUILDING.md#testing) for details on running the tests. + +Both new features and bug fixes should have associated tests. +To add a test, create a new function in `tests.sh` called `test_`. +Snapshot tests use the `bfs_diff` function to automatically compare the generated and expected outputs. +For example, + +```bash +function test_something() { + bfs_diff basic -name something +} +``` + +`basic` is one of the directory trees generated for test cases; others include `links`, `loops`, `deep`, and `rainbow`. + +Run `./tests.sh test_something --update` to generate the reference snapshot (and don't forget to `git add` it). +Finally, add the test case to one of the arrays `posix_tests`, `bsd_tests`, `gnu_tests`, or `bfs_tests`, depending on which `find` implementations it should be compatible with. diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 00000000..9f76f160 --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,161 @@ +Using `bfs` +=========== + +`bfs` has the same command line syntax as `find`, and almost any `find` command that works with a major `find` implementation will also work with `bfs`. +When invoked with no arguments, `bfs` will list everything under the current directory recursively, breadth-first: + +```console +$ bfs +. +./LICENSE +./Makefile +./README.md +./completions +./docs +./src +./tests.sh +./tests +./completions/bfs.bash +./completions/bfs.zsh +./docs/BUILDING.md +./docs/CHANGELOG.md +./docs/HACKING.md +./docs/USAGE.md +./docs/bfs.1 +./src/bfs.h +... +``` + + +Paths +----- + +Arguments that don't begin with `-` are treated as paths to search. +If one or more paths are specified, they are used instead of the current directory: + +```console +$ bfs /usr/bin /usr/lib +/usr/bin +/usr/lib +/usr/bin/bfs +... +/usr/lib/libc.so +... +``` + + +Expressions +----------- + +Arguments that start with `-` form an *expression* which `bfs` evaluates to filter the matched files, and to do things with the files that match. +The most common expression is probably `-name`, which matches filenames against a glob pattern: + +```console +$ bfs -name '*.md' +./README.md +./docs/BUILDING.md +./docs/CHANGELOG.md +./docs/HACKING.md +./docs/USAGE.md +``` + +### Operators + +When you put multiple expressions next to each other, both of them must match: + +```console +$ bfs -name '*.md' -name '*ING*' +./docs/BUILDING.md +./docs/HACKING.md +``` + +This works because the expressions are implicitly combined with *logical and*. +You could be explicit by writing + +```console +$ bfs -name '*.md' -and -name '*ING'` +``` + +There are other operators like `-or`: + +```console +$ bfs -name '*.md' -or -name '*.sh' +./README.md +./tests.sh +./tests/find-color.sh +./tests/ls-color.sh +./tests/remove-sibling.sh +./tests/sort-args.sh +./docs/CHANGELOG.md +./docs/HACKING.md +./docs/BUILDING.md +./docs/USAGE.md +``` + +and `-not`: + +```console +$ bfs -name '*.md' -and -not -name '*ING*' +./README.md +./docs/CHANGELOG.md +./docs/USAGE.md +``` + +### Actions + +Every `bfs` expression returns either `true` or `false`. +For expressions like `-name`, that's all they do. +But some expressions, called *actions*, have other side effects. + +If no actions are included in the expression, `bfs` adds the `-print` action automatically, which is why the above examples actually print any output. +The default `-print` is supressed if any actions are given explicitly. +Available actions include printing with alternate formats (`-ls`, `-printf`, etc.), executing commands (`-exec`, `-execdir`, etc.), deleting files (`-delete`), and stopping the search (`-quit`, `-exit`). + + +Extensions +---------- + +`bfs` implements a few extensions not found in other `find` implementations. + +### `-exclude` + +The `-exclude` operator skips an entire subtree whenever an expression matches. +For example, `-exclude -name .git` will exclude any files or directories named `.git` from the search results. +`-exclude` is easier to use than the standard `-prune` action; compare + + bfs -name config -exclude -name .git + +to the equivalent + + find ! \( -name .git -prune \) -name config + +Unlike `-prune`, `-exclude` even works in combination with `-depth`/`-delete`. + +--- + +### `-hidden`/`-nohidden` + +`-hidden` matches "hidden" files (dotfiles). +`bfs -hidden` is effectively shorthand for + + find \( -name '.*' -not -name . -not -name .. \) + +`-nohidden` is equivalent to `-exclude -hidden`. + +--- + +### `-unique` + +This option ensures that `bfs` only visits each file once, even if it's reachable through multiple hard or symbolic links. +It's particularly useful when following symbolic links (`-L`). + +--- + +### `-color`/`-nocolor` + +When printing to a terminal, `bfs` automatically colors paths like GNU `ls`, according to the `LS_COLORS` environment variable. +The `-color` and `-nocolor` options override the automatic behavior, which may be handy when you want to preserve colors through a pipe: + + bfs -color | less -R + +If the [`NO_COLOR`](https://no-color.org/) environment variable is set, colors will be disabled by default. diff --git a/bfs.1 b/docs/bfs.1 similarity index 100% rename from bfs.1 rename to docs/bfs.1