diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..4660d0f
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,75 @@
+# What software is installed by default:
+# https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
+
+name: build
+
+on:
+ push:
+ pull_request:
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os:
+ - ubuntu-22.04
+ - windows-latest
+ - macos-latest
+ - macos-13
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Check out repo
+ uses: actions/checkout@v4
+
+ - name: Set up rust
+ run: rustup update
+
+ - name: Build
+ run: cargo build --release
+
+ - name: Test
+ run: cargo test --release
+
+ - name: Record target triple
+ run: rustc -vV | awk '/^host/ { print $2 }' > target/release/host
+
+ - name: Upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: cove-${{ matrix.os }}
+ path: |
+ target/release/cove
+ target/release/cove.exe
+ target/release/host
+
+ release:
+ runs-on: ubuntu-latest
+ if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+ needs:
+ - build
+ permissions:
+ contents: write
+ steps:
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+
+ - name: Zip artifacts
+ run: |
+ chmod +x cove-ubuntu-22.04/cove
+ chmod +x cove-windows-latest/cove.exe
+ chmod +x cove-macos-latest/cove
+ chmod +x cove-macos-13/cove
+ zip -jr "cove-$(cat cove-ubuntu-22.04/host).zip" cove-ubuntu-22.04/cove
+ zip -jr "cove-$(cat cove-windows-latest/host).zip" cove-windows-latest/cove.exe
+ zip -jr "cove-$(cat cove-macos-latest/host).zip" cove-macos-latest/cove
+ zip -jr "cove-$(cat cove-macos-13/host).zip" cove-macos-13/cove
+
+ - name: Create new release
+ uses: softprops/action-gh-release@v2
+ with:
+ body: Automated release, see [CHANGELOG.md](CHANGELOG.md) for more details.
+ files: "*.zip"
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..4e428aa
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,8 @@
+{
+ "files.insertFinalNewline": true,
+ "rust-analyzer.cargo.features": "all",
+ "rust-analyzer.imports.granularity.enforce": true,
+ "rust-analyzer.imports.granularity.group": "crate",
+ "rust-analyzer.imports.group.enable": true,
+ "evenBetterToml.formatter.columnWidth": 100,
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c039516..3f9ce8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,34 +4,206 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
Procedure when bumping the version number:
+
1. Update dependencies in a separate commit
2. Set version number in `Cargo.toml`
3. Add new section in this changelog
-4. Commit with message `Bump version to X.Y.Z`
-5. Create tag named `vX.Y.Z`
-6. Fast-forward branch `latest`
-7. Push `master`, `latest` and the new tag
+4. Run `cargo run help-config > CONFIG.md`
+5. Commit with message `Bump version to X.Y.Z`
+6. Create tag named `vX.Y.Z`
+7. Push `master` and the new tag
## Unreleased
+### Changed
+
+- Display emoji user id hashes in the nick list
+- Compile linux binary with older glibc version
+
+## v0.9.3 - 2025-05-31
+
+### Added
+
+- Key bindings for emoji-based user id hashing
+
+### Fixed
+
+- `keys.rooms.action.connect_autojoin` connecting to non-autojoin rooms
+
+## v0.9.2 - 2025-03-14
+
+### Added
+
+- `bell_on_mention` config option
+
+## v0.9.1 - 2025-03-01
+
+### Fixed
+
+- Rendering glitches with unicode-based width estimation
+
+## v0.9.0 - 2025-02-23
+
+### Added
+
+- Unicode-based grapheme width estimation method
+ - `width_estimation_method` config option
+ - `--width-estimation-method` option
+- Room links are now included in the `I` message links list
+
+### Changed
+
+- Updated documentation for `time_zone` config option
+- When connecting to a room using `n` in the room list, the cursor now moves to that room
+- Updated list of emoji names
+
+### Removed
+
+- Special handling of &rl2dev
+
+### Fixed
+
+- Nick color in rare edge cases
+- Message link list rendering bug
+
+## v0.8.3 - 2024-05-20
+
+### Changed
+
+- Updated list of emoji names
+
+## v0.8.2 - 2024-04-25
+
+### Changed
+
+- Renamed `json-stream` export format to `json-lines` (see )
+- Changed `json-lines` file extension from `.json` to `.jsonl`
+
+### Fixed
+
+- Crash when window is too small while empty message editor is visible
+- Mistakes in output and docs
+- Cove not cleaning up terminal state properly
+
+## v0.8.1 - 2024-01-11
+
+### Added
+
+- Support for setting window title
+- More information to room list heading
+- Key bindings for live caesar cipher de- and encoding
+
+### Removed
+
+- Key binding to open present page
+
+## v0.8.0 - 2024-01-04
+
+### Added
+
+- Support for multiple euph server domains
+- Support for `TZ` environment variable
+- `time_zone` config option
+- `--domain` option to `cove export` command
+- `--domain` option to `cove clear-cookies` command
+- Domain field to "connect to new room" popup
+- Welcome info box next to room list
+
+### Changed
+
+- The default euph domain is now https://euphoria.leet.nu/ everywhere
+- The config file format was changed to support multiple euph servers with different domains.
+ Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`.
+- Tweaked F1 popup
+- Tweaked chat message editor when nick list is foused
+- Reduced connection timeout from 30 seconds to 10 seconds
+
+### Fixed
+
+- Room deletion popup accepting any room name
+- Duplicated key presses on Windows
+
+## v0.7.1 - 2023-08-31
+
+### Changed
+
+- Updated dependencies
+
+## v0.7.0 - 2023-05-14
+
+### Added
+
+- Auto-generated config documentation
+ - in [CONFIG.md](CONFIG.md)
+ - via `help-config` CLI command
+- `keys.*` config options
+- `measure_widths` config option
+
+### Changed
+
+- Overhauled widget system and extracted generic widgets to [toss](https://github.com/Garmelon/toss)
+- Overhauled config system to support auto-generating documentation
+- Overhauled key binding system to make key bindings configurable
+- Redesigned F1 popup. It can now be toggled with F1 like the F12 log
+- The F12 log can now be closed with escape
+- Some more small UI fixes and adjustments to the new key binding system
+- Reduced tearing when redrawing screen
+- Split up project into sub-crates
+- Simplified flake dependencies
+
+## v0.6.1 - 2023-04-10
+
+### Changed
+
+- Improved JSON export performance
+- Always show rooms from config file in room list
+
+### Fixed
+
+- Rooms reconnecting instead of showing error popups
+
+## v0.6.0 - 2023-04-04
+
+### Added
+
+- Emoji support
+- `flake.nix`, making cove available as a nix flake
+- `json-stream` room export format
+- Option to export to stdout via `--out -`
+- `--verbose` flag
+
+### Changed
+
+- Non-export info is now printed to stderr instead of stdout
+- Recognizes links without scheme (e.g. `euphoria.io` instead of `https://euphoria.io`)
+- Rooms waiting for reconnect are no longer sorted to bottom in default sort order
+
+### Fixed
+
+- Mentions not being stopped by `>`
+
## v0.5.2 - 2023-01-14
### Added
+
- Key binding to open present page
### Changed
+
- Always connect to &rl2dev in ephemeral mode
- Reduce amount of messages per &rl2dev log request
## v0.5.1 - 2022-11-27
### Changed
+
- Increase reconnect delay to one minute
- Print errors that occurred while cove was running more compactly
## v0.5.0 - 2022-09-26
### Added
+
- Key bindings to navigate nick list
- Room deletion confirmation popup
- Message inspection popup
@@ -40,10 +212,12 @@ Procedure when bumping the version number:
- `rooms_sort_order` config option
### Changed
+
- Use nick changes to detect sessions for nick list
- Support Unicode 15
### Fixed
+
- Cursor being visible through popups
- Cursor in lists when highlighted item moves off-screen
- User disappearing from nick list when only one of their sessions disconnects
@@ -51,6 +225,7 @@ Procedure when bumping the version number:
## v0.4.0 - 2022-09-01
### Added
+
- Config file and `--config` cli option
- `data_dir` config option
- `ephemeral` config option
@@ -66,11 +241,17 @@ Procedure when bumping the version number:
- Key bindings to view and open links in a message
### Changed
+
- Some key bindings in the rooms list
+### Fixed
+
+- Rooms being stuck in "Connecting" state
+
## v0.3.0 - 2022-08-22
### Added
+
- Account login and logout
- Authentication dialog for password-protected rooms
- Error popups in rooms when something goes wrong
@@ -78,10 +259,12 @@ Procedure when bumping the version number:
- Key binding to download more logs
### Changed
+
- Reduced amount of unnecessary redraws
- Description of `export` CLI command
### Fixed
+
- Crash when connecting to nonexistent rooms
- Crash when connecting to rooms that require authentication
- Pasting multi-line strings into the editor
@@ -89,15 +272,18 @@ Procedure when bumping the version number:
## v0.2.1 - 2022-08-11
### Added
+
- Support for modifiers on special keys via the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/)
### Fixed
+
- Joining new rooms no longer crashes cove
- Scrolling when exiting message editor
## v0.2.0 - 2022-08-10
### Added
+
- New messages are now marked as unseen
- Sub-trees can now be folded
- Support for pasting text into editors
@@ -110,10 +296,12 @@ Procedure when bumping the version number:
- Support for exporting multiple/all rooms at once
### Changed
+
- Reorganized export command
- Slowed down room history download speed
### Fixed
+
- Chat rendering when deleting and re-joining a room
- Spacing in some popups
diff --git a/CONFIG.md b/CONFIG.md
new file mode 100644
index 0000000..82a7242
--- /dev/null
+++ b/CONFIG.md
@@ -0,0 +1,711 @@
+# Config file format
+
+Cove's config file uses the [TOML](https://toml.io/) format.
+
+Here is an example config that changes a few different options:
+
+```toml
+measure_widths = true
+rooms_sort_order = "importance"
+
+[euph.servers."euphoria.leet.nu".rooms]
+welcome.autojoin = true
+test.username = "badingle"
+test.force_username = true
+private.password = "foobar"
+
+[keys]
+general.abort = ["esc", "ctrl+c"]
+general.exit = "ctrl+q"
+tree.action.fold_tree = "f"
+```
+
+## Key bindings
+
+Key bindings are specified as strings or lists of strings. Each string specifies
+a main key and zero or more modifier keys. The modifier keys (if any) are listed
+first, followed by the main key. They are separated by the `+` character and
+**no** whitespace.
+
+Examples of key bindings:
+- `"ctrl+c"`
+- `"X"` (not `"shift+x"`)
+- `"space"` or `" "` (both space bar)
+- `["g", "home"]`
+- `["K", "ctrl+up"]`
+- `["f1", "?"]`
+- `"ctrl+alt+f3"`
+- `["enter", "any+enter"]` (matches `enter` regardless of modifiers)
+
+Available main keys:
+- Any single character that can be typed
+- `esc`, `enter`, `space`, `tab`, `backtab`
+- `backspace`, `delete`, `insert`
+- `left`, `right`, `up`, `down`
+- `home`, `end`, `pageup`, `pagedown`
+- `f1`, `f2`, ...
+
+Available modifiers:
+- `shift` (must not be used with single characters)
+- `ctrl`
+- `alt`
+- `any` (matches as long as at least one modifier is pressed)
+
+## Available options
+
+### `bell_on_mention`
+
+**Required:** yes
+**Type:** boolean
+**Default:** `false`
+
+Ring the bell (character 0x07) when you are mentioned in a room.
+
+### `data_dir`
+
+**Required:** no
+**Type:** path
+**Default:** platform-dependent
+
+The directory that cove stores its data in when not running in ephemeral
+mode.
+
+Relative paths are interpreted relative to the user's home directory.
+
+See also the `--data-dir` command line option.
+
+### `ephemeral`
+
+**Required:** yes
+**Type:** boolean
+**Default:** `false`
+
+Whether to start in ephemeral mode.
+
+In ephemeral mode, cove doesn't store any data. It completely ignores
+any options related to the data dir.
+
+See also the `--ephemeral` command line option.
+
+### `euph.servers..rooms..autojoin`
+
+**Required:** yes
+**Type:** boolean
+**Default:** `false`
+
+Whether to automatically join this room on startup.
+
+### `euph.servers..rooms..force_username`
+
+**Required:** yes
+**Type:** boolean
+**Default:** `false`
+
+If `euph.servers..rooms..username` is set, this will force
+cove to set the username even if there is already a different username
+associated with the current session.
+
+### `euph.servers..rooms..password`
+
+**Required:** no
+**Type:** string
+
+If set, cove will try once to use this password to authenticate, should
+the room be password-protected.
+
+### `euph.servers..rooms..username`
+
+**Required:** no
+**Type:** string
+
+If set, cove will set this username upon joining if there is no username
+associated with the current session.
+
+### `keys.cursor.down`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["j", "down"]`
+
+Move down.
+
+### `keys.cursor.to_bottom`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["G", "end"]`
+
+Move to bottom.
+
+### `keys.cursor.to_top`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["g", "home"]`
+
+Move to top.
+
+### `keys.cursor.up`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["k", "up"]`
+
+Move up.
+
+### `keys.editor.action.backspace`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["ctrl+h", "backspace"]`
+
+Delete before cursor.
+
+### `keys.editor.action.clear`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"ctrl+l"`
+
+Clear editor contents.
+
+### `keys.editor.action.delete`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["ctrl+d", "delete"]`
+
+Delete after cursor.
+
+### `keys.editor.action.external`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["ctrl+x", "alt+e"]`
+
+Edit in external editor.
+
+### `keys.editor.cursor.down`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"down"`
+
+Move down.
+
+### `keys.editor.cursor.end`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["ctrl+e", "end"]`
+
+Move to end of line.
+
+### `keys.editor.cursor.left`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["ctrl+b", "left"]`
+
+Move left.
+
+### `keys.editor.cursor.left_word`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["alt+b", "ctrl+left"]`
+
+Move left a word.
+
+### `keys.editor.cursor.right`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["ctrl+f", "right"]`
+
+Move right.
+
+### `keys.editor.cursor.right_word`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["alt+f", "ctrl+right"]`
+
+Move right a word.
+
+### `keys.editor.cursor.start`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["ctrl+a", "home"]`
+
+Move to start of line.
+
+### `keys.editor.cursor.up`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"up"`
+
+Move up.
+
+### `keys.general.abort`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"esc"`
+
+Abort/close.
+
+### `keys.general.confirm`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"enter"`
+
+Confirm.
+
+### `keys.general.exit`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"ctrl+c"`
+
+Quit cove.
+
+### `keys.general.focus`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"tab"`
+
+Advance focus.
+
+### `keys.general.help`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"f1"`
+
+Show this help.
+
+### `keys.general.log`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"f12"`
+
+Show log.
+
+### `keys.room.action.account`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"A"`
+
+Manage account.
+
+### `keys.room.action.authenticate`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"a"`
+
+Authenticate.
+
+### `keys.room.action.more_messages`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"m"`
+
+Download more messages.
+
+### `keys.room.action.nick`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"n"`
+
+Change nick.
+
+### `keys.rooms.action.change_sort_order`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"s"`
+
+Change sort order.
+
+### `keys.rooms.action.connect`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"c"`
+
+Connect to selected room.
+
+### `keys.rooms.action.connect_all`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"C"`
+
+Connect to all rooms.
+
+### `keys.rooms.action.connect_autojoin`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"a"`
+
+Connect to all autojoin rooms.
+
+### `keys.rooms.action.delete`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"X"`
+
+Delete room.
+
+### `keys.rooms.action.disconnect`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"d"`
+
+Disconnect from selected room.
+
+### `keys.rooms.action.disconnect_all`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"D"`
+
+Disconnect from all rooms.
+
+### `keys.rooms.action.disconnect_non_autojoin`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"A"`
+
+Disconnect from all non-autojoin rooms.
+
+### `keys.rooms.action.new`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"n"`
+
+Connect to new room.
+
+### `keys.scroll.center_cursor`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"z"`
+
+Center cursor.
+
+### `keys.scroll.down_full`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["ctrl+f", "pagedown"]`
+
+Scroll down a full screen.
+
+### `keys.scroll.down_half`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"ctrl+d"`
+
+Scroll down half a screen.
+
+### `keys.scroll.down_line`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"ctrl+e"`
+
+Scroll down one line.
+
+### `keys.scroll.up_full`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["ctrl+b", "pageup"]`
+
+Scroll up a full screen.
+
+### `keys.scroll.up_half`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"ctrl+u"`
+
+Scroll up half a screen.
+
+### `keys.scroll.up_line`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"ctrl+y"`
+
+Scroll up one line.
+
+### `keys.tree.action.decrease_caesar`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"C"`
+
+Decrease caesar cipher rotation.
+
+### `keys.tree.action.fold_tree`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"space"`
+
+Fold current message's subtree.
+
+### `keys.tree.action.increase_caesar`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"c"`
+
+Increase caesar cipher rotation.
+
+### `keys.tree.action.inspect`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"i"`
+
+Inspect selected element.
+
+### `keys.tree.action.links`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"I"`
+
+List links found in message.
+
+### `keys.tree.action.mark_older_seen`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"ctrl+s"`
+
+Mark all older messages as seen.
+
+### `keys.tree.action.mark_visible_seen`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"S"`
+
+Mark all visible messages as seen.
+
+### `keys.tree.action.new_thread`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"t"`
+
+Start a new thread.
+
+### `keys.tree.action.reply`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"r"`
+
+Reply to message, inline if possible.
+
+### `keys.tree.action.reply_alternate`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"R"`
+
+Reply opposite to normal reply.
+
+### `keys.tree.action.toggle_nick_emoji`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"e"`
+
+Toggle agent id based nick emoji.
+
+### `keys.tree.action.toggle_seen`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"s"`
+
+Toggle current message's seen status.
+
+### `keys.tree.cursor.to_above_sibling`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["K", "ctrl+up"]`
+
+Move to above sibling.
+
+### `keys.tree.cursor.to_below_sibling`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["J", "ctrl+down"]`
+
+Move to below sibling.
+
+### `keys.tree.cursor.to_newer_message`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["l", "right"]`
+
+Move to newer message.
+
+### `keys.tree.cursor.to_newer_unseen_message`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["L", "ctrl+right"]`
+
+Move to newer unseen message.
+
+### `keys.tree.cursor.to_older_message`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["h", "left"]`
+
+Move to older message.
+
+### `keys.tree.cursor.to_older_unseen_message`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `["H", "ctrl+left"]`
+
+Move to older unseen message.
+
+### `keys.tree.cursor.to_parent`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"p"`
+
+Move to parent.
+
+### `keys.tree.cursor.to_root`
+
+**Required:** yes
+**Type:** key binding
+**Default:** `"P"`
+
+Move to root.
+
+### `measure_widths`
+
+**Required:** yes
+**Type:** boolean
+**Default:** `false`
+
+Whether to measure the width of graphemes (i.e. characters) as displayed
+by the terminal emulator instead of estimating the width.
+
+Enabling this makes rendering a bit slower but more accurate. The screen
+might also flash when encountering new graphemes.
+
+See also the `--measure-widths` command line option.
+
+### `offline`
+
+**Required:** yes
+**Type:** boolean
+**Default:** `false`
+
+Whether to start in offline mode.
+
+In offline mode, cove won't automatically join rooms marked via the
+`autojoin` option on startup. You can still join those rooms manually by
+pressing `a` in the rooms list.
+
+See also the `--offline` command line option.
+
+### `rooms_sort_order`
+
+**Required:** yes
+**Type:** string
+**Values:** `"alphabet"`, `"importance"`
+**Default:** `"alphabet"`
+
+Initial sort order of rooms list.
+
+`"alphabet"` sorts rooms in alphabetic order.
+
+`"importance"` sorts rooms by the following criteria (in descending
+order of priority):
+
+1. connected rooms before unconnected rooms
+2. rooms with unread messages before rooms without
+3. alphabetic order
+
+### `time_zone`
+
+**Required:** no
+**Type:** string
+**Default:** `$TZ` or local system time zone
+
+Time zone that chat timestamps should be displayed in.
+
+This option can either be the string `"localtime"`, a [POSIX TZ string],
+or a [tz identifier] from the [tz database].
+
+When not set or when set to `"localtime"`, cove attempts to use your
+system's configured time zone, falling back to UTC.
+
+When the string begins with a colon or doesn't match the a POSIX TZ
+string format, it is interpreted as a tz identifier and looked up in
+your system's tz database (or a bundled tz database on Windows).
+
+If the `TZ` environment variable exists, it overrides this option.
+
+[POSIX TZ string]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
+[tz identifier]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+[tz database]: https://en.wikipedia.org/wiki/Tz_database
+
+### `width_estimation_method`
+
+**Required:** yes
+**Type:** string
+**Values:** `"legacy"`, `"unicode"`
+**Default:** `"legacy"`
+
+How to estimate the width of graphemes (i.e. characters) as displayed by
+the terminal emulator.
+
+`"legacy"`: Use a legacy method that should mostly work on most terminal
+emulators. This method will never be correct in all cases since every
+terminal emulator handles grapheme widths slightly differently. However,
+those cases are usually rare (unless you view a lot of emoji).
+
+`"unicode"`: Use the unicode standard in a best-effort manner to
+determine grapheme widths. Some terminals (e.g. ghostty) can make use of
+this.
+
+This method is used when `measure_widths` is set to `false`.
+
+See also the `--width-estimation-method` command line option.
diff --git a/Cargo.lock b/Cargo.lock
index ec6c6ed..2f45a5a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,38 +1,104 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
+
+[[package]]
+name = "addr2line"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
-version = "0.7.6"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
- "getrandom",
+ "cfg-if",
"once_cell",
"version_check",
+ "zerocopy 0.7.35",
]
[[package]]
name = "aho-corasick"
-version = "0.7.20"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
-name = "anyhow"
-version = "1.0.68"
+name = "anstream"
+version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
+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 = "async-trait"
-version = "0.1.61"
+version = "0.1.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282"
+checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97"
dependencies = [
"proc-macro2",
"quote",
@@ -41,60 +107,120 @@ dependencies = [
[[package]]
name = "autocfg"
-version = "1.1.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
-name = "base64"
-version = "0.13.1"
+name = "aws-lc-rs"
+version = "1.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01"
+dependencies = [
+ "aws-lc-sys",
+ "zeroize",
+]
[[package]]
-name = "base64"
-version = "0.21.0"
+name = "aws-lc-sys"
+version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+checksum = "6bbe221bbf523b625a4dd8585c7f38166e31167ec2ca98051dbcb4c3b6e825d2"
+dependencies = [
+ "bindgen",
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.69.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+ "which",
+]
[[package]]
name = "bitflags"
-version = "1.3.2"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "block-buffer"
-version = "0.10.3"
+version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
-[[package]]
-name = "bumpalo"
-version = "3.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
-
-[[package]]
-name = "byteorder"
-version = "1.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
-
[[package]]
name = "bytes"
-version = "1.3.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "caseless"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8"
+dependencies = [
+ "unicode-normalization",
+]
[[package]]
name = "cc"
-version = "1.0.78"
+version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
+checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
+dependencies = [
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[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"
@@ -103,28 +229,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
-name = "clap"
-version = "4.1.1"
+name = "clang-sys"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
- "bitflags",
+ "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_builder"
+version = "4.5.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
+dependencies = [
+ "anstream",
+ "anstyle",
"clap_lex",
- "is-terminal",
- "once_cell",
"strsim",
- "termcolor",
]
[[package]]
name = "clap_derive"
-version = "4.1.0"
+version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
+checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [
"heck",
- "proc-macro-error",
"proc-macro2",
"quote",
"syn",
@@ -132,18 +275,30 @@ dependencies = [
[[package]]
name = "clap_lex"
-version = "0.3.1"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "cmake"
+version = "0.1.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
dependencies = [
- "os_str_bytes",
+ "cc",
]
[[package]]
-name = "cookie"
-version = "0.16.2"
+name = "colorchoice"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
+[[package]]
+name = "cookie"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"time",
"version_check",
@@ -151,9 +306,9 @@ dependencies = [
[[package]]
name = "core-foundation"
-version = "0.9.3"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
dependencies = [
"core-foundation-sys",
"libc",
@@ -161,59 +316,92 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
-version = "0.8.3"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cove"
-version = "0.5.2"
+version = "0.9.3"
dependencies = [
"anyhow",
"async-trait",
"clap",
"cookie",
+ "cove-config",
+ "cove-input",
"crossterm",
"directories",
- "edit",
"euphoxide",
+ "jiff",
"linkify",
"log",
"open",
"parking_lot",
"rusqlite",
- "serde",
+ "rustls",
"serde_json",
"thiserror",
- "time",
"tokio",
- "tokio-tungstenite",
- "toml",
"toss",
- "unicode-segmentation",
"unicode-width",
+ "vault",
+]
+
+[[package]]
+name = "cove-config"
+version = "0.9.3"
+dependencies = [
+ "cove-input",
+ "cove-macro",
+ "serde",
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "cove-input"
+version = "0.9.3"
+dependencies = [
+ "cove-macro",
+ "crossterm",
+ "edit",
+ "parking_lot",
+ "serde",
+ "serde_either",
+ "thiserror",
+ "toss",
+]
+
+[[package]]
+name = "cove-macro"
+version = "0.9.3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "cpufeatures"
-version = "0.2.5"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crossterm"
-version = "0.25.0"
+version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
"bitflags",
"crossterm_winapi",
- "libc",
"mio",
"parking_lot",
+ "rustix 0.38.44",
"signal-hook",
"signal-hook-mio",
"winapi",
@@ -221,9 +409,9 @@ dependencies = [
[[package]]
name = "crossterm_winapi"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
@@ -239,10 +427,25 @@ dependencies = [
]
[[package]]
-name = "digest"
-version = "0.10.6"
+name = "data-encoding"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
@@ -250,29 +453,36 @@ dependencies = [
[[package]]
name = "directories"
-version = "4.0.1"
+version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
+checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
-version = "0.3.7"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
+ "option-ext",
"redox_users",
- "winapi",
+ "windows-sys 0.59.0",
]
[[package]]
-name = "edit"
-version = "0.1.4"
+name = "dunce"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c562aa71f7bc691fde4c6bf5f93ae5a5298b617c2eb44c76c87832299a17fbb4"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
+name = "edit"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f364860e764787163c8c8f58231003839be31276e821e2ad2092ddf496b1aa09"
dependencies = [
"tempfile",
"which",
@@ -280,49 +490,51 @@ dependencies = [
[[package]]
name = "either"
-version = "1.8.0"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
-version = "0.2.8"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
- "errno-dragonfly",
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "errno-dragonfly"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
-dependencies = [
- "cc",
"libc",
+ "windows-sys 0.59.0",
]
[[package]]
name = "euphoxide"
-version = "0.2.0"
-source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.2.0#58e39fa5bc4d5de4fcc0d467a123823760381b51"
+version = "0.6.1"
+source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.6.1#7a292c429ad44aa6aa52fc381e3168841d6303b0"
dependencies = [
- "futures",
+ "async-trait",
+ "caseless",
+ "clap",
+ "cookie",
+ "futures-util",
+ "jiff",
+ "log",
"serde",
"serde_json",
- "time",
"tokio",
+ "tokio-stream",
"tokio-tungstenite",
+ "unicode-normalization",
]
[[package]]
name = "fallible-iterator"
-version = "0.2.0"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
+checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
@@ -332,12 +544,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
-version = "1.8.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
-dependencies = [
- "instant",
-]
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fnv"
@@ -346,74 +555,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
-name = "form_urlencoded"
-version = "1.1.0"
+name = "fs_extra"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
-dependencies = [
- "percent-encoding",
-]
-
-[[package]]
-name = "futures"
-version = "0.3.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-sink",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-channel"
-version = "0.3.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
-dependencies = [
- "futures-core",
- "futures-sink",
-]
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures-core"
-version = "0.3.25"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
-
-[[package]]
-name = "futures-io"
-version = "0.3.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-sink"
-version = "0.3.25"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
-version = "0.3.25"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
-version = "0.3.25"
+version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
- "futures-channel",
"futures-core",
- "futures-io",
"futures-sink",
"futures-task",
- "memchr",
"pin-project-lite",
"pin-utils",
"slab",
@@ -421,9 +594,9 @@ dependencies = [
[[package]]
name = "generic-array"
-version = "0.14.6"
+version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
@@ -431,53 +604,83 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.2.8"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
-name = "hashbrown"
-version = "0.12.3"
+name = "getrandom"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.13.3+wasi-0.2.2",
+ "windows-targets",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[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",
]
[[package]]
-name = "hashlink"
-version = "0.8.1"
+name = "hashbrown"
+version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+
+[[package]]
+name = "hashlink"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [
- "hashbrown",
+ "hashbrown 0.14.5",
]
[[package]]
name = "heck"
-version = "0.4.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
-name = "hermit-abi"
-version = "0.2.6"
+name = "home"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
- "libc",
+ "windows-sys 0.59.0",
]
[[package]]
name = "http"
-version = "0.2.8"
+version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
@@ -486,77 +689,153 @@ dependencies = [
[[package]]
name = "httparse"
-version = "1.8.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
-name = "idna"
-version = "0.3.0"
+name = "indexmap"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
- "unicode-bidi",
- "unicode-normalization",
+ "equivalent",
+ "hashbrown 0.15.2",
]
[[package]]
-name = "instant"
-version = "0.1.12"
+name = "is-docker"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
dependencies = [
- "cfg-if",
+ "once_cell",
]
[[package]]
-name = "io-lifetimes"
-version = "1.0.4"
+name = "is-wsl"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
dependencies = [
- "libc",
- "windows-sys",
+ "is-docker",
+ "once_cell",
]
[[package]]
-name = "is-terminal"
-version = "0.4.2"
+name = "is_terminal_polyfill"
+version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
- "hermit-abi",
- "io-lifetimes",
- "rustix",
- "windows-sys",
+ "either",
]
[[package]]
name = "itoa"
-version = "1.0.5"
+version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
-name = "js-sys"
-version = "0.3.60"
+name = "jiff"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e"
dependencies = [
- "wasm-bindgen",
+ "jiff-static",
+ "jiff-tzdb-platform",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde",
+ "windows-sys 0.59.0",
]
[[package]]
-name = "libc"
-version = "0.2.139"
+name = "jiff-static"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[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 = "jobserver"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[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",
+]
+
+[[package]]
+name = "libredox"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
+dependencies = [
+ "bitflags",
+ "libc",
+]
[[package]]
name = "libsqlite3-sys"
-version = "0.25.2"
+version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa"
+checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
dependencies = [
"cc",
"pkg-config",
@@ -565,24 +844,30 @@ dependencies = [
[[package]]
name = "linkify"
-version = "0.9.0"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96dd5884008358112bc66093362197c7248ece00d46624e2cf71e50029f8cff5"
+checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780"
dependencies = [
"memchr",
]
[[package]]
name = "linux-raw-sys"
-version = "0.1.4"
+version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+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 = "lock_api"
-version = "0.4.9"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
@@ -590,74 +875,120 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.17"
+version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "memchr"
-version = "2.5.0"
+version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
-name = "mio"
+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 = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
+checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
- "libc",
- "log",
- "wasi",
- "windows-sys",
+ "adler2",
]
[[package]]
-name = "num_cpus"
-version = "1.15.0"
+name = "mio"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
- "hermit-abi",
"libc",
+ "log",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.52.0",
+]
+
+[[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 = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
]
[[package]]
name = "once_cell"
-version = "1.17.0"
+version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]]
name = "open"
-version = "3.2.0"
+version = "5.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8"
+checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95"
dependencies = [
+ "is-wsl",
+ "libc",
"pathdiff",
- "windows-sys",
]
[[package]]
name = "openssl-probe"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
-name = "os_str_bytes"
-version = "6.4.1"
+name = "option-ext"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
[[package]]
name = "parking_lot"
-version = "0.12.1"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -665,34 +996,28 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.6"
+version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
- "windows-sys",
+ "windows-targets",
]
[[package]]
name = "pathdiff"
-version = "0.2.1"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
-
-[[package]]
-name = "percent-encoding"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "pin-project-lite"
-version = "0.2.9"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
@@ -702,74 +1027,84 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
-version = "0.3.26"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
-name = "ppv-lite86"
-version = "0.2.17"
+name = "portable-atomic"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
+name = "portable-atomic-util"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
- "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "syn",
- "version_check",
+ "portable-atomic",
]
[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
+name = "powerfmt"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy 0.8.23",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
dependencies = [
"proc-macro2",
- "quote",
- "version_check",
+ "syn",
]
[[package]]
name = "proc-macro2"
-version = "1.0.49"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.23"
+version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
-version = "0.8.5"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
- "libc",
"rand_chacha",
"rand_core",
+ "zerocopy 0.8.23",
]
[[package]]
name = "rand_chacha"
-version = "0.3.1"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
@@ -777,38 +1112,50 @@ dependencies = [
[[package]]
name = "rand_core"
-version = "0.6.4"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
- "getrandom",
+ "getrandom 0.3.1",
]
[[package]]
name = "redox_syscall"
-version = "0.2.16"
+version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
-version = "0.4.3"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [
- "getrandom",
- "redox_syscall",
+ "getrandom 0.2.15",
+ "libredox",
"thiserror",
]
[[package]]
name = "regex"
-version = "1.7.1"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
+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",
@@ -817,39 +1164,29 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.6.28"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
-
-[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
-dependencies = [
- "winapi",
-]
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ring"
-version = "0.16.20"
+version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
+ "cfg-if",
+ "getrandom 0.2.15",
"libc",
- "once_cell",
- "spin",
"untrusted",
- "web-sys",
- "winapi",
+ "windows-sys 0.52.0",
]
[[package]]
name = "rusqlite"
-version = "0.28.0"
+version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a"
+checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
dependencies = [
"bitflags",
"fallible-iterator",
@@ -861,88 +1198,114 @@ dependencies = [
]
[[package]]
-name = "rustix"
-version = "0.36.6"
+name = "rustc-demangle"
+version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
- "io-lifetimes",
"libc",
- "linux-raw-sys",
- "windows-sys",
+ "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",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.9.3",
+ "windows-sys 0.59.0",
]
[[package]]
name = "rustls"
-version = "0.20.8"
+version = "0.23.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
+checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
dependencies = [
+ "aws-lc-rs",
"log",
- "ring",
- "sct",
- "webpki",
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
]
[[package]]
name = "rustls-native-certs"
-version = "0.6.2"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
+checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
dependencies = [
"openssl-probe",
- "rustls-pemfile",
+ "rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
-name = "rustls-pemfile"
-version = "1.0.2"
+name = "rustls-pki-types"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
-dependencies = [
- "base64 0.21.0",
-]
-
-[[package]]
-name = "ryu"
-version = "1.0.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
-
-[[package]]
-name = "schannel"
-version = "0.1.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
-dependencies = [
- "windows-sys",
-]
-
-[[package]]
-name = "scopeguard"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
-
-[[package]]
-name = "sct"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
+ "aws-lc-rs",
"ring",
+ "rustls-pki-types",
"untrusted",
]
[[package]]
-name = "security-framework"
-version = "2.7.0"
+name = "ryu"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "security-framework"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
dependencies = [
"bitflags",
"core-foundation",
@@ -953,9 +1316,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.6.1"
+version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
@@ -963,18 +1326,28 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.152"
+version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
-name = "serde_derive"
-version = "1.0.152"
+name = "serde-value"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
@@ -982,21 +1355,41 @@ dependencies = [
]
[[package]]
-name = "serde_json"
-version = "1.0.91"
+name = "serde_either"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
+checksum = "689643f4e7826ffcd227d2cc166bfdf5869750191ffe9fd593531e6ba351f2fb"
+dependencies = [
+ "serde",
+ "serde-value",
+]
+
+[[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 = "sha1"
-version = "0.10.5"
+name = "serde_spanned"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -1004,10 +1397,16 @@ dependencies = [
]
[[package]]
-name = "signal-hook"
-version = "0.3.14"
+name = "shlex"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
+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",
@@ -1015,9 +1414,9 @@ dependencies = [
[[package]]
name = "signal-hook-mio"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
+checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
dependencies = [
"libc",
"mio",
@@ -1026,55 +1425,55 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
-version = "1.4.0"
+version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "slab"
-version = "0.4.7"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
-version = "1.10.0"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "socket2"
-version = "0.4.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
- "winapi",
+ "windows-sys 0.52.0",
]
[[package]]
-name = "spin"
-version = "0.5.2"
+name = "strsim"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
-name = "strsim"
-version = "0.10.0"
+name = "subtle"
+version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
-version = "1.0.107"
+version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
@@ -1083,41 +1482,31 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.3.0"
+version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600"
dependencies = [
- "cfg-if",
"fastrand",
- "libc",
- "redox_syscall",
- "remove_dir_all",
- "winapi",
-]
-
-[[package]]
-name = "termcolor"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
-dependencies = [
- "winapi-util",
+ "getrandom 0.3.1",
+ "once_cell",
+ "rustix 1.0.2",
+ "windows-sys 0.59.0",
]
[[package]]
name = "thiserror"
-version = "1.0.38"
+version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.38"
+version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
@@ -1126,11 +1515,14 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.17"
+version = "0.3.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
+checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
dependencies = [
+ "deranged",
"itoa",
+ "num-conv",
+ "powerfmt",
"serde",
"time-core",
"time-macros",
@@ -1138,59 +1530,58 @@ dependencies = [
[[package]]
name = "time-core"
-version = "0.1.0"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
[[package]]
name = "time-macros"
-version = "0.2.6"
+version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
+checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
dependencies = [
+ "num-conv",
"time-core",
]
[[package]]
name = "tinyvec"
-version = "1.6.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.24.1"
+version = "1.44.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
+checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
dependencies = [
- "autocfg",
+ "backtrace",
"bytes",
"libc",
- "memchr",
"mio",
- "num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
- "windows-sys",
+ "windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
-version = "1.8.2"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
@@ -1199,45 +1590,81 @@ dependencies = [
[[package]]
name = "tokio-rustls"
-version = "0.23.4"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [
"rustls",
"tokio",
- "webpki",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
]
[[package]]
name = "tokio-tungstenite"
-version = "0.18.0"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd"
+checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
dependencies = [
"futures-util",
"log",
"rustls",
"rustls-native-certs",
+ "rustls-pki-types",
"tokio",
"tokio-rustls",
"tungstenite",
- "webpki",
]
[[package]]
name = "toml"
-version = "0.5.10"
+version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
+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 = "toss"
-version = "0.1.0"
-source = "git+https://github.com/Garmelon/toss.git?rev=0a3b193f796bcb5e1a211d0e3c1589565d9848c2#0a3b193f796bcb5e1a211d0e3c1589565d9848c2"
+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",
+]
+
+[[package]]
+name = "toss"
+version = "0.3.4"
+source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.4#57aa8c59308f6f0aa82bde415a42b56c3d6f7c4d"
+dependencies = [
+ "async-trait",
"crossterm",
"unicode-linebreak",
"unicode-segmentation",
@@ -1246,90 +1673,67 @@ dependencies = [
[[package]]
name = "tungstenite"
-version = "0.18.0"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788"
+checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
dependencies = [
- "base64 0.13.1",
- "byteorder",
"bytes",
+ "data-encoding",
"http",
"httparse",
"log",
"rand",
"rustls",
+ "rustls-pki-types",
"sha1",
"thiserror",
- "url",
"utf-8",
- "webpki",
]
[[package]]
name = "typenum"
-version = "1.16.0"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
-
-[[package]]
-name = "unicode-bidi"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
-version = "1.0.6"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-linebreak"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
-dependencies = [
- "hashbrown",
- "regex",
-]
+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
name = "unicode-normalization"
-version = "0.1.22"
+version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
-version = "1.10.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
-version = "0.1.10"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "untrusted"
-version = "0.7.1"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
-
-[[package]]
-name = "url"
-version = "2.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
-dependencies = [
- "form_urlencoded",
- "idna",
- "percent-encoding",
-]
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "utf-8"
@@ -1337,6 +1741,21 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "vault"
+version = "0.4.0"
+source = "git+https://github.com/Garmelon/vault.git?tag=v0.4.0#a53254d2e787d15fd2d00584fddf9b84e79572ee"
+dependencies = [
+ "rusqlite",
+ "tokio",
+]
+
[[package]]
name = "vcpkg"
version = "0.2.15"
@@ -1345,9 +1764,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
-version = "0.9.4"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
@@ -1356,88 +1775,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
-name = "wasm-bindgen"
-version = "0.2.83"
+name = "wasi"
+version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
- "cfg-if",
- "wasm-bindgen-macro",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.83"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
-dependencies = [
- "bumpalo",
- "log",
- "once_cell",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.83"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.83"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.83"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
-
-[[package]]
-name = "web-sys"
-version = "0.3.60"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "webpki"
-version = "0.22.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
-dependencies = [
- "ring",
- "untrusted",
+ "wit-bindgen-rt",
]
[[package]]
name = "which"
-version = "4.3.0"
+version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
- "libc",
+ "home",
"once_cell",
+ "rustix 0.38.44",
]
[[package]]
@@ -1456,15 +1811,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-[[package]]
-name = "winapi-util"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
-dependencies = [
- "winapi",
-]
-
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -1473,13 +1819,32 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
-version = "0.42.0"
+version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
+ "windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@@ -1488,42 +1853,112 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.42.1"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.42.1"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
-version = "0.42.1"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+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.1"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.42.1"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.42.1"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.42.1"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[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",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "zerocopy-derive 0.7.35",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
+dependencies = [
+ "zerocopy-derive 0.8.23",
+]
+
+[[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",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
diff --git a/Cargo.toml b/Cargo.toml
index a8fdbe7..33f245f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,47 +1,72 @@
-[package]
-name = "cove"
-version = "0.5.2"
-edition = "2021"
+[workspace]
+resolver = "3"
+members = ["cove", "cove-*"]
-[dependencies]
-anyhow = "1.0.68"
-async-trait = "0.1.61"
-clap = { version = "4.1.1", features = ["derive", "deprecated"] }
-cookie = "0.16.2"
-crossterm = "0.25.0"
-directories = "4.0.1"
-edit = "0.1.4"
-linkify = "0.9.0"
-log = { version = "0.4.17", features = ["std"] }
-open = "3.2.0"
-parking_lot = "0.12.1"
-rusqlite = { version = "0.28.0", features = ["bundled", "time"] }
-serde = { version = "1.0.152", features = ["derive"] }
-serde_json = "1.0.91"
-thiserror = "1.0.38"
-tokio = { version = "1.24.1", features = ["full"] }
-toml = "0.5.10"
-unicode-segmentation = "1.10.0"
-unicode-width = "0.1.10"
+[workspace.package]
+version = "0.9.3"
+edition = "2024"
-[dependencies.time]
-version = "0.3.17"
-features = ["macros", "formatting", "parsing", "serde"]
+[workspace.dependencies]
+anyhow = "1.0.97"
+async-trait = "0.1.87"
+clap = { version = "4.5.32", features = ["derive", "deprecated"] }
+cookie = "0.18.1"
+crossterm = "0.28.1"
+directories = "6.0.0"
+edit = "0.1.5"
+jiff = "0.2.4"
+linkify = "0.10.0"
+log = { version = "0.4.26", features = ["std"] }
+open = "5.3.2"
+parking_lot = "0.12.3"
+proc-macro2 = "1.0.94"
+quote = "1.0.40"
+rusqlite = { version = "0.31.0", features = ["bundled", "time"] }
+rustls = "0.23.23"
+serde = { version = "1.0.219", features = ["derive"] }
+serde_either = "0.2.1"
+serde_json = "1.0.140"
+syn = "2.0.100"
+thiserror = "2.0.12"
+tokio = { version = "1.44.1", features = ["full"] }
+toml = "0.8.20"
+unicode-width = "0.2.0"
-[dependencies.tokio-tungstenite]
-version = "0.18.0"
-features = ["rustls-tls-native-roots"]
-
-[dependencies.euphoxide]
+[workspace.dependencies.euphoxide]
git = "https://github.com/Garmelon/euphoxide.git"
-tag = "v0.2.0"
+tag = "v0.6.1"
+features = ["bot"]
-# [patch."https://github.com/Garmelon/euphoxide.git"]
-# euphoxide = { path = "../euphoxide/" }
-
-[dependencies.toss]
+[workspace.dependencies.toss]
git = "https://github.com/Garmelon/toss.git"
-rev = "0a3b193f796bcb5e1a211d0e3c1589565d9848c2"
+tag = "v0.3.4"
-# [patch."https://github.com/Garmelon/toss.git"]
-# toss = { path = "../toss/" }
+[workspace.dependencies.vault]
+git = "https://github.com/Garmelon/vault.git"
+tag = "v0.4.0"
+features = ["tokio"]
+
+[workspace.lints]
+rust.unsafe_code = { level = "forbid", priority = 1 }
+# Lint groups
+rust.deprecated_safe = "warn"
+rust.future_incompatible = "warn"
+rust.keyword_idents = "warn"
+rust.rust_2018_idioms = "warn"
+rust.unused = "warn"
+# Individual lints
+rust.non_local_definitions = "warn"
+rust.redundant_imports = "warn"
+rust.redundant_lifetimes = "warn"
+rust.single_use_lifetimes = "warn"
+rust.unit_bindings = "warn"
+rust.unnameable_types = "warn"
+rust.unused_crate_dependencies = "warn"
+rust.unused_import_braces = "warn"
+rust.unused_lifetimes = "warn"
+rust.unused_qualifications = "warn"
+# Clippy
+clippy.use_self = "warn"
+
+[profile.dev.package."*"]
+opt-level = 3
diff --git a/README.md b/README.md
index e9b1ee8..22fef83 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,16 @@
# cove
-Cove is a TUI client for [euphoria.io](https://euphoria.io/), a threaded
+Cove is a TUI client for [euphoria.leet.nu](https://euphoria.leet.nu/), a threaded
real-time chat platform.

-It runs on Linux, Windows and macOS.
+It runs on Linux, Windows, and macOS.
+
+## Installing cove
+
+Download a binary of your choice from the
+[latest release on GitHub](https://github.com/Garmelon/cove/releases/latest).
## Using cove
@@ -18,147 +23,11 @@ things in) won't automatically shrink. If it takes up too much space, try
running `cove gc` and waiting for it to finish. This isn't done automatically
because it can take quite a while.
-## Manual installation
+## Configuring cove
-This section contains instructions on how to install cove by compiling it yourself.
-It doesn't assume you know how to program, but it does assume basic familiarity with the command line on your platform of choice.
-Cove runs in the terminal, after all.
-
-### Installing rustup
-
-Cove is written in Rust, so the first step is to install rustup. Either install
-it from your package manager of choice (if you have one) or use the
-[installer](https://rustup.rs/).
-
-Test your installation by running `rustup --version` and `cargo --version`. If
-rustup is installed correctly, both of these should show a version number.
-
-Cove is designed on the current version of the stable toolchain. If cove doesn't
-compile, you can try switching to the stable toolchain and updating it using the
-following commands:
-```bash
-$ rustup default stable
-$ rustup update
-```
-
-### Installing cove
-
-To install or update to the latest release of cove, run the following command:
-
-```bash
-$ cargo install --force --git https://github.com/Garmelon/cove --branch latest
-```
-
-If you like to live dangerously and want to install or update to the latest,
-bleeding-edge, possibly-broken commit from the repo's main branch, run the
-following command.
-
-**Warning:** This could corrupt your vault. Make sure to make a backup before
-running the command.
-
-```bash
-$ cargo install --force --git https://github.com/Garmelon/cove
-```
-
-To install a specific version of cove, run the following command and substitute
-in the full version you want to install:
-
-```bash
-$ cargo install --force --git https://github.com/Garmelon/cove --tag v0.1.0
-```
-
-## Config file
-
-Cove's config file uses the [TOML](https://toml.io/) format.
+A complete list of config options is available in the [CONFIG.md](CONFIG.md)
+file or via `cove help-config`.
When launched, cove prints the location it is loading its config file from. To
configure cove, create a config file at that location. This location can be
changed via the `--config` command line option.
-
-The following is a complete list of available options. If a command line option
-with the same purpose exists, it takes precedence over the option specified in
-the config file.
-
-### `data_dir`
-
-**Type:** String (representing path)
-**Default:** Platform dependent
-
-The directory that cove stores its data in when not running in ephemeral mode.
-
-Relative paths are interpreted relative to the user's home directory.
-
-See also the `--data-dir` command line option.
-
-### `ephemeral`
-
-**Type:** Boolean
-**Default:** `false`
-
-Whether to start in ephemeral mode.
-
-In ephemeral mode, cove doesn't store any data. It completely ignores any
-options related to the data dir.
-
-See also the `--ephemeral` command line option.
-
-### `offline`
-
-**Type:** Boolean
-**Default:** `false`
-
-Whether to start in offline mode.
-
-In offline mode, cove won't automatically join rooms marked via the `autojoin`
-option on startup. You can still join those rooms manually by pressing `a` in
-the rooms list.
-
-See also the `--offline` command line option.
-
-### `rooms_sort_order`
-
-**Type:** String, one of `alphabetic`, `importance`
-**Default:** `alphabetic`
-
-Initial sort order of rooms list.
-
-`alphabetic` sorts rooms in alphabetic order.
-
-`importance` sorts rooms by the following criteria (in descending order of
-priority):
-
-1. connected rooms before unconnected rooms
-2. rooms with unread messages before rooms without
-3. alphabetic order
-
-### `euph.rooms..autojoin`
-
-**Type:** Boolean
-**Default:** `false`
-
-Whether to automatically join this room on startup.
-
-### `euph.rooms..username`
-
-**Type:** String
-**Default:** Not set
-
-If set, cove will set this username upon joining if there is no username
-associated with the current session.
-
-### `euph.rooms..force_username`
-
-**Type:** Boolean
-**Default:** `false`
-
-If `euph.rooms..username` is set, this will force cove to set the username
-even if there is already a different username associated with the current
-session.
-
-### `euph.rooms..password`
-
-**Type:** String
-**Default:** Not set
-
-If set, cove will try once to use this password to authenticate, should the room
-be password-protected.
diff --git a/cove-config/CONFIG.md b/cove-config/CONFIG.md
new file mode 100644
index 0000000..e69de29
diff --git a/cove-config/Cargo.toml b/cove-config/Cargo.toml
new file mode 100644
index 0000000..9102bfd
--- /dev/null
+++ b/cove-config/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "cove-config"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+cove-input = { path = "../cove-input" }
+cove-macro = { path = "../cove-macro" }
+
+serde.workspace = true
+thiserror.workspace = true
+toml.workspace = true
+
+[lints]
+workspace = true
diff --git a/cove-config/src/doc.rs b/cove-config/src/doc.rs
new file mode 100644
index 0000000..35f6074
--- /dev/null
+++ b/cove-config/src/doc.rs
@@ -0,0 +1,267 @@
+//! Auto-generate markdown documentation.
+
+use std::{collections::HashMap, path::PathBuf};
+
+use cove_input::KeyBinding;
+pub use cove_macro::Document;
+use serde::Serialize;
+
+const MARKDOWN_INTRODUCTION: &str = r#"# Config file format
+
+Cove's config file uses the [TOML](https://toml.io/) format.
+
+Here is an example config that changes a few different options:
+
+```toml
+measure_widths = true
+rooms_sort_order = "importance"
+
+[euph.servers."euphoria.leet.nu".rooms]
+welcome.autojoin = true
+test.username = "badingle"
+test.force_username = true
+private.password = "foobar"
+
+[keys]
+general.abort = ["esc", "ctrl+c"]
+general.exit = "ctrl+q"
+tree.action.fold_tree = "f"
+```
+
+## Key bindings
+
+Key bindings are specified as strings or lists of strings. Each string specifies
+a main key and zero or more modifier keys. The modifier keys (if any) are listed
+first, followed by the main key. They are separated by the `+` character and
+**no** whitespace.
+
+Examples of key bindings:
+- `"ctrl+c"`
+- `"X"` (not `"shift+x"`)
+- `"space"` or `" "` (both space bar)
+- `["g", "home"]`
+- `["K", "ctrl+up"]`
+- `["f1", "?"]`
+- `"ctrl+alt+f3"`
+- `["enter", "any+enter"]` (matches `enter` regardless of modifiers)
+
+Available main keys:
+- Any single character that can be typed
+- `esc`, `enter`, `space`, `tab`, `backtab`
+- `backspace`, `delete`, `insert`
+- `left`, `right`, `up`, `down`
+- `home`, `end`, `pageup`, `pagedown`
+- `f1`, `f2`, ...
+
+Available modifiers:
+- `shift` (must not be used with single characters)
+- `ctrl`
+- `alt`
+- `any` (matches as long as at least one modifier is pressed)
+
+## Available options
+"#;
+
+pub fn toml_value_as_markdown(value: &T) -> String {
+ let mut result = String::new();
+ value
+ .serialize(toml::ser::ValueSerializer::new(&mut result))
+ .expect("not a valid toml value");
+ format!("`{result}`")
+}
+
+#[derive(Clone, Default)]
+pub struct ValueInfo {
+ pub required: Option,
+ pub r#type: Option,
+ pub values: Option>,
+ pub default: Option,
+}
+
+impl ValueInfo {
+ fn as_markdown(&self) -> String {
+ let mut lines = vec![];
+
+ if let Some(required) = self.required {
+ let yesno = if required { "yes" } else { "no" };
+ lines.push(format!("**Required:** {yesno}"));
+ }
+
+ if let Some(r#type) = &self.r#type {
+ lines.push(format!("**Type:** {type}"));
+ }
+
+ if let Some(values) = &self.values {
+ let values = values.join(", ");
+ lines.push(format!("**Values:** {values}"));
+ }
+
+ if let Some(default) = &self.default {
+ lines.push(format!("**Default:** {default}"));
+ }
+
+ lines.join(" \n")
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct StructInfo {
+ pub fields: HashMap>,
+}
+
+#[derive(Clone, Default)]
+pub struct WrapInfo {
+ pub inner: Option>,
+ pub metavar: Option,
+}
+
+#[derive(Clone, Default)]
+pub struct Doc {
+ pub description: Option,
+
+ pub value_info: ValueInfo,
+ pub struct_info: StructInfo,
+ pub wrap_info: WrapInfo,
+}
+
+struct Entry {
+ path: String,
+ description: String,
+ value_info: ValueInfo,
+}
+
+impl Entry {
+ fn new(description: String, value_info: ValueInfo) -> Self {
+ Self {
+ path: String::new(),
+ description,
+ value_info,
+ }
+ }
+
+ fn with_parent(mut self, segment: String) -> Self {
+ if self.path.is_empty() {
+ self.path = segment;
+ } else {
+ self.path = format!("{segment}.{}", self.path);
+ }
+ self
+ }
+}
+
+impl Doc {
+ fn entries(&self) -> Vec {
+ let mut entries = vec![];
+
+ if let Some(description) = &self.description {
+ entries.push(Entry::new(description.clone(), self.value_info.clone()));
+ }
+
+ for (segment, field) in &self.struct_info.fields {
+ entries.extend(
+ field
+ .entries()
+ .into_iter()
+ .map(|entry| entry.with_parent(segment.clone())),
+ );
+ }
+
+ if let Some(inner) = &self.wrap_info.inner {
+ let segment = match &self.wrap_info.metavar {
+ Some(metavar) => format!("<{metavar}>"),
+ None => "<...>".to_string(),
+ };
+ entries.extend(
+ inner
+ .entries()
+ .into_iter()
+ .map(|entry| entry.with_parent(segment.clone())),
+ );
+ }
+
+ entries
+ }
+
+ pub fn as_markdown(&self) -> String {
+ // Print entries in alphabetical order to make generated documentation
+ // format more stable.
+ let mut entries = self.entries();
+ entries.sort_unstable_by(|a, b| a.path.cmp(&b.path));
+
+ let mut result = String::new();
+
+ result.push_str(MARKDOWN_INTRODUCTION);
+
+ for entry in entries {
+ result.push_str(&format!("\n### `{}`\n", entry.path));
+
+ let value_info = entry.value_info.as_markdown();
+ if !value_info.is_empty() {
+ result.push_str(&format!("\n{value_info}\n"));
+ }
+
+ if !entry.description.is_empty() {
+ result.push_str(&format!("\n{}\n", entry.description));
+ }
+ }
+
+ result
+ }
+}
+
+pub trait Document {
+ fn doc() -> Doc;
+}
+
+impl Document for String {
+ fn doc() -> Doc {
+ let mut doc = Doc::default();
+ doc.value_info.required = Some(true);
+ doc.value_info.r#type = Some("string".to_string());
+ doc
+ }
+}
+
+impl Document for bool {
+ fn doc() -> Doc {
+ let mut doc = Doc::default();
+ doc.value_info.required = Some(true);
+ doc.value_info.r#type = Some("boolean".to_string());
+ doc
+ }
+}
+
+impl Document for PathBuf {
+ fn doc() -> Doc {
+ let mut doc = Doc::default();
+ doc.value_info.required = Some(true);
+ doc.value_info.r#type = Some("path".to_string());
+ doc
+ }
+}
+
+impl Document for Option {
+ fn doc() -> Doc {
+ let mut doc = I::doc();
+ assert_eq!(doc.value_info.required, Some(true));
+ doc.value_info.required = Some(false);
+ doc
+ }
+}
+
+impl Document for HashMap {
+ fn doc() -> Doc {
+ let mut doc = Doc::default();
+ doc.wrap_info.inner = Some(Box::new(I::doc()));
+ doc
+ }
+}
+
+impl Document for KeyBinding {
+ fn doc() -> Doc {
+ let mut doc = Doc::default();
+ doc.value_info.required = Some(true);
+ doc.value_info.r#type = Some("key binding".to_string());
+ doc
+ }
+}
diff --git a/cove-config/src/euph.rs b/cove-config/src/euph.rs
new file mode 100644
index 0000000..5ed0fb5
--- /dev/null
+++ b/cove-config/src/euph.rs
@@ -0,0 +1,47 @@
+use std::collections::HashMap;
+
+use serde::{Deserialize, Serialize};
+
+use crate::doc::Document;
+
+#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, Document)]
+#[serde(rename_all = "snake_case")]
+pub enum RoomsSortOrder {
+ #[default]
+ Alphabet,
+ Importance,
+}
+
+// TODO Mark favourite rooms via printable ascii characters
+#[derive(Debug, Clone, Default, Deserialize, Document)]
+pub struct EuphRoom {
+ /// Whether to automatically join this room on startup.
+ #[serde(default)]
+ pub autojoin: bool,
+
+ /// If set, cove will set this username upon joining if there is no username
+ /// associated with the current session.
+ pub username: Option,
+
+ /// If `euph.servers..rooms..username` is set, this will force
+ /// cove to set the username even if there is already a different username
+ /// associated with the current session.
+ #[serde(default)]
+ pub force_username: bool,
+
+ /// If set, cove will try once to use this password to authenticate, should
+ /// the room be password-protected.
+ pub password: Option,
+}
+
+#[derive(Debug, Default, Deserialize, Document)]
+pub struct EuphServer {
+ #[document(metavar = "room")]
+ pub rooms: HashMap,
+}
+
+#[derive(Debug, Default, Deserialize, Document)]
+pub struct Euph {
+ #[document(metavar = "domain")]
+ pub servers: HashMap,
+}
diff --git a/cove-config/src/keys.rs b/cove-config/src/keys.rs
new file mode 100644
index 0000000..47c171c
--- /dev/null
+++ b/cove-config/src/keys.rs
@@ -0,0 +1,427 @@
+use cove_input::{KeyBinding, KeyGroup, KeyGroupInfo};
+use serde::Deserialize;
+
+use crate::doc::Document;
+
+macro_rules! default_bindings {
+ ( $(
+ pub mod $mod:ident { $(
+ pub fn $name:ident => [ $($key:expr),* ];
+ )* }
+ )*) => {
+ mod default { $(
+ pub mod $mod { $(
+ pub fn $name() -> ::cove_input::KeyBinding {
+ ::cove_input::KeyBinding::new().with_keys([ $($key),* ]).unwrap()
+ }
+ )* }
+ )* }
+ };
+}
+
+default_bindings! {
+ pub mod general {
+ pub fn exit => ["ctrl+c"];
+ pub fn abort => ["esc"];
+ pub fn confirm => ["enter"];
+ pub fn focus => ["tab"];
+ pub fn help => ["f1"];
+ pub fn log => ["f12"];
+ }
+
+ pub mod scroll {
+ pub fn up_line => ["ctrl+y"];
+ pub fn down_line => ["ctrl+e"];
+ pub fn up_half => ["ctrl+u"];
+ pub fn down_half => ["ctrl+d"];
+ pub fn up_full => ["ctrl+b", "pageup"];
+ pub fn down_full => ["ctrl+f", "pagedown"];
+ pub fn center_cursor => ["z"];
+ }
+
+ pub mod cursor {
+ pub fn up => ["k", "up"];
+ pub fn down => ["j", "down"];
+ pub fn to_top => ["g", "home"];
+ pub fn to_bottom => ["G", "end"];
+ }
+
+ pub mod editor_cursor {
+ pub fn left => ["ctrl+b","left"];
+ pub fn right => ["ctrl+f", "right"];
+ pub fn left_word => ["alt+b", "ctrl+left"];
+ pub fn right_word => ["alt+f", "ctrl+right"];
+ pub fn start => ["ctrl+a", "home"];
+ pub fn end => ["ctrl+e", "end"];
+ pub fn up => ["up"];
+ pub fn down => ["down"];
+ }
+
+ pub mod editor_action {
+ pub fn backspace => ["ctrl+h", "backspace"];
+ pub fn delete => ["ctrl+d", "delete"];
+ pub fn clear => ["ctrl+l"];
+ pub fn external => ["ctrl+x", "alt+e"];
+ }
+
+ pub mod rooms_action {
+ pub fn connect => ["c"];
+ pub fn connect_all => ["C"];
+ pub fn disconnect => ["d"];
+ pub fn disconnect_all => ["D"];
+ pub fn connect_autojoin => ["a"];
+ pub fn disconnect_non_autojoin => ["A"];
+ pub fn new => ["n"];
+ pub fn delete => ["X"];
+ pub fn change_sort_order => ["s"];
+ }
+
+ pub mod room_action {
+ pub fn authenticate => ["a"];
+ pub fn nick => ["n"];
+ pub fn more_messages => ["m"];
+ pub fn account => ["A"];
+ }
+
+ pub mod tree_cursor {
+ pub fn to_above_sibling => ["K", "ctrl+up"];
+ pub fn to_below_sibling => ["J", "ctrl+down"];
+ pub fn to_parent => ["p"];
+ pub fn to_root => ["P"];
+ pub fn to_older_message => ["h", "left"];
+ pub fn to_newer_message => ["l", "right"];
+ pub fn to_older_unseen_message => ["H", "ctrl+left"];
+ pub fn to_newer_unseen_message => ["L", "ctrl+right"];
+ }
+
+ pub mod tree_action {
+ pub fn reply => ["r"];
+ pub fn reply_alternate => ["R"];
+ pub fn new_thread => ["t"];
+ pub fn fold_tree => [" "];
+ pub fn toggle_seen => ["s"];
+ pub fn mark_visible_seen => ["S"];
+ pub fn mark_older_seen => ["ctrl+s"];
+ pub fn info => ["i"];
+ pub fn links => ["I"];
+ pub fn toggle_nick_emoji => ["e"];
+ pub fn increase_caesar => ["c"];
+ pub fn decrease_caesar => ["C"];
+ }
+
+}
+
+#[derive(Debug, Deserialize, Document, KeyGroup)]
+/// General.
+pub struct General {
+ /// Quit cove.
+ #[serde(default = "default::general::exit")]
+ pub exit: KeyBinding,
+ /// Abort/close.
+ #[serde(default = "default::general::abort")]
+ pub abort: KeyBinding,
+ /// Confirm.
+ #[serde(default = "default::general::confirm")]
+ pub confirm: KeyBinding,
+ /// Advance focus.
+ #[serde(default = "default::general::focus")]
+ pub focus: KeyBinding,
+ /// Show this help.
+ #[serde(default = "default::general::help")]
+ pub help: KeyBinding,
+ /// Show log.
+ #[serde(default = "default::general::log")]
+ pub log: KeyBinding,
+}
+
+#[derive(Debug, Deserialize, Document, KeyGroup)]
+/// Scrolling.
+pub struct Scroll {
+ /// Scroll up one line.
+ #[serde(default = "default::scroll::up_line")]
+ pub up_line: KeyBinding,
+ /// Scroll down one line.
+ #[serde(default = "default::scroll::down_line")]
+ pub down_line: KeyBinding,
+ /// Scroll up half a screen.
+ #[serde(default = "default::scroll::up_half")]
+ pub up_half: KeyBinding,
+ /// Scroll down half a screen.
+ #[serde(default = "default::scroll::down_half")]
+ pub down_half: KeyBinding,
+ /// Scroll up a full screen.
+ #[serde(default = "default::scroll::up_full")]
+ pub up_full: KeyBinding,
+ /// Scroll down a full screen.
+ #[serde(default = "default::scroll::down_full")]
+ pub down_full: KeyBinding,
+ /// Center cursor.
+ #[serde(default = "default::scroll::center_cursor")]
+ pub center_cursor: KeyBinding,
+}
+
+#[derive(Debug, Deserialize, Document, KeyGroup)]
+/// Cursor movement.
+pub struct Cursor {
+ /// Move up.
+ #[serde(default = "default::cursor::up")]
+ pub up: KeyBinding,
+ /// Move down.
+ #[serde(default = "default::cursor::down")]
+ pub down: KeyBinding,
+ /// Move to top.
+ #[serde(default = "default::cursor::to_top")]
+ pub to_top: KeyBinding,
+ /// Move to bottom.
+ #[serde(default = "default::cursor::to_bottom")]
+ pub to_bottom: KeyBinding,
+}
+
+#[derive(Debug, Deserialize, Document, KeyGroup)]
+/// Editor cursor movement.
+pub struct EditorCursor {
+ /// Move left.
+ #[serde(default = "default::editor_cursor::left")]
+ pub left: KeyBinding,
+ /// Move right.
+ #[serde(default = "default::editor_cursor::right")]
+ pub right: KeyBinding,
+ /// Move left a word.
+ #[serde(default = "default::editor_cursor::left_word")]
+ pub left_word: KeyBinding,
+ /// Move right a word.
+ #[serde(default = "default::editor_cursor::right_word")]
+ pub right_word: KeyBinding,
+ /// Move to start of line.
+ #[serde(default = "default::editor_cursor::start")]
+ pub start: KeyBinding,
+ /// Move to end of line.
+ #[serde(default = "default::editor_cursor::end")]
+ pub end: KeyBinding,
+ /// Move up.
+ #[serde(default = "default::editor_cursor::up")]
+ pub up: KeyBinding,
+ /// Move down.
+ #[serde(default = "default::editor_cursor::down")]
+ pub down: KeyBinding,
+}
+
+#[derive(Debug, Deserialize, Document, KeyGroup)]
+/// Editor actions.
+pub struct EditorAction {
+ /// Delete before cursor.
+ #[serde(default = "default::editor_action::backspace")]
+ pub backspace: KeyBinding,
+ /// Delete after cursor.
+ #[serde(default = "default::editor_action::delete")]
+ pub delete: KeyBinding,
+ /// Clear editor contents.
+ #[serde(default = "default::editor_action::clear")]
+ pub clear: KeyBinding,
+ /// Edit in external editor.
+ #[serde(default = "default::editor_action::external")]
+ pub external: KeyBinding,
+}
+
+#[derive(Debug, Default, Deserialize, Document)]
+pub struct Editor {
+ #[serde(default)]
+ #[document(no_default)]
+ pub cursor: EditorCursor,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub action: EditorAction,
+}
+
+#[derive(Debug, Deserialize, Document, KeyGroup)]
+/// Room list actions.
+pub struct RoomsAction {
+ /// Connect to selected room.
+ #[serde(default = "default::rooms_action::connect")]
+ pub connect: KeyBinding,
+ /// Connect to all rooms.
+ #[serde(default = "default::rooms_action::connect_all")]
+ pub connect_all: KeyBinding,
+ /// Disconnect from selected room.
+ #[serde(default = "default::rooms_action::disconnect")]
+ pub disconnect: KeyBinding,
+ /// Disconnect from all rooms.
+ #[serde(default = "default::rooms_action::disconnect_all")]
+ pub disconnect_all: KeyBinding,
+ /// Connect to all autojoin rooms.
+ #[serde(default = "default::rooms_action::connect_autojoin")]
+ pub connect_autojoin: KeyBinding,
+ /// Disconnect from all non-autojoin rooms.
+ #[serde(default = "default::rooms_action::disconnect_non_autojoin")]
+ pub disconnect_non_autojoin: KeyBinding,
+ /// Connect to new room.
+ #[serde(default = "default::rooms_action::new")]
+ pub new: KeyBinding,
+ /// Delete room.
+ #[serde(default = "default::rooms_action::delete")]
+ pub delete: KeyBinding,
+ /// Change sort order.
+ #[serde(default = "default::rooms_action::change_sort_order")]
+ pub change_sort_order: KeyBinding,
+}
+
+#[derive(Debug, Default, Deserialize, Document)]
+pub struct Rooms {
+ #[serde(default)]
+ #[document(no_default)]
+ pub action: RoomsAction,
+}
+
+#[derive(Debug, Deserialize, Document, KeyGroup)]
+/// Room actions.
+pub struct RoomAction {
+ /// Authenticate.
+ #[serde(default = "default::room_action::authenticate")]
+ pub authenticate: KeyBinding,
+ /// Change nick.
+ #[serde(default = "default::room_action::nick")]
+ pub nick: KeyBinding,
+ /// Download more messages.
+ #[serde(default = "default::room_action::more_messages")]
+ pub more_messages: KeyBinding,
+ /// Manage account.
+ #[serde(default = "default::room_action::account")]
+ pub account: KeyBinding,
+}
+
+#[derive(Debug, Default, Deserialize, Document)]
+pub struct Room {
+ #[serde(default)]
+ #[document(no_default)]
+ pub action: RoomAction,
+}
+
+#[derive(Debug, Deserialize, Document, KeyGroup)]
+/// Tree cursor movement.
+pub struct TreeCursor {
+ /// Move to above sibling.
+ #[serde(default = "default::tree_cursor::to_above_sibling")]
+ pub to_above_sibling: KeyBinding,
+ /// Move to below sibling.
+ #[serde(default = "default::tree_cursor::to_below_sibling")]
+ pub to_below_sibling: KeyBinding,
+ /// Move to parent.
+ #[serde(default = "default::tree_cursor::to_parent")]
+ pub to_parent: KeyBinding,
+ /// Move to root.
+ #[serde(default = "default::tree_cursor::to_root")]
+ pub to_root: KeyBinding,
+ /// Move to older message.
+ #[serde(default = "default::tree_cursor::to_older_message")]
+ pub to_older_message: KeyBinding,
+ /// Move to newer message.
+ #[serde(default = "default::tree_cursor::to_newer_message")]
+ pub to_newer_message: KeyBinding,
+ /// Move to older unseen message.
+ #[serde(default = "default::tree_cursor::to_older_unseen_message")]
+ pub to_older_unseen_message: KeyBinding,
+ /// Move to newer unseen message.
+ #[serde(default = "default::tree_cursor::to_newer_unseen_message")]
+ pub to_newer_unseen_message: KeyBinding,
+ // TODO Bindings inspired by vim's ()/[]/{} bindings?
+}
+
+#[derive(Debug, Deserialize, Document, KeyGroup)]
+/// Tree actions.
+pub struct TreeAction {
+ /// Reply to message, inline if possible.
+ #[serde(default = "default::tree_action::reply")]
+ pub reply: KeyBinding,
+ /// Reply opposite to normal reply.
+ #[serde(default = "default::tree_action::reply_alternate")]
+ pub reply_alternate: KeyBinding,
+ /// Start a new thread.
+ #[serde(default = "default::tree_action::new_thread")]
+ pub new_thread: KeyBinding,
+ /// Fold current message's subtree.
+ #[serde(default = "default::tree_action::fold_tree")]
+ pub fold_tree: KeyBinding,
+ /// Toggle current message's seen status.
+ #[serde(default = "default::tree_action::toggle_seen")]
+ pub toggle_seen: KeyBinding,
+ /// Mark all visible messages as seen.
+ #[serde(default = "default::tree_action::mark_visible_seen")]
+ pub mark_visible_seen: KeyBinding,
+ /// Mark all older messages as seen.
+ #[serde(default = "default::tree_action::mark_older_seen")]
+ pub mark_older_seen: KeyBinding,
+ /// Inspect selected element.
+ #[serde(default = "default::tree_action::info")]
+ pub inspect: KeyBinding,
+ /// List links found in message.
+ #[serde(default = "default::tree_action::links")]
+ pub links: KeyBinding,
+ /// Toggle agent id based nick emoji.
+ #[serde(default = "default::tree_action::toggle_nick_emoji")]
+ pub toggle_nick_emoji: KeyBinding,
+ /// Increase caesar cipher rotation.
+ #[serde(default = "default::tree_action::increase_caesar")]
+ pub increase_caesar: KeyBinding,
+ /// Decrease caesar cipher rotation.
+ #[serde(default = "default::tree_action::decrease_caesar")]
+ pub decrease_caesar: KeyBinding,
+}
+
+#[derive(Debug, Default, Deserialize, Document)]
+pub struct Tree {
+ #[serde(default)]
+ #[document(no_default)]
+ pub cursor: TreeCursor,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub action: TreeAction,
+}
+
+#[derive(Debug, Default, Deserialize, Document)]
+pub struct Keys {
+ #[serde(default)]
+ #[document(no_default)]
+ pub general: General,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub scroll: Scroll,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub cursor: Cursor,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub editor: Editor,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub rooms: Rooms,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub room: Room,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub tree: Tree,
+}
+
+impl Keys {
+ pub fn groups(&self) -> Vec> {
+ vec![
+ KeyGroupInfo::new("general", &self.general),
+ KeyGroupInfo::new("scroll", &self.scroll),
+ KeyGroupInfo::new("cursor", &self.cursor),
+ KeyGroupInfo::new("editor.cursor", &self.editor.cursor),
+ KeyGroupInfo::new("editor.action", &self.editor.action),
+ KeyGroupInfo::new("rooms.action", &self.rooms.action),
+ KeyGroupInfo::new("room.action", &self.room.action),
+ KeyGroupInfo::new("tree.cursor", &self.tree.cursor),
+ KeyGroupInfo::new("tree.action", &self.tree.action),
+ ]
+ }
+}
diff --git a/cove-config/src/lib.rs b/cove-config/src/lib.rs
new file mode 100644
index 0000000..0cb6cc7
--- /dev/null
+++ b/cove-config/src/lib.rs
@@ -0,0 +1,158 @@
+use std::{
+ fs,
+ io::{self, ErrorKind},
+ path::{Path, PathBuf},
+};
+
+use doc::Document;
+use serde::{Deserialize, Serialize};
+
+pub use crate::{euph::*, keys::*};
+
+pub mod doc;
+mod euph;
+mod keys;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("failed to read config file")]
+ Io(#[from] io::Error),
+ #[error("failed to parse config file")]
+ Toml(#[from] toml::de::Error),
+}
+
+#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, Document)]
+#[serde(rename_all = "snake_case")]
+pub enum WidthEstimationMethod {
+ #[default]
+ Legacy,
+ Unicode,
+}
+
+#[derive(Debug, Default, Deserialize, Document)]
+pub struct Config {
+ /// The directory that cove stores its data in when not running in ephemeral
+ /// mode.
+ ///
+ /// Relative paths are interpreted relative to the user's home directory.
+ ///
+ /// See also the `--data-dir` command line option.
+ #[document(default = "platform-dependent")]
+ pub data_dir: Option,
+
+ /// Whether to start in ephemeral mode.
+ ///
+ /// In ephemeral mode, cove doesn't store any data. It completely ignores
+ /// any options related to the data dir.
+ ///
+ /// See also the `--ephemeral` command line option.
+ #[serde(default)]
+ pub ephemeral: bool,
+
+ /// How to estimate the width of graphemes (i.e. characters) as displayed by
+ /// the terminal emulator.
+ ///
+ /// `"legacy"`: Use a legacy method that should mostly work on most terminal
+ /// emulators. This method will never be correct in all cases since every
+ /// terminal emulator handles grapheme widths slightly differently. However,
+ /// those cases are usually rare (unless you view a lot of emoji).
+ ///
+ /// `"unicode"`: Use the unicode standard in a best-effort manner to
+ /// determine grapheme widths. Some terminals (e.g. ghostty) can make use of
+ /// this.
+ ///
+ /// This method is used when `measure_widths` is set to `false`.
+ ///
+ /// See also the `--width-estimation-method` command line option.
+ #[serde(default)]
+ pub width_estimation_method: WidthEstimationMethod,
+
+ /// Whether to measure the width of graphemes (i.e. characters) as displayed
+ /// by the terminal emulator instead of estimating the width.
+ ///
+ /// Enabling this makes rendering a bit slower but more accurate. The screen
+ /// might also flash when encountering new graphemes.
+ ///
+ /// See also the `--measure-widths` command line option.
+ #[serde(default)]
+ pub measure_widths: bool,
+
+ /// Whether to start in offline mode.
+ ///
+ /// In offline mode, cove won't automatically join rooms marked via the
+ /// `autojoin` option on startup. You can still join those rooms manually by
+ /// pressing `a` in the rooms list.
+ ///
+ /// See also the `--offline` command line option.
+ #[serde(default)]
+ pub offline: bool,
+
+ /// Initial sort order of rooms list.
+ ///
+ /// `"alphabet"` sorts rooms in alphabetic order.
+ ///
+ /// `"importance"` sorts rooms by the following criteria (in descending
+ /// order of priority):
+ ///
+ /// 1. connected rooms before unconnected rooms
+ /// 2. rooms with unread messages before rooms without
+ /// 3. alphabetic order
+ #[serde(default)]
+ pub rooms_sort_order: RoomsSortOrder,
+
+ /// Ring the bell (character 0x07) when you are mentioned in a room.
+ #[serde(default)]
+ pub bell_on_mention: bool,
+
+ /// Time zone that chat timestamps should be displayed in.
+ ///
+ /// This option can either be the string `"localtime"`, a [POSIX TZ string],
+ /// or a [tz identifier] from the [tz database].
+ ///
+ /// When not set or when set to `"localtime"`, cove attempts to use your
+ /// system's configured time zone, falling back to UTC.
+ ///
+ /// When the string begins with a colon or doesn't match the a POSIX TZ
+ /// string format, it is interpreted as a tz identifier and looked up in
+ /// your system's tz database (or a bundled tz database on Windows).
+ ///
+ /// If the `TZ` environment variable exists, it overrides this option.
+ ///
+ /// [POSIX TZ string]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
+ /// [tz identifier]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+ /// [tz database]: https://en.wikipedia.org/wiki/Tz_database
+ #[serde(default)]
+ #[document(default = "`$TZ` or local system time zone")]
+ pub time_zone: Option,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub euph: Euph,
+
+ #[serde(default)]
+ #[document(no_default)]
+ pub keys: Keys,
+}
+
+impl Config {
+ pub fn load(path: &Path) -> Result {
+ Ok(match fs::read_to_string(path) {
+ Ok(content) => toml::from_str(&content)?,
+ Err(err) if err.kind() == ErrorKind::NotFound => Self::default(),
+ Err(err) => Err(err)?,
+ })
+ }
+
+ pub fn euph_room(&self, domain: &str, name: &str) -> EuphRoom {
+ if let Some(server) = self.euph.servers.get(domain) {
+ if let Some(room) = server.rooms.get(name) {
+ return room.clone();
+ }
+ }
+ EuphRoom::default()
+ }
+
+ pub fn time_zone_ref(&self) -> Option<&str> {
+ self.time_zone.as_ref().map(|s| s as &str)
+ }
+}
diff --git a/cove-input/Cargo.toml b/cove-input/Cargo.toml
new file mode 100644
index 0000000..5005be2
--- /dev/null
+++ b/cove-input/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "cove-input"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+cove-macro = { path = "../cove-macro" }
+
+crossterm.workspace = true
+edit.workspace = true
+parking_lot.workspace = true
+serde.workspace = true
+serde_either.workspace = true
+thiserror.workspace = true
+toss.workspace = true
+
+[lints]
+workspace = true
diff --git a/cove-input/src/keys.rs b/cove-input/src/keys.rs
new file mode 100644
index 0000000..8d2fdf1
--- /dev/null
+++ b/cove-input/src/keys.rs
@@ -0,0 +1,252 @@
+use std::{fmt, num::ParseIntError, str::FromStr};
+
+use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
+use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
+use serde_either::SingleOrVec;
+
+#[derive(Debug, thiserror::Error)]
+pub enum ParseKeysError {
+ #[error("no key code specified")]
+ NoKeyCode,
+ #[error("unknown key code: {0:?}")]
+ UnknownKeyCode(String),
+ #[error("invalid function key number: {0}")]
+ InvalidFNumber(#[from] ParseIntError),
+ #[error("unknown modifier: {0:?}")]
+ UnknownModifier(String),
+ #[error("modifier {0} conflicts with previous modifier")]
+ ConflictingModifier(String),
+}
+
+fn conflicts_with_shift(code: KeyCode) -> bool {
+ match code {
+ KeyCode::Char(' ') => false,
+ KeyCode::Char(_) => true,
+ _ => false,
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct KeyPress {
+ pub code: KeyCode,
+ pub shift: bool,
+ pub ctrl: bool,
+ pub alt: bool,
+ pub any: bool,
+}
+
+impl KeyPress {
+ fn parse_key_code(code: &str) -> Result {
+ let code = match code {
+ "esc" => KeyCode::Esc,
+ "enter" => KeyCode::Enter,
+ "space" => KeyCode::Char(' '),
+ "tab" => KeyCode::Tab,
+ "backtab" => KeyCode::BackTab,
+
+ "backspace" => KeyCode::Backspace,
+ "delete" => KeyCode::Delete,
+ "insert" => KeyCode::Insert,
+
+ "left" => KeyCode::Left,
+ "right" => KeyCode::Right,
+ "up" => KeyCode::Up,
+ "down" => KeyCode::Down,
+
+ "home" => KeyCode::Home,
+ "end" => KeyCode::End,
+ "pageup" => KeyCode::PageUp,
+ "pagedown" => KeyCode::PageDown,
+
+ c if c.chars().count() == 1 => KeyCode::Char(c.chars().next().unwrap()),
+ c if c.starts_with('f') => KeyCode::F(c.strip_prefix('f').unwrap().parse()?),
+
+ "" => return Err(ParseKeysError::NoKeyCode),
+ c => return Err(ParseKeysError::UnknownKeyCode(c.to_string())),
+ };
+ Ok(Self {
+ code,
+ shift: false,
+ ctrl: false,
+ alt: false,
+ any: false,
+ })
+ }
+
+ fn display_key_code(code: KeyCode) -> String {
+ match code {
+ KeyCode::Esc => "esc".to_string(),
+ KeyCode::Enter => "enter".to_string(),
+ KeyCode::Char(' ') => "space".to_string(),
+ KeyCode::Tab => "tab".to_string(),
+ KeyCode::BackTab => "backtab".to_string(),
+
+ KeyCode::Backspace => "backspace".to_string(),
+ KeyCode::Delete => "delete".to_string(),
+ KeyCode::Insert => "insert".to_string(),
+
+ KeyCode::Left => "left".to_string(),
+ KeyCode::Right => "right".to_string(),
+ KeyCode::Up => "up".to_string(),
+ KeyCode::Down => "down".to_string(),
+
+ KeyCode::Home => "home".to_string(),
+ KeyCode::End => "end".to_string(),
+ KeyCode::PageUp => "pageup".to_string(),
+ KeyCode::PageDown => "pagedown".to_string(),
+
+ KeyCode::Char(c) => c.to_string(),
+ KeyCode::F(n) => format!("f{n}"),
+
+ _ => "unknown".to_string(),
+ }
+ }
+
+ fn parse_modifier(
+ &mut self,
+ modifier: &str,
+ shift_allowed: bool,
+ ) -> Result<(), ParseKeysError> {
+ match modifier {
+ m if self.any => return Err(ParseKeysError::ConflictingModifier(m.to_string())),
+ "shift" if shift_allowed && !self.shift => self.shift = true,
+ "ctrl" if !self.ctrl => self.ctrl = true,
+ "alt" if !self.alt => self.alt = true,
+ "any" if !self.shift && !self.ctrl && !self.alt => self.any = true,
+ m @ ("shift" | "ctrl" | "alt" | "any") => {
+ return Err(ParseKeysError::ConflictingModifier(m.to_string()));
+ }
+ m => return Err(ParseKeysError::UnknownModifier(m.to_string())),
+ }
+ Ok(())
+ }
+
+ pub fn matches(&self, event: KeyEvent) -> bool {
+ if event.code != self.code {
+ return false;
+ }
+
+ if self.any && !event.modifiers.is_empty() {
+ return true;
+ }
+
+ let ctrl = event.modifiers.contains(KeyModifiers::CONTROL) == self.ctrl;
+ let alt = event.modifiers.contains(KeyModifiers::ALT) == self.alt;
+ if conflicts_with_shift(self.code) {
+ ctrl && alt
+ } else {
+ let shift = event.modifiers.contains(KeyModifiers::SHIFT) == self.shift;
+ shift && ctrl && alt
+ }
+ }
+}
+
+impl FromStr for KeyPress {
+ type Err = ParseKeysError;
+
+ fn from_str(s: &str) -> Result {
+ let mut parts = s.split('+');
+ let code = parts.next_back().ok_or(ParseKeysError::NoKeyCode)?;
+
+ let mut keys = Self::parse_key_code(code)?;
+ let shift_allowed = !conflicts_with_shift(keys.code);
+ for modifier in parts {
+ keys.parse_modifier(modifier, shift_allowed)?;
+ }
+
+ Ok(keys)
+ }
+}
+
+impl fmt::Display for KeyPress {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let code = Self::display_key_code(self.code);
+
+ let mut segments = vec![];
+ if self.shift {
+ segments.push("shift");
+ }
+ if self.ctrl {
+ segments.push("ctrl");
+ }
+ if self.alt {
+ segments.push("alt");
+ }
+ if self.any {
+ segments.push("any");
+ }
+ segments.push(&code);
+
+ segments.join("+").fmt(f)
+ }
+}
+
+impl Serialize for KeyPress {
+ fn serialize(&self, serializer: S) -> Result {
+ format!("{self}").serialize(serializer)
+ }
+}
+
+impl<'de> Deserialize<'de> for KeyPress {
+ fn deserialize>(deserializer: D) -> Result {
+ String::deserialize(deserializer)?
+ .parse()
+ .map_err(|e| D::Error::custom(format!("{e}")))
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct KeyBinding(Vec);
+
+impl KeyBinding {
+ pub fn new() -> Self {
+ Self(vec![])
+ }
+
+ pub fn keys(&self) -> &[KeyPress] {
+ &self.0
+ }
+
+ pub fn with_key(self, key: &str) -> Result {
+ self.with_keys([key])
+ }
+
+ pub fn with_keys<'a, I>(mut self, keys: I) -> Result
+ where
+ I: IntoIterator- ,
+ {
+ for key in keys {
+ self.0.push(key.parse()?);
+ }
+ Ok(self)
+ }
+
+ pub fn matches(&self, event: KeyEvent) -> bool {
+ self.0.iter().any(|kp| kp.matches(event))
+ }
+}
+
+impl Default for KeyBinding {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Serialize for KeyBinding {
+ fn serialize(&self, serializer: S) -> Result {
+ if self.0.len() == 1 {
+ self.0[0].serialize(serializer)
+ } else {
+ self.0.serialize(serializer)
+ }
+ }
+}
+
+impl<'de> Deserialize<'de> for KeyBinding {
+ fn deserialize>(deserializer: D) -> Result {
+ Ok(match SingleOrVec::::deserialize(deserializer)? {
+ SingleOrVec::Single(key) => Self(vec![key]),
+ SingleOrVec::Vec(keys) => Self(keys),
+ })
+ }
+}
diff --git a/cove-input/src/lib.rs b/cove-input/src/lib.rs
new file mode 100644
index 0000000..f6b2e92
--- /dev/null
+++ b/cove-input/src/lib.rs
@@ -0,0 +1,102 @@
+use std::{io, sync::Arc};
+
+pub use cove_macro::KeyGroup;
+use crossterm::event::{Event, KeyEvent, KeyEventKind};
+use parking_lot::FairMutex;
+use toss::{Frame, Terminal, WidthDb};
+
+pub use crate::keys::*;
+
+mod keys;
+
+pub struct KeyBindingInfo<'a> {
+ pub name: &'static str,
+ pub binding: &'a KeyBinding,
+ pub description: &'static str,
+}
+
+/// A group of related key bindings.
+pub trait KeyGroup {
+ const DESCRIPTION: &'static str;
+
+ fn bindings(&self) -> Vec>;
+}
+
+pub struct KeyGroupInfo<'a> {
+ pub name: &'static str,
+ pub description: &'static str,
+ pub bindings: Vec>,
+}
+
+impl<'a> KeyGroupInfo<'a> {
+ pub fn new(name: &'static str, group: &'a G) -> Self {
+ Self {
+ name,
+ description: G::DESCRIPTION,
+ bindings: group.bindings(),
+ }
+ }
+}
+
+pub struct InputEvent<'a> {
+ event: Event,
+ terminal: &'a mut Terminal,
+ crossterm_lock: Arc>,
+}
+
+impl<'a> InputEvent<'a> {
+ pub fn new(
+ event: Event,
+ terminal: &'a mut Terminal,
+ crossterm_lock: Arc>,
+ ) -> Self {
+ Self {
+ event,
+ terminal,
+ crossterm_lock,
+ }
+ }
+
+ /// If the current event represents a key press, returns the [`KeyEvent`]
+ /// associated with that key press.
+ pub fn key_event(&self) -> Option {
+ if let Event::Key(event) = &self.event {
+ if matches!(event.kind, KeyEventKind::Press | KeyEventKind::Repeat) {
+ return Some(*event);
+ }
+ }
+ None
+ }
+
+ pub fn paste_event(&self) -> Option<&str> {
+ match &self.event {
+ Event::Paste(string) => Some(string),
+ _ => None,
+ }
+ }
+
+ pub fn matches(&self, binding: &KeyBinding) -> bool {
+ match self.key_event() {
+ Some(event) => binding.matches(event),
+ None => false,
+ }
+ }
+
+ pub fn frame(&mut self) -> &mut Frame {
+ self.terminal.frame()
+ }
+
+ pub fn widthdb(&mut self) -> &mut WidthDb {
+ self.terminal.widthdb()
+ }
+
+ pub fn prompt(&mut self, initial_text: &str) -> io::Result {
+ let guard = self.crossterm_lock.lock();
+ self.terminal.suspend().expect("failed to suspend");
+ let content = edit::edit(initial_text);
+ self.terminal.unsuspend().expect("fauled to unsuspend");
+ drop(guard);
+
+ content
+ }
+}
diff --git a/cove-macro/Cargo.toml b/cove-macro/Cargo.toml
new file mode 100644
index 0000000..6c01b7d
--- /dev/null
+++ b/cove-macro/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "cove-macro"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+proc-macro2.workspace = true
+quote.workspace = true
+syn.workspace = true
+
+[lib]
+proc-macro = true
+
+[lints]
+workspace = true
diff --git a/cove-macro/src/document.rs b/cove-macro/src/document.rs
new file mode 100644
index 0000000..afec84d
--- /dev/null
+++ b/cove-macro/src/document.rs
@@ -0,0 +1,152 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Ident, LitStr, spanned::Spanned};
+
+use crate::util::{self, SerdeDefault};
+
+#[derive(Default)]
+struct FieldInfo {
+ description: Option,
+ metavar: Option,
+ default: Option,
+ serde_default: Option,
+ no_default: bool,
+}
+
+impl FieldInfo {
+ fn initialize_from_field(&mut self, field: &Field) -> syn::Result<()> {
+ let docstring = util::docstring(&field.attrs)?;
+ if !docstring.is_empty() {
+ self.description = Some(docstring);
+ }
+
+ for arg in util::attribute_arguments(&field.attrs, "document")? {
+ if arg.path.is_ident("metavar") {
+ // Parse `#[document(metavar = "bla")]`
+ if let Some(metavar) = arg.value.and_then(util::into_litstr) {
+ self.metavar = Some(metavar);
+ } else {
+ util::bail(arg.path.span(), "must be of the form `key = \"value\"`")?;
+ }
+ } else if arg.path.is_ident("default") {
+ // Parse `#[document(default = "bla")]`
+ if let Some(value) = arg.value.and_then(util::into_litstr) {
+ self.default = Some(value);
+ } else {
+ util::bail(arg.path.span(), "must be of the form `key = \"value\"`")?;
+ }
+ } else if arg.path.is_ident("no_default") {
+ // Parse #[document(no_default)]
+ if arg.value.is_some() {
+ util::bail(arg.path.span(), "must not have a value")?;
+ }
+ self.no_default = true;
+ } else {
+ util::bail(arg.path.span(), "unknown argument name")?;
+ }
+ }
+
+ // Find `#[serde(default)]` or `#[serde(default = "bla")]`.
+ self.serde_default = util::serde_default(field)?;
+
+ Ok(())
+ }
+
+ fn from_field(field: &Field) -> syn::Result {
+ let mut result = Self::default();
+ result.initialize_from_field(field)?;
+ Ok(result)
+ }
+}
+
+fn from_struct(ident: Ident, data: DataStruct) -> syn::Result {
+ let mut fields = vec![];
+ for field in data.fields {
+ let Some(ident) = field.ident.as_ref() else {
+ return util::bail(field.span(), "must not be a tuple struct");
+ };
+ let ident = ident.to_string();
+
+ let info = FieldInfo::from_field(&field)?;
+
+ let mut setters = vec![];
+ if let Some(description) = info.description {
+ setters.push(quote! {
+ doc.description = Some(#description.to_string());
+ });
+ }
+ if let Some(metavar) = info.metavar {
+ setters.push(quote! {
+ doc.wrap_info.metavar = Some(#metavar.to_string());
+ });
+ }
+ if info.no_default {
+ } else if let Some(default) = info.default {
+ setters.push(quote! {
+ doc.value_info.default = Some(#default.to_string());
+ });
+ } else if let Some(serde_default) = info.serde_default {
+ let value = serde_default.value();
+ setters.push(quote! {
+ doc.value_info.default = Some(crate::doc::toml_value_as_markdown(value));
+ });
+ }
+
+ let ty = field.ty;
+ fields.push(quote! {
+ fields.insert(
+ #ident.to_string(),
+ {
+ let mut doc = <#ty as crate::doc::Document>::doc();
+ #( #setters )*
+ ::std::boxed::Box::new(doc)
+ }
+ );
+ });
+ }
+
+ let tokens = quote!(
+ impl crate::doc::Document for #ident {
+ fn doc() -> crate::doc::Doc {
+ let mut fields = ::std::collections::HashMap::new();
+ #( #fields )*
+
+ let mut doc = crate::doc::Doc::default();
+ doc.struct_info.fields = fields;
+ doc
+ }
+ }
+ );
+
+ Ok(tokens)
+}
+
+fn from_enum(ident: Ident, data: DataEnum) -> syn::Result {
+ let mut values = vec![];
+ for variant in data.variants {
+ let ident = variant.ident;
+ values.push(quote! {
+ crate::doc::toml_value_as_markdown(&Self::#ident)
+ });
+ }
+
+ let tokens = quote!(
+ impl crate::doc::Document for #ident {
+ fn doc() -> crate::doc::Doc {
+ let mut doc = ::doc();
+ doc.value_info.values = Some(vec![ #( #values ),* ]);
+ doc
+ }
+ }
+ );
+
+ Ok(tokens)
+}
+
+pub fn derive_impl(input: DeriveInput) -> syn::Result {
+ match input.data {
+ Data::Struct(data) => from_struct(input.ident, data),
+ Data::Enum(data) => from_enum(input.ident, data),
+ Data::Union(_) => util::bail(input.span(), "must be an enum or a struct"),
+ }
+}
diff --git a/cove-macro/src/key_group.rs b/cove-macro/src/key_group.rs
new file mode 100644
index 0000000..832bfd3
--- /dev/null
+++ b/cove-macro/src/key_group.rs
@@ -0,0 +1,74 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{Data, DeriveInput, spanned::Spanned};
+
+use crate::util;
+
+fn decapitalize(s: &str) -> String {
+ let mut chars = s.chars();
+ if let Some(char) = chars.next() {
+ char.to_lowercase().chain(chars).collect()
+ } else {
+ String::new()
+ }
+}
+
+pub fn derive_impl(input: DeriveInput) -> syn::Result {
+ let Data::Struct(data) = input.data else {
+ return util::bail(input.span(), "must be a struct");
+ };
+
+ let docstring = util::docstring(&input.attrs)?;
+ let description = docstring.strip_suffix('.').unwrap_or(&docstring);
+
+ let mut bindings = vec![];
+ let mut defaults = vec![];
+ for field in &data.fields {
+ if let Some(field_ident) = &field.ident {
+ let field_name = field_ident.to_string();
+
+ let docstring = util::docstring(&field.attrs)?;
+ let description = decapitalize(&docstring);
+ let description = description.strip_suffix('.').unwrap_or(&description);
+
+ let default = util::serde_default(field)?;
+ let Some(default) = default else {
+ return util::bail(field_ident.span(), "must have serde default");
+ };
+ let default_value = default.value();
+
+ bindings.push(quote! {
+ ::cove_input::KeyBindingInfo {
+ name: #field_name,
+ binding: &self.#field_ident,
+ description: #description
+ }
+ });
+
+ defaults.push(quote! {
+ #field_ident: #default_value,
+ });
+ }
+ }
+
+ let ident = input.ident;
+ Ok(quote! {
+ impl ::cove_input::KeyGroup for #ident {
+ const DESCRIPTION: &'static str = #description;
+
+ fn bindings(&self) -> Vec<::cove_input::KeyBindingInfo<'_>> {
+ vec![
+ #( #bindings, )*
+ ]
+ }
+ }
+
+ impl Default for #ident {
+ fn default() -> Self {
+ Self {
+ #( #defaults )*
+ }
+ }
+ }
+ })
+}
diff --git a/cove-macro/src/lib.rs b/cove-macro/src/lib.rs
new file mode 100644
index 0000000..c655f2a
--- /dev/null
+++ b/cove-macro/src/lib.rs
@@ -0,0 +1,23 @@
+use syn::{DeriveInput, parse_macro_input};
+
+mod document;
+mod key_group;
+mod util;
+
+#[proc_macro_derive(Document, attributes(document))]
+pub fn derive_document(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ match document::derive_impl(input) {
+ Ok(tokens) => tokens.into(),
+ Err(err) => err.into_compile_error().into(),
+ }
+}
+
+#[proc_macro_derive(KeyGroup)]
+pub fn derive_group(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ match key_group::derive_impl(input) {
+ Ok(tokens) => tokens.into(),
+ Err(err) => err.into_compile_error().into(),
+ }
+}
diff --git a/cove-macro/src/util.rs b/cove-macro/src/util.rs
new file mode 100644
index 0000000..d73b7ca
--- /dev/null
+++ b/cove-macro/src/util.rs
@@ -0,0 +1,117 @@
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use syn::{
+ Attribute, Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type, parse::Parse,
+ punctuated::Punctuated,
+};
+
+pub fn bail(span: Span, message: &str) -> syn::Result {
+ Err(syn::Error::new(span, message))
+}
+
+pub fn litstr(expr: &Expr) -> Option<&LitStr> {
+ match expr {
+ Expr::Lit(ExprLit {
+ lit: Lit::Str(lit), ..
+ }) => Some(lit),
+ _ => None,
+ }
+}
+
+pub fn into_litstr(expr: Expr) -> Option {
+ match expr {
+ Expr::Lit(ExprLit {
+ lit: Lit::Str(lit), ..
+ }) => Some(lit),
+ _ => None,
+ }
+}
+
+/// Given a struct field, this finds all attributes like `#[doc = "bla"]`,
+/// unindents, concatenates and returns them.
+pub fn docstring(attributes: &[Attribute]) -> syn::Result {
+ let mut lines = vec![];
+
+ for attr in attributes.iter().filter(|attr| attr.path().is_ident("doc")) {
+ if let Some(lit) = litstr(&attr.meta.require_name_value()?.value) {
+ let value = lit.value();
+ let value = value
+ .strip_prefix(' ')
+ .map(|value| value.to_string())
+ .unwrap_or(value);
+ lines.push(value);
+ }
+ }
+
+ Ok(lines.join("\n"))
+}
+
+pub struct AttributeArgument {
+ pub path: Path,
+ pub value: Option,
+}
+
+impl Parse for AttributeArgument {
+ fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result {
+ let path = Path::parse(input)?;
+ let value = if input.peek(Token![=]) {
+ input.parse::()?;
+ Some(Expr::parse(input)?)
+ } else {
+ None
+ };
+ Ok(Self { path, value })
+ }
+}
+
+/// Given a struct field, this finds all arguments of the form `#[path(key)]`
+/// and `#[path(key = value)]`. Multiple arguments may be specified in a single
+/// annotation, e.g. `#[foo(bar, baz = true)]`.
+pub fn attribute_arguments(
+ attributes: &[Attribute],
+ path: &str,
+) -> syn::Result> {
+ let mut attr_args = vec![];
+
+ for attr in attributes.iter().filter(|attr| attr.path().is_ident(path)) {
+ let args =
+ attr.parse_args_with(Punctuated::::parse_terminated)?;
+ attr_args.extend(args);
+ }
+
+ Ok(attr_args)
+}
+
+pub enum SerdeDefault {
+ Default(Type),
+ Path(ExprPath),
+}
+
+impl SerdeDefault {
+ pub fn value(&self) -> TokenStream {
+ match self {
+ Self::Default(ty) => quote! {
+ <#ty as Default>::default()
+ },
+ Self::Path(path) => quote! {
+ #path()
+ },
+ }
+ }
+}
+
+/// Find `#[serde(default)]` or `#[serde(default = "bla")]`.
+pub fn serde_default(field: &Field) -> syn::Result