bacon-3.12.0/.cargo_vcs_info.json 0000644 00000000136 00000000001 0012201 0 ustar {
"git": {
"sha1": "14f86c0ae2af18088e8721dcf3ef1441b87df182"
},
"path_in_vcs": ""
} bacon-3.12.0/.github/FUNDING.yml 0000644 0000000 0000000 00000000020 10461020230 0014136 0 ustar 0000000 0000000 github: [Canop]
bacon-3.12.0/.gitignore 0000644 0000000 0000000 00000000130 10461020230 0012753 0 ustar 0000000 0000000 /target
bacon.log
/*deploy.sh
/website/site
.bacon-locations
bacon-analysis.json
result
bacon-3.12.0/CHANGELOG.md 0000644 0000000 0000000 00000051373 10461020230 0012613 0 ustar 0000000 0000000
### v3.12.0 - 2025/03/14
- cargo-json analyzer (for bacon-ls): fix invalid spans for errors from proc-macros - Fix #332 - Thanks @Leandros
- `back` action, usually mapped to the `esc` key, no longer quits on first job. If you want the old behavior, add this binding: `esc = "back-or-quit"`. Fix #338 - Thanks @ian-h-chamberlain
### v3.11.0 - 2025/03/03
- hit `:` then type an integer to go to a diagnostic by number - Fix #104
- standard test analyzer: fix stack overflow not detected - Fix #326 - Thanks @gmorenz
#### Dynamic Completion
The script providing location aware completion needs to be sourced once.
This can be done for example in your .profile with
```bash
source <(COMPLETE=bash bacon)
```
(adapt for your shell) This feature is still experimental. Please give feedback, positive or negative, in the chat.
Thanks @bryceberger
### v3.10.0 - 2025/02/09
- all job parameters can now be specified at the root (to be applied to all jobs)
- `grace_period`, `show_change_count`, `sound.enabled`, and `sound.base_volume` can now be specified at job level
- `no-op` (no operation) internal, which can be used to disable a previously set binding
### v3.9.1 - 2025/01/28
- as compilation of Alsa can be a problem on some systems, the "sound" feature is now disabled by default. You can enable it by compiling with `--features "sound"` - Fix #319
### v3.9.0 - 2025/01/26
- sound can be enabled with `sound.enabled = true`, which allows adding job parameters such as `on_success = "play-sound(name=90s-game-ui-6,volume=50)"` and `on_failure = "play-sound(name=beep-warning)"` - Fix #303
- fix freeze on mac on config change - Fix #306 - Thanks @irh
- fix race condition on config reload when saved by helix - Fix #310
### v3.8.0 - 2025/01/17
- with `--headless`, bacon runs without TUI - Fix #293
- `--config-toml` argument - Fix #284
- fix workspace level Cargo.toml file not watched
- `copy-unstyled-output` internal that you can bind with eg `ctrl-c = "copy-unstyled-output"`. It's currently gated by the `"clipboard"` feature, please give feedback regarding compilation and usage - Fix #282 - Thanks @letsgetrusty
- list of loaded config files displayed in help page
### v3.7.0 - 2024/12/27
- search with the / key - Fix #224
- fix nextest analyzer not capturing test output with nextest 0.9.86+ - Fix #280
- show an error if the command fails to spawn - Thanks @jyn514
### v3.6.0 - 2024/12/15
- support for cpp (gcc & clang) with `analyzer = "cpp"` - Thanks @bryceberger
- removal of the `--path` argument, replaced with `--project` and `--watch` (overrides the list of watched files). The path to the project can also be given as trailing argument as today. - Fix #274
- the `cargo_json` analyzer can now be leveraged to export data from the cargo metadata `Diagnostic` and `DiagnosticSpan` structs - Fix #249
### v3.5.0 - 2024/12/05
- support for biome with `analyzer = "biome"`
- support for ruff with `analyzer = "python_ruff"`
- read bacon.toml in workspace/.config and package/.config - Fix #268
- read `workspace.metadata.bacon` and `package.metadata.bacon` config elements in `Cargo.toml` files - Fix #241
- fix locations export when launching bacon inside a rust workspace but with a non cargo tool
### v3.4.0 - 2024/11/30
- new analyzer framework, make it possible for bacon to call more tools
- Python Pytest analyzer
- Analyzer for `cargo check --message-format json-diagnostic-rendered-ansi` - see #269
- allow specifying scroll-pages action with a floating point number - Fix #264
### v3.3.0 - 2024/11/16
- bacon can now be launched without Cargo.toml file
- eslint analyzer (set `analyzer = "eslint"` in your job definition)
- Python Unittest analyzer (set `analyzer = "python_unittest"` in your job definition)
- fix Miri output seen as wrong when there's only warnings
- allow defining environment vars for all jobs - Thanks @joshka
- set `env.CARGO_TERM_COLOR = "always"` in default conf, thus making `"--color", "always"` useless in all cargo based job definition - Thanks @joshka
- new `ignore` job parameter, accepts a list of glob patterns
- more lenient detection of warnings and errors due to 'miri run' not supporting `--color` - Fix #251
### v3.2.0 - 2024/11/04
- allow defining `default_watch` and `watch` at global level, so that they apply to all jobs unless overridden.
### v3.1.2 - 2024/10/29
- "config loaded" message always automatically disappears after a few seconds
### v3.1.1 - 2024/10/18
#### Major feature: hot reload of config files
When a configuration file is modified, bacon automatically reloads its config. So you don't need to quit/relaunch when you add a new job, add a key-binding, change the allowed lints of clippy, etc. - Fix #29
### v3.0.0 - 2024/10/09
#### Major feature: nextest support
Hit `n` to launch the nextest job.
It's a default job, but you may define your own one by specifying `analyzer = "nextest"` in the job entry.
Internally, this is supported by a new analyzer framework which will allow easier analysis updates or addition of analysis for other tools (or languages).
Fix #196
#### Major feature: scope test job to failure
If you're running a test or nextest job and you want only the failing test to be retried, hit `f`.
If you want all tests to be executed again, hit `esc`.
Fix #214
#### Other features:
- grace period (by default 5ms) after a file event before the real launch of the command and during which other file events may be disregarded. Helps when saving a file changes several ones (eg backup then rename).
- new `exports` structure in configuration. New `analysis` export bound by default to `ctrl-e`. The old syntax defining locations export is still supported but won't appear in documentations anymore.
- recognize panic location in test - Fix #208
- lines to ignore can be specified as a set of regular expressions in a `ignored_lines` field either in the job or at the top of the prefs or bacon.toml - Fix #223
- `toggle-backtrace` accepts an optional level: `toggle-backtrace(1)` or `toggle-backtrace(full)` - Experimental - Fix #210
- configuration paths can be passed in `BACON_PREFS` and `BACON_CONFIG` env vars - Fix #76
#### Fixes:
- fix changing wrapping mode not always working in raw output mode - Fix #234
### v2.21.0 - 2024/09/14
With `show_changes_count=true`, you can see the number of file changes that occurred since last job start.
#### Major change: the `on_change_strategy` setting and a new default strategy
* With `on_change_strategy = "kill_then_restart"`, the current job is immediately killed and a new job restarted. This is the behavior that bacon had before this PR. It has the downside of never allowing any job to complete if you're always changing files and the job is just a little too long to finish between changes.
* With `on_change_strategy = "wait_then_restart"` (which is the new default, so you can omit it), bacon waits for the job to finish before restarting it. This is probably much better when the jobs aren't instant and you want to continue changing files while it's computing.
The on_change_strategy can be defined in the global prefs, in the project settings, and even for a specific job.
### v2.20.0 - 2024/08/13
- until now, when there was no `bacon.toml` file, the default one was applied, overriding the settings of `prefs.toml`. This is no longer the case: this default file is now applied before `prefs.toml` (which overrides it) - Fix #157
- `kill` job parameter - Thanks @pcapriotti
### v2.19.0 - 2024/08/07
- `extraneous_args` job parameter - Thanks @TheTollingBell
- pause/unpause bound to 'p' - Fix #194
### v2.18.2 - 2024/05/31
- fix failure to recognize location in test compilation output - Fix #190
### v2.18.1 - 2024/05/21
- update dependencies (especially locked ones) to fix compilation on nightly
### v2.18.0 - 2024/05/20
- new `{context}` possible part for exported locations, originally designed for [bacon-ls](https://github.com/crisidev/bacon-ls) but available for other purposes - Thanks @crisidev
### v2.17.0 - 2024/05/05
- default binding for 'c' in bacon.toml is now the new 'clippy-all' job which does what the old 'clippy' job was doing. 'clippy' job changed to not run on all targets. Default bacon.toml explain how to bind 'c' to clippy instead of 'clippy-all' - Fix #167
- expand env vars in job command unless the job specifies `expand_env_vars = false` - Fix #181
- some file events filtered out from watch (feedback welcome, especially if you notice some failures to recompute)
- parse test results even when tests are run with `-q`/`--quiet` - Thanks @narpfel
### v2.16.0 - 2024/03/30
- `on_success` triggered with warning or errors depending on `allow_warnings` and `allow_failures` - Fix #179
- `--no-help-line` option. This is experimental and may be removed depending on feedback and future additions to this line - Thanks @danielwolbach
### v2.15.0 - 2024/03/05
- insert features related arguments before the -- when there's some - Fix #171
- fix offset in Windows terminal - Fix #175
- better `--help` with examples and main shortcuts
- rewriten execution engine
### v2.14.2 - 2024/02/10
- update dependencies to fix bacon not compiling on nightly - Fix #168
### v2.14.1 - 2023/12/24
- fix output not cleared when cargo is in quiet mode and there's nothing - Fix #131
### v2.14.0 - 2023/10/04
- F5 now clears the output before running the job (now bound to `refresh` internal)
- new optional `background` job parameter, should be set to `false` for never ending jobs - Fix #161
### v2.13.0 - 2023/09/15
- fix mouse wheel scrolling not working on Windows - Fix #153 - Thanks @Adham-A
- detect locations in test failures, thus enabling jumps to those failures
- more relevant suggestions in help line
- add a default job for running examples
### v2.12.1 - 2023/07/22
- fix some scroll problem, especially in reverse - Fix #86
### v2.12.0 - 2023/07/20
- better `--help`
### v2.11.1 - 2023/07/13
- fix warning summary sometimes confused with a warning - Fix #149
### v2.11.0 - 2023/06/30
- allow defining env vars for jobs - Fix #145
### v2.10.0 - 2023/06/27
- accept bacon.toml file at workspace level - Fix #141
### v2.9.0 - 2023/06/19
- export format and path can now be configured
- default export format includes error/warning summary (nvim-bacon has been updated in parallel) - Fix #127
- fix output non scrollable when non parsable
- fix test non parsed when styled and sent to stdout instead of stderr - Fix #137
### v2.8.1 - 2023/04/22
- color rendering of cargo test - Fix #124
### v2.8.0 - 2023/03/23
- By default, "src", "tests", "benches", "examples" are now watched - Fix #119
- `default_watch` bool job parameter - Fix #92
### v2.7.0 - 2023/03/14
- watch "examples" directory in default run job
- fix warnings not recognized on Windows - Fix #70 - Thanks @crillon
### v2.6.3 - 2023/03/09
- remove keybindings from default bacon.toml - Fix #116
### v2.6.2 - 2023/03/03
- more consistent "pass!" - Thanks @zolrath
### v2.6.1 - 2023/02/22
- fix a dependency compilation problem - Fix #112
### v2.6.0 - 2023/02/21
- change default value of 'wrap' setting to true
- `--offline` experimental launch argument, prevents bacon (but not jobs) from accessing the network. Downside is a potentially less relevant list of watched files and directories - Fix #110
### v2.5.0 - 2023/01/19
- new `allow_failures` job parameter - Fix #99
- `rerun` internal bound by default to F5 - Fix #105
### v2.4.0 - 2023/01/12
Major feature:
The global prefs.toml and the local bacon.toml file now have the same properties, the local bacon.toml overriding the global prefs.toml file. Among the consequences: you can have a list of default global jobs; you can set a different preferences (eg wrapping, summary, etc.) for a specific repository. The default configuration files and the recommended best practices are unchanged - Fix #101
### v2.3.0 - 2022/12/30
- doesn't launch job when the modified file is excluded by gitignore rules - Fix #32
### v2.2.8 - 2022/12/15
- remove double-dash from default run configuration - Fix #96
### v2.2.7 - 2022/12/14
- capture output of "should panic" tests - Fix #95
### v2.2.6 - 2022/12/08
- fix a compilation problem - Fix #94
### v2.2.5 - 2022/10/08
- fix wrong scrollbar in several cases of wrapping
### v2.2.4 - 2022/10/05
- fix inability to scroll to last line sometimes
### v2.2.3 - 2022/09/17
- fix a compilation problem on Window - Thanks @Stargateur - Fix #87
### v2.2.2 - 2022/08/28
- define a new `allow_warnings` job setting. When it's true, the job is considered successful even when there are warnings. This is default on the `run` job, which means the `cargo run` output is displayed even when there are warnings - Fix #81
- allow `cargo --prefs` to be ran from outside cargo projects - Fix #84
### v2.2.1 - 2022/05/12
- update some dependencies
### v2.2.0 - 2022/05/12
- Locations exported in .bacon-locations now made absolute so that IDE plugins don't have to know the package's root
- job cancelling now works on unresponsive jobs too. This is a quite heavy change as the current implementation involves bringing in async and it's not 100% clean but it solves a major problem, further improvements could be welcome - Fix #78 - Thanks @nolanderc
- you can refer to cargo aliases by prefixing jobs with `alias:`, either when setting up keybindings, defaults, or when launching bacon. Example: `bacon alias:q` to launch the cargo task aliased as `q` - Fix #77
### v2.1.0 - 2022/03/26
Major feature:
The `export-locations` argument (shortened in `-e`) generates a `.bacon-locations` file which can be used by IDE plugins.
A plugin has been made for neovim: [nvim-bacon](https://github.com/Canop/nvim-bacon) and other ones would be welcome.
Minor changes:
- wrapping now applies to all outputs, even non interpreted ones like the output of `cargo run`.
### v2.0.1 - 2022/02/18
- fix summary of warnings counted as warning
### v2.0.0 - 2022/02/16
#### Major features:
- It's now possible to configure key bindings in the prefs.toml file. Those key bingings can trigger internal actions (scrolling, toggling, quitting) or jobs (for example you can launch `cargo test` on the `t` key. - Fix #52
#### Other changes:
- help page, listing all key-bindings
- a job is said to be *successful* when there's no error, test failure or warning. When a job is successful, its output is displayed by bacon. This makes it possible to have a `cargo run` job, for example.
- it's possible to define an *action* to run when a job is successful. For example you can launch a `cargo doc --open` job on a key, and have bacon switch to the previous job with the `on_success = "back` trigger so that you don't open a browser page on every change
- arguments given after `--` are given to the job - Fix #67
- there's a web documentation site now, you should have a look: https://dystroy.org/bacon
Minor changes:
- fix character being lost behind scrollbar on wrapping
- replaced argh with clap for launch arg parsing. The `--help` presentation is thus different. `bacon -h` now supported.
### v1.2.5 - 2022/01/29
- fix missing output of "no_run" doctests - Fix #64
- restrict naming of jobs to [\w-]+ regex (you were unlikely to use other chars due to the TOML format anyway)
### v1.2.4 - 2021/11/27
- fix inability to deal with some inter-member dependencies on Windows - Fix #59 - Thanks @jDomantas
- fix compilation broken due to change in anyhow 1.0.49 - Fix #63
### v1.2.3 - 2021/11/15
- add the "clippy-all" default job - Thanks @rukai
- alpha sort the table outputted by `bacon --list-jobs` - Thanks @rukai
### v1.2.2 - 2021/10/18
- solve a dependency build problem - Fix #55
### v1.2.1 - 2021/10/03
- propose to toggle backtraces when suggestion is found in cargo's output
### v1.1.8 - 2021/07/31
- move to more recent versions of some crates - Fix #51
- `bacon --list-jobs` (or `bacon -l`) lists all available jobs
### v1.1.7 - 2021/07/11
- recognize doc test output - Fix #49
- display 4 spaces for tabs - Fix #50
### v1.1.6 - 2021/06/22
- the default conf now contains a [doc] job
- `--all-features` launch option
### v1.1.5 - 2021/02/27
- fix wrong version number in bacon.log
### v1.1.4 - 2021/02/10
It's possible to define directories to watch in the bacon.toml config file. For example, by default the `test` job watches the `tests` directory if it exists - Thanks @SafariMonkey
### v1.1.3 - 2021/01/29
* `check-all` target now checks all - Fix #27
* `--no-default-features` and `--features` - Fix #31
### v1.1.2 - 2021/01/05
Revert standard job to ignore tests because compilation with them is too slow. A new default job is added.
### v1.1.1 - 2021/01/03
Don't consider test fails as command fails (ie display the count of test fails in `bacon test` instead of command error)
### v1.1.0 - 2020/12/26
If the job's command returns an error code and no error was read in the output, bacon now displays the output and the error code instead of letting the user think there's no error
### v1.0.1 - 2020/11/21
* vim key bindings can be enabled in prefs
* default job is now `cargo check --tests` to check the code for tests compiles too (without running them)
### v1.0.0 - 2020/11/19
* nothing new... so it's stable enough to be tagged 1.0
### v0.6.0 - 2020/11/15
* `bacon test` shows test failures - Fix #3 - Note that you need to remove then rebuild your bacon.toml file to use this new job
### v0.5.3 - 2020/11/14
* "reverse" option allows having the focus on bottom - Fix #19
* initial compilation autoscroll based on scroll position - Fix #22
* remove flickering
### v0.5.2 - 2020/11/14
* fix bacon ending with an error when prefs file is missing
### v0.5.1 - 2020/11/13
* `bacon --prefs` shows or creates a prefs file which can be changed to defined default display settings
(currently "summary" and "wrap")
### v0.5.0 - 2020/11/12
* `bacon --init` creates a default `bacon.toml` file which can be customized to add jobs or change the standard ones
* bacon launch arguments changed to ease use of customized jobs
### v0.4.3 - 2020/11/11
* fix report only taking the first package into account (for workspaces)
### v0.4.2 - 2020/11/11
* fix some regressions in error and warning detection
### v0.4.1 - 2020/11/10
* reduce useless redraws during computation
### v0.4.0 - 2020/11/10
* make it possible to watch only part of the sources: the passed directory (or the current one), when not a package directory (i.e. not containing a Cargo.toml file), will be the one watched - Thanks @nikhilmitrax and @jyn514 for their help
* logo - Thanks @petervaro
* line wrapping (and rewrapping on resize)
### v0.3.2 - 2020/11/08
* when quitting bacon, kill `cargo check` if running
### v0.3.1 - 2020/11/06
* better scroll position after toggling summary mode or resizing
* space key now usable for page down
### v0.3.0 - 2020/11/06
* keep lines with location in summary mode - Fix #11
* allow scrolling the report (arrow keys, page keys, home & end keys, mouse wheel) - Fix #6
* log file renamed to 'bacon.log' to avoid collisions
* initial execution is displayed raw before report computation - Fix #8
* initial execution can be interrupted, scrolled - Fix #12
### v0.2.0 - 2020-10-01
* add the summary mode
### v0.1.1 - 2020-09-29
* also watches Cargo.toml
### v0.1.0 - 2020-09-29
Initial version
bacon-3.12.0/Cargo.lock 0000644 00000240444 00000000001 0010164 0 ustar # This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "alsa"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43"
dependencies = [
"alsa-sys",
"bitflags 2.9.0",
"cfg-if",
"libc",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "arboard"
version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4"
dependencies = [
"clipboard-win",
"log",
"objc2",
"objc2-app-kit",
"objc2-foundation",
"parking_lot",
"x11rb",
]
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bacon"
version = "3.12.0"
dependencies = [
"anyhow",
"arboard",
"cargo_metadata",
"clap",
"clap-help",
"clap_complete",
"cli-log",
"crokey",
"ctrlc",
"directories-next",
"gix",
"glob",
"iq",
"lazy-regex",
"notify",
"rodio",
"rustc-hash 2.1.1",
"serde",
"serde_json",
"termimad",
"toml",
"unicode-width 0.2.0",
"vte",
]
[[package]]
name = "bindgen"
version = "0.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
dependencies = [
"bitflags 2.9.0",
"cexpr",
"clang-sys",
"itertools",
"proc-macro2",
"quote",
"regex",
"rustc-hash 1.1.0",
"shlex",
"syn 2.0.100",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "block2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
dependencies = [
"objc2",
]
[[package]]
name = "bstr"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
dependencies = [
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "camino"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
"thiserror 2.0.12",
]
[[package]]
name = "cc"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap-help"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc01b70b5fd7e87b2ae778cfd151120355002f44ab1504e9943151a52cae8171"
dependencies = [
"clap",
"termimad",
"terminal-light",
]
[[package]]
name = "clap_builder"
version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_complete"
version = "4.5.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5c5508ea23c5366f77e53f5a0070e5a84e51687ec3ef9e0464c86dc8d13ce98"
dependencies = [
"clap",
"clap_lex",
"is_executable",
"shlex",
]
[[package]]
name = "clap_derive"
version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "cli-log"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e220aa46e5395cd473a054f8e7e52403108ce147a4eb68c001afb01672a4e046"
dependencies = [
"chrono",
"file-size",
"log",
"proc-status",
]
[[package]]
name = "clipboard-win"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
dependencies = [
"error-code",
]
[[package]]
name = "clru"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59"
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "combine"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "coolor"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "691defa50318376447a73ced869862baecfab35f6aabaa91a4cd726b315bfe1a"
dependencies = [
"crossterm",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "coreaudio-rs"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
dependencies = [
"bitflags 1.3.2",
"core-foundation-sys",
"coreaudio-sys",
]
[[package]]
name = "coreaudio-sys"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b"
dependencies = [
"bindgen",
]
[[package]]
name = "cpal"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
dependencies = [
"alsa",
"core-foundation-sys",
"coreaudio-rs",
"dasp_sample",
"jni",
"js-sys",
"libc",
"mach2",
"ndk",
"ndk-context",
"oboe",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crokey"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520e83558f4c008ac06fa6a86e5c1d4357be6f994cce7434463ebcdaadf47bb1"
dependencies = [
"crokey-proc_macros",
"crossterm",
"once_cell",
"serde",
"strict",
]
[[package]]
name = "crokey-proc_macros"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370956e708a1ce65fe4ac5bb7185791e0ece7485087f17736d54a23a0895049f"
dependencies = [
"crossterm",
"proc-macro2",
"quote",
"strict",
"syn 1.0.109",
]
[[package]]
name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crossterm"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
"bitflags 2.9.0",
"crossterm_winapi",
"mio",
"parking_lot",
"rustix 0.38.44",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "ctrlc"
version = "3.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
dependencies = [
"nix",
"windows-sys 0.59.0",
]
[[package]]
name = "dasp_sample"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]]
name = "directories-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "error-code"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
[[package]]
name = "faster-hex"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
dependencies = [
"serde",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "file-size"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9544f10105d33957765016b8a9baea7e689bf1f0f2f32c2fa2f568770c38d2b3"
[[package]]
name = "filetime"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.59.0",
]
[[package]]
name = "flate2"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "gethostname"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
dependencies = [
"libc",
"windows-targets 0.48.5",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets 0.52.6",
]
[[package]]
name = "gix"
version = "0.67.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d3e78ddac368d3e3bfbc2862bc2aafa3d89f1b15fed898d9761e1ec6f3f17f"
dependencies = [
"gix-actor",
"gix-commitgraph",
"gix-config",
"gix-date",
"gix-diff",
"gix-discover",
"gix-features",
"gix-fs",
"gix-glob",
"gix-hash",
"gix-hashtable",
"gix-ignore",
"gix-index",
"gix-lock",
"gix-object",
"gix-odb",
"gix-pack",
"gix-path",
"gix-ref",
"gix-refspec",
"gix-revision",
"gix-revwalk",
"gix-sec",
"gix-tempfile",
"gix-trace",
"gix-traverse",
"gix-url",
"gix-utils",
"gix-validate",
"gix-worktree",
"once_cell",
"smallvec",
"thiserror 1.0.69",
]
[[package]]
name = "gix-actor"
version = "0.33.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20018a1a6332e065f1fcc8305c1c932c6b8c9985edea2284b3c79dc6fa3ee4b2"
dependencies = [
"bstr",
"gix-date",
"gix-utils",
"itoa",
"thiserror 2.0.12",
"winnow 0.6.26",
]
[[package]]
name = "gix-bitmap"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540"
dependencies = [
"thiserror 2.0.12",
]
[[package]]
name = "gix-chunk"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f"
dependencies = [
"thiserror 2.0.12",
]
[[package]]
name = "gix-commitgraph"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8da6591a7868fb2b6dabddea6b09988b0b05e0213f938dbaa11a03dd7a48d85"
dependencies = [
"bstr",
"gix-chunk",
"gix-features",
"gix-hash",
"memmap2",
"thiserror 2.0.12",
]
[[package]]
name = "gix-config"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bedd1bf1c7b994be9d57207e8e0de79016c05e2e8701d3015da906e65ac445e"
dependencies = [
"bstr",
"gix-config-value",
"gix-features",
"gix-glob",
"gix-path",
"gix-ref",
"gix-sec",
"memchr",
"once_cell",
"smallvec",
"thiserror 1.0.69",
"unicode-bom",
"winnow 0.6.26",
]
[[package]]
name = "gix-config-value"
version = "0.14.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11365144ef93082f3403471dbaa94cfe4b5e72743bdb9560719a251d439f4cee"
dependencies = [
"bitflags 2.9.0",
"bstr",
"gix-path",
"libc",
"thiserror 2.0.12",
]
[[package]]
name = "gix-date"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c57c477b645ee248b173bb1176b52dd528872f12c50375801a58aaf5ae91113f"
dependencies = [
"bstr",
"itoa",
"jiff",
"thiserror 2.0.12",
]
[[package]]
name = "gix-diff"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9850fd0c15af113db6f9e130d13091ba0d3754e570a2afdff9e2f3043da260e"
dependencies = [
"bstr",
"gix-hash",
"gix-object",
"thiserror 1.0.69",
]
[[package]]
name = "gix-discover"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c522e31f458f50af09dfb014e10873c5378f702f8049c96f508989aad59671f6"
dependencies = [
"bstr",
"dunce",
"gix-fs",
"gix-hash",
"gix-path",
"gix-ref",
"gix-sec",
"thiserror 1.0.69",
]
[[package]]
name = "gix-features"
version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d85d673f2e022a340dba4713bed77ef2cf4cd737d2f3e0f159d45e0935fd81f"
dependencies = [
"crc32fast",
"crossbeam-channel",
"flate2",
"gix-hash",
"gix-trace",
"gix-utils",
"libc",
"once_cell",
"parking_lot",
"prodash",
"sha1_smol",
"thiserror 2.0.12",
"walkdir",
]
[[package]]
name = "gix-fs"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3d4fac505a621f97e5ce2c69fdc425742af00c0920363ca4074f0eb48b1db9"
dependencies = [
"fastrand",
"gix-features",
"gix-utils",
]
[[package]]
name = "gix-glob"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf69a6bec0a3581567484bf99a4003afcaf6c469fd4214352517ea355cf3435"
dependencies = [
"bitflags 2.9.0",
"bstr",
"gix-features",
"gix-path",
]
[[package]]
name = "gix-hash"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5eccc17194ed0e67d49285e4853307e4147e95407f91c1c3e4a13ba9f4e4ce"
dependencies = [
"faster-hex",
"thiserror 2.0.12",
]
[[package]]
name = "gix-hashtable"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef65b256631078ef733bc5530c4e6b1c2e7d5c2830b75d4e9034ab3997d18fe"
dependencies = [
"gix-hash",
"hashbrown 0.14.5",
"parking_lot",
]
[[package]]
name = "gix-ignore"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b1fb24d2a4af0aa7438e2771d60c14a80cf2c9bd55c29cf1712b841f05bb8a"
dependencies = [
"bstr",
"gix-glob",
"gix-path",
"gix-trace",
"unicode-bom",
]
[[package]]
name = "gix-index"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27619009ca1ea33fd885041273f5fa5a09163a5c1d22a913b28d7b985e66fe29"
dependencies = [
"bitflags 2.9.0",
"bstr",
"filetime",
"fnv",
"gix-bitmap",
"gix-features",
"gix-fs",
"gix-hash",
"gix-lock",
"gix-object",
"gix-traverse",
"gix-utils",
"gix-validate",
"hashbrown 0.14.5",
"itoa",
"libc",
"memmap2",
"rustix 0.38.44",
"smallvec",
"thiserror 1.0.69",
]
[[package]]
name = "gix-lock"
version = "15.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd3ab68a452db63d9f3ebdacb10f30dba1fa0d31ac64f4203d395ed1102d940"
dependencies = [
"gix-tempfile",
"gix-utils",
"thiserror 2.0.12",
]
[[package]]
name = "gix-object"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a77b6e7753d298553d9ae8b1744924481e7a49170983938bb578dccfbc6fc1a"
dependencies = [
"bstr",
"gix-actor",
"gix-date",
"gix-features",
"gix-hash",
"gix-hashtable",
"gix-utils",
"gix-validate",
"itoa",
"smallvec",
"thiserror 1.0.69",
"winnow 0.6.26",
]
[[package]]
name = "gix-odb"
version = "0.64.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb86aadf7f1b2f980601b4fc94309706f9700f8008f935dc512d556c9e60f61"
dependencies = [
"arc-swap",
"gix-date",
"gix-features",
"gix-fs",
"gix-hash",
"gix-hashtable",
"gix-object",
"gix-pack",
"gix-path",
"gix-quote",
"parking_lot",
"tempfile",
"thiserror 1.0.69",
]
[[package]]
name = "gix-pack"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "363e6e59a855ba243672408139db68e2478126cdcfeabb420777df4a1f20026b"
dependencies = [
"clru",
"gix-chunk",
"gix-features",
"gix-hash",
"gix-hashtable",
"gix-object",
"gix-path",
"memmap2",
"smallvec",
"thiserror 1.0.69",
]
[[package]]
name = "gix-path"
version = "0.10.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c40f12bb65a8299be0cfb90fe718e3be236b7a94b434877012980863a883a99f"
dependencies = [
"bstr",
"gix-trace",
"home",
"once_cell",
"thiserror 2.0.12",
]
[[package]]
name = "gix-quote"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e49357fccdb0c85c0d3a3292a9f6db32d9b3535959b5471bb9624908f4a066c6"
dependencies = [
"bstr",
"gix-utils",
"thiserror 2.0.12",
]
[[package]]
name = "gix-ref"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47385e71fa2d9da8c35e642ef4648808ddf0a52bc93425879088c706dfeaea2"
dependencies = [
"gix-actor",
"gix-features",
"gix-fs",
"gix-hash",
"gix-lock",
"gix-object",
"gix-path",
"gix-tempfile",
"gix-utils",
"gix-validate",
"memmap2",
"thiserror 1.0.69",
"winnow 0.6.26",
]
[[package]]
name = "gix-refspec"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0022038a09d80d9abf773be8efcbb502868d97f6972b8633bfb52ab6edaac442"
dependencies = [
"bstr",
"gix-hash",
"gix-revision",
"gix-validate",
"smallvec",
"thiserror 1.0.69",
]
[[package]]
name = "gix-revision"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ee8eb4088fece3562af4a5d751e069f90e93345524ad730512185234c4b55f1"
dependencies = [
"bstr",
"gix-commitgraph",
"gix-date",
"gix-hash",
"gix-object",
"gix-revwalk",
"thiserror 1.0.69",
]
[[package]]
name = "gix-revwalk"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c9a9496da98d36ff19063a8576bf09a87425583b709a56dc5594fffa9d39b2"
dependencies = [
"gix-commitgraph",
"gix-date",
"gix-hash",
"gix-hashtable",
"gix-object",
"smallvec",
"thiserror 1.0.69",
]
[[package]]
name = "gix-sec"
version = "0.10.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d84dae13271f4313f8d60a166bf27e54c968c7c33e2ffd31c48cafe5da649875"
dependencies = [
"bitflags 2.9.0",
"gix-path",
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "gix-tempfile"
version = "15.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2feb86ef094cc77a4a9a5afbfe5de626897351bbbd0de3cb9314baf3049adb82"
dependencies = [
"gix-fs",
"libc",
"once_cell",
"parking_lot",
"tempfile",
]
[[package]]
name = "gix-trace"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7"
[[package]]
name = "gix-traverse"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f20f1b13cc4fa6ba92b24e6aa0c2fb6a34beb4458ef88c6300212db504e818df"
dependencies = [
"bitflags 2.9.0",
"gix-commitgraph",
"gix-date",
"gix-hash",
"gix-hashtable",
"gix-object",
"gix-revwalk",
"smallvec",
"thiserror 1.0.69",
]
[[package]]
name = "gix-url"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d096fb733ba6bd3f5403dba8bd72bdd8809fe2b347b57844040b8f49c93492d9"
dependencies = [
"bstr",
"gix-features",
"gix-path",
"percent-encoding",
"thiserror 2.0.12",
"url",
]
[[package]]
name = "gix-utils"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff08f24e03ac8916c478c8419d7d3c33393da9bb41fa4c24455d5406aeefd35f"
dependencies = [
"fastrand",
"unicode-normalization",
]
[[package]]
name = "gix-validate"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eaa01c3337d885617c0a42e92823922a2aea71f4caeace6fe87002bdcadbd90"
dependencies = [
"bstr",
"thiserror 2.0.12",
]
[[package]]
name = "gix-worktree"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d345e5b523550fe4fa0e912bf957de752011ccfc87451968fda1b624318f29c"
dependencies = [
"bstr",
"gix-features",
"gix-fs",
"gix-glob",
"gix-hash",
"gix-ignore",
"gix-index",
"gix-object",
"gix-path",
]
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core 0.52.0",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
]
[[package]]
name = "inotify"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
dependencies = [
"bitflags 1.3.2",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "iq"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5965c1d0f04558bc506736debf41699d807a1c6e1a7963efca7d85871257ee91"
dependencies = [
"lazy-regex",
"serde",
"serde_json",
]
[[package]]
name = "is_executable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2"
dependencies = [
"winapi",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c04ef77ae73f3cf50510712722f0c4e8b46f5aaa1bf5ffad2ae213e6495e78e5"
dependencies = [
"jiff-tzdb-platform",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys 0.59.0",
]
[[package]]
name = "jiff-tzdb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "962e1dfe9b2d75a84536cf5bf5eaaa4319aa7906c7160134a22883ac316d5f31"
[[package]]
name = "jiff-tzdb-platform"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a63c62e404e7b92979d2792352d885a7f8f83fd1d0d31eea582d77b2ceca697e"
dependencies = [
"jiff-tzdb",
]
[[package]]
name = "jni"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
dependencies = [
"cesu8",
"cfg-if",
"combine",
"jni-sys",
"log",
"thiserror 1.0.69",
"walkdir",
"windows-sys 0.45.0",
]
[[package]]
name = "jni-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "kqueue"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
]
[[package]]
name = "lazy-regex"
version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126"
dependencies = [
"lazy-regex-proc_macros",
"once_cell",
"regex",
]
[[package]]
name = "lazy-regex-proc_macros"
version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.100",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libloading"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
]
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.9.0",
"libc",
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]]
name = "litemap"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "mach2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
dependencies = [
"libc",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
dependencies = [
"libc",
]
[[package]]
name = "minimad"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c5d708226d186590a7b6d4a9780e2bdda5f689e0d58cd17012a298efd745d2"
dependencies = [
"once_cell",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
]
[[package]]
name = "ndk"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
dependencies = [
"bitflags 2.9.0",
"jni-sys",
"log",
"ndk-sys",
"num_enum",
"thiserror 1.0.69",
]
[[package]]
name = "ndk-context"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
[[package]]
name = "ndk-sys"
version = "0.5.0+25.2.9519653"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
dependencies = [
"jni-sys",
]
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.9.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "notify"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
dependencies = [
"bitflags 2.9.0",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"notify-types",
"walkdir",
"windows-sys 0.52.0",
]
[[package]]
name = "notify-types"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
dependencies = [
"instant",
]
[[package]]
name = "num-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "num_enum"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "objc-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
[[package]]
name = "objc2"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
dependencies = [
"objc-sys",
"objc2-encode",
]
[[package]]
name = "objc2-app-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
dependencies = [
"bitflags 2.9.0",
"block2",
"libc",
"objc2",
"objc2-core-data",
"objc2-core-image",
"objc2-foundation",
"objc2-quartz-core",
]
[[package]]
name = "objc2-core-data"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
dependencies = [
"bitflags 2.9.0",
"block2",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-image"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
dependencies = [
"block2",
"objc2",
"objc2-foundation",
"objc2-metal",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.9.0",
"block2",
"libc",
"objc2",
]
[[package]]
name = "objc2-metal"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
dependencies = [
"bitflags 2.9.0",
"block2",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
dependencies = [
"bitflags 2.9.0",
"block2",
"objc2",
"objc2-foundation",
"objc2-metal",
]
[[package]]
name = "oboe"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
dependencies = [
"jni",
"ndk",
"ndk-context",
"num-derive",
"num-traits",
"oboe-sys",
]
[[package]]
name = "oboe-sys"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d"
dependencies = [
"cc",
]
[[package]]
name = "once_cell"
version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "proc-macro-crate"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proc-status"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0e0c0ac915e7b76b47850ba4ffc377abde6c6ff9eeace61d0a89623db449712"
dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "prodash"
version = "29.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ee7ce24c980b976607e2d6ae4aae92827994d23fed71659c3ede3f92528b58b"
dependencies = [
"log",
"parking_lot",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [
"bitflags 2.9.0",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.15",
"libredox",
"thiserror 1.0.69",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rodio"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1"
dependencies = [
"cpal",
"symphonia",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags 2.9.0",
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825"
dependencies = [
"bitflags 2.9.0",
"errno",
"libc",
"linux-raw-sys 0.9.3",
"windows-sys 0.59.0",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
[[package]]
name = "sha1_smol"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strict"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f42444fea5b87a39db4218d9422087e66a85d0e7a0963a439b07bcdf91804006"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "symphonia"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9"
dependencies = [
"lazy_static",
"symphonia-bundle-mp3",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-bundle-mp3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4"
dependencies = [
"lazy_static",
"log",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-core"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3"
dependencies = [
"arrayvec",
"bitflags 1.3.2",
"bytemuck",
"lazy_static",
"log",
]
[[package]]
name = "symphonia-metadata"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c"
dependencies = [
"encoding_rs",
"lazy_static",
"log",
"symphonia-core",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "tempfile"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600"
dependencies = [
"fastrand",
"getrandom 0.3.1",
"once_cell",
"rustix 1.0.2",
"windows-sys 0.59.0",
]
[[package]]
name = "termimad"
version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8e19c6dbf107bec01d0e216bb8219485795b7d75328e4fa5ef2756c1be4f8dc"
dependencies = [
"coolor",
"crokey",
"crossbeam",
"lazy-regex",
"minimad",
"serde",
"thiserror 1.0.69",
"unicode-width 0.1.14",
]
[[package]]
name = "terminal-light"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a9474484d1a0c031cd7d065c6f027a376859c9fedb32c94df3d7a797218bbb7"
dependencies = [
"coolor",
"crossterm",
"thiserror 1.0.69",
"xterm-query",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.7.4",
]
[[package]]
name = "unicode-bom"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vte"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd"
dependencies = [
"arrayvec",
"memchr",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn 2.0.100",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
dependencies = [
"windows-core 0.54.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
dependencies = [
"windows-result",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
[[package]]
name = "windows-result"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28"
dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags 2.9.0",
]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "x11rb"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12"
dependencies = [
"gethostname",
"rustix 0.38.44",
"x11rb-protocol",
]
[[package]]
name = "x11rb-protocol"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
[[package]]
name = "xterm-query"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fc10a48747cba9264d985322216216cdafd823080cc8af5488f2afc8dc776c"
dependencies = [
"nix",
"thiserror 1.0.69",
]
[[package]]
name = "yoke"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"synstructure",
]
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
bacon-3.12.0/Cargo.toml 0000644 00000004652 00000000001 0010206 0 ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.76"
name = "bacon"
version = "3.12.0"
authors = ["dystroy "]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "background rust compiler"
readme = "README.md"
keywords = [
"rust",
"background",
"compiler",
"watch",
"inotify",
]
categories = [
"command-line-utilities",
"development-tools",
]
license = "AGPL-3.0"
repository = "https://github.com/Canop/bacon"
[features]
clipboard = ["arboard"]
default = []
sound = ["rodio"]
[lib]
name = "bacon"
path = "src/lib.rs"
[[bin]]
name = "bacon"
path = "src/main.rs"
[dependencies.anyhow]
version = "1.0"
[dependencies.arboard]
version = "3.4"
optional = true
default-features = false
[dependencies.cargo_metadata]
version = "0.19"
[dependencies.clap]
version = "4.5"
features = [
"derive",
"cargo",
]
[dependencies.clap-help]
version = "1.3.2"
[dependencies.clap_complete]
version = "4.5.44"
features = ["unstable-dynamic"]
[dependencies.cli-log]
version = "2.1"
[dependencies.crokey]
version = "1.1"
[dependencies.ctrlc]
version = "3.4"
[dependencies.directories-next]
version = "2.0.0"
[dependencies.gix]
version = "0.67"
features = [
"index",
"excludes",
"parallel",
]
default-features = false
[dependencies.glob]
version = "0.3"
[dependencies.iq]
version = "0.2"
features = ["template"]
[dependencies.lazy-regex]
version = "3.4.1"
[dependencies.notify]
version = "7.0"
[dependencies.rodio]
version = "0.20"
features = ["mp3"]
optional = true
default-features = false
[dependencies.rustc-hash]
version = "2"
[dependencies.serde]
version = "1.0"
features = ["derive"]
[dependencies.serde_json]
version = "1.0"
[dependencies.termimad]
version = "0.31.1"
[dependencies.toml]
version = "0.8"
[dependencies.unicode-width]
version = "0.2"
[dependencies.vte]
version = "0.15"
[profile.release]
lto = "fat"
codegen-units = 1
debug = 0
strip = "symbols"
bacon-3.12.0/Cargo.toml.orig 0000644 00000003045 00000000001 0011140 0 ustar [package]
name = "bacon"
version = "3.12.0"
authors = ["dystroy "]
repository = "https://github.com/Canop/bacon"
description = "background rust compiler"
edition = "2021"
keywords = ["rust", "background", "compiler", "watch", "inotify"]
license = "AGPL-3.0"
categories = ["command-line-utilities", "development-tools"]
readme = "README.md"
rust-version = "1.76"
[features]
default = []
clipboard = ["arboard"]
sound = ["rodio"]
[dependencies]
anyhow = "1.0"
arboard = { version = "3.4", default-features = false, optional = true }
cargo_metadata = "0.19"
clap = { version = "4.5", features = ["derive", "cargo"] }
clap-help = "1.3.2"
clap_complete = { version = "4.5.44", features = ["unstable-dynamic"] }
cli-log = "2.1"
crokey = "1.1"
ctrlc = "3.4"
directories-next = "2.0.0"
gix = { version = "0.67", default-features = false, features = ["index", "excludes", "parallel"] }
glob = "0.3"
iq = { version = "0.2", features = ["template"] }
lazy-regex = "3.4.1"
notify = "7.0"
rodio = { version = "0.20", optional = true, default-features = false, features = ["mp3"] }
rustc-hash = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
termimad = "0.31.1"
toml = "0.8"
unicode-width = "0.2"
vte = "0.15"
[profile.release]
debug = false
lto = "fat"
strip = "symbols"
codegen-units = 1
[patch.crates-io]
# clap-help = { path = "../clap-help" }
# termimad = { path = "../termimad" }
# crokey = { path = "../crokey" }
# coolor = { path = "../coolor" }
# iq = { path = "../iq" }
# lazy-regex = { path = "../lazy-regex" }
bacon-3.12.0/Cargo.toml.orig 0000644 0000000 0000000 00000003045 10461020230 0013662 0 ustar 0000000 0000000 [package]
name = "bacon"
version = "3.12.0"
authors = ["dystroy "]
repository = "https://github.com/Canop/bacon"
description = "background rust compiler"
edition = "2021"
keywords = ["rust", "background", "compiler", "watch", "inotify"]
license = "AGPL-3.0"
categories = ["command-line-utilities", "development-tools"]
readme = "README.md"
rust-version = "1.76"
[features]
default = []
clipboard = ["arboard"]
sound = ["rodio"]
[dependencies]
anyhow = "1.0"
arboard = { version = "3.4", default-features = false, optional = true }
cargo_metadata = "0.19"
clap = { version = "4.5", features = ["derive", "cargo"] }
clap-help = "1.3.2"
clap_complete = { version = "4.5.44", features = ["unstable-dynamic"] }
cli-log = "2.1"
crokey = "1.1"
ctrlc = "3.4"
directories-next = "2.0.0"
gix = { version = "0.67", default-features = false, features = ["index", "excludes", "parallel"] }
glob = "0.3"
iq = { version = "0.2", features = ["template"] }
lazy-regex = "3.4.1"
notify = "7.0"
rodio = { version = "0.20", optional = true, default-features = false, features = ["mp3"] }
rustc-hash = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
termimad = "0.31.1"
toml = "0.8"
unicode-width = "0.2"
vte = "0.15"
[profile.release]
debug = false
lto = "fat"
strip = "symbols"
codegen-units = 1
[patch.crates-io]
# clap-help = { path = "../clap-help" }
# termimad = { path = "../termimad" }
# crokey = { path = "../crokey" }
# coolor = { path = "../coolor" }
# iq = { path = "../iq" }
# lazy-regex = { path = "../lazy-regex" }
bacon-3.12.0/LICENSE 0000644 0000000 0000000 00000103333 10461020230 0012001 0 ustar 0000000 0000000 GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
.
bacon-3.12.0/README.md 0000644 0000000 0000000 00000006456 10461020230 0012263 0 ustar 0000000 0000000 ![bacon][logo]
[logo]: img/logo-text.png?raw=true "bacon"
[![Latest Version][s1]][l1] [![site][s4]][l4] [![Chat on Miaou][s2]][l2] [![License: AGPL v3][s3]][l3]
[s1]: https://img.shields.io/crates/v/bacon.svg
[l1]: https://crates.io/crates/bacon
[s2]: https://dystroy.org/chat-shield.svg
[l2]: https://miaou.dystroy.org/4683?bacon
[s3]: https://img.shields.io/badge/License-AGPL_v3-blue.svg
[l3]: https://www.gnu.org/licenses/agpl-3.0
[s4]: https://dystroy.org/dystroy-doc-pink-shield.svg
[l4]: https://dystroy.org/bacon
**bacon** is a background code checker.
It's designed for minimal interaction so that you can just let it run, alongside your editor, and be notified of warnings, errors, or test failures in your Rust code.

# Documentation
The **[bacon website](https://dystroy.org/bacon)** is a complete guide.
Below is only a short overview.
## install
cargo install --locked bacon
Run this command too if you want to update bacon. Configuration has always been retro-compatible so you won't lose anything.
Some features are disabled by default. You may enable them with
cargo install --features "clipboard sound"
## check the current project
bacon
That's how you'll usually launch bacon, because other jobs like `test`, `clippy`, `doc`, your own ones, are just a key away: You'll hit c to see Clippy warnings, t for the tests, d to open the documentation, etc.
## check another project
bacon --path ../broot
or
bacon ../broot
## check all targets (tests, examples, benches, etc)
bacon --job check-all
When there's no ambiguity, you may omit the `--job` part:
bacon check-all
## run clippy instead of cargo check
bacon clippy
This will run against all targets like `check-all` does.
## run tests
bacon test
or `bacon nextest` if you're a nextest user.

When there's a failure, hit f to restrict the job to the failing test.
Hit esc to get back to all tests.
## define your own jobs
First create a `bacon.toml` file by running
bacon --init
This file already contains some standard jobs. Add your own, for example
```toml
[jobs.check-win]
command = ["cargo", "check", "--target", "x86_64-pc-windows-gnu"]
```
or
```toml
[jobs.check-examples]
command = ["cargo", "check", "--examples"]
watch = ["examples"] # src is implicitly included
```
and run
bacon check-win
or
bacon check-examples
The `bacon.toml` file may evolve with the features and settings of your project and should be added to source control.
## Optional features
Some bacon features can be disabled or enabled at compilation:
* `"clipboard"` - disabled by default : necessary for the `copy-unstyled-output` internal
* `"sound"` - enabled by default : necessary for the `play-sound` internal
## Licences
Bacon is licenced under [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.en.html).
You're free to use it to compile the Rust projects of your choice, even commercial.
The logo is designed by [Peter Varo][pv] and licensed under a
[Creative Commons Attribution-ShareAlike 4.0 International License][cc-lic].
[![license][cc-img]][cc-lic]
[pv]: https://petervaro.com
[cc-lic]: https://creativecommons.org/licenses/by-sa/4.0
[cc-img]: https://i.creativecommons.org/l/by-sa/4.0/80x15.png
bacon-3.12.0/bacon.toml 0000644 0000000 0000000 00000003667 10461020230 0012764 0 ustar 0000000 0000000 # a bacon.toml file dedicated to the bacon tool
default_job = "check-all"
env.CARGO_TERM_COLOR = "always"
# uncomment for sound
#sound.enabled = true
#sound.base_volume = "20%"
[jobs]
# This alternative to 'check' demonstrates the use of the JSON output
# of cargo check - see https://github.com/Canop/bacon/issues/249
[jobs.json-check]
command = [
"cargo", "check",
"--message-format", "json-diagnostic-rendered-ansi",
]
need_stdout = true
analyzer = "cargo_json"
[jobs.check]
command = ["cargo", "check"]
need_stdout = false
default_watch = false
[jobs.check-all]
command = [
"cargo", "check",
"--all-targets",
]
need_stdout = false
[jobs.fmt]
command = ["cargo", "+nightly", "fmt"]
[jobs.nightly]
command = [
"cargo",
"+nightly",
"check",
"--all-targets",
]
need_stdout = false
[jobs.win]
command = [
"cross", "build",
"--target", "x86_64-pc-windows-gnu",
]
[jobs.test]
command = ["cargo", "test"]
need_stdout = true
on_success = "play-sound(name=90s-game-ui-6,volume=50)"
on_failure = "play-sound(name=beep-warning,volume=100)"
[jobs.doc]
command = ["cargo", "doc", "--no-deps"]
need_stdout = false
# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change
[jobs.clippy-all]
command = [
"cargo", "clippy",
"--",
"-A", "clippy::bool_to_int_with_if",
"-A", "clippy::collapsible_else_if",
"-A", "clippy::collapsible_if",
"-A", "clippy::derive_partial_eq_without_eq",
"-A", "clippy::get_first",
"-A", "clippy::if_same_then_else",
"-A", "clippy::len_without_is_empty",
"-A", "clippy::map_entry",
"-A", "clippy::while_let_on_iterator",
]
need_stdout = false
[keybindings]
c = "job:clippy-all"
ctrl-c = "copy-unstyled-output"
#n = "next-match"
#shift-n = "previous-match"
#h = "play-sound(name=car-horn)"
#b = "play-sound(name=beep-6)"
f = "job:fmt"
bacon-3.12.0/defaults/default-bacon.toml 0000644 0000000 0000000 00000006413 10461020230 0016205 0 ustar 0000000 0000000 # This is a configuration file for the bacon tool
#
# Complete help on configuration: https://dystroy.org/bacon/config/
#
# You may check the current default at
# https://github.com/Canop/bacon/blob/main/defaults/default-bacon.toml
default_job = "check"
env.CARGO_TERM_COLOR = "always"
[jobs.check]
command = ["cargo", "check"]
need_stdout = false
[jobs.check-all]
command = ["cargo", "check", "--all-targets"]
need_stdout = false
# Run clippy on the default target
[jobs.clippy]
command = ["cargo", "clippy"]
need_stdout = false
# Run clippy on all targets
# To disable some lints, you may change the job this way:
# [jobs.clippy-all]
# command = [
# "cargo", "clippy",
# "--all-targets",
# "--",
# "-A", "clippy::bool_to_int_with_if",
# "-A", "clippy::collapsible_if",
# "-A", "clippy::derive_partial_eq_without_eq",
# ]
# need_stdout = false
[jobs.clippy-all]
command = ["cargo", "clippy", "--all-targets"]
need_stdout = false
# This job lets you run
# - all tests: bacon test
# - a specific test: bacon test -- config::test_default_files
# - the tests of a package: bacon test -- -- -p config
[jobs.test]
command = ["cargo", "test"]
need_stdout = true
[jobs.nextest]
command = [
"cargo", "nextest", "run",
"--hide-progress-bar", "--failure-output", "final"
]
need_stdout = true
analyzer = "nextest"
[jobs.doc]
command = ["cargo", "doc", "--no-deps"]
need_stdout = false
# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change
# You can run your application and have the result displayed in bacon,
# if it makes sense for this crate.
[jobs.run]
command = [
"cargo", "run",
# put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = true
# Run your long-running application (eg server) and have the result displayed in bacon.
# For programs that never stop (eg a server), `background` is set to false
# to have the cargo run output immediately displayed instead of waiting for
# program's end.
# 'on_change_strategy' is set to `kill_then_restart` to have your program restart
# on every change (an alternative would be to use the 'F5' key manually in bacon).
# If you often use this job, it makes sense to override the 'r' key by adding
# a binding `r = job:run-long` at the end of this file .
[jobs.run-long]
command = [
"cargo", "run",
# put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = false
on_change_strategy = "kill_then_restart"
# This parameterized job runs the example of your choice, as soon
# as the code compiles.
# Call it as
# bacon ex -- my-example
[jobs.ex]
command = ["cargo", "run", "--example"]
need_stdout = true
allow_warnings = true
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target
bacon-3.12.0/defaults/default-prefs.toml 0000644 0000000 0000000 00000006325 10461020230 0016244 0 ustar 0000000 0000000 # This is a preferences file for the bacon tool
# More info at https://github.com/Canop/bacon
# Uncomment and change the value (true/false) to
# specify whether bacon should start in summary mode
#
# summary = true
# Uncomment and change the value (true/false) to
# specify whether bacon should start with lines wrapped
#
# wrap = false
# In "reverse" mode, the focus is at the bottom, item
# order is reversed, and the status bar is on top
#
# reverse = true
# The grace period is a delay after a file event before the real
# task is launched and during which other events will be ignored.
# This is mostly useful when your editor does several operations
# when saving a file and the state is temporarily wrong (eg it
# moves the file to a backup name before recreating the right one)
# You can set it to "none" if it's useless for you.
#
# grace_period = "15ms"
# Uncomment and change the value (true/false) to
# specify whether bacon should show a help line.
#
# help_line = false
# Uncomment and change the value (true/false) to
# set whether to display the count of changes since last job start
#
# show_changes_count = false
# Uncomment one of those lines if you don't want the default
# behavior triggered by a file change. This property can also
# be set directly in a specific job.
#
# on_change_strategy = "kill_then_restart"
# on_change_strategy = "wait_then_restart"
# Exporting "locations" (by setting its 'auto' to true) lets you use
# them in an external tool, for example as a list of jump locations
# in an IDE or in a language server.
# (See https://dystroy.org/bacon/config/#export-locations),
#
# Possible line_format parts:
# - kind: warning|error|test
# - path: complete absolute path to the file
# - line: 1-based line number
# - column: 1-based column
# - message: description of the item
# - context: unstyled lines of output, separated with escaped newlines (`\\n`)
[exports.locations]
auto = false
exporter = "locations"
path = ".bacon-locations"
line_format = "{kind} {path}:{line}:{column} {message}"
# If you want some job to emit a beep on success or on failure,
# you need to globally enable sound, and you may set up the max volume here
#
# With sound enabled, you may set up sound on a job with eg
# on_success = "play-sound(name=90s-game-ui-6,volume=50)"
# on_failure = "play-sound(name=beep-warning,volume=100)"
[sound]
enabled = false # set true to allow sound
base_volume = "100%" # global volume multiplier
# Uncomment and change the key-bindings you want to define
# (some of those ones are the defaults and are just here for illustration)
[keybindings]
# esc = "back"
# g = "scroll-to-top"
# shift-g = "scroll-to-bottom"
# k = "scroll-lines(-1)"
# j = "scroll-lines(1)"
# ctrl-c = "quit"
# ctrl-q = "quit"
# q = "quit"
# F5 = "rerun"
# alt-s = "toggle-summary"
# alt-w = "toggle-wrap"
# alt-b = "toggle-backtrace"
# Home = "scroll-to-top"
# End = "scroll-to-bottom"
# Up = "scroll-lines(-1)"
# Down = "scroll-lines(1)"
# PageUp = "scroll-pages(-1)"
# PageDown = "scroll-pages(1)"
# Space = "scroll-pages(1)"
# a = "job:check-all"
# i = "job:initial"
# c = "job:clippy"
# c = "job:clippy-all"
# d = "job:doc-open"
# t = "job:test"
# r = "job:run"
# ctrl-e = "export:analysis"
# ctrl-c = "copy-unstyled-output"
bacon-3.12.0/flake.lock 0000644 0000000 0000000 00000004516 10461020230 0012733 0 ustar 0000000 0000000 {
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1736429655,
"narHash": "sha256-BwMekRuVlSB9C0QgwKMICiJ5EVbLGjfe4qyueyNQyGI=",
"owner": "nix-community",
"repo": "naersk",
"rev": "0621e47bd95542b8e1ce2ee2d65d6a1f887a13ce",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"naersk": "naersk",
"nixpkgs": "nixpkgs_2"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
bacon-3.12.0/flake.nix 0000644 0000000 0000000 00000001774 10461020230 0012604 0 ustar 0000000 0000000 {
inputs = {
flake-utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs =
{
self,
flake-utils,
naersk,
nixpkgs,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = (import nixpkgs) {
inherit system;
};
naersk' = pkgs.callPackage naersk { };
bacon = naersk'.buildPackage {
buildInputs = if pkgs.stdenv.isLinux then [ pkgs.alsa-lib pkgs.pkg-config ] else [];
src = ./.;
};
in
{
# For `nix build` & `nix run`:
defaultPackage = bacon;
# For `nix develop`:
devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
rustc
cargo
];
};
# Overlay for package usage in other Nix configurations
overlay = final: prev: {
bacon = bacon;
};
}
);
}
bacon-3.12.0/resources/README.md 0000644 0000000 0000000 00000000641 10461020230 0014263 0 ustar 0000000 0000000 Resources here are embedded in bacon, depending on features enabled at compilation.
All mp3 files in this directory come from https://pixabay.com/
Their name in this directory is the name in pixabay, to ease retrieval.
They're free to use.
You may propose other sounds for inclusion in bacon, provided that their usage isn't limited and that they're light enough.
See src/sound/play_sound.rs for more information.
bacon-3.12.0/rustfmt.toml 0000644 0000000 0000000 00000000176 10461020230 0013376 0 ustar 0000000 0000000 edition = "2021"
style_edition = "2024"
imports_granularity = "One"
imports_layout = "Vertical"
fn_params_layout = "Vertical"
bacon-3.12.0/src/analysis/analyzer.rs 0000644 0000000 0000000 00000003455 10461020230 0015605 0 ustar 0000000 0000000 use {
super::{
biome,
cargo_json,
cpp,
eslint,
nextest,
python,
standard,
},
crate::*,
serde::{
Deserialize,
Serialize,
},
};
/// A stateless operator building a report from a list of command output lines.
///
/// Implementation routing will probably change at some point
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnalyzerRef {
#[default]
Standard,
CargoJson,
Nextest,
Eslint,
Biome,
PythonPytest,
PythonRuff,
PythonUnittest,
Cpp,
CppDoctest,
}
impl AnalyzerRef {
pub fn create_analyzer(self) -> Box {
match self {
Self::Standard => Box::new(standard::StandardAnalyzer::default()),
Self::Nextest => Box::new(nextest::NextestAnalyzer::default()),
Self::Eslint => Box::new(eslint::EslintAnalyzer::default()),
Self::Biome => Box::new(biome::BiomeAnalyzer::default()),
Self::PythonUnittest => Box::new(python::unittest::UnittestAnalyzer::default()),
Self::PythonPytest => Box::new(python::pytest::PytestAnalyzer::default()),
Self::PythonRuff => Box::new(python::ruff::RuffAnalyzer::default()),
Self::CargoJson => Box::new(cargo_json::CargoJsonAnalyzer::default()),
Self::Cpp => Box::new(cpp::CppAnalyzer::default()),
Self::CppDoctest => Box::new(cpp::CppDoctestAnalyzer::default()),
}
}
}
pub trait Analyzer {
fn start(
&mut self,
mission: &Mission,
);
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
);
fn build_report(&mut self) -> anyhow::Result;
}
bacon-3.12.0/src/analysis/biome/mod.rs 0000644 0000000 0000000 00000006240 10461020230 0015625 0 ustar 0000000 0000000 //! An analyzer for biome ( / )
use {
super::*,
crate::*,
anyhow::Result,
lazy_regex::*,
};
#[derive(Debug, Default)]
pub struct BiomeAnalyzer {
lines: Vec,
}
#[derive(Debug)]
struct LocationCode<'l> {
location: &'l str, // path with line and column
code: &'l str, // eg "lint/complexity/noForEach"
tag: Option<&'l TString>, // eg "FIXABLE"
}
#[derive(Debug)]
enum BiomeLine<'l> {
LocationCode(LocationCode<'l>),
Other,
}
impl Analyzer for BiomeAnalyzer {
fn start(
&mut self,
_mission: &Mission,
) {
self.lines.clear();
}
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
self.lines.push(line.clone());
command_output.push(line);
}
fn build_report(&mut self) -> Result {
build_report(&self.lines)
}
}
fn recognize_line(tline: &TLine) -> BiomeLine {
if let Some(lc) = recognize_location_code(tline) {
return BiomeLine::LocationCode(lc);
}
BiomeLine::Other
}
fn recognize_location_code(tline: &TLine) -> Option {
if let Some(s) = tline.if_unstyled() {
// untagged
if let Some((_, location, code)) = regex_captures!(r"([^\s:]+:\d+:\d+) (\S+) ━+$", s) {
let tag = None;
return Some(LocationCode {
location,
code,
tag,
});
}
}
let mut strings = tline.strings.iter();
let a = strings.next();
let b = strings.next();
let c = strings.next();
if let (Some(a), Some(b), Some(c)) = (a, b, c) {
if a.is_unstyled() && c.is_unstyled() && regex_is_match!("^ ━+$", &c.raw) {
if let Some((_, location, code)) = regex_captures!(r"([^\s:]+:\d+:\d+) (\S+) ", &a.raw)
{
let tag = Some(b);
return Some(LocationCode {
location,
code,
tag,
});
}
}
}
None
}
/// Build a report from the output of biome
pub fn build_report(cmd_lines: &[CommandOutputLine]) -> anyhow::Result {
let mut items = ItemAccumulator::default();
let mut last_is_blank = true;
for cmd_line in cmd_lines {
let bline = recognize_line(&cmd_line.content);
if let BiomeLine::LocationCode(lc) = bline {
let mut error_line = burp::error_line(lc.code);
if let Some(tag) = lc.tag {
error_line.strings.push(TString::new("", " "));
error_line.strings.push(tag.clone());
}
items.push_error_title(error_line);
items.push_line(
LineType::Location,
burp::location_line(lc.location.to_string()),
);
last_is_blank = false;
} else {
let is_blank = cmd_line.content.is_blank();
if !(is_blank && last_is_blank) {
items.push_line(LineType::Normal, cmd_line.content.clone());
}
last_is_blank = is_blank;
}
}
Ok(items.report())
}
bacon-3.12.0/src/analysis/cargo_json/cargo_json_export.rs 0000644 0000000 0000000 00000002776 10461020230 0021636 0 ustar 0000000 0000000 use {
crate::*,
cargo_metadata::diagnostic::{
Diagnostic,
DiagnosticSpan,
},
serde::Serialize,
};
/// An export in progress for the cargo_json analyzer
pub struct CargoJsonExport {
pub name: String,
/// The written data to write to the file
pub export: String,
pub line_template: iq::Template,
}
/// The data provided to the template, once per span
#[derive(Debug, Clone, Serialize)]
struct OnSpanData<'d> {
diagnostic: &'d Diagnostic,
span: &'d DiagnosticSpan,
}
impl CargoJsonExport {
pub fn new(
name: String,
settings: &ExportSettings,
) -> Self {
Self {
name,
export: String::new(),
line_template: iq::Template::new(&settings.line_format),
}
}
pub fn receive_diagnostic(
&mut self,
diagnostic: &Diagnostic,
) {
for span in &diagnostic.spans {
let data = {
// This is a diagnostic that originates from a proc-macro.
if let Some(expansion) = &span.expansion {
OnSpanData {
diagnostic,
span: &expansion.span,
}
} else {
OnSpanData { diagnostic, span }
}
};
let line = self.line_template.render(&data);
if !line.is_empty() {
self.export.push_str(&line);
self.export.push('\n');
}
}
}
}
bacon-3.12.0/src/analysis/cargo_json/mod.rs 0000644 0000000 0000000 00000007646 10461020230 0016671 0 ustar 0000000 0000000 mod cargo_json_export;
use {
super::*,
crate::{
analysis::standard,
*,
},
anyhow::Result,
cargo_json_export::*,
cargo_metadata::{
Message,
diagnostic::Diagnostic,
},
};
/// An analyzer able to read the output
/// of `cargo check --message-format=json-diagnostic-rendered-ansi`
///
/// In order to be useful, this analyzer would have to read more than
/// just the 'rendered' field of the diagnostic. It would have to read
/// for example the spans, detect what's a suggestion, etc.
/// See https://github.com/Canop/bacon/issues/249
///
/// There are so many problems with this approach, though, that I'm
/// not sure this is worth it.
#[derive(Default)]
pub struct CargoJsonAnalyzer {
lines: Vec,
exports: Vec,
}
impl Analyzer for CargoJsonAnalyzer {
fn start(
&mut self,
mission: &Mission,
) {
self.lines.clear();
self.exports.clear();
for (name, export_settings) in &mission.settings.exports.exports {
if export_settings.exporter == Exporter::Analyser {
let export = CargoJsonExport::new(name.clone(), export_settings);
self.exports.push(export);
}
}
}
fn receive_line(
&mut self,
cmd_line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
let Some(content) = cmd_line.content.if_unstyled() else {
return; // right now we're not expecting styled output
};
match serde_json::from_str::(content) {
Ok(message) => {
self.receive_cargo_message(message, cmd_line.origin, command_output);
}
Err(err) => {
let line = TLine::from_tty(&format!("Error parsing JSON: {}", err));
let cmd_line = CommandOutputLine {
content: line,
origin: cmd_line.origin,
};
command_output.push(cmd_line);
}
}
}
fn build_report(&mut self) -> Result {
let line_analyzer = standard::StandardLineAnalyzer {};
let mut report = standard::build_report(&self.lines, line_analyzer)?;
for export in self.exports.drain(..) {
report.analyzer_exports.insert(export.name, export.export);
}
Ok(report)
}
}
impl CargoJsonAnalyzer {
fn receive_cargo_message(
&mut self,
message: Message,
origin: CommandStream,
command_output: &mut CommandOutput,
) {
match message {
Message::CompilerArtifact(_) => {}
Message::CompilerMessage(compiler_message) => {
self.receive_diagnostic(compiler_message.message, origin, command_output);
}
Message::BuildScriptExecuted(_) => {}
Message::BuildFinished(_) => {}
Message::TextLine(_) => {}
_ => {
// non exhaustive enum
}
}
}
fn receive_diagnostic(
&mut self,
diagnostic: Diagnostic,
origin: CommandStream,
command_output: &mut CommandOutput,
) {
for export in &mut self.exports {
export.receive_diagnostic(&diagnostic);
}
let Diagnostic {
//message,
//code,
//level,
//spans,
children,
//suggestion,
rendered,
..
} = diagnostic;
if let Some(rendered) = rendered {
for line in rendered.trim().lines() {
let content = TLine::from_tty(line);
let cmd_line = CommandOutputLine { content, origin };
command_output.push(cmd_line.clone());
self.lines.push(cmd_line);
}
}
for child in children {
self.receive_diagnostic(child, origin, command_output);
}
}
}
bacon-3.12.0/src/analysis/cpp.rs 0000644 0000000 0000000 00000015656 10461020230 0014550 0 ustar 0000000 0000000 use {
crate::*,
anyhow::Result,
std::{
borrow::Cow,
iter::once,
},
};
#[derive(Debug, Default)]
pub struct CppAnalyzer {
lines: Vec,
}
impl Analyzer for CppAnalyzer {
fn start(
&mut self,
_mission: &Mission,
) {
self.lines.clear()
}
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
self.lines.push(line.clone());
command_output.push(line);
}
fn build_report(&mut self) -> Result {
build_report(&self.lines)
}
}
struct Diagnostic<'l> {
level: Kind,
path: &'l str,
line: &'l str,
column: &'l str,
message: Cow<'l, [TString]>,
}
fn build_report(lines: &[CommandOutputLine]) -> Result {
let mut items = ItemAccumulator::default();
for line in lines {
let diag = recognize_diagnostic(&line.content);
if let Some(Diagnostic {
level,
path,
line,
column,
message,
}) = diag
{
let content = match level {
Kind::Error => burp::error_line_ts(&message),
Kind::Warning => burp::warning_line_ts(&message),
_ => unreachable!(),
};
items.start_item(level);
items.push_line(LineType::Title(level), content);
items.push_line(
LineType::Location,
burp::location_line(format!("{path}:{line}:{column}")),
);
} else {
items.push_line(LineType::Normal, line.content.clone())
}
}
Ok(items.report())
}
/// Expect that diagnostics look like the following:
///
/// ```text
/// [path/to/source:line:column:] [level:] [message...]
/// ```
///
/// ... where each part is contained within a potentially styled section.
fn recognize_diagnostic(line: &TLine) -> Option {
let (pos, level, kind_end) = line.strings.iter().enumerate().find_map(|(idx, section)| {
if let Some(found) = section.raw.find("error:") {
let remaining = (found + "error: ".len()).min(section.raw.len());
Some((idx, Kind::Error, remaining))
} else if let Some(found) = section.raw.find("warning:") {
let remaining = (found + "warning: ".len()).min(section.raw.len());
Some((idx, Kind::Warning, remaining))
} else {
None
}
})?;
let (path, loc_line, loc_col) = line.strings.iter().take(pos + 1).find_map(|section| {
let mut it = section.raw.split(':');
Some((it.next()?.trim(), it.next()?.trim(), it.next()?.trim()))
})?;
let message = if kind_end == 0 {
// if the "warning:" section only contains the warning text,
// take all remaining sections as the message
Cow::Borrowed(line.strings.get(pos + 1..).unwrap_or_default())
} else {
// otherwise, we need to extract whatever is left in the type section
// specifically:
// - raw[kind_end..] -> the rest of the type section
// - strings[pos+1..] -> all remaining sections
let level_section = &line.strings[pos];
let first = TString {
csi: level_section.csi.clone(),
raw: String::from(level_section.raw[kind_end..].trim_start_matches(' ')),
};
let rest = line.strings.get(pos + 1..).unwrap_or_default();
let sections = once(first).chain(rest.iter().cloned());
Cow::Owned(sections.collect())
};
Some(Diagnostic {
level,
path,
line: loc_line,
column: loc_col,
message,
})
}
#[derive(Debug, Default)]
pub struct CppDoctestAnalyzer {
lines: Vec,
}
impl Analyzer for CppDoctestAnalyzer {
fn start(
&mut self,
_mission: &Mission,
) {
self.lines.clear()
}
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
self.lines.push(line.clone());
command_output.push(line);
}
fn build_report(&mut self) -> Result {
build_doctest_report(&self.lines)
}
}
fn build_doctest_report(lines: &[CommandOutputLine]) -> Result {
let mut items = ItemAccumulator::default();
let mut current_test_case = String::from("(unknown test)");
let mut empty_count = 0;
for line in lines {
match recognize_doctest_line(¤t_test_case, &line.content) {
Some(DoctestDiagnostic::StartTestCase(tc)) => current_test_case = tc.into(),
Some(DoctestDiagnostic::Failure {
level,
location,
first_line,
}) => {
let content = match level {
Kind::Error => burp::error_line_ts(&first_line),
Kind::Warning => burp::warning_line_ts(&first_line),
_ => unreachable!(),
};
items.push_failure_title(content);
items.push_line(LineType::Location, burp::location_line(location));
empty_count = 0;
}
Some(DoctestDiagnostic::StartOrEnd) => current_test_case = "(unknown test)".into(),
None => {
if empty_count < 1 {
items.push_line(LineType::Normal, line.content.clone());
}
if line.content.strings.iter().all(|l| l.raw.is_empty()) {
empty_count += 1;
}
}
}
}
Ok(items.report())
}
enum DoctestDiagnostic<'a> {
StartTestCase(&'a str),
Failure {
location: &'a str,
level: Kind,
first_line: Vec,
},
StartOrEnd,
}
fn recognize_doctest_line<'l>(
current_test_case: &str,
line: &'l TLine,
) -> Option> {
let get = |idx: usize| line.strings.get(idx).map(|l| l.raw.as_str());
if let Some("===============================================================================") =
get(0)
{
return Some(DoctestDiagnostic::StartOrEnd);
}
if let Some("TEST CASE: ") = get(0) {
return Some(DoctestDiagnostic::StartTestCase(
line.strings.get(1).map_or("", |l| &l.raw),
));
}
if let Some(s @ ("WARNING: " | "ERROR: " | "FATAL ERROR: ")) = get(1) {
let level = match s {
"WARNING: " => Kind::Warning,
"ERROR: " | "FATAL ERROR: " => Kind::Error,
_ => unreachable!(),
};
let rest_of_line = once(TString::new("", current_test_case))
.chain(once(TString::new("", " ")))
.chain(line.strings.get(2..).unwrap_or_default().iter().cloned())
.collect();
return Some(DoctestDiagnostic::Failure {
level,
location: line.strings.first().unwrap().raw.as_str().trim(),
first_line: rest_of_line,
});
}
None
}
bacon-3.12.0/src/analysis/eslint/eslint_line_analyzer.rs 0000644 0000000 0000000 00000007734 10461020230 0021474 0 ustar 0000000 0000000 //! An analyzer for eslint ( )
use {
super::*,
crate::*,
lazy_regex::*,
};
const CSI_LOCATION_PATH: &str = "\u{1b}[4m";
const CSI_LINE_COL: &str = "\u{1b}[2m";
const CSI_ERROR: &str = "\u{1b}[31m";
const CSI_WARNING: &str = "\u{1b}[33m";
const CSI_SUM: &str = "\u{1b}[31m\u{1b}[1m";
#[derive(Debug, Default)]
pub struct EslintLineAnalyzer;
impl LineAnalyzer for EslintLineAnalyzer {
fn analyze_line(
&self,
cmd_line: &CommandOutputLine,
) -> LineAnalysis {
if is_line_col_error_line(&cmd_line.content) {
LineAnalysis {
line_type: LineType::Title(Kind::Error),
key: None,
}
} else if is_line_col_warning_line(&cmd_line.content) {
LineAnalysis {
line_type: LineType::Title(Kind::Warning),
key: None,
}
} else if get_location_path(&cmd_line.content).is_some() {
LineAnalysis {
line_type: LineType::Location,
key: None,
}
} else if is_sum(&cmd_line.content) {
LineAnalysis {
line_type: LineType::Title(Kind::Sum),
key: None,
}
} else {
LineAnalysis {
line_type: LineType::Normal,
key: None,
}
}
}
}
/// Return true when the line is like
/// "67:52 error Unnecessary escape character: \/ no-useless-escape"
fn is_line_col_error_line(content: &TLine) -> bool {
let mut strings = content.strings.iter();
let (Some(first), Some(second), Some(third), Some(fourth)) = (
strings.next(),
strings.next(),
strings.next(),
strings.next(),
) else {
return false;
};
first.is_blank()
&& second.csi == CSI_LINE_COL
&& regex_is_match!(r"^\d+:\d+$", &second.raw)
&& third.is_blank()
&& fourth.csi == CSI_ERROR
&& fourth.raw == "error"
}
/// Return true when the line is like
/// "67:52 warning bla bla"
fn is_line_col_warning_line(content: &TLine) -> bool {
let mut strings = content.strings.iter();
let (Some(first), Some(second), Some(third), Some(fourth)) = (
strings.next(),
strings.next(),
strings.next(),
strings.next(),
) else {
return false;
};
first.is_blank()
&& second.csi == CSI_LINE_COL
&& regex_is_match!(r"^\d+:\d+$", &second.raw)
&& third.is_blank()
&& fourth.csi == CSI_WARNING
&& fourth.raw == "warning"
}
fn is_sum(content: &TLine) -> bool {
let mut strings = content.strings.iter();
let Some(first) = strings.next() else {
return false;
};
if !(first.csi == CSI_SUM
&& regex_is_match!(r"^✖ \d+ problems \(\d+ errors, \d+ warnings\)$", &first.raw))
{
return false;
}
for string in strings {
if !string.is_blank() {
return false;
}
}
true
}
pub fn get_location_path(content: &TLine) -> Option {
let mut strings = content.strings.iter();
let first = strings.next()?;
if first.csi != CSI_LOCATION_PATH {
return None;
}
// trying to recognize a path, I might make some wrong assumptions here,
// especially for windows...
if !regex_is_match!(r"^\s*/\S+\.\w+s\s*$", &first.raw) {
return None;
}
Some(first.raw.to_string())
}
pub fn cleaned_tline(content: &TLine) -> TLine {
let mut tline = TLine::default();
let mut last_is_blank = true;
for ts in &content.strings {
if ts.csi == CSI_LINE_COL && regex_is_match!(r"^\d+:\d+$", &ts.raw) {
continue; // useless line:col at start of title
}
let raw = regex_replace_all!(r"\s+", &ts.raw, " ").to_string();
let is_blank = raw.trim().is_empty();
if !(is_blank && last_is_blank) {
tline.strings.push(TString::new(&ts.csi, raw));
}
last_is_blank = is_blank;
}
tline
}
bacon-3.12.0/src/analysis/eslint/mod.rs 0000644 0000000 0000000 00000005432 10461020230 0016032 0 ustar 0000000 0000000 //! An analyzer for eslint ( )
mod eslint_line_analyzer;
use {
super::*,
crate::*,
anyhow::Result,
eslint_line_analyzer::*,
};
#[derive(Debug, Default)]
pub struct EslintAnalyzer {
lines: Vec,
}
impl Analyzer for EslintAnalyzer {
fn start(
&mut self,
_mission: &Mission,
) {
self.lines.clear();
}
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
self.lines.push(line.clone());
command_output.push(line);
}
fn build_report(&mut self) -> Result {
build_report(&self.lines)
}
}
/// Build a report from the output of eslint
///
/// The main specificity of eslint is that the path of a file with error is given
/// before the errors of the file, each error coming with the line and column
/// in the file.
pub fn build_report(cmd_lines: &[CommandOutputLine]) -> anyhow::Result {
let line_analyzer = EslintLineAnalyzer {};
let mut items = ItemAccumulator::default();
let mut last_location_path = None;
for cmd_line in cmd_lines {
let line_analysis = line_analyzer.analyze_line(cmd_line);
let line_type = line_analysis.line_type;
match line_type {
LineType::Garbage => {
continue;
}
LineType::Title(kind) => {
items.start_item(kind);
}
LineType::Normal => {}
LineType::Location => {
let path = get_location_path(&cmd_line.content);
if let Some(path) = path {
last_location_path = Some(path);
continue;
} else {
warn!("unconsistent line parsing");
}
}
_ => {}
}
items.push_line(line_type, cleaned_tline(&cmd_line.content));
if matches!(line_type, LineType::Title(_)) {
// We just added the title, we must now add the location
// As it's something which isn't present in eslint output, we're free
// to choose the format we want so we're choosing the BURP one
let line_col: Option<&str> = cmd_line.content.strings.get(1).map(|s| s.raw.as_ref());
let Some(line_col) = line_col else {
warn!("unconsistent line parsing");
continue;
};
let Some(location_path) = last_location_path.as_ref() else {
warn!("no location given before error");
continue;
};
items.push_line(
LineType::Location,
burp::location_line(format!("{location_path}:{line_col}")),
);
}
}
Ok(items.report())
}
bacon-3.12.0/src/analysis/item_accumulator.rs 0000644 0000000 0000000 00000004310 10461020230 0017304 0 ustar 0000000 0000000 use crate::*;
/// Receives lines and accumulates them into items, used
/// at end to build a sorted list of lines.
///
/// This is a small optional utility for report makers
#[derive(Default)]
pub struct ItemAccumulator {
curr_kind: Option,
errors: Vec,
test_fails: Vec,
warnings: Vec,
}
impl ItemAccumulator {
pub fn start_item(
&mut self,
kind: Kind,
) {
self.curr_kind = Some(kind);
}
pub fn close_item(&mut self) {
self.curr_kind = None;
}
pub fn push_line(
&mut self,
line_type: LineType,
content: TLine,
) {
let line = Line {
item_idx: 0, // will be filled later
line_type,
content,
};
match self.curr_kind {
Some(Kind::Warning) => self.warnings.push(line),
Some(Kind::Error) => self.errors.push(line),
Some(Kind::TestFail) => self.test_fails.push(line),
_ => {} // before warnings and errors, or in a sum
}
}
pub fn push_error_title(
&mut self,
content: TLine,
) {
self.curr_kind = Some(Kind::Error);
self.push_line(LineType::Title(Kind::Error), content);
}
pub fn push_failure_title(
&mut self,
content: TLine,
) {
self.curr_kind = Some(Kind::TestFail);
self.push_line(LineType::Title(Kind::TestFail), content);
}
pub fn lines(mut self) -> Vec {
let mut lines = self.errors;
lines.append(&mut self.test_fails);
lines.append(&mut self.warnings);
let mut item_idx = 0;
for line in &mut lines {
if matches!(line.line_type, LineType::Title(_)) {
item_idx += 1;
}
line.item_idx = item_idx;
}
lines
}
pub fn report(self) -> Report {
let lines = self.lines();
let stats = Stats::from(&lines);
info!("stats: {:#?}", &stats);
Report {
lines,
stats,
suggest_backtrace: false,
output: Default::default(),
failure_keys: Vec::new(),
analyzer_exports: Default::default(),
}
}
}
bacon-3.12.0/src/analysis/line_analysis.rs 0000644 0000000 0000000 00000002240 10461020230 0016601 0 ustar 0000000 0000000 use {
crate::*,
serde::{
Deserialize,
Serialize,
},
};
/// result of the "parsing" of the line
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LineAnalysis {
pub line_type: LineType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option,
}
impl LineAnalysis {
pub fn of_type(line_type: LineType) -> Self {
Self {
line_type,
key: None,
}
}
pub fn normal() -> Self {
Self::of_type(LineType::Normal)
}
pub fn garbage() -> Self {
Self::of_type(LineType::Garbage)
}
pub fn title_key(
kind: Kind,
key: String,
) -> Self {
Self {
line_type: LineType::Title(kind),
key: Some(key),
}
}
pub fn fail>(key: S) -> Self {
Self {
line_type: LineType::Title(Kind::TestFail),
key: Some(key.into()),
}
}
pub fn test_result(
key: String,
pass: bool,
) -> Self {
Self {
line_type: LineType::TestResult(pass),
key: Some(key),
}
}
}
bacon-3.12.0/src/analysis/line_analyzer.rs 0000644 0000000 0000000 00000000371 10461020230 0016606 0 ustar 0000000 0000000 use crate::*;
// an utility for those analyzers that can work with the LineAnalysis struct
pub trait LineAnalyzer {
/// this function will disappear
fn analyze_line(
&self,
line: &CommandOutputLine,
) -> LineAnalysis;
}
bacon-3.12.0/src/analysis/line_type.rs 0000644 0000000 0000000 00000006065 10461020230 0015750 0 ustar 0000000 0000000 use {
crate::*,
anyhow::*,
serde::{
Deserialize,
Serialize,
},
std::io::Write,
termimad::crossterm::style::Stylize,
};
/// a kind of section
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Kind {
/// a warning
Warning,
/// an error
Error,
/// a test failure
TestFail,
/// a sum of errors and/or warnings, typically occuring
/// at the end of the compilation of a package
Sum,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LineType {
/// the start of a section
Title(Kind),
/// the end of a section (not part of the section)
SectionEnd,
/// a line locating the problem
Location,
/// the line saying if a test was passed
TestResult(bool),
/// a suggestion to try with backtrace
BacktraceSuggestion,
/// a line we know is useless noise
Garbage,
/// Raw line, unclassified
Raw(CommandStream),
/// Continuation of a previous line
Continuation {
/// offset to count back to get to first (starting at 1)
offset: usize,
/// whether the line is a summary
summary: bool,
},
/// any other line
Normal,
}
impl LineType {
/// Width on screen for the specific prefix of line of this type
pub fn cols(self) -> usize {
match self {
Self::Title(_) => 3,
_ => 0,
}
}
pub fn at_index_in(
idx: usize,
lines: &[Line],
) -> Option {
let line = lines.get(idx)?;
match line.line_type {
Self::Continuation { offset, .. } => {
if offset > idx {
error!("unconsistent offset in continuation line");
return None;
}
let idx = idx - offset;
let line = lines.get(idx)?;
Some(line.line_type)
}
line_type => Some(line_type),
}
}
pub fn is_summary(self) -> bool {
match self {
Self::Normal | Self::Raw(_) => false,
Self::Continuation { summary, .. } => summary,
_ => true,
}
}
pub fn matches(
self,
summary: bool,
) -> bool {
!summary || self.is_summary()
}
pub fn draw(
self,
w: &mut W,
item_idx: usize,
) -> Result<()> {
match self {
Self::Title(Kind::Error) => {
write!(w, "{}", format!("{:^3}", item_idx).black().bold().on_red())?;
}
Self::Title(Kind::TestFail) => {
write!(
w,
"\u{1b}[1m\u{1b}[38;5;235m\u{1b}[48;5;208m{:^3}\u{1b}[0m\u{1b}[0m",
item_idx
)?;
}
Self::Title(Kind::Warning) => {
write!(
w,
"{}",
format!("{:^3}", item_idx).black().bold().on_yellow()
)?;
}
_ => {}
}
Ok(())
}
}
bacon-3.12.0/src/analysis/mod.rs 0000644 0000000 0000000 00000000477 10461020230 0014540 0 ustar 0000000 0000000 mod analyzer;
mod biome;
mod cargo_json;
mod cpp;
mod eslint;
mod item_accumulator;
mod line_analysis;
mod line_analyzer;
mod line_type;
mod nextest;
mod python;
mod standard;
mod stats;
pub use {
analyzer::*,
item_accumulator::*,
line_analysis::*,
line_analyzer::*,
line_type::*,
stats::*,
};
bacon-3.12.0/src/analysis/nextest/mod.rs 0000644 0000000 0000000 00000001362 10461020230 0016224 0 ustar 0000000 0000000 mod nextest_line_analyser;
use {
crate::*,
anyhow::Result,
nextest_line_analyser::NextestLineAnalyzer,
};
#[derive(Debug, Default)]
pub struct NextestAnalyzer {
lines: Vec,
}
impl Analyzer for NextestAnalyzer {
fn start(
&mut self,
_mission: &Mission,
) {
self.lines.clear();
}
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
self.lines.push(line.clone());
command_output.push(line);
}
fn build_report(&mut self) -> Result {
let line_analyzer = NextestLineAnalyzer::default();
crate::analysis::standard::build_report(&self.lines, line_analyzer)
}
}
bacon-3.12.0/src/analysis/nextest/nextest_line_analyser.rs 0000644 0000000 0000000 00000017405 10461020230 0022051 0 ustar 0000000 0000000 use {
crate::{
analysis::standard::StandardLineAnalyzer,
*,
},
lazy_regex::*,
};
const CSI_TITLE: &str = "\u{1b}[35;1m";
const CSI_PASS: &str = "\u{1b}[32;1m";
const CSI_ERROR: &str = "\u{1b}[31;1m";
#[derive(Debug, Default)]
pub struct NextestLineAnalyzer {
default_analyzer: StandardLineAnalyzer,
}
impl LineAnalyzer for NextestLineAnalyzer {
fn analyze_line(
&self,
cmd_line: &CommandOutputLine,
) -> LineAnalysis {
let content = &cmd_line.content;
if let Some(key) = title_key(content).or_else(|| title_key_v2(content)) {
return LineAnalysis::title_key(Kind::TestFail, key);
}
if let Some((key, pass)) = as_test_result(content) {
return LineAnalysis::test_result(key, pass);
}
if is_canceling(content) {
return LineAnalysis::of_type(LineType::SectionEnd);
}
if is_error_test_run_failed(content) {
return LineAnalysis::of_type(LineType::Garbage);
}
if let Some(content) = cmd_line.content.if_unstyled() {
if regex_is_match!(r"^running \d+ tests?$", content) {
return LineAnalysis::of_type(LineType::Garbage);
}
if content == "------------" {
return LineAnalysis::of_type(LineType::SectionEnd);
}
if content == "────────────" {
return LineAnalysis::of_type(LineType::SectionEnd);
}
}
// compilation warnings and errors are still composed with the standard cargo tool
self.default_analyzer.analyze_line(cmd_line)
}
}
fn title_key(content: &TLine) -> Option {
title_key_v1(content).or_else(|| title_key_v2(content))
}
/// Return the key when the line is like "--- STD(OUT|ERR): somekey ---"
fn title_key_v1(content: &TLine) -> Option {
let mut strings = content.strings.iter();
let first = strings.next()?;
if !regex_is_match!(r"^--- STD(OUT|ERR):\s*", &first.raw) {
return None;
}
extract_key_after_crate_name_v1(strings)
}
/// Return the key when the line is like "──── STD(OUT|ERR): cratename some::key"
fn title_key_v2(content: &TLine) -> Option {
let mut strings = content.strings.iter();
let ts = strings.next()?;
if ts.csi != CSI_ERROR || ts.raw != "────" {
return None;
}
let ts = strings.find(|ts| ts.raw != " ")?;
if !regex_is_match!(r"^STD(OUT|ERR):\s*", &ts.raw) {
return None;
}
extract_key_after_crate_name_v2(strings)
}
fn extract_key_after_crate_name_v1(mut strings: std::slice::Iter<'_, TString>) -> Option {
let _ = strings.next(); // skip blank
let mut key = String::new();
for s in &mut strings {
if s.csi.is_empty() {
continue;
}
if s.raw == " ---" || s.csi == CSI_TITLE {
break;
}
key.push_str(&s.raw);
}
if strings.next().is_some() {
return None;
}
if key.is_empty() { None } else { Some(key) }
}
fn extract_key_after_crate_name_v2(mut strings: std::slice::Iter<'_, TString>) -> Option {
// skip blank and crate name
let ts = strings.find(|ts| ts.csi != CSI_TITLE && ts.raw != " ")?;
let mut key = String::new();
key.push_str(&ts.raw);
for s in &mut strings {
if s.csi.is_empty() || s.csi == CSI_TITLE {
break;
}
key.push_str(&s.raw);
}
if key.is_empty() { None } else { Some(key) }
}
fn is_error_test_run_failed(content: &TLine) -> bool {
let mut strings = content.strings.iter();
let (Some(first), Some(second), None) = (strings.next(), strings.next(), strings.next()) else {
return false;
};
first.csi == CSI_ERROR
&& first.raw.trim() == "error"
&& second.raw.trim() == ": test run failed"
}
fn is_canceling(content: &TLine) -> bool {
let Some(first) = content.strings.first() else {
return false;
};
first.csi == CSI_ERROR && first.raw.trim() == "Canceling"
}
/// return the key and whether the tests passes, when the line is a test
/// result (like " PASS [ 0.003s] bacon tests::failing_test3")
///
/// In the future, we might want to return the duration too.
fn as_test_result(content: &TLine) -> Option<(String, bool)> {
as_test_result_v1(content).or_else(|| as_test_result_v2(content))
}
fn as_test_result_v1(content: &TLine) -> Option<(String, bool)> {
let mut strings = content.strings.iter();
let first = strings.next()?;
let pass = match (first.csi.as_str(), first.raw.trim()) {
(CSI_PASS, "PASS") => true,
(CSI_ERROR, "FAIL") => false,
_ => return None,
};
let _duration = match strings.next() {
Some(s) if s.csi.is_empty() => s.raw.trim(),
_ => return None,
};
let key = extract_key_after_crate_name_v1(strings)?;
Some((key, pass))
}
/// return the key and whether the tests passes, when the line is a test
/// result (like " PASS [ 0.003s] bacon tests::failing_test3")
///
/// In the future, we might want to return the duration too.
fn as_test_result_v2(content: &TLine) -> Option<(String, bool)> {
let mut strings = content.strings.iter();
let first = strings.next()?;
let pass = match (first.csi.as_str(), first.raw.trim()) {
(CSI_PASS, "PASS") => true,
(CSI_ERROR, "FAIL") => false,
_ => return None,
};
let _duration = match strings.next() {
Some(s) if s.csi.is_empty() => s.raw.trim(),
_ => return None,
};
let key = extract_key_after_crate_name_v1(strings)?;
Some((key, pass))
}
#[test]
fn test_title_key_v1() {
let content = TLine {
strings: vec![
TString::new("\u{1b}[35;1m", "--- STDOUT: bacon-test"),
TString::new("", " "),
TString::new("\u{1b}[36m", "tests::"),
TString::new("\u{1b}[34;1m", "failing_test3"),
TString::new("\u{1b}[35;1m", " ---"),
],
};
assert_eq!(
title_key_v1(&content),
Some("tests::failing_test3".to_string())
);
let content = TLine {
strings: vec![
TString::new("\u{1b}[31;1m", "--- STDERR: bacon"),
TString::new("", " "),
TString::new("\u{1b}[36m", "analysis::nextest_analyzer::"),
TString::new("\u{1b}[34;1m", "test_as_test_result"),
TString::new("\u{1b}[31;1m", " ---"),
],
};
assert_eq!(
title_key_v1(&content),
Some("analysis::nextest_analyzer::test_as_test_result".to_string())
);
}
#[test]
fn test_canceling() {
let content = TLine {
strings: vec![
TString::new("\u{1b}[31;1m", " Canceling"),
TString::new("", " due to "),
TString::new("\u{1b}[31;1m", "test failure"),
TString::new("", ": "),
TString::new("\u{1b}[1m", "1"),
TString::new("", " test still running"),
],
};
assert_eq!(is_canceling(&content), true);
}
#[test]
fn test_as_test_result() {
let content = TLine {
strings: vec![
TString::new("\u{1b}[32;1m", " PASS"),
TString::new("", " [ 0.003s] "),
TString::new("\u{1b}[35;1m", "bacon"),
TString::new("", " "),
TString::new("\u{1b}[36m", "analysis::nextest_analyzer::test_canceling"),
],
};
assert_eq!(
as_test_result(&content),
Some((
"analysis::nextest_analyzer::test_canceling".to_string(),
true
))
);
}
#[test]
fn test_recognize_test_run_failed() {
let content = TLine {
strings: vec![
TString::new("\u{1b}[31;1m", "error"),
TString::new("", ": test run failed"),
],
};
assert!(is_error_test_run_failed(&content));
}
bacon-3.12.0/src/analysis/python/mod.rs 0000644 0000000 0000000 00000000060 10461020230 0016045 0 ustar 0000000 0000000 pub mod pytest;
pub mod ruff;
pub mod unittest;
bacon-3.12.0/src/analysis/python/pytest.rs 0000644 0000000 0000000 00000006522 10461020230 0016627 0 ustar 0000000 0000000 //! An analyzer for Python's Pytest test framework.
use {
crate::*,
anyhow::Result,
lazy_regex::*,
};
#[derive(Debug, Default)]
pub struct PytestAnalyzer {
lines: Vec,
}
enum PytLineFormat<'l> {
H1(&'l str), // big title with `=`
H2(&'l str), // smaller title with `_`
Location { path: &'l str, line: &'l str },
Other,
}
enum Section {
Errors,
Failures,
Other,
}
impl Analyzer for PytestAnalyzer {
fn start(
&mut self,
_mission: &Mission,
) {
self.lines.clear();
}
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
self.lines.push(line.clone());
command_output.push(line);
}
fn build_report(&mut self) -> Result {
build_report(&self.lines)
}
}
fn recognize_format(content: &str) -> PytLineFormat<'_> {
regex_switch!(content,
r"^(?:={2,39}) (?.+) (?:={2,39})$" => PytLineFormat::H1(title),
r"^(?:_{2,39}) (?.+) (?:_{2,39})$" => PytLineFormat::H2(title),
r"^file (?\S+\.py), line (?\d{1,8})$" => PytLineFormat::Location { path, line },
r"^(?\S+\.py):(?\d{1,8})" => PytLineFormat::Location { path, line },
)
.unwrap_or(PytLineFormat::Other)
}
/// Build a report from the output of Python unittest
///
/// The main special thing here is transforming the location line in
/// a BURP location line.
pub fn build_report(cmd_lines: &[CommandOutputLine]) -> anyhow::Result {
let mut current_section = Section::Other;
let mut items = ItemAccumulator::default();
let mut last_location_in_item = None; // to deduplicate locations
for cmd_line in cmd_lines {
let Some(content) = cmd_line.content.if_unstyled() else {
continue; // right now we're not expecting styled output
};
let format = recognize_format(content);
match format {
PytLineFormat::H1(title) => {
current_section = match title {
"ERRORS" => Section::Errors,
"FAILURES" => Section::Failures,
_ => Section::Other,
};
items.close_item();
}
PytLineFormat::H2(title) => match current_section {
Section::Errors => {
items.push_error_title(burp::error_line(title));
last_location_in_item = None;
}
Section::Failures => {
items.push_failure_title(burp::failure_line(title));
last_location_in_item = None;
}
_ => {}
},
PytLineFormat::Location { path, line } => {
if let Some(last_location) = last_location_in_item {
if last_location == (path, line) {
continue;
}
}
last_location_in_item = Some((path, line));
items.push_line(
LineType::Location,
burp::location_line(format!("{path}:{line}")),
);
}
PytLineFormat::Other => {
items.push_line(LineType::Normal, cmd_line.content.clone());
}
}
}
Ok(items.report())
}
bacon-3.12.0/src/analysis/python/ruff.rs 0000644 0000000 0000000 00000005765 10461020230 0016251 0 ustar 0000000 0000000 //! An analyzer for ruff ( )
use {
crate::*,
anyhow::Result,
lazy_regex::*,
};
#[derive(Debug, Default)]
pub struct RuffAnalyzer {
lines: Vec,
}
#[derive(Debug)]
struct LocationTitle<'l> {
path: &'l str,
line: &'l str,
column: &'l str,
message: &'l [TString],
}
#[derive(Debug)]
enum RuffLine<'l> {
LocationTitle(LocationTitle<'l>),
Other,
}
impl Analyzer for RuffAnalyzer {
fn start(
&mut self,
_mission: &Mission,
) {
self.lines.clear();
}
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
self.lines.push(line.clone());
command_output.push(line);
}
fn build_report(&mut self) -> Result {
build_report(&self.lines)
}
}
fn recognize_line(tline: &TLine) -> RuffLine {
if let Some(lt) = recognize_location_message(tline) {
return RuffLine::LocationTitle(lt);
}
RuffLine::Other
}
fn recognize_location_message(tline: &TLine) -> Option {
let tstrings = &tline.strings;
if tstrings.len() < 8 {
return None;
}
if tstrings[0].csi != "\u{1b}[1m" || tstrings[1].raw.is_empty() // path
|| tstrings[1].csi != "\u{1b}[36m" || tstrings[1].raw != ":"
|| tstrings[2].is_styled() || !regex_is_match!(r"^\d+$", &tstrings[2].raw) // line
|| tstrings[3].csi != "\u{1b}[36m" || tstrings[3].raw != ":"
|| tstrings[4].is_styled() || !regex_is_match!(r"^\d+$", &tstrings[4].raw) // column
|| tstrings[5].csi != "\u{1b}[36m" || tstrings[5].raw != ":"
|| tstrings[6].is_styled() || tstrings[6].raw != " "
{
return None;
}
Some(LocationTitle {
path: &tstrings[0].raw,
line: &tstrings[2].raw,
column: &tstrings[4].raw,
message: &tstrings[7..],
})
}
/// Build a report from the output of biome
pub fn build_report(cmd_lines: &[CommandOutputLine]) -> anyhow::Result {
let mut items = ItemAccumulator::default();
let mut last_is_blank = true;
let mut i = 0;
for cmd_line in cmd_lines {
if i < 5 {
info!("cmd_line: {:#?}", &cmd_line);
i += 1;
}
let bline = recognize_line(&cmd_line.content);
if let RuffLine::LocationTitle(LocationTitle {
path,
line,
column,
message,
}) = bline
{
items.push_error_title(burp::error_line_ts(message));
items.push_line(
LineType::Location,
burp::location_line(format!("{}:{}:{}", path, line, column)),
);
last_is_blank = false;
} else {
let is_blank = cmd_line.content.is_blank();
if !(is_blank && last_is_blank) {
items.push_line(LineType::Normal, cmd_line.content.clone());
}
last_is_blank = is_blank;
}
}
Ok(items.report())
}
bacon-3.12.0/src/analysis/python/unittest.rs 0000644 0000000 0000000 00000005776 10461020230 0017170 0 ustar 0000000 0000000 //! An analyzer for Python unittest
use {
crate::*,
anyhow::Result,
lazy_regex::*,
};
#[derive(Debug, Default)]
pub struct UnittestAnalyzer {
lines: Vec,
}
impl Analyzer for UnittestAnalyzer {
fn start(
&mut self,
_mission: &Mission,
) {
self.lines.clear();
}
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
self.lines.push(line.clone());
command_output.push(line);
}
fn build_report(&mut self) -> Result {
build_report(&self.lines)
}
}
pub fn analyze_line(cmd_line: &CommandOutputLine) -> LineAnalysis {
// we're not expecting styled output for unittest (it's probable
// some users use decorators, but I don't know those today)
let Some(content) = cmd_line.content.if_unstyled() else {
return LineAnalysis::normal();
};
regex_switch!(content,
r"^FAIL:\s+\S+\s+\((?.+)\)" => LineAnalysis::fail(key),
r#"^\s+File ".+", line \d+"# => LineAnalysis::of_type(LineType::Location),
"^={50,}$" => LineAnalysis::garbage(),
"^-{50,}$" => LineAnalysis::garbage(),
r"^Traceback \(most recent call last\)" => LineAnalysis::garbage(),
)
.unwrap_or_else(LineAnalysis::normal)
}
/// Build a report from the output of Python unittest
///
/// The main special thing here is transforming the location line in
/// a BURP location line.
pub fn build_report(cmd_lines: &[CommandOutputLine]) -> anyhow::Result {
let mut items = ItemAccumulator::default();
let mut item_location_written = false;
for cmd_line in cmd_lines {
let line_analysis = analyze_line(cmd_line);
let line_type = line_analysis.line_type;
match line_type {
LineType::Garbage => {
continue;
}
LineType::Title(kind) => {
items.start_item(kind);
item_location_written = false;
}
LineType::Normal => {}
LineType::Location => {
if !item_location_written {
if let Some(content) = cmd_line.content.if_unstyled() {
// we rewrite the location as a BURP location
if let Some((_, path, line)) =
regex_captures!(r#"\s+File "(.+)", line (\d+)"#, content,)
{
items.push_line(
LineType::Location,
burp::location_line(format!("{path}:{line}")),
);
item_location_written = true;
} else {
warn!("unconsistent line parsing");
}
continue;
}
}
}
_ => {}
}
items.push_line(line_type, cmd_line.content.clone());
}
Ok(items.report())
}
bacon-3.12.0/src/analysis/standard/mod.rs 0000644 0000000 0000000 00000001455 10461020230 0016335 0 ustar 0000000 0000000 mod standard_line_analyser;
mod standard_report_building;
pub use {
standard_line_analyser::StandardLineAnalyzer,
standard_report_building::build_report,
};
use {
crate::*,
anyhow::Result,
};
#[derive(Debug, Default)]
pub struct StandardAnalyzer {
lines: Vec,
}
impl Analyzer for StandardAnalyzer {
fn start(
&mut self,
_mission: &Mission,
) {
self.lines.clear();
}
fn receive_line(
&mut self,
line: CommandOutputLine,
command_output: &mut CommandOutput,
) {
self.lines.push(line.clone());
command_output.push(line);
}
fn build_report(&mut self) -> Result {
let line_analyzer = StandardLineAnalyzer {};
build_report(&self.lines, line_analyzer)
}
}
bacon-3.12.0/src/analysis/standard/standard_line_analyser.rs 0000644 0000000 0000000 00000020256 10461020230 0022263 0 ustar 0000000 0000000 use {
crate::*,
lazy_regex::*,
};
#[cfg(not(windows))]
const CSI_ERROR_BODY: &str = CSI_BOLD;
#[cfg(windows)]
const CSI_ERROR_BODY: &str = CSI_BOLD_WHITE;
#[derive(Debug, Default)]
pub struct StandardLineAnalyzer;
impl LineAnalyzer for StandardLineAnalyzer {
fn analyze_line(
&self,
cmd_line: &CommandOutputLine,
) -> LineAnalysis {
analyze_line(cmd_line)
}
}
fn analyze_line(cmd_line: &CommandOutputLine) -> LineAnalysis {
let content = &cmd_line.content;
//debug!("content: {:?}", &content);
let mut key = None;
let line_type = if cmd_line.content.is_blank() {
LineType::Normal
} else if let Some(content) = cmd_line.content.if_unstyled() {
if let Some((k, r)) = as_test_result(content) {
key = Some(k.to_string());
LineType::TestResult(r)
} else if let Some(k) = as_fail_result_title(content) {
key = Some(k.to_string());
LineType::Title(Kind::TestFail)
} else if let Some(k) = as_stack_overflow_message(content) {
key = Some(k.to_string());
LineType::Title(Kind::TestFail)
} else if cmd_line.origin == CommandStream::StdErr
&& regex_is_match!("^error: ", content)
&& !regex_is_match!("^error: aborting due to", content)
{
// This recognizes the error in case there's no styling (eg miri run, see issue #251).
// If there proves to be too many false positives, this might be moved to an "unstyled"
// analyzer dedicated to those cases when we can't get any styling information.
LineType::Title(Kind::Error)
} else if cmd_line.origin == CommandStream::StdErr
&& regex_is_match!("^warning: ", content)
&& !regex_is_match!(r"generated \d+ warnings?$", content)
{
// This recognizes the warning in case there's no styling (eg miri run, see issue #251)
LineType::Title(Kind::Warning)
} else if regex_is_match!("^failures:$", content) {
// this isn't very discriminant...
LineType::Title(Kind::Sum)
} else if regex_is_match!("[Rr]un with (`)?RUST_BACKTRACE=", content) {
LineType::BacktraceSuggestion
} else if regex_is_match!(r#", [^:\s'"]+:\d+:\d+$"#, content) {
// this kind of location comes up in test failures
LineType::Location
} else if regex_is_match!(r#"^\s+--> [^:\s'"]+:\d+:\d+$"#, content) {
// this comes up in test failures to compile
LineType::Location
} else if regex_is_match!(r#"^thread '.+' panicked at [^:\s'"]+:\d+:\d+:$"#, content) {
// this comes up in test failures
LineType::Location
} else {
LineType::Normal
}
} else {
let ts0 = content.strings.get(0);
let ts1 = content.strings.get(1);
match (ts0, ts1) {
(Some(title), Some(body)) => {
match (
title.csi.as_ref(),
title.raw.as_ref(),
body.csi.as_ref(),
body.raw.as_ref(),
) {
(CSI_BOLD_RED, "error", CSI_ERROR_BODY, body_raw)
if body_raw.starts_with(": aborting due to") =>
{
LineType::Title(Kind::Sum)
}
(CSI_BOLD_RED, title_raw, CSI_ERROR_BODY, _)
if title_raw.starts_with("error") =>
{
LineType::Title(Kind::Error)
}
#[cfg(not(windows))]
(CSI_BOLD_YELLOW, "warning", _, body_raw) => {
determine_warning_type(body_raw, content)
}
#[cfg(windows)]
(CSI_BOLD_YELLOW | CSI_BOLD_4BIT_YELLOW, "warning", _, body_raw) => {
determine_warning_type(body_raw, content)
}
("", title_raw, CSI_BOLD_BLUE, "--> ") if is_spaces(title_raw) => {
LineType::Location
}
("", title_raw, CSI_BOLD_BLUE, "::: ") if is_spaces(title_raw) => {
LineType::Location
}
("", k, CSI_BOLD_RED | CSI_RED, "FAILED") if content.strings.len() == 2 => {
if let Some(k) = as_test_name(k) {
key = Some(k.to_string());
LineType::TestResult(false)
} else {
LineType::Normal
}
}
("", k, CSI_GREEN, "ok") => {
if let Some(k) = as_test_name(k) {
key = Some(k.to_string());
LineType::TestResult(true)
} else {
LineType::Normal
}
}
_ => LineType::Normal,
}
}
(Some(content), None) => {
if regex_is_match!(
r#"^thread '.+' panicked at [^:\s'"]+:\d+:\d+:$"#,
&content.raw
) {
// this comes up in nextest failures
LineType::Location
} else {
LineType::Normal
}
}
_ => LineType::Normal, // empty line
}
};
LineAnalysis { line_type, key }
}
fn determine_warning_type(
body_raw: &str,
content: &TLine,
) -> LineType {
if is_n_warnings_emitted(body_raw)
|| is_generated_n_warnings(&content.strings)
|| is_build_failed(content.strings.get(2))
{
LineType::Title(Kind::Sum)
} else {
LineType::Title(Kind::Warning)
}
}
fn is_spaces(s: &str) -> bool {
s.chars().all(|c| c.is_ascii_whitespace())
}
/// check if the string starts with something like ": 15 warnings emitted"
fn is_n_warnings_emitted(s: &str) -> bool {
regex_is_match!(r#"^: \d+ warnings? emitted"#, s)
}
fn is_generated_n_warnings(ts: &[TString]) -> bool {
ts.iter()
.any(|ts| regex_is_match!(r#"generated \d+ warnings?"#, &ts.raw))
}
fn is_build_failed(ts: Option<&TString>) -> bool {
ts.is_some_and(|ts| regex_is_match!(r#"^\s*build failed"#, &ts.raw))
}
/// similar to as_test_result but without the FAILED|ok part
/// This is used in case of styled output (because the FAILED|ok
/// part is in another TString)
fn as_test_name(s: &str) -> Option<&str> {
regex_captures!(
r#"^(?:test\s+)?(.+?)(?: - should panic\s*)?(?: - compile\s*)?\s+...\s*$"#,
s
)
.map(|(_, key)| key)
}
/// return Some when the line is the non detailled
/// result of a test, for example
///
/// "test str_fit::fitting_count_tests::test_count_fitting ... FAILED"
/// or
/// "test wrap::wrap_tests::check_space_removing ... ok"
/// or
/// "test src/lib.rs - (line 6) ... FAILED"
/// or
/// "test src/lib.rs - (line 10) ... ok"
/// or
/// "test src/mode.rs - mode::Mode::new (line 121) - compile ... FAILED"
/// (in this case, the " - compile" part isn't in the key, see #64)
/// or
/// "test tests::another - should panic ... FAILED"
/// (in this case, the " - should panic" part isn't in the key, see #95)
fn as_test_result(s: &str) -> Option<(&str, bool)> {
regex_captures!(
r#"^(?:test\s+)?(.+?)(?: - should panic\s*)?(?: - compile\s*)?\s+...\s+(\w+)$"#,
s
)
.and_then(|(_, key, outcome)| match outcome {
"ok" => Some((key, true)),
"FAILED" => Some((key, false)),
other => {
warn!("unrecognized doctest outcome: {:?}", other);
None
}
})
}
/// return Some(key) when the line is like this:
/// "---- key stdout ----"
fn as_fail_result_title(s: &str) -> Option<&str> {
regex_captures!(r#"^---- (.+) stdout ----$"#, s).map(|(_, key)| key)
}
/// Returns Some(key) when the line is like this:
/// thread 'key' has overflowed its stack
fn as_stack_overflow_message(s: &str) -> Option<&str> {
regex_captures!("^thread '(.+)' has overflowed its stack$", s).map(|(_, key)| key)
}
bacon-3.12.0/src/analysis/standard/standard_report_building.rs 0000644 0000000 0000000 00000013032 10461020230 0022620 0 ustar 0000000 0000000 use {
crate::*,
rustc_hash::FxHashMap,
};
pub fn build_report(
cmd_lines: &[CommandOutputLine],
line_analyzer: L,
) -> anyhow::Result {
#[derive(Debug, Default)]
struct Failure {
has_title: bool,
}
// we first accumulate warnings, test fails and errors in separate vectors
let mut warnings = Vec::new();
let mut errors = Vec::new();
let mut fails = Vec::new();
let mut failures: FxHashMap = Default::default();
let mut passed_tests = 0;
let mut cur_err_kind = None; // the current kind among stderr lines
let mut is_in_out_fail = false;
let mut suggest_backtrace = false;
for cmd_line in cmd_lines {
let line_analysis = line_analyzer.analyze_line(cmd_line);
let line_type = line_analysis.line_type;
let mut line = Line {
item_idx: 0, // will be filled later
line_type,
content: cmd_line.content.clone(),
};
match (line_type, line_analysis.key) {
(LineType::Garbage, _) => {
continue;
}
(LineType::TestResult(r), Some(key)) => {
if r {
passed_tests += 1;
} else if !failures.contains_key(&key) {
// we should receive the test failure section later,
// right now we just whitelist it
failures.insert(key, Failure::default());
}
}
(LineType::Title(Kind::TestFail), Some(key)) => {
let failure = failures.entry(key.clone()).or_default();
is_in_out_fail = true;
cur_err_kind = Some(Kind::TestFail);
if failure.has_title {
// we already have a title for this failure
// (for nextest, we have a title for stdout and one for stderr)
continue;
}
failure.has_title = true;
line.content = TLine::failed(&key);
fails.push(line);
}
(LineType::Normal, None) => {
if line.content.is_blank() {
if cur_err_kind == Some(Kind::TestFail) {
// beautification: we remove some blank lines
if let Some(last) = fails.last() {
if last.line_type != LineType::Normal || last.content.is_blank() {
continue;
}
}
} else {
is_in_out_fail = false;
}
}
if is_in_out_fail {
fails.push(line);
} else {
match cur_err_kind {
Some(Kind::Warning) => warnings.push(line),
Some(Kind::Error) => errors.push(line),
_ => {}
}
}
}
(LineType::Title(Kind::Sum), None) => {
// we're not interested in this section
cur_err_kind = None;
is_in_out_fail = false;
}
(LineType::SectionEnd, None) => {
cur_err_kind = None;
is_in_out_fail = false;
}
(LineType::Title(kind), _) => {
cur_err_kind = Some(kind);
match cur_err_kind {
Some(Kind::Warning) => warnings.push(line),
Some(Kind::Error) => errors.push(line),
_ => {} // before warnings and errors, or in a sum
}
}
(LineType::BacktraceSuggestion, _) => {
suggest_backtrace = true;
}
(LineType::Location, _) => {
match cur_err_kind {
Some(Kind::Warning) => warnings.push(line),
Some(Kind::Error) => errors.push(line),
Some(Kind::TestFail) => fails.push(line),
_ => {} // before warnings and errors, or in a sum
}
}
_ => {}
}
}
for (key, failure) in &failures {
// if we know of a failure but there was no content, we add some
if failure.has_title {
continue;
}
fails.push(Line {
item_idx: 0, // will be filled later
line_type: LineType::Title(Kind::TestFail),
content: TLine::failed(key),
});
fails.push(Line {
item_idx: 0,
line_type: LineType::Normal,
content: TLine::italic("no output".to_string()),
});
}
// we now build a common vector, with errors first
let mut lines = errors;
lines.append(&mut fails);
lines.append(&mut warnings);
// and we assign the indexes
let mut item_idx = 0;
for line in &mut lines {
if matches!(line.line_type, LineType::Title(_)) {
item_idx += 1;
}
line.item_idx = item_idx;
}
// we compute the stats at end because some lines may
// have been read but not added (at start or end)
let mut stats = Stats::from(&lines);
stats.passed_tests = passed_tests;
debug!("stats: {:#?}", &stats);
let failure_keys = failures.keys().cloned().collect();
let report = Report {
lines,
stats,
suggest_backtrace,
output: Default::default(),
failure_keys,
analyzer_exports: Default::default(),
};
Ok(report)
}
bacon-3.12.0/src/analysis/stats.rs 0000644 0000000 0000000 00000002544 10461020230 0015114 0 ustar 0000000 0000000 use {
crate::*,
serde::{
Deserialize,
Serialize,
},
};
/// number of lines per type in a report
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Stats {
pub warnings: usize,
pub errors: usize,
pub test_fails: usize,
pub passed_tests: usize,
pub location_lines: usize,
pub normal_lines: usize,
}
impl From<&Vec> for Stats {
fn from(lines: &Vec) -> Self {
lines.iter().fold(Stats::default(), |mut stats, line| {
match line.line_type {
LineType::Title(Kind::Error) => stats.errors += 1,
LineType::Title(Kind::Warning) => stats.warnings += 1,
LineType::Title(Kind::TestFail) => stats.test_fails += 1,
LineType::Location => stats.location_lines += 1,
_ => stats.normal_lines += 1,
}
stats
})
}
}
impl Stats {
pub fn lines(
&self,
summary: bool,
) -> usize {
let mut sum = self.warnings + self.errors + self.test_fails + self.location_lines;
if !summary {
sum += self.normal_lines;
}
sum
}
pub fn items(&self) -> usize {
self.warnings + self.errors + self.test_fails
}
pub fn can_scope_tests(&self) -> bool {
self.passed_tests > 0 && self.test_fails > 0
}
}
bacon-3.12.0/src/burp/mod.rs 0000644 0000000 0000000 00000003253 10461020230 0013660 0 ustar 0000000 0000000 //! Utilities related to BURP
//! See
use crate::*;
/// Make a BURP compliant location line
pub fn location_line>(location: S) -> TLine {
let mut line = TLine::default();
line.strings
.push(TString::new("\u{1b}[1m\u{1b}[38;5;12m", " --> "));
line.strings.push(TString::new("", location));
line
}
/// Make a BURP compliant error line (title)
pub fn error_line(error: &str) -> TLine {
let mut line = TLine::default();
line.strings.push(TString::new(CSI_BOLD_RED, "error"));
line.strings.push(TString::new("", ": "));
line.strings.push(TString::new("", error.to_string()));
line
}
/// Make a BURP compliant error line (title) from a [TString]
pub fn error_line_ts(error: &[TString]) -> TLine {
let mut line = TLine::default();
line.strings.push(TString::new(CSI_BOLD_RED, "error"));
line.strings.push(TString::new("", ": "));
line.strings.extend(error.iter().cloned());
line
}
/// Make a BURP compliant warning line (title) from a [TString]
pub fn warning_line_ts(warning: &[TString]) -> TLine {
let mut line = TLine::default();
line.strings.push(TString::new(CSI_BOLD_YELLOW, "warning"));
line.strings.push(TString::new("", ": "));
line.strings.extend(warning.iter().cloned());
line
}
/// Make a BURP compliant test failure line (title)
/// (this one isn't based on cargo)
pub fn failure_line(error: &str) -> TLine {
let mut line = TLine::default();
line.strings.push(TString::new(CSI_BOLD_YELLOW, "failure"));
line.strings.push(TString::new("", ": "));
line.strings.push(TString::new("", error.to_string()));
line
}
bacon-3.12.0/src/cli/args.rs 0000644 0000000 0000000 00000013377 10461020230 0013644 0 ustar 0000000 0000000 use {
crate::*,
anyhow::{
Result,
bail,
},
clap::{
CommandFactory,
Parser,
},
clap_complete::ArgValueCandidates,
termimad::ansi,
};
static INTRO: &str = "
**bacon** watches your project and runs jobs in background.
Use shortcuts to:
* switch job: *t* for `test`, *c* for `clippy`, *d* to open rust doc, etc.
* toggle display: *s* for summary, *w* for wrapped lines, etc.
* search: */*
* see all shortcuts: *?*
Complete documentation at https://dystroy.org/bacon
";
/// Launch arguments
#[derive(Debug, Parser)]
#[command(
author,
about,
version,
disable_version_flag = true,
disable_help_flag = true
)]
pub struct Args {
/// Print help information
#[arg(long)]
pub help: bool,
/// Print the version
#[arg(long)]
pub version: bool,
/// Print the path to the prefs file, create it if it doesn't exist
#[clap(long)]
pub prefs: bool,
/// Run without user interface: just run the default job on change
#[clap(long)]
pub headless: bool,
/// Start in summary mode
#[clap(short = 's', long)]
pub summary: bool,
/// Start in full mode (not summary)
#[clap(short = 'S', long)]
pub no_summary: bool,
/// Start with lines wrapped
#[clap(short = 'w', long)]
pub wrap: bool,
/// Start with lines not wrapped
#[clap(short = 'W', long)]
pub no_wrap: bool,
/// Start with gui vertical order reversed
#[clap(long)]
pub reverse: bool,
/// Start with standard gui order
#[clap(long)]
pub no_reverse: bool,
/// Display a help line
#[clap(long)]
pub help_line: bool,
/// Do not display a help line
#[clap(long)]
pub no_help_line: bool,
/// List available jobs
#[clap(short = 'l', long)]
pub list_jobs: bool,
#[clap(long, hide = true)]
pub completion_list_jobs: bool,
/// Don't access the network
#[clap(long)]
pub offline: bool,
/// Create a bacon.toml file, ready to be customized
#[clap(long)]
pub init: bool,
/// Job to launch: `check`, `clippy`, custom ones...
#[clap(short = 'j', long, value_name = "job", add = ArgValueCandidates::new(crate::cli::completions::list_jobs))]
pub job: Option,
/// Ignore features of both the package and the bacon job
#[clap(long)]
pub no_default_features: bool,
/// Comma separated list of features
/// (if the job defines some, they're merged)
#[clap(long, value_name = "features")]
pub features: Option,
/// Activate all available features
#[clap(long)]
pub all_features: bool,
/// Export locations in `.bacon-locations` file
#[clap(short = 'e', long)]
pub export_locations: bool,
/// Don't export locations
#[clap(short = 'E', long)]
pub no_export_locations: bool,
/// Path to watch (overriding what's normally computed from the project's
/// type, bacon.toml file, etc.)
#[clap(long, value_name = "watch", value_hint = clap::ValueHint::FilePath)]
pub watch: Option,
/// Project to run jobs on, and use as working directory
#[clap(long, value_name = "project", value_hint = clap::ValueHint::DirPath)]
pub project: Option,
/// Configuration passed as a TOML string
#[clap(long)]
pub config_toml: Option,
#[clap(add = ArgValueCandidates::new(crate::cli::completions::list_jobs))]
/// What to do: either a job, or a path, or both
pub args: Vec,
#[clap(last = true)]
/// Arguments given to the job
pub additional_job_args: Vec,
}
impl Args {
/// positional arguments in bacon command are a convenience
/// allowing to skip writing `-j`, `-p`, or both.
/// To be used, they must be copied to the `job` or
/// `path` values.
pub fn fix(&mut self) -> Result<()> {
let mut args = self.args.drain(..);
match (
args.next(),
args.next(),
self.job.is_none(),
self.project.is_none(),
) {
(Some(a), b, true, true) => {
if a.contains('.') || a.contains('/') {
// a is a path, it can't be job
self.project = Some(a);
self.job = b.map(|b| b.as_str().into());
} else {
self.job = Some(a.as_str().into());
self.project = b;
}
}
(Some(_), Some(_), _, _) => {
bail!("Too many arguments");
}
(Some(a), None, true, false) => {
self.job = Some(a.as_str().into());
}
(Some(a), None, false, true) => {
self.project = Some(a);
}
(Some(a), None, false, false) => {
bail!("Unexpected argument {:?}", a);
}
_ => {}
}
Ok(())
}
pub fn print_help(&self) {
let mut printer = clap_help::Printer::new(Args::command())
.with("introduction", INTRO)
.with("options", clap_help::TEMPLATE_OPTIONS_MERGED_VALUE)
.without("author");
let skin = printer.skin_mut();
skin.headers[0].compound_style.set_fg(ansi(204));
skin.bold.set_fg(ansi(204));
skin.italic = termimad::CompoundStyle::with_fg(ansi(204)); // 2, 81, 73, 38
printer.template_keys_mut().push("examples");
printer.set_template("examples", EXAMPLES_TEMPLATE);
for (i, example) in EXAMPLES.iter().enumerate() {
printer
.expander_mut()
.sub("examples")
.set("example-number", i + 1)
.set("example-title", example.title)
.set("example-cmd", example.cmd);
}
printer.print_help();
}
}
bacon-3.12.0/src/cli/completions.rs 0000644 0000000 0000000 00000001160 10461020230 0015227 0 ustar 0000000 0000000 use {
clap_complete::CompletionCandidate,
std::process::Command,
};
fn with_self_command(
f: impl FnOnce(Command) -> Option>
) -> Vec {
std::env::current_exe()
.ok()
.and_then(|command| f(Command::new(command)))
.unwrap_or_default()
}
pub fn list_jobs() -> Vec {
with_self_command(|mut c| {
let output = c.arg("--completion-list-jobs").output().ok()?;
let output: String = String::from_utf8(output.stdout).ok()?;
Some(output.split('\0').map(CompletionCandidate::new).collect())
})
}
bacon-3.12.0/src/cli/mod.rs 0000644 0000000 0000000 00000005761 10461020230 0013465 0 ustar 0000000 0000000 mod args;
mod completions;
pub use args::*;
use {
crate::*,
anyhow::anyhow,
clap::{
CommandFactory,
Parser,
},
std::{
fs,
io::Write,
},
termimad::crossterm::{
QueueableCommand,
cursor,
terminal::{
EnterAlternateScreen,
LeaveAlternateScreen,
},
},
};
#[cfg(windows)]
use termimad::crossterm::event::{
DisableMouseCapture,
EnableMouseCapture,
};
/// The Write type used by all GUI writing functions
pub type W = std::io::BufWriter;
/// return the writer used by the application
pub fn writer() -> W {
std::io::BufWriter::new(std::io::stdout())
}
pub fn run() -> anyhow::Result<()> {
if std::env::var_os("COMPLETE").is_some() {
clap_complete::CompleteEnv::with_factory(Args::command).complete();
}
let mut args: Args = Args::parse();
args.fix()?;
info!("args: {:#?}", &args);
let headless = args.headless;
if args.help {
args.print_help();
return Ok(());
}
if args.version {
println!("bacon {}", env!("CARGO_PKG_VERSION"));
return Ok(());
}
if args.prefs {
let prefs_path =
bacon_prefs_path().ok_or(anyhow!("No preferences location known for this system."))?;
if !prefs_path.exists() {
fs::create_dir_all(prefs_path.parent().unwrap())?;
fs::write(&prefs_path, DEFAULT_PREFS.trim_start())?;
// written to stderr to allow initialization with commands like
// $EDITOR "$(bacon --prefs)"
eprintln!("Preferences file written.");
}
println!("{}", prefs_path.to_string_lossy());
return Ok(());
}
let context = Context::new(&args)?;
debug!("mission context: {:#?}", &context);
if args.init {
let package_config_path = context.package_config_path();
if !package_config_path.exists() {
fs::write(&package_config_path, DEFAULT_PACKAGE_CONFIG.trim_start())?;
eprintln!("bacon project configuration file written.");
} else {
eprintln!("bacon configuration file already exists.");
}
println!("{}", package_config_path.to_string_lossy());
return Ok(());
}
let settings = Settings::read(&args, &context)?;
if args.list_jobs {
print_jobs(&settings);
return Ok(());
}
if args.completion_list_jobs {
for job in settings.jobs.keys() {
print!("{job}\0");
}
return Ok(());
}
let mut w = writer();
if !headless {
w.queue(EnterAlternateScreen)?;
w.queue(cursor::Hide)?;
#[cfg(windows)]
w.queue(EnableMouseCapture)?;
w.flush()?;
}
let result = tui::app::run(&mut w, settings, &args, context, headless);
if !headless {
#[cfg(windows)]
w.queue(DisableMouseCapture)?;
w.queue(cursor::Show)?;
w.queue(LeaveAlternateScreen)?;
}
w.flush()?;
result
}
bacon-3.12.0/src/conf/action.rs 0000644 0000000 0000000 00000012521 10461020230 0014331 0 ustar 0000000 0000000 use {
crate::*,
lazy_regex::*,
serde::{
Deserialize,
Deserializer,
Serialize,
Serializer,
de,
},
std::{
fmt,
str::FromStr,
},
};
/// An action that can be mapped to a key
///
/// While it's not really specified, export names, just like
/// job names, must be gentle enough so as to be correctly parsed
/// (if not, they won't go very far in the system).
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Action {
Export(String),
Internal(Internal),
Job(JobRef),
}
impl Action {
/// Return the action description to show in doc/help
pub fn doc(&self) -> String {
match self {
Self::Export(export_name) => format!("run *{export_name}* export"),
Self::Internal(internal) => internal.doc(),
Self::Job(job_name) => format!("start the *{job_name}* job"),
}
}
}
#[derive(Debug, PartialEq)]
pub enum ParseActionError {
UnknownAction(String),
UnknowCategory(String),
UnknownInternal(String),
}
impl fmt::Display for ParseActionError {
fn fmt(
&self,
f: &mut fmt::Formatter,
) -> fmt::Result {
match self {
Self::UnknownAction(s) => {
write!(
f,
"Action not understood: {s:?} (did you mean \"job:{s}\"?)"
)
}
Self::UnknowCategory(s) => {
write!(f, "Unknown category: {s:?}")
}
Self::UnknownInternal(s) => {
write!(f, "Internal not understood: {s:?}")
}
}
}
}
impl std::error::Error for ParseActionError {}
impl fmt::Display for Action {
fn fmt(
&self,
f: &mut fmt::Formatter,
) -> fmt::Result {
match self {
Self::Export(name) => write!(f, "export:{}", name),
Self::Internal(internal) => internal.fmt(f),
Self::Job(job_ref) => write!(f, "job:{}", job_ref),
}
}
}
impl FromStr for Action {
type Err = ParseActionError;
fn from_str(s: &str) -> Result {
if let Some((_, cat, con)) = regex_captures!(r#"^(\w+)\s*:\s*(.+)$"#, s) {
match cat {
"export" => Ok(Self::Export(con.into())),
"internal" => {
// this prefix is optional
if let Ok(internal) = Internal::from_str(con) {
Ok(Self::Internal(internal))
} else {
Err(ParseActionError::UnknownInternal(con.to_string()))
}
}
"job" => Ok(Self::Job(con.into())),
_ => Err(ParseActionError::UnknowCategory(cat.to_string())),
}
} else if let Ok(internal) = Internal::from_str(s) {
Ok(Self::Internal(internal))
} else {
Err(ParseActionError::UnknownAction(s.to_string()))
}
}
}
impl From for Action {
fn from(i: Internal) -> Self {
Self::Internal(i)
}
}
impl From for Action {
fn from(jr: JobRef) -> Self {
Self::Job(jr)
}
}
impl Serialize for Action {
fn serialize(
&self,
serializer: S,
) -> Result
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for Action {
fn deserialize(deserializer: D) -> Result
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(de::Error::custom)
}
}
#[test]
fn test_action_string_round_trip() {
let actions = vec![
Action::Job(JobRef::Default),
Action::Job(JobRef::Initial),
Action::Job(JobRef::PreviousOrQuit),
Action::Job(JobRef::Previous),
Action::Job(JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Name("run".to_string()),
scope: Scope::default(),
})),
Action::Job(JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Name("nextest".to_string()),
scope: Scope {
tests: vec!["first::test".to_string(), "second_test".to_string()],
},
})),
Action::Job(JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Alias("my-check".to_string()),
scope: Scope::default(),
})),
Action::Job(JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Alias("my-test".to_string()),
scope: Scope {
tests: vec!["abc".to_string()],
},
})),
Action::Job(JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Alias("a forbidden name!".to_string()),
scope: Scope {
tests: vec!["abc".to_string()],
},
})),
Action::Internal(Internal::Help),
Action::Internal(Internal::Scroll(ScrollCommand::MilliPages(1500))),
Action::Internal(Internal::Scroll(ScrollCommand::MilliPages(-500))),
Action::Internal(Internal::Scroll(ScrollCommand::MilliPages(-2000))),
Action::Export("my export".to_string()),
];
for action in actions {
println!("action: {}", action.to_string());
assert_eq!(action.to_string().parse(), Ok(action));
}
}
bacon-3.12.0/src/conf/auto_refresh.rs 0000644 0000000 0000000 00000000564 10461020230 0015546 0 ustar 0000000 0000000 #[derive(Debug, Clone, Copy, PartialEq)]
pub enum AutoRefresh {
/// Don't rerun the job on file changes.
Paused,
/// Run the job on file changes.
Enabled,
}
impl AutoRefresh {
pub fn is_enabled(self) -> bool {
matches!(self, AutoRefresh::Enabled)
}
pub fn is_paused(self) -> bool {
matches!(self, AutoRefresh::Paused)
}
}
bacon-3.12.0/src/conf/cargo_wrapped_config.rs 0000644 0000000 0000000 00000002275 10461020230 0017223 0 ustar 0000000 0000000 use {
super::Config,
anyhow::*,
serde::Deserialize,
std::path::Path,
};
#[derive(Deserialize)]
struct CargoWrappedConfig {
workspace: Option,
package: Option,
}
#[derive(Deserialize)]
struct WrappedMetadata {
metadata: Option,
}
#[derive(Deserialize)]
struct WrappedConfig {
bacon: Option,
}
pub fn load_config_from_cargo_toml(cargo_file_path: &Path) -> Result> {
if !cargo_file_path.exists() {
return Ok(Vec::default());
}
let cargo_toml = std::fs::read_to_string(cargo_file_path)?;
let mut cargo: CargoWrappedConfig = toml::from_str(&cargo_toml)?;
let mut configs = Vec::new();
let worskpace_config = cargo
.workspace
.take()
.and_then(|workspace| workspace.metadata)
.and_then(|metadata| metadata.bacon);
if let Some(config) = worskpace_config {
configs.push(config);
}
let worskpace_config = cargo
.package
.take()
.and_then(|package| package.metadata)
.and_then(|metadata| metadata.bacon);
if let Some(config) = worskpace_config {
configs.push(config);
}
Ok(configs)
}
bacon-3.12.0/src/conf/config.rs 0000644 0000000 0000000 00000006040 10461020230 0014320 0 ustar 0000000 0000000 use {
crate::*,
anyhow::*,
lazy_regex::regex_is_match,
serde::Deserialize,
std::{
collections::HashMap,
fs,
path::Path,
},
};
/// A configuration item which may be stored in various places, eg as `bacon.toml`
/// along a `Cargo.toml` file or as `prefs.toml` in the xdg config directory.
///
/// Leaf values are options (and not Default) so that they don't
/// override previously set values when applied to settings.
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub additional_alias_args: Option>,
pub default_job: Option,
/// Default config for a job
#[serde(flatten)]
pub all_jobs: Job,
/// locations export
#[deprecated(since = "2.22.0", note = "use exports.locations")]
pub export: Option,
#[deprecated(since = "2.9.0", note = "use exports.locations.auto")]
pub export_locations: Option,
#[serde(default)]
pub exports: HashMap,
pub help_line: Option,
#[serde(default)]
pub jobs: HashMap,
pub keybindings: Option,
pub reverse: Option,
pub summary: Option,
#[deprecated(since = "2.0.0", note = "use keybindings")]
pub vim_keys: Option,
pub wrap: Option,
}
impl Config {
/// Load from zero to two configuration items from the provided path which
/// must be in TOML format but may not exist.
///
/// Expected structures are either bacon config or a cargo.toml file (which
/// may contain a workspace.metadata.bacon key and a package.metadata.bacon key).
pub fn from_path_detect(path: &Path) -> Result> {
if !path.exists() {
return Ok(Vec::default());
}
let file_name = path.file_name().and_then(|f| f.to_str());
if file_name == Some("Cargo.toml") {
load_config_from_cargo_toml(path)
} else {
Ok(vec![Self::from_path(path)?])
}
}
/// Load a configuration item filling the provided path in TOML
pub fn from_path(path: &Path) -> Result {
let conf = toml::from_str::(&fs::read_to_string(path)?)
.with_context(|| format!("Failed to parse configuration file at {:?}", path))?;
for (name, job) in &conf.jobs {
if !regex_is_match!(r#"^[\w-]+$"#, name) {
bail!("Invalid configuration : Illegal job name : {:?}", name);
}
if job.command.is_empty() {
bail!("Invalid configuration : empty command for job {:?}", name);
}
}
Ok(conf)
}
pub fn default_package_config() -> Self {
toml::from_str(DEFAULT_PACKAGE_CONFIG).unwrap()
}
pub fn default_prefs() -> Self {
toml::from_str(DEFAULT_PREFS).unwrap()
}
}
#[test]
fn test_default_files() {
let mut settings = Settings::default();
settings.apply_config(&Config::default_prefs());
settings.apply_config(&Config::default_package_config());
settings.check().unwrap();
}
bacon-3.12.0/src/conf/defaults.rs 0000644 0000000 0000000 00000000262 10461020230 0014662 0 ustar 0000000 0000000 pub static DEFAULT_PREFS: &str = include_str!("../../defaults/default-prefs.toml");
pub static DEFAULT_PACKAGE_CONFIG: &str = include_str!("../../defaults/default-bacon.toml");
bacon-3.12.0/src/conf/keybindings.rs 0000644 0000000 0000000 00000014250 10461020230 0015363 0 ustar 0000000 0000000 use {
crate::*,
crokey::*,
serde::Deserialize,
std::{
collections::{
HashMap,
hash_map,
},
fmt,
},
};
/// A mapping from key combinations to actions.
///
/// Several key combinations can go to the same action.
#[derive(Clone, Deserialize)]
pub struct KeyBindings {
#[serde(flatten)]
map: HashMap,
}
impl Default for KeyBindings {
fn default() -> Self {
let mut bindings = Self {
map: HashMap::default(),
};
bindings.set(key!('?'), Internal::Help);
bindings.set(key!(h), Internal::Help);
bindings.set(key!(ctrl - c), Internal::Quit);
bindings.set(key!(ctrl - q), Internal::Quit);
bindings.set(key!(q), Internal::Quit);
bindings.set(key!(F5), Internal::Refresh);
bindings.set(key!(s), Internal::ToggleSummary);
bindings.set(key!(w), Internal::ToggleWrap);
bindings.set(key!(b), Internal::ToggleBacktrace("1"));
bindings.set(key!(Home), Internal::Scroll(ScrollCommand::Top));
bindings.set(key!(End), Internal::Scroll(ScrollCommand::Bottom));
bindings.set(key!(Up), Internal::Scroll(ScrollCommand::Lines(-1)));
bindings.set(key!(Down), Internal::Scroll(ScrollCommand::Lines(1)));
bindings.set(key!(PageUp), Internal::Scroll(ScrollCommand::pages(-1)));
bindings.set(key!(PageDown), Internal::Scroll(ScrollCommand::pages(1)));
bindings.set(
key!(Space),
Internal::Scroll(ScrollCommand::MilliPages(800)),
);
bindings.set(key!(f), Internal::ScopeToFailures);
bindings.set(key!(esc), Internal::Back);
bindings.set(key!(ctrl - d), JobRef::Default);
bindings.set(key!(i), JobRef::Initial);
bindings.set(key!(p), Internal::TogglePause);
bindings.set(key!('/'), Internal::FocusSearch);
bindings.set(key!(':'), Internal::FocusGoto);
bindings.set(key!(enter), Internal::Validate);
bindings.set(key!(tab), Internal::NextMatch);
bindings.set(key!(backtab), Internal::PreviousMatch);
bindings.set(key!(shift - backtab), Internal::PreviousMatch);
// keybindings for some common jobs
bindings.set(key!(a), JobRef::from_job_name("check-all"));
bindings.set(key!(c), JobRef::from_job_name("clippy"));
bindings.set(key!(d), JobRef::from_job_name("doc-open"));
bindings.set(key!(t), JobRef::from_job_name("test"));
bindings.set(key!(n), JobRef::from_job_name("nextest"));
bindings.set(key!(r), JobRef::from_job_name("run"));
bindings
}
}
impl KeyBindings {
pub fn set>(
&mut self,
ck: KeyCombination,
action: A,
) {
self.map.insert(ck, action.into());
}
pub fn add_vim_keys(&mut self) {
self.set(key!(g), Internal::Scroll(ScrollCommand::Top));
self.set(key!(shift - g), Internal::Scroll(ScrollCommand::Bottom));
self.set(key!(k), Internal::Scroll(ScrollCommand::Lines(-1)));
self.set(key!(j), Internal::Scroll(ScrollCommand::Lines(1)));
}
pub fn add_all(
&mut self,
other: &KeyBindings,
) {
for (ck, action) in other.map.iter() {
self.map.insert(*ck, action.clone());
}
}
pub fn get(
&self,
key: KeyCombination,
) -> Option<&Action> {
self.map.get(&key)
}
/// return the shortest key.to_string for the internal, if any
pub fn shortest_action_key(
&self,
filter: F,
) -> Option
where
F: Fn(&Action) -> bool,
{
let mut shortest: Option = None;
for (ck, action) in &self.map {
if filter(action) {
let s = ck.to_string();
match &shortest {
Some(previous) if previous.len() < s.len() => {}
_ => {
shortest = Some(s);
}
}
}
}
shortest
}
/// return the shortest key.to_string for the internal, if any
pub fn shortest_internal_key(
&self,
internal: Internal,
) -> Option {
let internal_action = Action::Internal(internal);
self.shortest_action_key(|action| action == &internal_action)
}
/// build and return a map from actions to all the possible shortcuts
pub fn build_reverse_map(&self) -> HashMap<&Action, Vec> {
let mut reverse_map = HashMap::new();
for (ck, action) in &self.map {
reverse_map.entry(action).or_insert_with(Vec::new).push(*ck);
}
reverse_map
}
}
impl<'a> IntoIterator for &'a KeyBindings {
type Item = (&'a KeyCombination, &'a Action);
type IntoIter = hash_map::Iter<'a, KeyCombination, Action>;
fn into_iter(self) -> Self::IntoIter {
self.map.iter()
}
}
impl fmt::Debug for KeyBindings {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let mut ds = f.debug_struct("KeyBindings");
for (kc, action) in &self.map {
ds.field(&kc.to_string(), &action.to_string());
}
ds.finish()
}
}
#[test]
fn test_deserialize_keybindings() {
#[derive(Deserialize)]
struct Config {
keybindings: KeyBindings,
}
let toml = r#"
[keybindings]
Ctrl-U = "internal:scroll-pages(-.5)"
Ctrl-d = "internal:scroll-page(1)"
alt-q = "internal:quit"
alt-p = "job:previous"
"#;
let conf = toml::from_str::(toml).unwrap();
assert_eq!(
conf.keybindings.get(key!(ctrl - u)),
Some(&Action::Internal(Internal::Scroll(
ScrollCommand::MilliPages(-500)
))),
);
assert_eq!(
conf.keybindings.get(key!(ctrl - d)),
Some(&Action::Internal(Internal::Scroll(
ScrollCommand::MilliPages(1000)
))),
);
assert_eq!(conf.keybindings.get(key!(z)), None,);
assert_eq!(
conf.keybindings.get(key!(alt - q)),
Some(&Action::Internal(Internal::Quit)),
);
assert_eq!(
conf.keybindings.get(key!(alt - p)),
Some(&Action::Job(JobRef::Previous)),
);
}
bacon-3.12.0/src/conf/mod.rs 0000644 0000000 0000000 00000002331 10461020230 0013631 0 ustar 0000000 0000000 mod action;
mod auto_refresh;
mod cargo_wrapped_config;
mod config;
mod defaults;
mod keybindings;
mod settings;
pub use {
action::*,
auto_refresh::*,
cargo_wrapped_config::*,
config::*,
defaults::*,
keybindings::*,
settings::*,
};
use std::path::{
Path,
PathBuf,
};
/// If the system can manage application preferences, return the
/// canonical path to the bacon preferences file
pub fn bacon_prefs_path() -> Option {
directories_next::ProjectDirs::from("org", "dystroy", "bacon")
.map(|project_dir| project_dir.config_dir().join("prefs.toml"))
}
/// Return the path given by the env var, if it exists (doesn't check whether
/// it's a correct configuration file)
pub fn config_path_from_env(env_var_name: &str) -> Option {
let path = std::env::var_os(env_var_name)?;
let path = Path::new(&path);
if path.exists() {
Some(path.to_path_buf())
} else {
// some users may want to use an env var to point to a file that may not always exist
// so we don't throw an error here
warn!(
"Env var {:?} points to file {:?} which does not exist",
env_var_name, path
);
None
}
}
bacon-3.12.0/src/conf/settings.rs 0000644 0000000 0000000 00000016436 10461020230 0014725 0 ustar 0000000 0000000 use {
crate::*,
anyhow::{
Result,
bail,
},
std::{
collections::HashMap,
path::PathBuf,
},
};
/// The settings used in the application.
///
/// They're immutable during the execution of the missions.
#[derive(Debug, Clone)]
pub struct Settings {
pub additional_alias_args: Option>,
pub additional_job_args: Vec,
pub all_features: bool,
pub arg_job: Option,
/// Path of the files which were used to build the settings
/// (note that not all settings come from files)
pub config_files: Vec,
pub default_job: ConcreteJobRef,
pub exports: ExportsSettings,
pub features: Option, // comma separated list
pub help_line: bool,
pub jobs: HashMap,
pub keybindings: KeyBindings,
pub no_default_features: bool,
pub reverse: bool,
pub summary: bool,
pub wrap: bool,
pub all_jobs: Job,
}
impl Default for Settings {
fn default() -> Self {
Self {
arg_job: Default::default(),
additional_job_args: Default::default(),
additional_alias_args: Default::default(),
summary: false,
wrap: true,
reverse: false,
help_line: true,
no_default_features: Default::default(),
all_features: Default::default(),
features: Default::default(),
keybindings: Default::default(),
jobs: Default::default(),
default_job: Default::default(),
exports: Default::default(),
config_files: Default::default(),
all_jobs: Default::default(),
}
}
}
impl Settings {
/// Read the settings from all configuration files and arguments.
///
///
/// Hardcoded defaults are overridden by the following configuration elements, in order:
/// * the default `bacon.toml` file (embedded in the binary)
/// * the global `prefs.toml`, from user config directory
/// * the file whose path is in environment variable `BACON_PREFS`
/// * the workspace.metadata.bacon config in the workspace level `Cargo.toml` file
/// * the workspace level `bacon.toml` file in workspace-root/bacon.toml
/// * the workspace level `bacon.toml` file in workspace-root/.config/.bacon.toml
/// * the package.metadata.bacon config in the package level `Cargo.toml` file
/// * the package level `bacon.toml` file in package-root/.bacon.toml
/// * the package level `bacon.toml` file in package-root/.config/.bacon.toml
/// * the file whose path is in environment variable `BACON_CONFIG`
/// * the content of the `--config-toml` argument
/// * args given as arguments, coming from the cli call
pub fn read(
args: &Args,
context: &Context,
) -> Result {
let mut settings = Settings::default();
let default_package_config = Config::default_package_config();
settings.apply_config(&default_package_config);
let paths = vec![
bacon_prefs_path(),
config_path_from_env("BACON_PREFS"),
context.workspace_cargo_path(),
context.workspace_config_path(),
context.workspace_dot_config_path(),
Some(context.package_cargo_path()),
Some(context.package_config_path()),
Some(context.package_dot_config_path()),
config_path_from_env("BACON_CONFIG"),
];
for path in paths.into_iter().flatten() {
if path.exists() {
let configs = Config::from_path_detect(&path)?;
if !configs.is_empty() {
info!("config loaded from {:?}", path);
settings.register_config_file(path.clone());
for config in configs {
settings.apply_config(&config);
}
}
}
}
if let Some(toml) = &args.config_toml {
let config = toml::from_str(toml)?;
info!("config loaded from --config-toml: {:#?}", &config);
settings.apply_config(&config);
}
settings.apply_args(args);
settings.check()?;
info!("settings: {:#?}", &settings);
Ok(settings)
}
pub fn register_config_file(
&mut self,
path: PathBuf,
) {
self.config_files.push(path);
}
/// Apply one of the configuration elements, overriding
/// defaults and previously applied configuration elements
pub fn apply_config(
&mut self,
config: &Config,
) {
self.all_jobs.apply(&config.all_jobs);
if let Some(b) = config.summary {
self.summary = b;
}
if let Some(b) = config.wrap {
self.wrap = b;
}
if let Some(b) = config.reverse {
self.reverse = b;
}
if let Some(b) = config.help_line {
self.help_line = b;
}
#[allow(deprecated)] // for compatibility
if config.vim_keys == Some(true) {
self.keybindings.add_vim_keys();
}
if let Some(keybindings) = config.keybindings.as_ref() {
self.keybindings.add_all(keybindings);
}
if config.additional_alias_args.is_some() {
self.additional_alias_args
.clone_from(&config.additional_alias_args);
}
for (name, job) in &config.jobs {
self.jobs.insert(name.clone(), job.clone());
}
if let Some(default_job) = &config.default_job {
self.default_job = default_job.clone();
}
self.exports.apply_config(config);
}
pub fn apply_args(
&mut self,
args: &Args,
) {
if let Some(job) = &args.job {
self.arg_job = Some(job.clone());
}
if args.no_summary {
self.summary = false;
}
if args.summary {
self.summary = true;
}
if args.no_wrap {
self.wrap = false;
}
if args.wrap {
self.wrap = true;
}
if args.no_reverse {
self.reverse = false;
}
if args.help_line {
self.help_line = true;
}
if args.no_help_line {
self.help_line = false;
}
if args.export_locations {
self.exports.set_locations_export_auto(true);
}
if args.no_export_locations {
self.exports.set_locations_export_auto(false);
}
if args.reverse {
self.reverse = true;
}
if args.no_default_features {
self.no_default_features = true;
}
if args.all_features {
self.all_features = true;
}
if args.features.is_some() {
self.features.clone_from(&args.features);
}
self.additional_job_args
.clone_from(&args.additional_job_args);
}
pub fn check(&self) -> Result<()> {
if self.jobs.is_empty() {
bail!("Invalid configuration : no job found");
}
if let NameOrAlias::Name(name) = &self.default_job.name_or_alias {
if !self.jobs.contains_key(name) {
bail!("Invalid configuration : default job ({name:?}) not found in jobs");
}
}
Ok(())
}
}
bacon-3.12.0/src/context.rs 0000644 0000000 0000000 00000020577 10461020230 0013625 0 ustar 0000000 0000000 use {
crate::*,
anyhow::{
Result,
bail,
},
cargo_metadata::MetadataCommand,
std::{
env,
fmt,
path::{
Path,
PathBuf,
},
},
};
static DEFAULT_WATCHES: &[&str] = &[
"Cargo.toml",
"src",
"tests",
"benches",
"examples",
"build.rs",
];
/// information on the paths which are relevant for a mission
#[derive(Debug)]
pub struct Context {
pub name: String,
pub nature: ContextNature,
/// The current package/project
pub package_directory: PathBuf,
/// The root of the workspace, only defined when it makes sense
/// and it's different from the package directory.
///
/// Today it's only obtained from cargo metadata but in the future
/// it could be obtained from other kind of sources.
pub workspace_root: Option,
cargo_mission_location: Option,
/// An optional path to watch, given at launch and overriding the settings
/// of the Cargo.toml file, bacon.toml file, etc.
pub path_to_watch: Option,
}
/// Specific data for a cargo related mission
struct CargoContext {
pub cargo_toml_file: PathBuf,
pub packages: Vec,
}
impl fmt::Debug for CargoContext {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
f.debug_struct("CargoContext")
.field("cargo_toml_file", &self.cargo_toml_file)
.finish_non_exhaustive()
}
}
impl Context {
pub fn new(args: &Args) -> Result {
let package_directory = args
.project
.as_ref()
.map_or_else(|| env::current_dir().unwrap(), PathBuf::from);
if !package_directory.exists() || !package_directory.is_dir() {
bail!("The project path must be a directory");
}
let name = package_directory
.file_name()
.unwrap_or(package_directory.as_os_str())
.to_string_lossy()
.to_string();
let path_to_watch = args.watch.as_ref().map(PathBuf::from);
// A cargo project is one directly containing a Cargo.toml file.
// When the project is a Cargo project, some additional rules apply and
// the Cargo.toml file(s) is/are used to determine the paths to watch.
let mut cargo_toml_file = package_directory.join("Cargo.toml");
let nature = if cargo_toml_file.exists() && cargo_toml_file.is_file() {
ContextNature::Cargo
} else {
ContextNature::Other
};
let mut workspace_root = None;
let mut cargo_mission_location = None;
if nature == ContextNature::Cargo {
let metadata = if args.offline {
MetadataCommand::new()
.current_dir(&package_directory)
.no_deps()
.other_options(["--frozen".to_string(), "--offline".to_string()])
.exec()
} else {
MetadataCommand::new()
.current_dir(&package_directory)
.exec()
};
let metadata = metadata?;
if let Some(resolved_root) = metadata.resolve.and_then(|resolve| resolve.root) {
cargo_toml_file = metadata
.packages
.iter()
.find(|p| p.id == resolved_root)
.map(|p| p.manifest_path.as_std_path().to_path_buf())
.expect("resolved manifest was not in package list");
if metadata.workspace_root.as_std_path() != package_directory {
workspace_root = Some(metadata.workspace_root.as_std_path().to_path_buf());
}
}
cargo_mission_location = Some(CargoContext {
cargo_toml_file,
packages: metadata.packages,
});
}
Ok(Self {
name,
nature,
package_directory,
workspace_root,
cargo_mission_location,
path_to_watch,
})
}
pub fn mission<'s>(
&self,
concrete_job_ref: ConcreteJobRef,
leaf_job: &Job, // the raw job as defined, without using root settings
settings: &'s Settings,
) -> Result> {
// the real job used in the mission is built from settings.all_jobs
// on which the provided leaf job is applied
let mut job = settings.all_jobs.clone();
job.apply(leaf_job);
let location_name = self.name.clone();
let mut paths_to_watch: Vec = Vec::new();
if let Some(path_to_watch) = &self.path_to_watch {
paths_to_watch.push(path_to_watch.clone());
} else {
// Automatically watch all kinds of source files.
// "watches", at this point, aren't full path, they still must be joined
// with the right path which may depend on the
let mut watches = Vec::new();
if let Some(v) = &job.watch {
for watch in v.iter() {
watches.push(watch.as_str());
}
}
let add_default = job.default_watch.unwrap_or(true);
if add_default {
for watch in DEFAULT_WATCHES {
if !watches.contains(watch) {
watches.push(watch);
}
}
}
debug!("watches: {watches:?}");
add_to_paths_to_watch(&watches, &self.package_directory, &mut paths_to_watch);
if let Some(workspace_root) = &self.workspace_root {
// there's usually not much src at the workspace level but we must
// at least watch the Cargo.toml file
add_to_paths_to_watch(&watches, workspace_root, &mut paths_to_watch);
}
if let Some(location) = &self.cargo_mission_location {
for item in &location.packages {
// if it's a local package
if item.source.is_none() {
let item_path = item
.manifest_path
.parent()
.expect("parent of a target folder is a root folder");
add_to_paths_to_watch(
&watches,
item_path.as_std_path(),
&mut paths_to_watch,
);
if item.manifest_path.exists() {
paths_to_watch.push(item.manifest_path.clone().into());
} else {
warn!("missing manifest file: {:?}", item.manifest_path);
}
}
}
}
}
let execution_directory = self.package_directory.clone();
Ok(Mission {
location_name,
concrete_job_ref,
execution_directory,
package_directory: self.package_directory.clone(),
workspace_directory: self.workspace_root.clone(),
job,
paths_to_watch,
settings,
})
}
pub fn workspace_cargo_path(&self) -> Option {
self.workspace_root.as_ref().map(|p| p.join("Cargo.toml"))
}
/// return the location of the workspace level bacon.toml file
/// (if it's different from the package level bacon.toml file)
pub fn workspace_config_path(&self) -> Option {
self.workspace_root.as_ref().map(|p| p.join("bacon.toml"))
}
pub fn workspace_dot_config_path(&self) -> Option {
self.workspace_root
.as_ref()
.map(|p| p.join(".config/bacon.toml"))
}
pub fn package_cargo_path(&self) -> PathBuf {
self.package_directory.join("Cargo.toml")
}
pub fn package_config_path(&self) -> PathBuf {
self.package_directory.join("bacon.toml")
}
pub fn package_dot_config_path(&self) -> PathBuf {
self.package_directory.join(".config/bacon.toml")
}
}
fn add_to_paths_to_watch(
watches: &[&str],
base_path: &Path,
paths_to_watch: &mut Vec,
) {
for watch in watches {
let full_path = base_path.join(watch);
if !paths_to_watch.contains(&full_path) && full_path.exists() {
paths_to_watch.push(full_path);
}
}
}
bacon-3.12.0/src/context_nature.rs 0000644 0000000 0000000 00000000271 10461020230 0015170 0 ustar 0000000 0000000 /// The kind of projec/context, as it impacts computing features,
/// files to watch, etc.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextNature {
Cargo,
Other,
}
bacon-3.12.0/src/exec/command_builder.rs 0000644 0000000 0000000 00000005051 10461020230 0016177 0 ustar 0000000 0000000 use std::{
collections::HashMap,
ffi::{
OsStr,
OsString,
},
path::{
Path,
PathBuf,
},
process::{
Command,
Stdio,
},
};
#[derive(Debug, Clone)]
pub struct CommandBuilder {
exe: String,
current_dir: Option,
args: Vec,
with_stdout: bool,
envs: HashMap,
}
impl CommandBuilder {
pub fn new(exe: &str) -> Self {
Self {
exe: exe.to_string(),
current_dir: None,
args: Vec::new(),
with_stdout: false,
envs: Default::default(),
}
}
pub fn build(&self) -> Command {
let mut command = Command::new(&self.exe);
if let Some(dir) = &self.current_dir {
command.current_dir(dir);
}
command.args(&self.args);
command.envs(&self.envs);
command
.envs(&self.envs)
.stdin(Stdio::null())
.stderr(Stdio::piped())
.stdout(if self.with_stdout {
Stdio::piped()
} else {
Stdio::null()
});
command
}
pub fn with_stdout(
&mut self,
b: bool,
) -> &mut Self {
self.with_stdout = b;
self
}
pub fn is_with_stdout(&self) -> bool {
self.with_stdout
}
pub fn current_dir>(
&mut self,
dir: P,
) -> &mut Self {
self.current_dir = Some(dir.as_ref().to_path_buf());
self
}
pub fn arg>(
&mut self,
arg: S,
) -> &mut Self {
self.args.push(arg.as_ref().to_os_string());
self
}
pub fn args(
&mut self,
args: I,
) -> &mut Self
where
I: IntoIterator,
S: AsRef,
{
for arg in args {
self.args.push(arg.as_ref().to_os_string());
}
self
}
pub fn env(
&mut self,
key: K,
val: V,
) -> &mut Self
where
K: AsRef,
V: AsRef,
{
self.envs
.insert(key.as_ref().to_os_string(), val.as_ref().to_os_string());
self
}
pub fn envs(
&mut self,
vars: I,
) -> &mut Self
where
I: IntoIterator,
K: AsRef,
V: AsRef,
{
for (k, v) in vars {
self.envs
.insert(k.as_ref().to_os_string(), v.as_ref().to_os_string());
}
self
}
}
bacon-3.12.0/src/exec/executor.rs 0000644 0000000 0000000 00000022442 10461020230 0014714 0 ustar 0000000 0000000 use {
crate::*,
std::{
io::{
self,
BufRead,
BufReader,
},
process::{
Child,
Command,
},
thread,
time::Instant,
},
termimad::crossbeam::channel::{
self,
Receiver,
Sender,
},
};
/// an executor calling a cargo (or similar) command in a separate
/// thread when asked to and sending the lines of output in a channel,
/// and finishing by None.
/// Channel sizes are designed to avoid useless computations.
pub struct MissionExecutor {
command_builder: CommandBuilder,
kill_command: Option>,
line_sender: Sender,
pub line_receiver: Receiver,
}
/// Dedicated to one execution of the job (so there's usually
/// several task executors during the lifetime of a mission executor)
pub struct TaskExecutor {
/// the thread running the current task
child_thread: thread::JoinHandle<()>,
stop_sender: Sender,
grace_period_start: Option, // forgotten at end of grace period
grace_period: Period,
}
/// A message sent to the child_thread on end
#[derive(Clone, Copy)]
enum StopMessage {
SendStatus, // process already finished, just get status
Kill, // kill the process, don't bother about the status
}
impl TaskExecutor {
/// Interrupt the process
pub fn interrupt(self) {
let _ = self.stop_sender.send(StopMessage::Kill);
}
/// Kill the process, and wait until it finished
pub fn die(self) {
if let Err(e) = self.stop_sender.send(StopMessage::Kill) {
debug!("failed to send 'die' signal: {e}");
}
if self.child_thread.join().is_err() {
warn!("child_thread.join() failed"); // should not happen
}
}
pub fn is_in_grace_period(&mut self) -> bool {
if let Some(grace_period_start) = self.grace_period_start {
if grace_period_start.elapsed() < self.grace_period.duration {
return true;
}
self.grace_period_start = None;
}
false
}
}
impl MissionExecutor {
/// Prepare the executor (no task/process/thread is started at this point)
pub fn new(mission: &Mission) -> anyhow::Result {
let command_builder = mission.get_command()?;
let kill_command = mission.kill_command();
let (line_sender, line_receiver) = channel::unbounded();
Ok(Self {
command_builder,
kill_command,
line_sender,
line_receiver,
})
}
/// Start the job's command, once, with the given settings
pub fn start(
&mut self,
task: Task,
) -> anyhow::Result {
info!("start task {task:?}");
let grace_period = task.grace_period;
let grace_period_start = if grace_period.is_zero() {
None
} else {
Some(Instant::now())
};
let mut command_builder = self.command_builder.clone();
command_builder.env("RUST_BACKTRACE", task.backtrace.unwrap_or("0"));
let kill_command = self.kill_command.clone();
let with_stdout = command_builder.is_with_stdout();
let line_sender = self.line_sender.clone();
let (stop_sender, stop_receiver) = channel::bounded(1);
let err_stop_sender = stop_sender.clone();
// Global task executor thread
let child_thread = thread::spawn(move || {
// before starting the command, we wait some time, so that a bunch
// of quasi-simultaneous file events can be finished before the command
// starts (during this time, no other command is started by bacon in app.rs)
if !grace_period.is_zero() {
thread::sleep(grace_period.duration);
}
let mut cmd = command_builder.build();
let mut child = match cmd.spawn() {
Ok(child) => child,
Err(e) => {
let _ = line_sender.send(CommandExecInfo::Error(
anyhow::anyhow!(e).context(format!("failed to spawn {cmd:?}")),
));
return;
}
};
// thread piping the stdout lines
if with_stdout {
let sender = line_sender.clone();
let Some(stdout) = child.stdout.take() else {
warn!("process has no stdout"); // unlikely
return;
};
let mut buf_reader = BufReader::new(stdout);
thread::spawn(move || {
let mut line = String::new();
loop {
match buf_reader.read_line(&mut line) {
Err(e) => {
warn!("error : {e}");
}
Ok(0) => {
// there won't be anything more, quitting
break;
}
Ok(_) => {
let response = CommandExecInfo::Line(RawCommandOutputLine {
content: line.clone(),
origin: CommandStream::StdOut,
});
if sender.send(response).is_err() {
break; // channel closed
}
}
}
line.clear();
}
});
}
// starting a thread to handle stderr lines until program
// ends (then ask the child_thread to send status)
let err_line_sender = line_sender.clone();
let stderr = child.stderr.take().expect("child missing stderr");
let mut buf_reader = BufReader::new(stderr);
thread::spawn(move || {
let mut line = String::new();
loop {
match buf_reader.read_line(&mut line) {
Err(e) => {
warn!("error : {e}");
}
Ok(0) => {
if let Err(e) = err_stop_sender.send(StopMessage::SendStatus) {
warn!("sending stop message failed: {e}");
}
break;
}
Ok(_) => {
let response = CommandExecInfo::Line(RawCommandOutputLine {
content: line.clone(),
origin: CommandStream::StdErr,
});
if err_line_sender.send(response).is_err() {
break; // channel closed
}
}
}
line.clear();
}
});
// now waiting for the stop event
match stop_receiver.recv() {
Ok(stop) => match stop {
StopMessage::SendStatus => {
let status = child.try_wait();
if let Ok(status) = status {
let _ = line_sender.send(CommandExecInfo::End { status });
}
}
StopMessage::Kill => {
debug!("explicit interrupt received");
kill(kill_command.as_deref(), &mut child);
}
},
Err(e) => {
debug!("recv error: {e}"); // probably just the executor dropped
kill(kill_command.as_deref(), &mut child);
}
}
if let Err(e) = child.wait() {
warn!("waiting for child failed: {e}");
}
});
Ok(TaskExecutor {
child_thread,
stop_sender,
grace_period_start,
grace_period,
})
}
}
/// kill the child process, either by using a specific command or by
/// using the default platform kill method if the specific command
/// failed or wasn't provided.
fn kill(
kill_command: Option<&[String]>,
child: &mut Child,
) {
if let Some(kill_command) = kill_command {
info!("launch specific kill command {kill_command:?}");
let Err(e) = run_kill_command(kill_command, child) else {
return;
};
warn!("specific kill command failed: {e}");
}
child.kill().expect("command couldn't be killed")
}
fn run_kill_command(
kill_command: &[String],
child: &mut Child,
) -> io::Result<()> {
let (exe, args) = kill_command
.split_first()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "empty kill command"))?;
let mut kill = Command::new(exe);
kill.args(args);
kill.arg(child.id().to_string());
let mut proc = kill.spawn()?;
let status = proc.wait()?;
if !status.success() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("kill command returned nonzero status: {status}"),
));
}
child.wait()?;
Ok(())
}
bacon-3.12.0/src/exec/mod.rs 0000644 0000000 0000000 00000000317 10461020230 0013632 0 ustar 0000000 0000000 mod command_builder;
mod executor;
mod on_change_strategy;
mod period;
mod task;
pub use {
command_builder::CommandBuilder,
executor::*,
on_change_strategy::*,
period::*,
task::Task,
};
bacon-3.12.0/src/exec/on_change_strategy.rs 0000644 0000000 0000000 00000000273 10461020230 0016717 0 ustar 0000000 0000000 use serde::Deserialize;
#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OnChangeStrategy {
KillThenRestart,
WaitThenRestart,
}
bacon-3.12.0/src/exec/period.rs 0000644 0000000 0000000 00000003036 10461020230 0014336 0 ustar 0000000 0000000 use {
anyhow::anyhow,
lazy_regex::*,
serde::{
Deserialize,
Deserializer,
de,
},
std::{
str::FromStr,
time::Duration,
},
};
/// A small wrapper over time::Duration, to allow reading from a string in
/// config. There's no symetric serialization and the input format is
/// quite crude (eg "25ms" or "254ns" or "none")
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Period {
pub duration: Duration,
}
impl Period {
pub const fn is_zero(&self) -> bool {
self.duration.is_zero()
}
pub fn sleep(&self) {
std::thread::sleep(self.duration);
}
}
impl From for Period {
fn from(duration: Duration) -> Self {
Self { duration }
}
}
impl FromStr for Period {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result {
let duration = regex_switch!(s,
r"^(?\d+)\s*ns$" => Duration::from_nanos(n.parse()?),
r"^(?\d+)\s*ms$" => Duration::from_millis(n.parse()?),
r"^(?\d+)\s*s$" => Duration::from_secs(n.parse()?),
r"^[^1-9]*$" => Duration::new(0, 0), // eg "none", "0", "off"
)
.ok_or_else(|| anyhow!("Invalid period: {}", s))?;
Ok(Self { duration })
}
}
impl<'de> Deserialize<'de> for Period {
fn deserialize(deserializer: D) -> Result
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(de::Error::custom)
}
}
bacon-3.12.0/src/exec/task.rs 0000644 0000000 0000000 00000000335 10461020230 0014015 0 ustar 0000000 0000000 use crate::Period;
/// Settings for one execution of a job's command
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Task {
pub backtrace: Option<&'static str>, // ("1" or "full")
pub grace_period: Period,
}
bacon-3.12.0/src/export/export_config.rs 0000644 0000000 0000000 00000000561 10461020230 0016317 0 ustar 0000000 0000000 use {
crate::*,
serde::Deserialize,
std::path::PathBuf,
};
/// A generic configuration for all exports, whatever the exporter.
#[derive(Debug, Clone, Deserialize)]
pub struct ExportConfig {
pub exporter: Option,
#[serde(alias = "enabled")]
pub auto: Option,
pub path: Option,
pub line_format: Option,
}
bacon-3.12.0/src/export/export_settings.rs 0000644 0000000 0000000 00000003061 10461020230 0016710 0 ustar 0000000 0000000 use {
crate::*,
std::{
fs::File,
path::PathBuf,
},
};
/// Settings for one export
#[derive(Debug, Clone)]
pub struct ExportSettings {
pub exporter: Exporter,
pub auto: bool,
pub path: PathBuf,
pub line_format: String,
}
impl ExportSettings {
pub fn do_export(
&self,
name: &str,
state: &AppState<'_>,
) -> anyhow::Result<()> {
let path = if self.path.is_relative() {
state.mission.package_directory.join(&self.path)
} else {
self.path.to_path_buf()
};
info!("exporting to {:?}", path);
let Some(report) = state.cmd_result.report() else {
info!("No report to export");
return Ok(());
};
match self.exporter {
Exporter::Analyser => {
if let Some(export) = report.analyzer_exports.get(name) {
std::fs::write(&path, export)?;
} else {
info!("Analyzer didn't build export {:?}", name);
}
}
Exporter::Analysis => {
error!("Aanlysis export not currently implemented");
}
Exporter::JsonReport => {
let json = serde_json::to_string_pretty(&report)?;
std::fs::write(&path, json)?;
}
Exporter::Locations => {
let mut file = File::create(path)?;
report.write_locations(&mut file, &state.mission, &self.line_format)?;
}
}
Ok(())
}
}
bacon-3.12.0/src/export/exporter.rs 0000644 0000000 0000000 00000000627 10461020230 0015324 0 ustar 0000000 0000000 use serde::Deserialize;
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Exporter {
/// The analyzer is tasked with doing an export while analyzing the
/// command output
#[serde(alias = "analyzer")]
Analyser,
/// This exporter doesn't exist at the moment
#[serde(alias = "analyzis")]
Analysis,
JsonReport,
Locations,
}
bacon-3.12.0/src/export/exports_settings.rs 0000644 0000000 0000000 00000016231 10461020230 0017076 0 ustar 0000000 0000000 use {
crate::*,
std::{
collections::HashMap,
path::PathBuf,
},
};
/// Settings for all exports
#[derive(Debug, Clone, Default)]
pub struct ExportsSettings {
pub exports: HashMap,
}
impl ExportsSettings {
pub fn set_locations_export_auto(
&mut self,
enabled: bool,
) {
let locations_export = self
.exports
.entry("locations".to_string())
.or_insert_with(default_locations_export_settings);
locations_export.auto = enabled;
}
pub fn do_auto_exports(
&self,
state: &AppState<'_>,
) {
for (name, export) in &self.exports {
if export.auto {
info!("doing auto export {:?}", name);
if let Err(e) = export.do_export(name, state) {
error!("error while exporting {:?}: {:?}", name, e);
}
}
}
}
pub fn do_named_export(
&self,
requested_name: &str,
state: &AppState<'_>,
) {
if let Some(export) = self.exports.get(requested_name) {
if let Err(e) = export.do_export(requested_name, state) {
error!("error while exporting {:?}: {:?}", requested_name, e);
}
} else {
warn!("no export named {:?}", requested_name);
}
}
/// We apply different parts of the config, matching
/// different generations of the config format:
///
/// - the exports map (current)
/// - the export object (recently deprecated since 2.22.0)
/// - the export_locations field (deprecated since 2.9.0)
///
/// FIXME Should we prevent having two exporters with the
/// same path ?
pub fn apply_config(
&mut self,
config: &Config,
) {
// normal [exports] map
for (name, ec) in &config.exports {
if let Some(e) = self.exports.get_mut(name) {
if let Some(exporter) = ec.exporter {
e.exporter = exporter;
}
if let Some(b) = ec.auto {
e.auto = b;
}
if let Some(p) = &ec.path {
e.path = p.clone();
}
if let Some(lf) = &ec.line_format {
e.line_format = lf.clone();
}
continue;
}
let exporter = match ec.exporter {
Some(e) => e,
None => match name.as_str() {
"analysis" => Exporter::Analysis,
"json-report" => Exporter::JsonReport,
"locations" => Exporter::Locations,
_ => {
warn!(
"Exporter not specified for export {:?}, using 'locations'",
name
);
Exporter::Locations
}
},
};
let auto = ec.auto.unwrap_or(true);
let path = ec.path.clone().unwrap_or_else(|| match exporter {
Exporter::Analyser => default_analyser_path(),
Exporter::Analysis => default_analysis_path(),
Exporter::Locations => default_locations_path(),
Exporter::JsonReport => default_json_report_path(),
});
let line_format = ec.line_format.clone().unwrap_or_else(|| match exporter {
Exporter::Locations => default_locations_line_format().to_string(),
_ => "".to_string(),
});
self.exports.insert(
name.clone(),
ExportSettings {
exporter,
auto,
path,
line_format,
},
);
}
// [export] object
#[allow(deprecated)] // for compatibility
if let Some(ec) = &config.export {
match ec.exporter {
Some(Exporter::Analysis) => {
let analysis_export = self
.exports
.entry("analysis".to_string())
.or_insert_with(default_analysis_export_settings);
if let Some(b) = ec.auto {
analysis_export.auto = b;
}
if let Some(p) = &ec.path {
analysis_export.path = p.clone();
}
}
Some(Exporter::JsonReport) => {
let json_report_export = self
.exports
.entry("json-report".to_string())
.or_insert_with(default_json_report_export_settings);
if let Some(b) = ec.auto {
json_report_export.auto = b;
}
if let Some(p) = &ec.path {
json_report_export.path = p.clone();
}
}
_ => {
let locations_export = self
.exports
.entry("locations".to_string())
.or_insert_with(default_locations_export_settings);
if let Some(b) = ec.auto {
locations_export.auto = b;
}
if let Some(p) = &ec.path {
locations_export.path = p.clone();
}
if let Some(lf) = &ec.line_format {
locations_export.line_format = lf.clone();
}
}
}
}
#[allow(deprecated)] // for compatibility
if let Some(b) = config.export_locations {
let locations_export = self
.exports
.entry("locations".to_string())
.or_insert_with(default_locations_export_settings);
locations_export.auto = b;
}
}
}
fn default_analysis_export_settings() -> ExportSettings {
ExportSettings {
exporter: Exporter::Analysis,
auto: true,
path: default_analysis_path(),
line_format: "".to_string(), // not used
}
}
fn default_json_report_export_settings() -> ExportSettings {
ExportSettings {
exporter: Exporter::JsonReport,
auto: true,
path: default_json_report_path(),
line_format: "".to_string(), // not used
}
}
fn default_locations_export_settings() -> ExportSettings {
ExportSettings {
exporter: Exporter::Locations,
auto: true,
path: default_locations_path(),
line_format: default_locations_line_format().to_string(),
}
}
pub fn default_locations_line_format() -> &'static str {
"{kind} {path}:{line}:{column} {message}"
}
pub fn default_analyser_path() -> PathBuf {
PathBuf::from("bacon-analyser.json")
}
pub fn default_analysis_path() -> PathBuf {
PathBuf::from("bacon-analysis.json")
}
pub fn default_json_report_path() -> PathBuf {
PathBuf::from("bacon-report.json")
}
pub fn default_locations_path() -> PathBuf {
PathBuf::from(".bacon-locations")
}
bacon-3.12.0/src/export/mod.rs 0000644 0000000 0000000 00000000262 10461020230 0014226 0 ustar 0000000 0000000 mod export_config;
mod export_settings;
mod exporter;
mod exports_settings;
pub use {
export_config::*,
export_settings::*,
exporter::*,
exports_settings::*,
};
bacon-3.12.0/src/help/examples.rs 0000644 0000000 0000000 00000001503 10461020230 0014673 0 ustar 0000000 0000000 /// A bacon launch example to display in the --help message
pub struct Example {
pub title: &'static str,
pub cmd: &'static str,
}
pub static EXAMPLES_TEMPLATE: &str = "
**Examples:**
${examples
*${example-number})* ${example-title}: `${example-cmd}`
}
";
/// Examples to display in the --help message
pub static EXAMPLES: &[Example] = &[
Example {
title: "Start with the default job",
cmd: "bacon",
},
Example {
title: "Start with a specific job",
cmd: "bacon clippy",
},
Example {
title: "Start with features",
cmd: "bacon --features clipboard",
},
Example {
title: "Start a specific job on another path",
cmd: "bacon ../broot test",
},
Example {
title: "Start in summary mode",
cmd: "bacon -s",
},
];
bacon-3.12.0/src/help/help_line.rs 0000644 0000000 0000000 00000013013 10461020230 0015013 0 ustar 0000000 0000000 use crate::*;
pub struct HelpLine {
quit: String,
toggle_summary: Option,
wrap: Option,
not_wrap: Option,
toggle_backtrace: Option,
help: Option,
close_help: Option,
pause: Option,
unpause: Option,
scope: Option,
search: Option,
next_match: Option,
previous_match: Option,
clear_search: Option,
validate_search: Option,
}
impl HelpLine {
pub fn new(settings: &Settings) -> Self {
let kb = &settings.keybindings;
let quit = kb
.shortest_internal_key(Internal::Quit)
.map(|k| format!("*{k}* to quit"))
.expect("the app to be quittable");
let toggle_summary = kb
.shortest_internal_key(Internal::ToggleSummary)
.map(|k| format!("*{k}* to toggle summary mode"));
let wrap = kb
.shortest_internal_key(Internal::ToggleWrap)
.map(|k| format!("*{k}* to wrap lines"));
let not_wrap = kb
.shortest_internal_key(Internal::ToggleWrap)
.map(|k| format!("*{k}* to not wrap lines"));
let toggle_backtrace = kb
.shortest_action_key(|action| {
matches!(action, Action::Internal(Internal::ToggleBacktrace(_)))
})
.map(|k| format!("*{k}* to toggle backtraces"));
let help = kb
.shortest_internal_key(Internal::Help)
.map(|k| format!("*{k}* for help"));
let close_help = kb
.shortest_internal_key(Internal::Back)
.or_else(|| kb.shortest_internal_key(Internal::Help))
.map(|k| format!("*{k}* to close this help"));
let pause = kb
.shortest_internal_key(Internal::Pause)
.or(kb.shortest_internal_key(Internal::TogglePause))
.map(|k| format!("*{k}* to pause"));
let unpause = kb
.shortest_internal_key(Internal::Unpause)
.or(kb.shortest_internal_key(Internal::TogglePause))
.map(|k| format!("*{k}* to unpause"));
let scope = kb
.shortest_internal_key(Internal::ScopeToFailures)
.map(|k| format!("*{k}* to scope to failures"));
let search = kb
.shortest_internal_key(Internal::FocusSearch)
.map(|k| format!("*{k}* to search"));
let next_match = kb
.shortest_internal_key(Internal::NextMatch)
.map(|k| format!("*{k}* for next match"));
let previous_match = kb
.shortest_internal_key(Internal::PreviousMatch)
.map(|k| format!("*{k}* for previous match"));
let clear_search = kb
.shortest_internal_key(Internal::Back)
.map(|k| format!("*{k}* to clear"));
let validate_search = kb
.shortest_internal_key(Internal::Validate)
.map(|k| format!("*{k}* to validate"));
Self {
quit,
toggle_summary,
wrap,
not_wrap,
toggle_backtrace,
help,
close_help,
pause,
unpause,
scope,
search,
next_match,
previous_match,
clear_search,
validate_search,
}
}
fn applicable_parts(
&self,
state: &AppState,
) -> Vec<&str> {
let mut parts: Vec<&str> = Vec::new();
if state.is_help() {
parts.push(&self.quit);
if let Some(s) = &self.close_help {
parts.push(s);
}
return parts;
}
if state.has_search() {
if let Some(s) = &self.next_match {
parts.push(s);
}
if let Some(s) = &self.previous_match {
parts.push(s);
}
if let Some(s) = &self.clear_search {
parts.push(s);
}
if state.search.focused() {
if let Some(s) = &self.validate_search {
parts.push(s);
}
}
return parts;
}
if state.can_be_scoped() {
if let Some(s) = &self.scope {
parts.push(s);
}
}
if state.auto_refresh.is_paused() {
if let Some(s) = &self.unpause {
parts.push(s);
}
}
if state.cmd_result.suggest_backtrace() {
if let Some(s) = &self.toggle_backtrace {
parts.push(s);
}
}
if let Some(s) = &self.search {
parts.push(s);
}
if let CommandResult::Report(report) = &state.cmd_result {
if !state.mission.is_success(report) {
if let Some(s) = &self.toggle_summary {
parts.push(s);
}
}
}
if let Some(s) = &self.help {
parts.push(s);
}
if state.wrap {
if let Some(s) = &self.not_wrap {
parts.push(s);
}
} else {
if let Some(s) = &self.wrap {
parts.push(s);
}
}
if state.auto_refresh.is_enabled() {
if let Some(s) = &self.pause {
parts.push(s);
}
}
parts.push(&self.quit);
parts
}
pub fn markdown(
&self,
state: &AppState,
) -> String {
let parts = self.applicable_parts(state);
format!("Hit {}", parts.join(", "))
}
}
bacon-3.12.0/src/help/help_page.rs 0000644 0000000 0000000 00000007372 10461020230 0015013 0 ustar 0000000 0000000 use {
crate::*,
anyhow::Result,
termimad::{
Area,
CompoundStyle,
FmtText,
MadSkin,
TextView,
crossterm::style::{
Attribute,
Color::*,
},
minimad::{
Alignment,
OwningTemplateExpander,
TextTemplate,
},
},
};
static TEMPLATE: &str = r#"
# bacon ${version}
**bac*o*n** is a background compiler, watching your sources and executing your cargo jobs on change.
See *https://dystroy.org/bacon* for a complete guide.
|:-:|:-:
|**action**|**shortcut**
|:-|:-:
${keybindings
|${action}|${keys}
}
|-:
Key bindings, jobs, and other preferences were loaded from these files:
* internal default configuration files
${config_files
* ${config_file_path}
}
"#;
pub struct HelpPage {
area: Area,
skin: MadSkin,
expander: OwningTemplateExpander<'static>,
template: TextTemplate<'static>,
scroll: usize,
}
impl HelpPage {
pub fn new(settings: &Settings) -> Self {
let mut skin = MadSkin::default();
skin.paragraph.align = Alignment::Center;
skin.italic = CompoundStyle::new(Some(AnsiValue(204)), None, Attribute::Bold.into());
skin.table.align = Alignment::Center;
skin.bullet.set_fg(AnsiValue(204));
let mut expander = OwningTemplateExpander::new();
expander.set("version", env!("CARGO_PKG_VERSION"));
let mut bindings: Vec<(String, String)> = settings
.keybindings
.build_reverse_map()
.into_iter()
.map(|(action, cks)| {
let cks: Vec = cks.iter().map(|ck| format!("*{ck}*")).collect();
let cks = cks.join(" or ");
(action.doc(), cks)
})
.collect();
bindings.sort_by(|a, b| a.0.cmp(&b.0));
for (action, key) in bindings.drain(..) {
expander
.sub("keybindings")
.set_md("keys", key)
.set_md("action", action);
}
for config_file in &settings.config_files {
expander
.sub("config_files")
.set_md("config_file_path", config_file.to_string_lossy());
}
let template = TextTemplate::from(TEMPLATE);
Self {
area: Area::default(),
skin,
expander,
template,
scroll: 0,
}
}
/// draw the state on the whole terminal
pub fn draw(
&mut self,
w: &mut W,
area: Area,
) -> Result<()> {
self.area = area;
let text = self.expander.expand(&self.template);
let fmt_text = FmtText::from_text(&self.skin, text, Some((self.area.width - 1) as usize));
let mut text_view = TextView::from(&self.area, &fmt_text);
self.scroll = text_view.set_scroll(self.scroll);
Ok(text_view.write_on(w)?)
}
pub fn apply_scroll_command(
&mut self,
cmd: ScrollCommand,
) {
let text = self.expander.expand(&self.template);
let fmt_text = FmtText::from_text(&self.skin, text, Some((self.area.width - 1) as usize));
let mut text_view = TextView::from(&self.area, &fmt_text);
text_view.set_scroll(self.scroll);
match cmd {
ScrollCommand::Top => {
text_view.scroll = 0;
}
ScrollCommand::Bottom => {
text_view.set_scroll(text_view.content_height());
}
ScrollCommand::Lines(lines) => {
text_view.try_scroll_lines(lines);
}
ScrollCommand::MilliPages(milli_pages) => {
text_view.try_scroll_pages(milli_pages as f64 / 1000f64);
}
}
self.scroll = text_view.scroll;
}
}
bacon-3.12.0/src/help/list_jobs.rs 0000644 0000000 0000000 00000001547 10461020230 0015055 0 ustar 0000000 0000000 use {
crate::*,
termimad::{
MadSkin,
minimad::{
OwningTemplateExpander,
TextTemplate,
},
},
};
pub fn print_jobs(settings: &Settings) {
static MD: &str = r#"
|:-:|:-|
|**job**|**command**|
|:-:|:-|
${jobs
|${job_name}|${job_command}|
}
|-|-|
default job: ${default_job}
"#;
let mut expander = OwningTemplateExpander::new();
let mut jobs: Vec<_> = settings.jobs.iter().collect();
jobs.sort_by_key(|(name, _)| name.to_string());
for (name, job) in &jobs {
expander
.sub("jobs")
.set("job_name", name)
.set("job_command", job.command.join(" "));
}
expander.set("default_job", &settings.default_job);
let skin = MadSkin::default();
skin.print_owning_expander(&expander, &TextTemplate::from(MD));
}
bacon-3.12.0/src/help/mod.rs 0000644 0000000 0000000 00000000220 10461020230 0013627 0 ustar 0000000 0000000 mod examples;
mod help_line;
mod help_page;
mod list_jobs;
pub use {
examples::*,
help_line::*,
help_page::*,
list_jobs::*,
};
bacon-3.12.0/src/ignorer/git_ignorer.rs 0000644 0000000 0000000 00000004567 10461020230 0016117 0 ustar 0000000 0000000 use {
crate::*,
anyhow::{
Context,
Result,
},
gix::{
self as git,
Repository,
},
std::path::Path,
};
/// An object able to tell whether a file is excluded
/// by gitignore rules
pub struct GitIgnorer {
repo: Repository,
}
impl GitIgnorer {
/// Create an Ignorer from any directory path: the closest
/// surrounding git repository will be found (if there's one)
/// and its gitignore rules used.
///
/// root_path is assumed to exist and be a directory
pub(crate) fn new(root_path: &Path) -> Result {
let repo = git::discover(root_path)?;
Ok(Self { repo })
}
}
impl Ignorer for GitIgnorer {
fn excludes(
&mut self,
paths: &Path,
) -> Result {
self.excludes_all_paths(&[paths])
}
}
impl GitIgnorer {
fn excludes_all_paths(
&mut self,
paths: &[&Path],
) -> Result {
let worktree = self.repo.worktree().context("a worktree should exist")?;
// The "Cache" is the structure allowing checking exclusion.
// Building it is the most expensive operation, and we could store it
// in the Ignorer instead of the repo (by having the repo in the mission),
// but it's still about just 1ms and I'm not sure we know if it always
// stays valid.
let mut cache = time!(Debug, worktree.excludes(None)?);
for path in paths {
// cache.at_path panics if not provided a path relative
// to the work directory, so we compute the relative path
let Some(work_dir) = self.repo.work_dir() else {
return Ok(false);
};
let Ok(relative_path) = path.strip_prefix(work_dir) else {
return Ok(false);
};
// cache.at_path panics if the relative path is empty, so
// we must check that
if relative_path.as_os_str().is_empty() {
return Ok(true);
};
if path.is_dir() {
// we're not interested in directories (we should not receive them anyway)
return Ok(false);
}
let platform = cache.at_path(relative_path, Some(gix::index::entry::Mode::FILE))?;
if !platform.is_excluded() {
return Ok(false);
}
}
Ok(true)
}
}
bacon-3.12.0/src/ignorer/glob_ignorer.rs 0000644 0000000 0000000 00000002103 10461020230 0016237 0 ustar 0000000 0000000 use {
crate::*,
anyhow::Result,
std::path::Path,
};
#[derive(Default)]
pub struct GlobIgnorer {
globs: Vec,
}
impl GlobIgnorer {
pub fn add(
&mut self,
pattern: &str,
root: &Path,
) -> Result<()> {
if pattern.starts_with('/') {
self.globs.push(glob::Pattern::new(pattern)?);
// it's probably a path relative to the root of the package
let pattern = root.join(pattern);
let pattern = pattern.to_string_lossy();
self.globs.push(glob::Pattern::new(&pattern)?);
} else {
// as glob doesn't work with non absolute paths, we make it absolute
self.globs
.push(glob::Pattern::new(&format!("/**/{}", pattern))?);
}
Ok(())
}
}
impl Ignorer for GlobIgnorer {
fn excludes(
&mut self,
paths: &Path,
) -> Result {
for glob in &self.globs {
if glob.matches_path(paths) {
return Ok(true);
}
}
Ok(false)
}
}
bacon-3.12.0/src/ignorer/mod.rs 0000644 0000000 0000000 00000002504 10461020230 0014353 0 ustar 0000000 0000000 use {
anyhow::Result,
std::path::{
Path,
PathBuf,
},
};
mod git_ignorer;
mod glob_ignorer;
pub use {
git_ignorer::GitIgnorer,
glob_ignorer::GlobIgnorer,
};
pub trait Ignorer {
/// Tell whether all given paths are excluded according to
/// either the global gitignore rules or the ones of the repository.
///
/// Return Ok(false) when at least one file is included (i.e. we should
/// execute the job)
fn excludes(
&mut self,
paths: &Path,
) -> Result;
}
/// A set of ignorers
#[derive(Default)]
pub struct IgnorerSet {
ignorers: Vec>,
}
impl IgnorerSet {
pub fn add(
&mut self,
ignorer: Box,
) {
self.ignorers.push(ignorer);
}
pub fn excludes_all_pathbufs(
&mut self,
paths: &[PathBuf],
) -> Result {
if self.ignorers.is_empty() {
return Ok(false);
}
for path in paths {
let mut excluded = false;
for ignorer in &mut self.ignorers {
if ignorer.excludes(path)? {
excluded = true;
break;
}
}
if !excluded {
return Ok(false);
}
}
Ok(true)
}
}
bacon-3.12.0/src/internal.rs 0000644 0000000 0000000 00000023505 10461020230 0013747 0 ustar 0000000 0000000 use {
crate::{
PlaySoundCommand,
ScrollCommand,
Volume,
},
lazy_regex::*,
serde::{
Deserialize,
Deserializer,
Serialize,
Serializer,
de,
},
std::{
fmt,
str::FromStr,
},
};
/// one of the hardcoded actions that can be mapped
/// to a key or ran after a successful job
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Internal {
Back, // leave help, clear search, go to previous job, leave, etc.
BackOrQuit, // same as Back but quits if there is nothing to go back to
CopyUnstyledOutput,
FocusSearch,
FocusGoto,
Help,
NextMatch,
NoOp, // no operation, can be used to clear a binding
Pause,
PlaySound(PlaySoundCommand),
PreviousMatch,
Quit,
ReRun,
Refresh, // clear and rerun
ReloadConfig,
ScopeToFailures,
Scroll(ScrollCommand),
ToggleBacktrace(&'static str),
TogglePause, // either pause or unpause
ToggleRawOutput,
ToggleSummary,
ToggleWrap,
Unpause,
Validate, // validate search entry
}
impl Internal {
/// Return the action description to show in doc/help
pub fn doc(&self) -> String {
match self {
Self::Back => "back to previous page or job".to_string(),
Self::BackOrQuit => {
"back to previous page or job, quitting if there is none".to_string()
}
Self::CopyUnstyledOutput => "copy current job's output".to_string(),
Self::FocusSearch => "focus search".to_string(),
Self::FocusGoto => "focus goto".to_string(),
Self::Help => "help".to_string(),
Self::NextMatch => "next match".to_string(),
Self::NoOp => "no operation".to_string(),
Self::Pause => "pause".to_string(),
Self::PlaySound(_) => "play sound".to_string(),
Self::PreviousMatch => "previous match".to_string(),
Self::Quit => "quit".to_string(),
Self::ReRun => "run current job again".to_string(),
Self::Refresh => "clear then run current job again".to_string(),
Self::ReloadConfig => "reload configuration files".to_string(),
Self::ScopeToFailures => "scope to failures".to_string(),
Self::Scroll(scroll_command) => scroll_command.doc(),
Self::ToggleBacktrace(level) => format!("toggle backtrace ({level})"),
Self::TogglePause => "toggle pause".to_string(),
Self::ToggleRawOutput => "toggle raw output".to_string(),
Self::ToggleSummary => "toggle summary".to_string(),
Self::ToggleWrap => "toggle wrap".to_string(),
Self::Unpause => "unpause".to_string(),
Self::Validate => "validate".to_string(),
}
}
}
impl fmt::Display for Internal {
fn fmt(
&self,
f: &mut fmt::Formatter,
) -> fmt::Result {
match self {
Self::Back => write!(f, "back"),
Self::BackOrQuit => write!(f, "back-or-quit"),
Self::CopyUnstyledOutput => write!(f, "copy-unstyled-output"),
Self::Help => write!(f, "help"),
Self::NoOp => write!(f, "no-op"),
Self::Pause => write!(f, "pause"),
Self::Quit => write!(f, "quit"),
Self::ReRun => write!(f, "rerun"),
Self::Refresh => write!(f, "refresh"),
Self::ReloadConfig => write!(f, "reload-config"),
Self::ScopeToFailures => write!(f, "scope-to-failures"),
Self::Scroll(scroll_command) => scroll_command.fmt(f),
Self::ToggleBacktrace(level) => write!(f, "toggle-backtrace({level})"),
Self::TogglePause => write!(f, "toggle-pause"),
Self::ToggleRawOutput => write!(f, "toggle-raw-output"),
Self::ToggleSummary => write!(f, "toggle-summary"),
Self::ToggleWrap => write!(f, "toggle-wrap"),
Self::Unpause => write!(f, "unpause"),
Self::FocusSearch => write!(f, "focus-search"),
Self::FocusGoto => write!(f, "focus-goto"),
Self::Validate => write!(f, "validate"),
Self::NextMatch => write!(f, "next-match"),
Self::PreviousMatch => write!(f, "previous-match"),
Self::PlaySound(PlaySoundCommand { name, volume }) => {
write!(f, "play-sound(")?;
if let Some(name) = name {
write!(f, "name={},", name)?;
}
write!(f, "volume={})", volume)
}
}
}
}
impl std::str::FromStr for Internal {
type Err = String;
fn from_str(s: &str) -> Result {
if let Ok(scroll_command) = ScrollCommand::from_str(s) {
return Ok(Self::Scroll(scroll_command));
}
match s {
"back" => Ok(Self::Back),
"back-or-quit" => Ok(Self::BackOrQuit),
"help" => Ok(Self::Help),
"quit" => Ok(Self::Quit),
"refresh" => Ok(Self::Refresh),
"reload-config" => Ok(Self::ReloadConfig),
"rerun" => Ok(Self::ReRun),
"scope-to-failures" => Ok(Self::ScopeToFailures),
"toggle-raw-output" => Ok(Self::ToggleRawOutput),
"toggle-backtrace" => Ok(Self::ToggleBacktrace("1")),
"toggle-backtrace(1)" => Ok(Self::ToggleBacktrace("1")),
"toggle-backtrace(2)" => Ok(Self::ToggleBacktrace("2")),
"toggle-backtrace(full)" => Ok(Self::ToggleBacktrace("full")),
"toggle-summary" => Ok(Self::ToggleSummary),
"toggle-wrap" => Ok(Self::ToggleWrap),
"noop" | "no-op" | "no-operation" => Ok(Self::NoOp),
"pause" => Ok(Self::Pause),
"unpause" => Ok(Self::Unpause),
"toggle-pause" => Ok(Self::TogglePause),
"focus-search" => Ok(Self::FocusSearch),
"focus-goto" => Ok(Self::FocusGoto),
"validate" => Ok(Self::Validate),
"next-match" => Ok(Self::NextMatch),
"previous-match" => Ok(Self::PreviousMatch),
"copy-unstyled-output" => Ok(Self::CopyUnstyledOutput),
"play-sound" => Ok(Self::PlaySound(PlaySoundCommand::default())),
_ => {
if let Some((_, props)) = regex_captures!(r"^play[_-]sound\((.*)\)$", s) {
let iter = regex_captures_iter!(r"([^=,]+)=([^=,]+)", props);
let mut volume = Volume::default();
let mut name = None;
for (_, [prop_name, prop_value]) in iter.map(|c| c.extract()) {
let prop_value = prop_value.trim();
match prop_name.trim() {
"name" => {
name = Some(prop_value.to_string());
}
"volume" => {
volume = prop_value.parse()?;
}
_ => {
return Err("invalid play-sound parameter: {prop_name}".to_string());
}
}
}
return Ok(Self::PlaySound(PlaySoundCommand { name, volume }));
}
Err("invalid internal".to_string())
}
}
}
}
impl Serialize for Internal {
fn serialize(
&self,
serializer: S,
) -> Result
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for Internal {
fn deserialize(deserializer: D) -> Result
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(de::Error::custom)
}
}
#[test]
fn test_internal_string_round_trip() {
use crate::Volume;
let internals = [
Internal::Back,
Internal::BackOrQuit,
Internal::FocusSearch,
Internal::Help,
Internal::NoOp,
Internal::Pause,
Internal::Quit,
Internal::ReRun,
Internal::ReloadConfig,
Internal::ScopeToFailures,
Internal::Scroll(ScrollCommand::MilliPages(-3000)),
Internal::Scroll(ScrollCommand::MilliPages(-350)),
Internal::Scroll(ScrollCommand::MilliPages(1561)),
Internal::Scroll(ScrollCommand::Top),
Internal::ToggleBacktrace("1"),
Internal::ToggleBacktrace("full"),
Internal::TogglePause,
Internal::ToggleSummary,
Internal::ToggleWrap,
Internal::Unpause,
Internal::Validate,
Internal::NextMatch,
Internal::PreviousMatch,
Internal::PlaySound(PlaySoundCommand::default()),
Internal::PlaySound(PlaySoundCommand {
name: None,
volume: Volume::new(50),
}),
Internal::PlaySound(PlaySoundCommand {
name: Some("beep-beep".to_string()),
volume: Volume::new(100),
}),
Internal::PlaySound(PlaySoundCommand {
name: None,
volume: Volume::new(0),
}),
];
for internal in internals {
println!("testing {:?}", internal.to_string());
assert_eq!(internal.to_string().parse(), Ok(internal));
}
}
/// Check that white space is allowed around play-sound parameters
/// See https://github.com/Canop/bacon/issues/322
#[test]
fn test_play_sound_parsing_with_space() {
use crate::Action;
let strings = [
"play-sound(name=car-horn,volume=5)",
"play-sound(name=car-horn, volume=5)",
"play-sound( name = car-horn , volume = 5 )",
];
let psc = PlaySoundCommand {
name: Some("car-horn".to_string()),
volume: Volume::new(5),
};
for string in &strings {
let action: Action = string.parse().unwrap();
assert_eq!(action, Action::Internal(Internal::PlaySound(psc.clone())));
}
}
bacon-3.12.0/src/jobs/concrete_job_ref.rs 0000644 0000000 0000000 00000006523 10461020230 0016361 0 ustar 0000000 0000000 use {
crate::*,
lazy_regex::*,
serde::{
Deserialize,
Deserializer,
Serialize,
Serializer,
de,
},
std::{
fmt,
str::FromStr,
},
};
/// A "concrete" job ref is one which can be used from the start, without
/// referring to the job stack
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ConcreteJobRef {
pub name_or_alias: NameOrAlias,
pub scope: Scope,
}
impl ConcreteJobRef {
pub fn from_job_name>(s: S) -> Self {
Self {
name_or_alias: NameOrAlias::Name(s.into()),
scope: Default::default(),
}
}
pub fn badge_label(&self) -> String {
let mut s = String::new();
match &self.name_or_alias {
NameOrAlias::Name(name) => {
s.push_str(name);
}
NameOrAlias::Alias(alias) => {
s.push_str(alias);
}
}
if self.scope.has_tests() {
s.push_str(" (scoped)");
}
s
}
pub fn with_scope(
mut self,
scope: Scope,
) -> Self {
self.scope = scope;
self
}
}
impl Default for ConcreteJobRef {
fn default() -> Self {
Self {
name_or_alias: NameOrAlias::Name("check".to_string()),
scope: Default::default(),
}
}
}
impl fmt::Display for ConcreteJobRef {
fn fmt(
&self,
f: &mut fmt::Formatter,
) -> fmt::Result {
match &self.name_or_alias {
NameOrAlias::Alias(alias) => write!(f, "alias:{alias}")?,
NameOrAlias::Name(name) => write!(f, "{name}")?,
}
if self.scope.has_tests() {
write!(f, "({})", self.scope.tests.join(","))?;
}
Ok(())
}
}
impl FromStr for ConcreteJobRef {
type Err = &'static str;
fn from_str(s: &str) -> Result {
if s.is_empty() {
Err("empty job name")
} else {
Ok(s.into())
}
}
}
impl Serialize for ConcreteJobRef {
fn serialize(
&self,
serializer: S,
) -> Result
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for ConcreteJobRef {
fn deserialize(deserializer: D) -> Result
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(de::Error::custom)
}
}
impl From<&str> for ConcreteJobRef {
fn from(str_entry: &str) -> Self {
let Some((_, alias_prefix, name_or_alias, scope)) =
regex_captures!(r"^(alias:)?([^\(\)]+)(?:\(([^\)]+)\))?$", str_entry,)
else {
warn!("unexpected job ref: {:?}", str_entry);
return Self::from_job_name(str_entry.to_string());
};
let name_or_alias = if alias_prefix.is_empty() {
NameOrAlias::Name(name_or_alias.to_string())
} else {
NameOrAlias::Alias(name_or_alias.to_string())
};
let scope = Scope {
tests: scope
.split(',')
.filter(|t| !t.trim().is_empty())
.map(|s| s.to_string())
.collect(),
};
Self {
name_or_alias,
scope,
}
}
}
bacon-3.12.0/src/jobs/job.rs 0000644 0000000 0000000 00000021304 10461020230 0013635 0 ustar 0000000 0000000 use {
crate::*,
serde::Deserialize,
std::collections::HashMap,
};
/// One of the possible jobs that bacon can run
/// One of the possible job that bacon can run
#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
pub struct Job {
/// Whether to consider that we can have a success
/// when we have test failures
pub allow_failures: Option,
/// Whether to consider that we can have a success
/// when we have warnings. This is especially useful
/// for "cargo run" jobs
pub allow_warnings: Option,
/// The analyzer interpreting the output of the command, the
/// standard cargo dedicated one if not provided
pub analyzer: Option,
/// Whether gitignore rules must be applied
pub apply_gitignore: Option,
/// Whether to wait for the computation to finish before
/// to display it on screen
///
/// This is true by default. Set it to false if you want
/// the previous computation result to be replaced with
/// the new one as soon as it starts.
pub background: Option,
/// The tokens making the command to execute (first one
/// is the executable).
#[serde(default)]
pub command: Vec,
/// Whether to apply the default watch list, which is
/// `["src", "tests", "benches", "examples", "build.rs"]`
///
/// This is true by default. Set it to false if you want
/// to watch nothing, or only the directories you set in
/// `watch`.
pub default_watch: Option,
/// Env vars to set for this job execution
#[serde(default)]
pub env: HashMap,
/// Whether to expand environment variables in the command
pub expand_env_vars: Option,
/// Whether to insert extraneous arguments provided by bacon or end users
///
/// Eg: --all-features or anything after -- in bacon incantation
pub extraneous_args: Option,
/// A list of glob patterns to ignore
#[serde(default)]
pub ignore: Vec,
/// Patterns of lines which should be ignored. Patterns of
/// the prefs or bacon.toml can be overridden at the job
pub ignored_lines: Option>,
/// A kill command. If not provided, SIGKILL is used.
pub kill: Option>,
/// Whether we need to capture stdout too (stderr is
/// always captured)
pub need_stdout: Option,
/// How to handle changes: either immediately kill the current job
/// then restart it, or wait for the current job to finish before
/// restarting it.
pub on_change_strategy: Option,
/// The optional action to run when there's no
/// error, warning or test failures
/// (depending on whether allow_warnings is true or false)
///
/// Could be made a vec in the future but that would involve
/// explaining subtleties like the fact that those actions stop
/// after the first one ending the mission or doing a refresh
#[serde(default)]
pub on_success: Option,
pub grace_period: Option,
/// The optional action to run when it's not a success
#[serde(default)]
pub on_failure: Option,
/// A list of directories that will be watched if the job
/// is run on a package.
/// src, examples, tests, and benches are implicitly included
/// unless you `set default_watch` to false.
pub watch: Option>,
pub show_changes_count: Option,
#[serde(default)]
pub sound: SoundConfig,
}
static DEFAULT_ARGS: &[&str] = &["--color", "always"];
impl Job {
/// Build a `Job` for a cargo alias
pub fn from_alias(
alias_name: &str,
settings: &Settings,
) -> Self {
let mut command = vec!["cargo".to_string(), alias_name.to_string()];
if let Some(additional_args) = settings.additional_alias_args.as_ref() {
for arg in additional_args {
command.push(arg.to_string())
}
} else {
for arg in DEFAULT_ARGS {
command.push(arg.to_string())
}
}
Self {
command,
..Default::default()
}
}
pub fn allow_failures(&self) -> bool {
self.allow_failures.unwrap_or(false)
}
pub fn allow_warnings(&self) -> bool {
self.allow_warnings.unwrap_or(false)
}
pub fn background(&self) -> bool {
self.background.unwrap_or(true)
}
pub fn default_watch(&self) -> bool {
self.default_watch.unwrap_or(true)
}
pub fn expand_env_vars(&self) -> bool {
self.expand_env_vars.unwrap_or(true)
}
pub fn need_stdout(&self) -> bool {
self.need_stdout.unwrap_or(false)
}
pub fn extraneous_args(&self) -> bool {
self.extraneous_args.unwrap_or(true)
}
pub fn show_changes_count(&self) -> bool {
self.show_changes_count.unwrap_or(false)
}
pub fn grace_period(&self) -> Period {
self.grace_period
.unwrap_or(std::time::Duration::from_millis(15).into())
}
pub fn on_change_strategy(&self) -> OnChangeStrategy {
self.on_change_strategy
.unwrap_or(OnChangeStrategy::WaitThenRestart)
}
pub fn apply(
&mut self,
job: &Job,
) {
if let Some(b) = job.allow_failures {
self.allow_failures = Some(b);
}
if let Some(b) = job.allow_warnings {
self.allow_warnings = Some(b);
}
if let Some(v) = job.analyzer {
self.analyzer = Some(v);
}
if let Some(b) = job.apply_gitignore {
self.apply_gitignore = Some(b);
}
if let Some(b) = job.background {
self.background = Some(b);
}
if !job.command.is_empty() {
self.command.clone_from(&job.command);
}
if let Some(b) = job.default_watch {
self.default_watch = Some(b);
}
for (k, v) in &job.env {
self.env.insert(k.clone(), v.clone());
}
if let Some(b) = job.expand_env_vars {
self.expand_env_vars = Some(b);
}
if let Some(b) = job.extraneous_args {
self.extraneous_args = Some(b);
}
for v in &job.ignore {
if !self.ignore.contains(v) {
self.ignore.push(v.clone());
}
}
if let Some(v) = job.ignored_lines.as_ref() {
self.ignored_lines = Some(v.clone());
}
if let Some(v) = job.kill.as_ref() {
self.kill = Some(v.clone());
}
if let Some(b) = job.need_stdout {
self.need_stdout = Some(b);
}
if let Some(v) = job.on_change_strategy {
self.on_change_strategy = Some(v);
}
if let Some(v) = job.on_success.as_ref() {
self.on_success = Some(v.clone());
}
if let Some(v) = job.grace_period {
self.grace_period = Some(v);
}
if let Some(v) = job.on_failure.as_ref() {
self.on_failure = Some(v.clone());
}
if let Some(v) = job.watch.as_ref() {
self.watch = Some(v.clone());
}
if let Some(b) = job.show_changes_count {
self.show_changes_count = Some(b);
}
self.sound.apply(&job.sound);
}
}
#[test]
fn test_job_apply() {
use std::str::FromStr;
let mut base_job = Job::default();
let job_to_apply = Job {
allow_failures: Some(true),
allow_warnings: Some(false),
analyzer: Some(AnalyzerRef::Nextest),
apply_gitignore: Some(false),
background: Some(false),
command: vec!["cargo".to_string(), "test".to_string()],
default_watch: Some(false),
env: vec![("RUST_LOG".to_string(), "debug".to_string())]
.into_iter()
.collect(),
expand_env_vars: Some(false),
extraneous_args: Some(false),
ignore: vec!["special-target".to_string(), "generated".to_string()],
ignored_lines: Some(vec![LinePattern::from_str("half-error.*").unwrap()]),
kill: Some(vec!["die".to_string()]),
need_stdout: Some(true),
grace_period: Some(Period::from_str("20ms").unwrap()),
on_change_strategy: Some(OnChangeStrategy::KillThenRestart),
on_success: Some(Action::from_str("refresh").unwrap()),
on_failure: Some(Action::from_str("play-sound(name=car-horn)").unwrap()),
watch: Some(vec!["src".to_string(), "tests".to_string()]),
show_changes_count: Some(true),
sound: SoundConfig {
enabled: Some(true),
base_volume: Some(Volume::from_str("50").unwrap()),
},
};
base_job.apply(&job_to_apply);
dbg!(&base_job);
assert_eq!(&base_job, &job_to_apply);
}
bacon-3.12.0/src/jobs/job_ref.rs 0000644 0000000 0000000 00000006375 10461020230 0014504 0 ustar 0000000 0000000 use {
crate::*,
lazy_regex::*,
std::fmt,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum JobRef {
Default,
Initial,
Previous,
PreviousOrQuit,
Concrete(ConcreteJobRef),
Scope(Scope),
}
impl JobRef {
pub fn from_job_name>(s: S) -> Self {
Self::Concrete(ConcreteJobRef::from_job_name(s))
}
}
impl From for JobRef {
fn from(scope: Scope) -> Self {
Self::Scope(scope)
}
}
impl From for JobRef {
fn from(concrete: ConcreteJobRef) -> Self {
Self::Concrete(concrete)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum NameOrAlias {
Name(String),
Alias(String),
}
impl fmt::Display for JobRef {
fn fmt(
&self,
f: &mut fmt::Formatter,
) -> fmt::Result {
match self {
Self::Default => write!(f, "default"),
Self::Initial => write!(f, "initial"),
Self::Previous => write!(f, "previous"),
Self::PreviousOrQuit => write!(f, "previous-or-quit"),
Self::Scope(Scope { tests }) => write!(f, "scope:{}", tests.join(",")),
Self::Concrete(concrete) => write!(f, "{}", concrete),
}
}
}
impl From<&str> for JobRef {
fn from(s: &str) -> Self {
regex_switch!(s,
"^default$"i => Self::Default,
"^initial$"i => Self::Initial,
"^previous$"i => Self::Previous,
"^previous-or-quit$"i => Self::PreviousOrQuit,
"^scope:(?.+)$"i => Self::Scope(Scope {
tests: tests
.split(',')
.filter(|t| !t.trim().is_empty())
.map(|s| s.to_string())
.collect(),
}),
)
.unwrap_or_else(|| Self::Concrete(ConcreteJobRef::from(s)))
}
}
#[test]
fn test_job_ref_string_round_trip() {
let job_refs = vec![
JobRef::Default,
JobRef::Initial,
JobRef::Previous,
JobRef::PreviousOrQuit,
JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Name("run".to_string()),
scope: Scope::default(),
}),
JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Name("nextest".to_string()),
scope: Scope {
tests: vec!["first::test".to_string(), "second_test".to_string()],
},
}),
JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Alias("my-check".to_string()),
scope: Scope::default(),
}),
JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Alias("my-test".to_string()),
scope: Scope {
tests: vec!["abc".to_string()],
},
}),
JobRef::Concrete(ConcreteJobRef {
name_or_alias: NameOrAlias::Name("nextest".to_string()),
scope: Scope {
tests: vec!["abc".to_string()],
},
}),
JobRef::Scope(Scope {
tests: vec!["first::test".to_string(), "second_test".to_string()],
}),
];
for job_ref in job_refs {
let s = job_ref.to_string();
let job_ref2 = JobRef::from(s.as_str());
assert_eq!(job_ref, job_ref2);
}
}
bacon-3.12.0/src/jobs/job_stack.rs 0000644 0000000 0000000 00000005554 10461020230 0015033 0 ustar 0000000 0000000 use {
crate::*,
anyhow::{
Result,
anyhow,
},
};
/// The stack of jobs that bacon ran, allowing to get back to the previous one,
/// or to scope the current one
#[derive(Default)]
pub struct JobStack {
entries: Vec,
}
impl JobStack {
/// Apply the job ref instruction to determine the job to run, updating the stack.
///
/// When no job is returned, the application is supposed to quit.
pub fn pick_job(
&mut self,
job_ref: &JobRef,
settings: &Settings,
) -> Result