├── tests
├── assets
│ ├── a.pdf
│ ├── a.png
│ ├── b.png
│ ├── empty
│ ├── empty.txt
│ ├── cat
│ ├── p.html
│ ├── no_html_tags.html
│ ├── mimeapps_no_added.list
│ ├── mimeapps_duplicate.list
│ ├── mimeapps_empty_entry.list
│ ├── mimeapps_no_default.list
│ ├── nonsense_binary_data
│ ├── rust.vim
│ ├── empty_exec.desktop
│ ├── org.wezfurlong.wezterm.desktop
│ ├── empty_name.desktop
│ ├── cmus.desktop
│ ├── mimeapps_sorted.list
│ ├── mimeapps_anomalous_semicolons.list
│ ├── vlc.desktop
│ ├── Helix.desktop
│ └── SettingsWidgetFdoSecrets.ui
├── snapshots
│ ├── terminal_override__terminal_output_tests_force_false.snap
│ └── terminal_override__terminal_output_tests_force_true.snap
└── terminal_override.rs
├── .envrc
├── .gitattributes
├── .gitignore
├── .rustfmt.toml
├── src
├── apps
│ ├── mod.rs
│ ├── snapshots
│ │ ├── handlr__apps__user__tests__remove_handlers_expand_wildcards.snap
│ │ ├── handlr__apps__user__tests__unset_handlers_expand_wildcards.snap
│ │ ├── handlr__apps__user__tests__set_handlers_expand_wildcards.snap
│ │ └── handlr__apps__user__tests__add_handlers_expand_wildcards.snap
│ └── system.rs
├── config
│ ├── mod.rs
│ ├── snapshots
│ │ ├── handlr__config__main_config__tests__show_handler.snap
│ │ ├── handlr__config__main_config__tests__show_handler_terminal.snap
│ │ ├── handlr__config__main_config__tests__show_handler_json_terminal.snap
│ │ ├── handlr__config__main_config__tests__show_handler_json.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_json-2.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_json.snap
│ │ ├── handlr__config__main_config__tests__dont_override_selector.snap
│ │ ├── handlr__config__main_config__tests__override_selector.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_detailed_json-2.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_detailed_json.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_piped.snap
│ │ ├── handlr__config__main_config__tests__terminal_command_fallback.snap
│ │ ├── handlr__config__main_config__tests__language_variable_parsing.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_detailed_piped.snap
│ │ ├── handlr__config__main_config__tests__terminal_command_set.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_default.snap
│ │ ├── handlr__config__main_config__tests__show_handler-2.snap
│ │ ├── handlr__config__main_config__tests__show_handler_terminal-2.snap
│ │ ├── handlr__config__main_config__tests__show_handler_json_terminal-2.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_detailed.snap
│ │ ├── handlr__config__main_config__tests__show_handler_json-2.snap
│ │ ├── handlr__config__main_config__tests__wildcard_mimes.snap
│ │ ├── handlr__config__main_config__tests__set_and_unset_handlers.snap
│ │ ├── handlr__config__main_config__tests__add_and_unset_handlers.snap
│ │ ├── handlr__config__main_config__tests__set_and_remove_handlers.snap
│ │ ├── handlr__config__main_config__tests__add_and_remove_handlers.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_piped-2.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_default-2.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_detailed-2.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_detailed_piped-2.snap
│ │ ├── handlr__config__main_config__tests__properly_assign_files_to_handlers.snap
│ │ ├── handlr__config__main_config__tests__print_handlers_json-3.snap
│ │ └── handlr__config__main_config__tests__print_handlers_detailed_json-3.snap
│ └── config_file.rs
├── common
│ ├── mod.rs
│ ├── snapshots
│ │ ├── handlr__common__handler__tests__regex_handlers.snap
│ │ ├── handlr__common__path__tests__mime_table_json-2.snap
│ │ ├── handlr__common__path__tests__mime_table_json.snap
│ │ ├── handlr__common__path__tests__mime_table_piped.snap
│ │ ├── handlr__common__table__tests__piped_output.snap
│ │ ├── handlr__common__path__tests__mime_table_terminal.snap
│ │ └── handlr__common__table__tests__terminal_output.snap
│ ├── db.rs
│ ├── table.rs
│ ├── path.rs
│ ├── mime_types.rs
│ ├── handler.rs
│ └── desktop_entry.rs
├── testing.rs
├── main.rs
├── error.rs
└── logging.rs
├── shell.nix
├── default.nix
├── .github
└── workflows
│ ├── build_nix.yml
│ └── main.yml
├── LICENSE
├── flake.nix
├── Cargo.toml
├── flake.lock
└── README.md
/tests/assets/a.pdf:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/assets/a.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/assets/b.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/assets/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | use flake
2 |
--------------------------------------------------------------------------------
/tests/assets/empty.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/assets/cat:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | bat "$@"
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | completions/* linguist-vendored
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /mutants.out*
3 | /result
4 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | max_width = 80
2 | merge_imports = true
3 |
--------------------------------------------------------------------------------
/tests/assets/p.html:
--------------------------------------------------------------------------------
1 |
2 |
asdf
3 |
4 |
--------------------------------------------------------------------------------
/tests/assets/no_html_tags.html:
--------------------------------------------------------------------------------
1 | Hello World!
2 |
--------------------------------------------------------------------------------
/tests/assets/mimeapps_no_added.list:
--------------------------------------------------------------------------------
1 | [Default Applications]
2 | text/*=nvim.desktop;Helix.desktop;
3 |
--------------------------------------------------------------------------------
/tests/assets/mimeapps_duplicate.list:
--------------------------------------------------------------------------------
1 | [Default Applications]
2 | text/*=nvim.desktop;Helix.desktop;nvim.desktop;
3 |
--------------------------------------------------------------------------------
/tests/assets/mimeapps_empty_entry.list:
--------------------------------------------------------------------------------
1 | [Default Applications]
2 | text/*=nvim.desktop;Helix.desktop;
3 | text/plain=
4 |
--------------------------------------------------------------------------------
/src/apps/mod.rs:
--------------------------------------------------------------------------------
1 | mod system;
2 | mod user;
3 |
4 | pub use system::SystemApps;
5 | pub use user::{DesktopList, MimeApps};
6 |
--------------------------------------------------------------------------------
/tests/assets/mimeapps_no_default.list:
--------------------------------------------------------------------------------
1 | [Added Associations]
2 | x-scheme-handler/terminal=org.codeberg.dnkl.foot.desktop;
3 |
--------------------------------------------------------------------------------
/tests/assets/nonsense_binary_data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Anomalocaridid/handlr-regex/HEAD/tests/assets/nonsense_binary_data
--------------------------------------------------------------------------------
/tests/assets/rust.vim:
--------------------------------------------------------------------------------
1 | highlight CocRustChainingHint ctermfg=0
2 | hi link rustDerive SpecialComment
3 | hi link rustDeriveTrait SpecialComment
4 |
--------------------------------------------------------------------------------
/src/config/mod.rs:
--------------------------------------------------------------------------------
1 | mod config_file;
2 | mod main_config;
3 |
4 | pub use config_file::ConfigFile;
5 | pub use main_config::{get_languages, Config, Languages};
6 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__show_handler.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | tests/assets/Helix.desktop
6 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__show_handler_terminal.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | tests/assets/Helix.desktop
6 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__show_handler_json_terminal.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | {"cmd":"hx","handler":"tests/assets/Helix.desktop","name":"Helix"}
6 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | (import (
2 | fetchTarball {
3 | url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
4 | sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
5 | ) {
6 | src = ./.;
7 | }).shellNix
8 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__show_handler_json.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | {"cmd":"wezterm start --cwd . -e hx","handler":"tests/assets/Helix.desktop","name":"Helix"}
6 |
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | (import (
2 | fetchTarball {
3 | url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
4 | sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
5 | ) {
6 | src = ./.;
7 | }).defaultNix
8 |
--------------------------------------------------------------------------------
/tests/assets/empty_exec.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Encoding=UTF-8
3 | Version=1.0
4 | Type=Application
5 | Display=true
6 | Exec=
7 | Terminal=false
8 | Name=cmus-remote
9 | Comment=Music player cmus-remote control
10 | NoDisplay=true
11 | Icon=cmus
12 | MimeType=audio/mp3;audio/ogg;
13 |
--------------------------------------------------------------------------------
/.github/workflows/build_nix.yml:
--------------------------------------------------------------------------------
1 | name: "Build legacy Nix package on Ubuntu"
2 |
3 | on:
4 | push:
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: cachix/install-nix-action@v26
12 | - name: Building package
13 | run: nix build
14 |
--------------------------------------------------------------------------------
/tests/assets/org.wezfurlong.wezterm.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=WezTerm
3 | Comment=Wez's Terminal Emulator
4 | Keywords=shell;prompt;command;commandline;cmd;
5 | Icon=org.wezfurlong.wezterm
6 | StartupWMClass=org.wezfurlong.wezterm
7 | TryExec=wezterm
8 | Exec=wezterm start --cwd .
9 | Type=Application
10 | Categories=System;TerminalEmulator;Utility;
11 | Terminal=false
12 |
--------------------------------------------------------------------------------
/tests/assets/empty_name.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Encoding=UTF-8
3 | Version=1.0
4 | Type=Application
5 | Display=true
6 | Exec=bash -c "(! pgrep cmus && tilix -e cmus && tilix -a session-add-down -e cava); sleep 0.1 && cmus-remote -q %f"
7 | Terminal=false
8 | Name=
9 | Comment=Music player cmus-remote control
10 | NoDisplay=true
11 | Icon=cmus
12 | MimeType=audio/mp3;audio/ogg;
13 |
--------------------------------------------------------------------------------
/tests/assets/cmus.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Encoding=UTF-8
3 | Version=1.0
4 | Type=Application
5 | Display=true
6 | Exec=bash -c "(! pgrep cmus && tilix -e cmus && tilix -a session-add-down -e cava); sleep 0.1 && cmus-remote -q %f"
7 | Terminal=false
8 | Name=cmus-remote
9 | Comment=Music player cmus-remote control
10 | NoDisplay=true
11 | Icon=cmus
12 | MimeType=audio/mp3;audio/ogg;
13 |
--------------------------------------------------------------------------------
/src/common/mod.rs:
--------------------------------------------------------------------------------
1 | mod db;
2 | mod desktop_entry;
3 | mod handler;
4 | mod mime_types;
5 | mod path;
6 | mod table;
7 |
8 | pub use self::db::MIME_TYPES;
9 | pub use desktop_entry::{DesktopEntry, Mode as ExecMode};
10 | pub use handler::{
11 | DesktopHandler, Handleable, Handler, RegexApps, RegexHandler,
12 | };
13 | pub use mime_types::MimeType;
14 | pub use path::{mime_table, UserPath};
15 | pub use table::render_table;
16 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_json-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | [{"mime":"application/vnd.oasis.opendocument.*","handlers":["startcenter.desktop"]},{"mime":"application/vnd.openxmlformats-officedocument.*","handlers":["startcenter.desktop"]},{"mime":"text/plain","handlers":["helix.desktop","nvim.desktop","kakoune.desktop"]},{"mime":"video/asdf","handlers":["mpv.desktop"]},{"mime":"video/mp4","handlers":["mpv.desktop"]},{"mime":"video/webm","handlers":["brave.desktop"]}]
6 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_json.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer).unwrap()"
4 | ---
5 | [{"mime":"application/vnd.oasis.opendocument.*","handlers":["startcenter.desktop"]},{"mime":"application/vnd.openxmlformats-officedocument.*","handlers":["startcenter.desktop"]},{"mime":"text/plain","handlers":["helix.desktop","nvim.desktop","kakoune.desktop"]},{"mime":"video/asdf","handlers":["mpv.desktop"]},{"mime":"video/mp4","handlers":["mpv.desktop"]},{"mime":"video/webm","handlers":["brave.desktop"]}]
6 |
--------------------------------------------------------------------------------
/src/common/snapshots/handlr__common__handler__tests__regex_handlers.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/common/handler.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::common::handler[0m[2m:[0m Regex matches found in `"freetube %u" (Regex Handler)` for `https://youtu.be/dQw4w9WgXcQ`: [Regex(Regex("(https://)?(www\\.)?youtu(be\\.com|\\.be)/*"))]
6 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::common::handler[0m[2m:[0m No regex matches found in `"freetube %u" (Regex Handler)` for `https://en.wikipedia.org/`
7 |
--------------------------------------------------------------------------------
/src/common/db.rs:
--------------------------------------------------------------------------------
1 | use std::sync::LazyLock;
2 |
3 | use itertools::Itertools;
4 |
5 | /// Comprehensive list of mimetypes
6 | pub static MIME_TYPES: LazyLock> = LazyLock::new(|| {
7 | // Mimes that are not in mime_db::TYPES
8 | [
9 | "inode/directory",
10 | "x-scheme-handler/http",
11 | "x-scheme-handler/https",
12 | "x-scheme-handler/terminal",
13 | ]
14 | .iter()
15 | .map(|s| s.to_string())
16 | .chain(
17 | mime_db::TYPES
18 | .into_iter()
19 | .map(|(mime, _, _)| mime.to_string()),
20 | )
21 | .collect_vec()
22 | });
23 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__dont_override_selector.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::config_file[0m[2m:[0m Selector enabled: false
6 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::config_file[0m[2m:[0m Selector enabled: false
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::config_file[0m[2m:[0m Selector enabled: true
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::config_file[0m[2m:[0m Selector enabled: true
9 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__override_selector.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::config_file[0m[2m:[0m Overriding selector command: fzf
6 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::config_file[0m[2m:[0m Selector enabled: true
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::config_file[0m[2m:[0m Overriding selector command: fuzzel --dmenu --prompt='Open With: '
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::config_file[0m[2m:[0m Selector enabled: false
9 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_detailed_json-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | {"added_associations":[{"mime":"x-scheme-handler/terminal","handlers":["org.wezfurlong.wezterm.desktop"]}],"default_apps":[{"mime":"application/vnd.oasis.opendocument.*","handlers":["startcenter.desktop"]},{"mime":"application/vnd.openxmlformats-officedocument.*","handlers":["startcenter.desktop"]},{"mime":"text/plain","handlers":["helix.desktop","nvim.desktop","kakoune.desktop"]},{"mime":"video/asdf","handlers":["mpv.desktop"]},{"mime":"video/mp4","handlers":["mpv.desktop"]},{"mime":"video/webm","handlers":["brave.desktop"]}],"system_apps":[]}
6 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_detailed_json.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | {"added_associations":[{"mime":"x-scheme-handler/terminal","handlers":["org.wezfurlong.wezterm.desktop"]}],"default_apps":[{"mime":"application/vnd.oasis.opendocument.*","handlers":["startcenter.desktop"]},{"mime":"application/vnd.openxmlformats-officedocument.*","handlers":["startcenter.desktop"]},{"mime":"text/plain","handlers":["helix.desktop","nvim.desktop","kakoune.desktop"]},{"mime":"video/asdf","handlers":["mpv.desktop"]},{"mime":"video/mp4","handlers":["mpv.desktop"]},{"mime":"video/webm","handlers":["brave.desktop"]}],"system_apps":[]}
6 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_piped.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | mime handlers
6 | application/vnd.oasis.opendocument.* startcenter.desktop
7 | application/vnd.openxmlformats-officedocument.* startcenter.desktop
8 | text/plain helix.desktop, nvim.desktop, kakoune.desktop
9 | video/asdf mpv.desktop
10 | video/mp4 mpv.desktop
11 | video/webm brave.desktop
12 |
--------------------------------------------------------------------------------
/src/common/snapshots/handlr__common__path__tests__mime_table_json-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/common/path.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | [{"path":"tests/assets","mime":"inode/directory"},{"path":"tests/assets/cat","mime":"application/x-shellscript"},{"path":"tests/assets/cmus.desktop","mime":"application/x-desktop"},{"path":"tests/assets/empty.txt","mime":"text/plain"},{"path":"tests/assets/no_html_tags.html","mime":"text/html"},{"path":"tests/assets/org.wezfurlong.wezterm.desktop","mime":"application/x-desktop"},{"path":"tests/assets/p.html","mime":"text/html"},{"path":"tests/assets/rust.vim","mime":"text/plain"},{"path":"tests/assets/SettingsWidgetFdoSecrets.ui","mime":"application/x-designer"},{"path":"https://duckduckgo.com/","mime":"x-scheme-handler/https"},{"path":".","mime":"inode/directory"},{"path":"README.md","mime":"text/markdown"}]
6 |
--------------------------------------------------------------------------------
/src/common/snapshots/handlr__common__path__tests__mime_table_json.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/common/path.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | [{"path":"tests/assets","mime":"inode/directory"},{"path":"tests/assets/cat","mime":"application/x-shellscript"},{"path":"tests/assets/cmus.desktop","mime":"application/x-desktop"},{"path":"tests/assets/empty.txt","mime":"text/plain"},{"path":"tests/assets/no_html_tags.html","mime":"text/html"},{"path":"tests/assets/org.wezfurlong.wezterm.desktop","mime":"application/x-desktop"},{"path":"tests/assets/p.html","mime":"text/html"},{"path":"tests/assets/rust.vim","mime":"text/plain"},{"path":"tests/assets/SettingsWidgetFdoSecrets.ui","mime":"application/x-designer"},{"path":"https://duckduckgo.com/","mime":"x-scheme-handler/https"},{"path":".","mime":"inode/directory"},{"path":"README.md","mime":"text/markdown"}]
6 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | publish:
10 | name: Publish for ${{ matrix.os }}
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | include:
15 | - os: ubuntu-latest
16 | asset_name: handlr
17 |
18 | steps:
19 | - uses: hecrj/setup-rust-action@v2
20 | with:
21 | rust-version: stable
22 |
23 | - uses: actions/checkout@v4
24 |
25 | - name: Test
26 | run: cargo test
27 |
28 | - name: Build
29 | run: cargo build --release --locked
30 |
31 | - name: Strip binary
32 | run: strip target/release/handlr
33 |
34 | - name: Upload binaries to release
35 | uses: svenstaro/upload-release-action@v2
36 | with:
37 | repo_token: ${{ secrets.GITHUB_TOKEN }}
38 | file: target/release/handlr
39 | asset_name: ${{ matrix.asset_name }}
40 | tag: ${{ github.ref }}
41 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__terminal_command_fallback.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m No handlers configured for `x-scheme-handler/terminal` in mimeapps.list Default associations
6 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No match for `x-scheme-handler/terminal` in mimeapps.list Default Associations
7 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching entries for `x-scheme-handler/terminal` in mimeapps.list Added Associations
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::system[0m[2m:[0m No installed handlers found for `x-scheme-handler/terminal`
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching installed handlers found for `x-scheme-handler/terminal`
10 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__language_variable_parsing.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG not set
6 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG not set
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG set: 'es'
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG not set
9 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG not set
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANGUAGE set: 'ja:fr:nl'
11 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG set: 'bn'
12 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANGUAGE set: 'hu:pa:it:ru'
13 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_detailed_piped.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | Default Apps
6 | mime handlers
7 | application/vnd.oasis.opendocument.* startcenter.desktop
8 | application/vnd.openxmlformats-officedocument.* startcenter.desktop
9 | text/plain helix.desktop, nvim.desktop, kakoune.desktop
10 | video/asdf mpv.desktop
11 | video/mp4 mpv.desktop
12 | video/webm brave.desktop
13 | Added associations
14 | mime handlers
15 | x-scheme-handler/terminal org.wezfurlong.wezterm.desktop
16 | System Apps
17 | mime handlers
18 |
--------------------------------------------------------------------------------
/tests/snapshots/terminal_override__terminal_output_tests_force_false.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: tests/terminal_override.rs
3 | info:
4 | program: handlr
5 | args:
6 | - "--force-terminal-output=false"
7 | - "-vvv"
8 | - "--disable-notifications"
9 | - mime
10 | - "./tests/assets"
11 | ---
12 | success: true
13 | exit_code: 0
14 | ----- stdout -----
15 | path mime
16 | ./tests/assets inode/directory
17 |
18 | ----- stderr -----
19 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG not set
20 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG not set
21 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr[0m[2m:[0m Interactive terminal detected: false
22 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::common::path[0m[2m:[0m Printing mime information for paths: ["./tests/assets"]
23 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::common::path[0m[2m:[0m JSON output: false
24 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::common::path[0m[2m:[0m Finished printing mime information
25 |
--------------------------------------------------------------------------------
/src/common/snapshots/handlr__common__path__tests__mime_table_piped.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/common/path.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | path mime
6 | tests/assets inode/directory
7 | tests/assets/cat application/x-shellscript
8 | tests/assets/cmus.desktop application/x-desktop
9 | tests/assets/empty.txt text/plain
10 | tests/assets/no_html_tags.html text/html
11 | tests/assets/org.wezfurlong.wezterm.desktop application/x-desktop
12 | tests/assets/p.html text/html
13 | tests/assets/rust.vim text/plain
14 | tests/assets/SettingsWidgetFdoSecrets.ui application/x-designer
15 | https://duckduckgo.com/ x-scheme-handler/https
16 | . inode/directory
17 | README.md text/markdown
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Gregory Petrosyan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/terminal_override.rs:
--------------------------------------------------------------------------------
1 | #[path = "../src/testing.rs"]
2 | mod testing;
3 |
4 | use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
5 | use std::process::Command;
6 |
7 | /// Helper function to test terminal output detection
8 | fn test_terminal_output(terminal_output: bool) -> Command {
9 | let mut cmd = Command::new(get_cargo_bin("handlr"));
10 | cmd.arg(format!("--force-terminal-output={}", terminal_output))
11 | .arg("-vvv") // Maximum verbosity
12 | .arg("--disable-notifications") // Not much point showing these in tests
13 | .arg("mime")
14 | .arg("./tests/assets")
15 | .env_clear(); // Ensure no environment variables compromise the tests
16 | cmd
17 | }
18 |
19 | #[test]
20 | fn terminal_output_tests_force_true() {
21 | insta::with_settings!(
22 | {
23 | filters => testing::timestamp_filter()
24 | },
25 | { assert_cmd_snapshot!(test_terminal_output(true)) }
26 | )
27 | }
28 |
29 | #[test]
30 | fn terminal_output_tests_force_false() {
31 | insta::with_settings!(
32 | {
33 | filters => testing::timestamp_filter()
34 | },
35 | { assert_cmd_snapshot!(test_terminal_output(false)) }
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/common/snapshots/handlr__common__table__tests__piped_output.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/common/table.rs
3 | expression: "render_table(&rows(LOREM_IPSUM), false)"
4 | ---
5 | col1 col2
6 | Lorem ipsum
7 | dolor sit
8 | amet, consectetur
9 | adipiscing elit,
10 | sed do
11 | eiusmod tempor
12 | incididunt ut
13 | labore et
14 | dolore magna
15 | aliqua. Ut
16 | enim ad
17 | minim veniam,
18 | quis nostrud
19 | exercitation ullamco
20 | laboris nisi
21 | ut aliquip
22 | ex ea
23 | commodo consequat.
24 | Duis aute
25 | irure dolor
26 | in reprehenderit
27 | in voluptate
28 | velit esse
29 | cillum dolore
30 | eu fugiat
31 | nulla pariatur.
32 | Excepteur sint
33 | occaecat cupidatat
34 | non proident,
35 | sunt in
36 | culpa qui
37 | officia deserunt
38 | mollit anim
39 | id est
40 |
--------------------------------------------------------------------------------
/tests/snapshots/terminal_override__terminal_output_tests_force_true.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: tests/terminal_override.rs
3 | info:
4 | program: handlr
5 | args:
6 | - "--force-terminal-output=true"
7 | - "-vvv"
8 | - "--disable-notifications"
9 | - mime
10 | - "./tests/assets"
11 | ---
12 | success: true
13 | exit_code: 0
14 | ----- stdout -----
15 | ┌────────────────┬─────────────────┐
16 | │[37m [39m[37mpath[39m[37m [39m[37m [39m│[37m [39m[37mmime[39m[37m [39m[37m [39m│
17 | ├────────────────┼─────────────────┤
18 | │[40m [49m[40m./tests/assets[49m[40m [49m│[40m [49m[40minode/directory[49m[40m [49m│
19 | └────────────────┴─────────────────┘
20 |
21 | ----- stderr -----
22 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG not set
23 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m $LANG not set
24 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr[0m[2m:[0m Interactive terminal detected: true
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::common::path[0m[2m:[0m Printing mime information for paths: ["./tests/assets"]
26 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::common::path[0m[2m:[0m JSON output: false
27 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::common::path[0m[2m:[0m Finished printing mime information
28 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__terminal_command_set.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding tests/assets/org.wezfurlong.wezterm.desktop to list of handlers for `x-scheme-handler/terminal`
6 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `x-scheme-handler/terminal`: tests/assets/org.wezfurlong.wezterm.desktop;
8 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
9 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `x-scheme-handler/terminal` in mimeapps.list Default Associations: tests/assets/org.wezfurlong.wezterm.desktop;
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
11 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
12 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `x-scheme-handler/terminal` in mimeapps.list Default Associations
13 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_default.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | ┌─────────────────────────────────────────────────┬─────────────────────┐
6 | │[37m [39m[37mmime[39m[37m [39m[37m [39m│[37m [39m[37mhandlers[39m[37m [39m[37m [39m│
7 | ├─────────────────────────────────────────────────┼─────────────────────┤
8 | │[40m [49m[40mapplication/vnd.oasis.opendocument.*[49m[40m [49m[40m [49m│[40m [49m[40mstartcenter.desktop[49m[40m [49m│
9 | │[37m [39m[37mapplication/vnd.openxmlformats-officedocument.*[39m[37m [39m│[37m [39m[37mstartcenter.desktop[39m[37m [39m│
10 | │[40m [49m[40mtext/plain[49m[40m [49m[40m [49m│[40m [49m[40mhelix.desktop,[49m[40m [49m [40m [49m│
11 | │[40m [49m│[40m [49m[40mnvim.desktop,[49m[40m [49m [40m [49m│
12 | │[40m [49m│[40m [49m[40mkakoune.desktop[49m[40m [49m[40m [49m│
13 | │[37m [39m[37mvideo/asdf[39m[37m [39m[37m [39m│[37m [39m[37mmpv.desktop[39m[37m [39m[37m [39m│
14 | │[40m [49m[40mvideo/mp4[49m[40m [49m[40m [49m│[40m [49m[40mmpv.desktop[49m[40m [49m[40m [49m│
15 | │[37m [39m[37mvideo/webm[39m[37m [39m[37m [39m│[37m [39m[37mbrave.desktop[39m[37m [39m[37m [39m│
16 | └─────────────────────────────────────────────────┴─────────────────────┘
17 |
--------------------------------------------------------------------------------
/tests/assets/mimeapps_sorted.list:
--------------------------------------------------------------------------------
1 | [Added Associations]
2 | video/vnd.youtube.yt=freetube.desktop;
3 | x-scheme-handler/terminal=org.wezfurlong.wezterm.desktop;org.codeberg.dnkl.foot.desktop;
4 | [Default Applications]
5 | application/msword=writer.desktop;
6 | application/pdf=org.pwmt.zathura-pdf-mupdf.desktop;
7 | application/vnd.ms-excel=calc.desktop;
8 | application/vnd.ms-powerpoint=impress.desktop;
9 | application/vnd.oasis.opendocument.database=base.desktop;
10 | application/vnd.oasis.opendocument.formula=math.desktop;
11 | application/vnd.oasis.opendocument.graphics=draw.desktop;
12 | application/vnd.oasis.opendocument.presentation=impress.desktop;
13 | application/vnd.oasis.opendocument.presentation-template=impress.desktop;
14 | application/vnd.oasis.opendocument.spreadsheet=calc.desktop;
15 | application/vnd.oasis.opendocument.spreadsheet-flat-xml=calc.desktop;
16 | application/vnd.oasis.opendocument.text=writer.desktop;
17 | application/vnd.oasis.opendocument.text-template=writer.desktop;
18 | application/vnd.openxmlformats-officedocument.presentationml.presentation=impress.desktop;
19 | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet=calc.desktop;
20 | application/vnd.openxmlformats-officedocument.wordprocessingml.document=writer.desktop;
21 | application/vnd.openxmlformats-officedocument.wordprocessingml.template=writer.desktop;
22 | application/x-yaml=Helix.desktop;
23 | audio/*=org.strawberrymusicplayer.strawberry.desktop;
24 | image/*=imv.desktop;
25 | inode/directory=yazi.desktop;
26 | text/*=Helix.desktop;nvim.desktop;
27 | text/html=firefox.desktop;Helix.desktop;nvim.desktop;
28 | video/*=mpv.desktop;
29 | x-scheme-handler/http=firefox.desktop;
30 | x-scheme-handler/https=firefox.desktop;
31 | x-scheme-handler/terminal=org.wezfurlong.wezterm.desktop;
32 |
--------------------------------------------------------------------------------
/tests/assets/mimeapps_anomalous_semicolons.list:
--------------------------------------------------------------------------------
1 | [Added Associations]
2 | video/vnd.youtube.yt=freetube.desktop
3 | x-scheme-handler/terminal=org.wezfurlong.wezterm.desktop;;org.codeberg.dnkl.foot.desktop
4 | [Default Applications]
5 | application/msword=writer.desktop
6 | application/pdf=org.pwmt.zathura-pdf-mupdf.desktop;
7 | application/vnd.ms-excel=calc.desktop
8 | application/vnd.ms-powerpoint=impress.desktop
9 | application/vnd.oasis.opendocument.database=base.desktop;
10 | application/vnd.oasis.opendocument.formula=math.desktop
11 | application/vnd.oasis.opendocument.graphics=draw.desktop;
12 | application/vnd.oasis.opendocument.presentation=impress.desktop
13 | application/vnd.oasis.opendocument.presentation-template=impress.desktop;
14 | application/vnd.oasis.opendocument.spreadsheet=calc.desktop
15 | application/vnd.oasis.opendocument.spreadsheet-flat-xml=calc.desktop;
16 | application/vnd.oasis.opendocument.text=writer.desktop
17 | application/vnd.oasis.opendocument.text-template=writer.desktop;
18 | application/vnd.openxmlformats-officedocument.presentationml.presentation=impress.desktop
19 | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet=calc.desktop;
20 | application/vnd.openxmlformats-officedocument.wordprocessingml.document=writer.desktop
21 | application/vnd.openxmlformats-officedocument.wordprocessingml.template=writer.desktop;
22 | application/x-yaml=Helix.desktop;
23 | audio/*=org.strawberrymusicplayer.strawberry.desktop;
24 | image/*=imv.desktop;
25 | inode/directory=yazi.desktop;
26 | text/*=Helix.desktop;;nvim.desktop;
27 | text/html=firefox.desktop;Helix.desktop;nvim.desktop;
28 | video/*=mpv.desktop;
29 | x-scheme-handler/http=firefox.desktop;
30 | x-scheme-handler/https=firefox.desktop
31 | x-scheme-handler/terminal=org.wezfurlong.wezterm.desktop;
32 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | naersk.url = "github:nix-community/naersk/master";
4 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
5 | utils.url = "github:numtide/flake-utils";
6 | };
7 |
8 | outputs =
9 | {
10 | self,
11 | nixpkgs,
12 | utils,
13 | naersk,
14 | }:
15 | utils.lib.eachDefaultSystem (
16 | system:
17 | let
18 | pkgs = import nixpkgs { inherit system; };
19 | naersk-lib = pkgs.callPackage naersk { };
20 | in
21 | {
22 | packages.default = naersk-lib.buildPackage {
23 | src = ./.;
24 | nativeBuildInputs = with pkgs; [
25 | installShellFiles
26 | shared-mime-info
27 | ];
28 | buildInputs = with pkgs; [ libiconv ];
29 |
30 | precheck = ''
31 | export HOME=$TEMPDIR
32 | '';
33 |
34 | postInstall = ''
35 | installShellCompletion --cmd handlr \
36 | --zsh <(COMPLETE=zsh $out/bin/handlr) \
37 | --bash <(COMPLETE=bash $out/bin/handlr) \
38 | --fish <(COMPLETE=fish $out/bin/handlr) \
39 |
40 | installManPage target/release/build/handlr-regex-*/out/manual/man1/*
41 | '';
42 | };
43 | devShell =
44 | with pkgs;
45 | mkShell {
46 | buildInputs = [
47 | cargo
48 | cargo-insta # Adds `cargo insta` command for reviewing snapshots
49 | cargo-mutants # Adds `cargo mutants` command for mutation testing
50 | clippy # Adds more checks to cargo
51 | pre-commit
52 | rust-analyzer # Rust language server
53 | rustc
54 | rustfmt
55 | ];
56 | RUST_SRC_PATH = rustPlatform.rustLibSrc;
57 | };
58 | }
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "handlr-regex"
3 | version = "0.13.0"
4 | authors = ["Duncan Russell ", "Gregory "]
5 | edition = "2021"
6 | license = "MIT"
7 | description = "Fork of handlr with regex support"
8 | repository = "https://github.com/Anomalocaridid/handlr-regex"
9 |
10 | [dependencies]
11 | clap = { version = "4.5.2", features = ["derive"] }
12 | url = "2.2.1"
13 | itertools = "0.10.0"
14 | thiserror = "1.0.24"
15 | xdg = "2.2.0"
16 | mime = "0.3.16"
17 | mime-db = "1.3.0"
18 | confy = "0.4.0"
19 | serde = { version = "1.0.125", features = ["derive"] }
20 | xdg-mime = "0.4.0"
21 | tabled = "0.15.0"
22 | serde_json = "1.0"
23 | enum_dispatch = "0.3.13"
24 | derive_more = { version = "0.99.18", default-features = false, features = ["deref", "deref_mut", "display"] }
25 | serde_ini = "0.2.0"
26 | serde_with = "3.8.3"
27 | wildmatch = "2.3.4"
28 | mutants = "0.0.3"
29 | clap_complete = { version = "4.5.33", features = ["unstable-dynamic"] }
30 | tracing = "0.1.41"
31 | tracing-subscriber = "0.3.19"
32 | tracing-appender = "0.2.3"
33 | tracing-unwrap = "1.0.1"
34 | clap-verbosity-flag = { version = "3.0.3", default-features = false, features = ["tracing"] }
35 | notify-rust = "4.11.7"
36 | execute = "0.2.13"
37 | lazy-regex = { version = "3.4.1", default-features = false, features = ["lite"] }
38 | freedesktop_entry_parser = "1.3.0"
39 |
40 | [[bin]]
41 | name = "handlr"
42 | path = "src/main.rs"
43 |
44 | [dev-dependencies]
45 | insta = { version = "1.42.2", features = ["filters"] }
46 | insta-cmd = "0.6.0"
47 | pipe = "0.4.0"
48 | similar-asserts = "1.7.0"
49 | temp-env = "0.3.6"
50 |
51 | [build-dependencies]
52 | clap = { version = "4.5.2", features = ["derive"] }
53 | clap_complete = { version = "4.5.33", features = ["unstable-dynamic"] }
54 | clap_mangen = "0.2.20"
55 | clap-verbosity-flag = { version = "3.0.3", default-features = false, features = ["tracing"] }
56 |
57 | [profile.release]
58 | opt-level = "z"
59 | lto = true
60 | strip = true
61 | codegen-units = 1
62 | panic = "abort"
63 |
64 | [profile.dev.package]
65 | insta.opt-level = 3
66 |
--------------------------------------------------------------------------------
/src/testing.rs:
--------------------------------------------------------------------------------
1 | //! Testing helpers
2 | #![cfg(test)]
3 |
4 | /// Helper function for insta settings
5 | pub fn timestamp_filter() -> Vec<(&'static str, &'static str)> {
6 | vec![(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d*\.\d*Z", "[TIMESTAMP]")]
7 | }
8 |
9 | /// Helper macro to snapshot test logs
10 | #[macro_export]
11 | macro_rules! logs_snapshot_test {
12 | ($name:ident, $block:block) => {
13 | #[test]
14 | fn $name() -> $crate::error::Result<()> {
15 | use std::io::Read;
16 |
17 | use tracing::{subscriber, Level};
18 | use tracing_subscriber::{
19 | filter, fmt, layer::SubscriberExt, registry,
20 | };
21 |
22 | let (mut pipe_read, pipe_write) = pipe::pipe();
23 |
24 | // Create scope so guards are dropped before assertion and logs are flushed
25 | // Otherwise, the tests using this macro will freeze
26 | {
27 | let (writer, _writer_guard) =
28 | tracing_appender::non_blocking(pipe_write);
29 | let _default_guard = subscriber::set_default(
30 | registry()
31 | .with(fmt::Layer::new().with_writer(writer))
32 | // Filter out all logs from other crates so the user is not overwhelmed looking at the logs
33 | .with(
34 | filter::Targets::new()
35 | .with_target("handlr", Level::TRACE)
36 | .with_target("tracing_unwrap", Level::WARN),
37 | ),
38 | );
39 | $block
40 | }
41 |
42 | let mut buffer = Vec::::new();
43 | pipe_read.read_to_end(&mut buffer)?;
44 |
45 | insta::with_settings!(
46 | {
47 | filters => $crate::testing::timestamp_filter()
48 | },
49 | { insta::assert_snapshot!(String::from_utf8(buffer).expect("Buffer is invalid utf8")) }
50 | );
51 |
52 | Ok(())
53 | }
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__show_handler-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding tests/assets/Helix.desktop to list of handlers for `text/plain`
6 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: tests/assets/Helix.desktop;
8 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding tests/assets/org.wezfurlong.wezterm.desktop to list of handlers for `x-scheme-handler/terminal`
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
11 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `x-scheme-handler/terminal`: tests/assets/org.wezfurlong.wezterm.desktop;
12 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
13 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Showing handler for `text/plain`
14 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: false
15 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: tests/assets/Helix.desktop;
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
17 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished showing handler
20 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__show_handler_terminal-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding tests/assets/Helix.desktop to list of handlers for `text/plain`
6 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: tests/assets/Helix.desktop;
8 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding tests/assets/org.wezfurlong.wezterm.desktop to list of handlers for `x-scheme-handler/terminal`
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
11 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `x-scheme-handler/terminal`: tests/assets/org.wezfurlong.wezterm.desktop;
12 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
13 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Showing handler for `text/plain`
14 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: false
15 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: tests/assets/Helix.desktop;
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
17 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished showing handler
20 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__show_handler_json_terminal-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding tests/assets/Helix.desktop to list of handlers for `text/plain`
6 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: tests/assets/Helix.desktop;
8 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding tests/assets/org.wezfurlong.wezterm.desktop to list of handlers for `x-scheme-handler/terminal`
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
11 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `x-scheme-handler/terminal`: tests/assets/org.wezfurlong.wezterm.desktop;
12 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
13 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Showing handler for `text/plain`
14 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: true
15 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: tests/assets/Helix.desktop;
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
17 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished showing handler
20 |
--------------------------------------------------------------------------------
/src/common/table.rs:
--------------------------------------------------------------------------------
1 | use tabled::{
2 | settings::{themes::Colorization, Alignment, Color, Padding, Style},
3 | Table, Tabled,
4 | };
5 |
6 | /// Render a table from a vector of instances of Tabled structs
7 | pub fn render_table(rows: &Vec, terminal_output: bool) -> String {
8 | let mut table = Table::new(rows);
9 |
10 | if terminal_output {
11 | // If output is going to a terminal, print as a table
12 | table
13 | .with(Style::sharp())
14 | .with(Colorization::rows([Color::FG_WHITE, Color::BG_BLACK]))
15 | } else {
16 | // If output is being piped, print as tab-delimited text
17 | table
18 | .with(Style::empty().vertical('\t'))
19 | .with(Alignment::left())
20 | .with(Padding::zero())
21 | }
22 | .to_string()
23 | }
24 |
25 | #[cfg(test)]
26 | mod tests {
27 | use super::*;
28 | use crate::error::Result;
29 | use itertools::Itertools;
30 |
31 | #[derive(Tabled)]
32 | struct TestRow<'a> {
33 | col1: &'a str,
34 | col2: &'a str,
35 | }
36 |
37 | // Arbitrary sample text
38 | const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
39 |
40 | // Helper function to create test data
41 | fn rows(test_text: &str) -> Vec> {
42 | test_text
43 | .split(' ')
44 | .collect_vec()
45 | .chunks_exact(2)
46 | .map(|chunk| TestRow {
47 | col1: chunk[0],
48 | col2: chunk[1],
49 | })
50 | .collect_vec()
51 | }
52 |
53 | #[test]
54 | fn terminal_output() -> Result<()> {
55 | insta::assert_snapshot!(render_table(&rows(LOREM_IPSUM), true));
56 | Ok(())
57 | }
58 |
59 | #[test]
60 | fn piped_output() -> Result<()> {
61 | insta::assert_snapshot!(render_table(&rows(LOREM_IPSUM), false));
62 | Ok(())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_detailed.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | Default Apps
6 | ┌─────────────────────────────────────────────────┬─────────────────────┐
7 | │[37m [39m[37mmime[39m[37m [39m[37m [39m│[37m [39m[37mhandlers[39m[37m [39m[37m [39m│
8 | ├─────────────────────────────────────────────────┼─────────────────────┤
9 | │[40m [49m[40mapplication/vnd.oasis.opendocument.*[49m[40m [49m[40m [49m│[40m [49m[40mstartcenter.desktop[49m[40m [49m│
10 | │[37m [39m[37mapplication/vnd.openxmlformats-officedocument.*[39m[37m [39m│[37m [39m[37mstartcenter.desktop[39m[37m [39m│
11 | │[40m [49m[40mtext/plain[49m[40m [49m[40m [49m│[40m [49m[40mhelix.desktop,[49m[40m [49m [40m [49m│
12 | │[40m [49m│[40m [49m[40mnvim.desktop,[49m[40m [49m [40m [49m│
13 | │[40m [49m│[40m [49m[40mkakoune.desktop[49m[40m [49m[40m [49m│
14 | │[37m [39m[37mvideo/asdf[39m[37m [39m[37m [39m│[37m [39m[37mmpv.desktop[39m[37m [39m[37m [39m│
15 | │[40m [49m[40mvideo/mp4[49m[40m [49m[40m [49m│[40m [49m[40mmpv.desktop[49m[40m [49m[40m [49m│
16 | │[37m [39m[37mvideo/webm[39m[37m [39m[37m [39m│[37m [39m[37mbrave.desktop[39m[37m [39m[37m [39m│
17 | └─────────────────────────────────────────────────┴─────────────────────┘
18 | Added associations
19 | ┌───────────────────────────┬────────────────────────────────┐
20 | │[37m [39m[37mmime[39m[37m [39m[37m [39m│[37m [39m[37mhandlers[39m[37m [39m[37m [39m│
21 | ├───────────────────────────┼────────────────────────────────┤
22 | │[40m [49m[40mx-scheme-handler/terminal[49m[40m [49m│[40m [49m[40morg.wezfurlong.wezterm.desktop[49m[40m [49m│
23 | └───────────────────────────┴────────────────────────────────┘
24 | System Apps
25 | ┌──────┬──────────┐
26 | │[37m [39m[37mmime[39m[37m [39m│[37m [39m[37mhandlers[39m[37m [39m│
27 | ├──────┼──────────┤
28 |
--------------------------------------------------------------------------------
/src/apps/snapshots/handlr__apps__user__tests__remove_handlers_expand_wildcards.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/apps/user.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | [Default Applications]
6 | text/*=nvim.desktop;
7 | text/cache-manifest=nvim.desktop;
8 | text/calendar=nvim.desktop;
9 | text/coffeescript=nvim.desktop;
10 | text/css=nvim.desktop;
11 | text/csv=nvim.desktop;
12 | text/html=nvim.desktop;
13 | text/jade=nvim.desktop;
14 | text/javascript=nvim.desktop;
15 | text/jsx=nvim.desktop;
16 | text/less=nvim.desktop;
17 | text/markdown=nvim.desktop;
18 | text/mathml=nvim.desktop;
19 | text/mdx=nvim.desktop;
20 | text/n3=nvim.desktop;
21 | text/plain=nvim.desktop;
22 | text/prs.lines.tag=nvim.desktop;
23 | text/richtext=nvim.desktop;
24 | text/rtf=nvim.desktop;
25 | text/sgml=nvim.desktop;
26 | text/shex=nvim.desktop;
27 | text/slim=nvim.desktop;
28 | text/spdx=nvim.desktop;
29 | text/stylus=nvim.desktop;
30 | text/tab-separated-values=nvim.desktop;
31 | text/troff=nvim.desktop;
32 | text/turtle=nvim.desktop;
33 | text/uri-list=nvim.desktop;
34 | text/vcard=nvim.desktop;
35 | text/vnd.curl=nvim.desktop;
36 | text/vnd.curl.dcurl=nvim.desktop;
37 | text/vnd.curl.mcurl=nvim.desktop;
38 | text/vnd.curl.scurl=nvim.desktop;
39 | text/vnd.dvb.subtitle=nvim.desktop;
40 | text/vnd.familysearch.gedcom=nvim.desktop;
41 | text/vnd.fly=nvim.desktop;
42 | text/vnd.fmi.flexstor=nvim.desktop;
43 | text/vnd.graphviz=nvim.desktop;
44 | text/vnd.in3d.3dml=nvim.desktop;
45 | text/vnd.in3d.spot=nvim.desktop;
46 | text/vnd.sun.j2me.app-descriptor=nvim.desktop;
47 | text/vnd.wap.wml=nvim.desktop;
48 | text/vnd.wap.wmlscript=nvim.desktop;
49 | text/vtt=nvim.desktop;
50 | text/wgsl=nvim.desktop;
51 | text/x-asm=nvim.desktop;
52 | text/x-c=nvim.desktop;
53 | text/x-component=nvim.desktop;
54 | text/x-fortran=nvim.desktop;
55 | text/x-handlebars-template=nvim.desktop;
56 | text/x-java-source=nvim.desktop;
57 | text/x-lua=nvim.desktop;
58 | text/x-markdown=nvim.desktop;
59 | text/x-nfo=nvim.desktop;
60 | text/x-opml=nvim.desktop;
61 | text/x-org=nvim.desktop;
62 | text/x-pascal=nvim.desktop;
63 | text/x-processing=nvim.desktop;
64 | text/x-sass=nvim.desktop;
65 | text/x-scss=nvim.desktop;
66 | text/x-setext=nvim.desktop;
67 | text/x-sfv=nvim.desktop;
68 | text/x-suse-ymp=nvim.desktop;
69 | text/x-uuencode=nvim.desktop;
70 | text/x-vcalendar=nvim.desktop;
71 | text/x-vcard=nvim.desktop;
72 | text/xml=nvim.desktop;
73 | text/yaml=nvim.desktop;
74 |
--------------------------------------------------------------------------------
/src/common/snapshots/handlr__common__path__tests__mime_table_terminal.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/common/path.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | ┌─────────────────────────────────────────────┬───────────────────────────┐
6 | │[37m [39m[37mpath[39m[37m [39m[37m [39m│[37m [39m[37mmime[39m[37m [39m[37m [39m│
7 | ├─────────────────────────────────────────────┼───────────────────────────┤
8 | │[40m [49m[40mtests/assets[49m[40m [49m[40m [49m│[40m [49m[40minode/directory[49m[40m [49m[40m [49m│
9 | │[37m [39m[37mtests/assets/cat[39m[37m [39m[37m [39m│[37m [39m[37mapplication/x-shellscript[39m[37m [39m│
10 | │[40m [49m[40mtests/assets/cmus.desktop[49m[40m [49m[40m [49m│[40m [49m[40mapplication/x-desktop[49m[40m [49m[40m [49m│
11 | │[37m [39m[37mtests/assets/empty.txt[39m[37m [39m[37m [39m│[37m [39m[37mtext/plain[39m[37m [39m[37m [39m│
12 | │[40m [49m[40mtests/assets/no_html_tags.html[49m[40m [49m[40m [49m│[40m [49m[40mtext/html[49m[40m [49m[40m [49m│
13 | │[37m [39m[37mtests/assets/org.wezfurlong.wezterm.desktop[39m[37m [39m│[37m [39m[37mapplication/x-desktop[39m[37m [39m[37m [39m│
14 | │[40m [49m[40mtests/assets/p.html[49m[40m [49m[40m [49m│[40m [49m[40mtext/html[49m[40m [49m[40m [49m│
15 | │[37m [39m[37mtests/assets/rust.vim[39m[37m [39m[37m [39m│[37m [39m[37mtext/plain[39m[37m [39m[37m [39m│
16 | │[40m [49m[40mtests/assets/SettingsWidgetFdoSecrets.ui[49m[40m [49m[40m [49m│[40m [49m[40mapplication/x-designer[49m[40m [49m[40m [49m│
17 | │[37m [39m[37mhttps://duckduckgo.com/[39m[37m [39m[37m [39m│[37m [39m[37mx-scheme-handler/https[39m[37m [39m[37m [39m│
18 | │[40m [49m[40m.[49m[40m [49m[40m [49m│[40m [49m[40minode/directory[49m[40m [49m[40m [49m│
19 | │[37m [39m[37mREADME.md[39m[37m [39m[37m [39m│[37m [39m[37mtext/markdown[39m[37m [39m[37m [39m│
20 | └─────────────────────────────────────────────┴───────────────────────────┘
21 |
--------------------------------------------------------------------------------
/src/apps/snapshots/handlr__apps__user__tests__unset_handlers_expand_wildcards.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/apps/user.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | [Default Applications]
6 | text/cache-manifest=Helix.desktop;
7 | text/calendar=Helix.desktop;
8 | text/coffeescript=Helix.desktop;
9 | text/css=Helix.desktop;
10 | text/csv=Helix.desktop;
11 | text/html=Helix.desktop;
12 | text/jade=Helix.desktop;
13 | text/javascript=Helix.desktop;
14 | text/jsx=Helix.desktop;
15 | text/less=Helix.desktop;
16 | text/markdown=Helix.desktop;
17 | text/mathml=Helix.desktop;
18 | text/mdx=Helix.desktop;
19 | text/n3=Helix.desktop;
20 | text/plain=Helix.desktop;
21 | text/prs.lines.tag=Helix.desktop;
22 | text/richtext=Helix.desktop;
23 | text/rtf=Helix.desktop;
24 | text/sgml=Helix.desktop;
25 | text/shex=Helix.desktop;
26 | text/slim=Helix.desktop;
27 | text/spdx=Helix.desktop;
28 | text/stylus=Helix.desktop;
29 | text/tab-separated-values=Helix.desktop;
30 | text/troff=Helix.desktop;
31 | text/turtle=Helix.desktop;
32 | text/uri-list=Helix.desktop;
33 | text/vcard=Helix.desktop;
34 | text/vnd.curl=Helix.desktop;
35 | text/vnd.curl.dcurl=Helix.desktop;
36 | text/vnd.curl.mcurl=Helix.desktop;
37 | text/vnd.curl.scurl=Helix.desktop;
38 | text/vnd.dvb.subtitle=Helix.desktop;
39 | text/vnd.familysearch.gedcom=Helix.desktop;
40 | text/vnd.fly=Helix.desktop;
41 | text/vnd.fmi.flexstor=Helix.desktop;
42 | text/vnd.graphviz=Helix.desktop;
43 | text/vnd.in3d.3dml=Helix.desktop;
44 | text/vnd.in3d.spot=Helix.desktop;
45 | text/vnd.sun.j2me.app-descriptor=Helix.desktop;
46 | text/vnd.wap.wml=Helix.desktop;
47 | text/vnd.wap.wmlscript=Helix.desktop;
48 | text/vtt=Helix.desktop;
49 | text/wgsl=Helix.desktop;
50 | text/x-asm=Helix.desktop;
51 | text/x-c=Helix.desktop;
52 | text/x-component=Helix.desktop;
53 | text/x-fortran=Helix.desktop;
54 | text/x-handlebars-template=Helix.desktop;
55 | text/x-java-source=Helix.desktop;
56 | text/x-lua=Helix.desktop;
57 | text/x-markdown=Helix.desktop;
58 | text/x-nfo=Helix.desktop;
59 | text/x-opml=Helix.desktop;
60 | text/x-org=Helix.desktop;
61 | text/x-pascal=Helix.desktop;
62 | text/x-processing=Helix.desktop;
63 | text/x-sass=Helix.desktop;
64 | text/x-scss=Helix.desktop;
65 | text/x-setext=Helix.desktop;
66 | text/x-sfv=Helix.desktop;
67 | text/x-suse-ymp=Helix.desktop;
68 | text/x-uuencode=Helix.desktop;
69 | text/x-vcalendar=Helix.desktop;
70 | text/x-vcard=Helix.desktop;
71 | text/xml=Helix.desktop;
72 | text/yaml=Helix.desktop;
73 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | mod apps;
2 | mod cli;
3 | mod common;
4 | mod config;
5 | mod error;
6 | mod logging;
7 | mod testing;
8 |
9 | use std::process::ExitCode;
10 |
11 | use cli::{Cli, Cmd};
12 | use common::mime_table;
13 | use config::Config;
14 | use error::Result;
15 | use logging::init_tracing;
16 |
17 | use clap::{CommandFactory, Parser};
18 | use clap_complete::CompleteEnv;
19 | use tracing::debug;
20 |
21 | #[mutants::skip] // Cannot test directly at the moment
22 | fn main() -> ExitCode {
23 | // Shell completions
24 | CompleteEnv::with_factory(|| Cli::command().name("handlr"))
25 | .completer("handlr")
26 | .complete();
27 |
28 | let cli = Cli::parse();
29 |
30 | let _guard = init_tracing(&cli)
31 | .expect("handlr error: Could not initialize global tracing subscriber");
32 |
33 | error::handle(run(cli))
34 | }
35 |
36 | /// Run main program logic
37 | #[mutants::skip] // Cannot test directly at the moment
38 | fn run(cli: Cli) -> Result<()> {
39 | let mut config = Config::new(cli.terminal_output())?;
40 |
41 | let mut stdout = std::io::stdout().lock();
42 |
43 | debug!("Interactive terminal detected: {}", config.terminal_output);
44 |
45 | match cli.command {
46 | Cmd::Set { mime, handler } => config.set_handler(&mime, &handler),
47 | Cmd::Add { mime, handler } => config.add_handler(&mime, &handler),
48 | Cmd::Launch {
49 | mime,
50 | args,
51 | selector_args,
52 | } => {
53 | config.override_selector(selector_args);
54 | config.launch_handler(&mime, args)
55 | }
56 | Cmd::Get {
57 | mime,
58 | json,
59 | selector_args,
60 | } => {
61 | config.override_selector(selector_args);
62 | config.show_handler(&mut stdout, &mime, json)
63 | }
64 | Cmd::Open {
65 | paths,
66 | selector_args,
67 | } => {
68 | config.override_selector(selector_args);
69 | config.open_paths(&paths)
70 | }
71 | Cmd::Mime { paths, json } => {
72 | mime_table(&mut stdout, &paths, json, config.terminal_output)
73 | }
74 | Cmd::List { all, json } => config.print(&mut stdout, all, json),
75 | Cmd::Unset { mime } => config.unset_handler(&mime),
76 | Cmd::Remove { mime, handler } => config.remove_handler(&mime, &handler),
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/config/config_file.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | cli::SelectorArgs,
3 | common::{RegexApps, RegexHandler, UserPath},
4 | error::Result,
5 | };
6 | use serde::{Deserialize, Serialize};
7 | use tracing::debug;
8 |
9 | /// The config file
10 | #[derive(Debug, Serialize, Deserialize)]
11 | #[serde(default)]
12 | pub struct ConfigFile {
13 | /// Whether to enable the selector when multiple handlers are set
14 | pub enable_selector: bool,
15 | /// The selector command to run
16 | pub selector: String,
17 | /// Extra arguments to pass to terminal application
18 | pub term_exec_args: Option,
19 | /// Whether to expand wildcards when saving mimeapps.list
20 | pub expand_wildcards: bool,
21 | /// Regex handlers
22 | // NOTE: Serializing is only necessary for generating a default config file
23 | #[serde(skip_serializing)]
24 | pub handlers: RegexApps,
25 | }
26 |
27 | impl Default for ConfigFile {
28 | fn default() -> Self {
29 | ConfigFile {
30 | enable_selector: false,
31 | selector: "rofi -dmenu -i -p 'Open With: '".into(),
32 | // Required for many xterm-compatible terminal emulators
33 | // Unfortunately, messes up emulators that don't accept it
34 | term_exec_args: Some("-e".into()),
35 | expand_wildcards: false,
36 | handlers: Default::default(),
37 | }
38 | }
39 | }
40 |
41 | impl ConfigFile {
42 | /// Get the handler associated with a given mime from the config file's regex handlers
43 | pub fn get_regex_handler(&self, path: &UserPath) -> Result {
44 | self.handlers.get_handler(path)
45 | }
46 |
47 | /// Load ~/.config/handlr/handlr.toml
48 | #[mutants::skip] // Cannot test directly, depends on system state
49 | pub fn load() -> Result {
50 | Ok(confy::load("handlr")?)
51 | }
52 |
53 | /// Override the set selector
54 | /// Currently assumes the config file will never be saved to
55 | pub fn override_selector(&mut self, selector_args: SelectorArgs) {
56 | if let Some(selector) = selector_args.selector {
57 | debug!("Overriding selector command: {}", selector);
58 | self.selector = selector;
59 | }
60 |
61 | self.enable_selector = selector_args
62 | .enable_selector
63 | .unwrap_or(self.enable_selector);
64 |
65 | debug!("Selector enabled: {}", self.enable_selector);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 | use std::{path::PathBuf, process::ExitCode};
2 |
3 | use tracing::{error, info};
4 |
5 | /// Custom error type
6 | #[derive(Debug, thiserror::Error)]
7 | pub enum Error {
8 | #[error(transparent)]
9 | Io(#[from] std::io::Error),
10 | #[error(transparent)]
11 | Xdg(#[from] xdg::BaseDirectoriesError),
12 | #[error(transparent)]
13 | Config(#[from] confy::ConfyError),
14 | #[error("No handlers found for '{0}'")]
15 | NotFound(String),
16 | #[error(
17 | "Could not find a mimetype associated with the file extension: '{0}'"
18 | )]
19 | AmbiguousExtension(String),
20 | #[error(transparent)]
21 | BadMimeType(#[from] mime::FromStrError),
22 | #[error("Bad mime: {0}")]
23 | InvalidMime(mime::Mime),
24 | #[error("The desktop entry at '{0}' lacks a valid '{1}' field")]
25 | BadEntry(PathBuf, String),
26 | #[error("Error spawning selector process '{0}'")]
27 | Selector(String),
28 | #[error("Selection cancelled")]
29 | Cancelled,
30 | #[error("Please specify the default terminal with handlr set x-scheme-handler/terminal")]
31 | NoTerminal,
32 | #[error("Bad path: {0}")]
33 | BadPath(String),
34 | #[error(transparent)]
35 | SerdeJson(#[from] serde_json::Error),
36 | #[error(transparent)]
37 | SerdeIniDe(#[from] serde_ini::de::Error),
38 | #[error(transparent)]
39 | SerdeIniSer(#[from] serde_ini::ser::Error),
40 | #[error(transparent)]
41 | TracingGlobalDefault(#[from] tracing::dispatcher::SetGlobalDefaultError),
42 | #[error("Could not find file at path: '{0}'")]
43 | NonexistentFile(String),
44 | #[cfg(test)]
45 | #[error(transparent)]
46 | BadUrl(#[from] url::ParseError),
47 | #[cfg(test)]
48 | #[error(transparent)]
49 | FromUtf8(#[from] std::string::FromUtf8Error),
50 | }
51 |
52 | pub type Result = std::result::Result;
53 |
54 | #[mutants::skip] // Cannot completely test, relies on user input
55 | pub fn handle(result: Result<()>) -> ExitCode {
56 | if let Err(error) = result {
57 | match error {
58 | // Cancelling the selector is an acceptable outcome
59 | Error::Cancelled => {
60 | info!("{}", error);
61 | ExitCode::SUCCESS
62 | }
63 | _ => {
64 | error!("{}", error);
65 | ExitCode::FAILURE
66 | }
67 | }
68 | } else {
69 | ExitCode::SUCCESS
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__show_handler_json-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding tests/assets/Helix.desktop to list of handlers for `text/plain`
6 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: tests/assets/Helix.desktop;
8 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding tests/assets/org.wezfurlong.wezterm.desktop to list of handlers for `x-scheme-handler/terminal`
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
11 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `x-scheme-handler/terminal`: tests/assets/org.wezfurlong.wezterm.desktop;
12 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
13 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Showing handler for `text/plain`
14 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: true
15 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: tests/assets/Helix.desktop;
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
17 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
19 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `x-scheme-handler/terminal` in mimeapps.list Default Associations: tests/assets/org.wezfurlong.wezterm.desktop;
20 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
21 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
22 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `x-scheme-handler/terminal` in mimeapps.list Default Associations
23 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished showing handler
24 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "naersk": {
4 | "inputs": {
5 | "nixpkgs": "nixpkgs"
6 | },
7 | "locked": {
8 | "lastModified": 1745925850,
9 | "narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
10 | "owner": "nix-community",
11 | "repo": "naersk",
12 | "rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "nix-community",
17 | "ref": "master",
18 | "repo": "naersk",
19 | "type": "github"
20 | }
21 | },
22 | "nixpkgs": {
23 | "locked": {
24 | "lastModified": 1747426788,
25 | "narHash": "sha256-N4cp0asTsJCnRMFZ/k19V9akkxb7J/opG+K+jU57JGc=",
26 | "owner": "NixOS",
27 | "repo": "nixpkgs",
28 | "rev": "12a55407652e04dcf2309436eb06fef0d3713ef3",
29 | "type": "github"
30 | },
31 | "original": {
32 | "owner": "NixOS",
33 | "ref": "nixpkgs-unstable",
34 | "repo": "nixpkgs",
35 | "type": "github"
36 | }
37 | },
38 | "nixpkgs_2": {
39 | "locked": {
40 | "lastModified": 1747426788,
41 | "narHash": "sha256-N4cp0asTsJCnRMFZ/k19V9akkxb7J/opG+K+jU57JGc=",
42 | "owner": "NixOS",
43 | "repo": "nixpkgs",
44 | "rev": "12a55407652e04dcf2309436eb06fef0d3713ef3",
45 | "type": "github"
46 | },
47 | "original": {
48 | "owner": "NixOS",
49 | "ref": "nixpkgs-unstable",
50 | "repo": "nixpkgs",
51 | "type": "github"
52 | }
53 | },
54 | "root": {
55 | "inputs": {
56 | "naersk": "naersk",
57 | "nixpkgs": "nixpkgs_2",
58 | "utils": "utils"
59 | }
60 | },
61 | "systems": {
62 | "locked": {
63 | "lastModified": 1681028828,
64 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
65 | "owner": "nix-systems",
66 | "repo": "default",
67 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
68 | "type": "github"
69 | },
70 | "original": {
71 | "owner": "nix-systems",
72 | "repo": "default",
73 | "type": "github"
74 | }
75 | },
76 | "utils": {
77 | "inputs": {
78 | "systems": "systems"
79 | },
80 | "locked": {
81 | "lastModified": 1731533236,
82 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
83 | "owner": "numtide",
84 | "repo": "flake-utils",
85 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
86 | "type": "github"
87 | },
88 | "original": {
89 | "owner": "numtide",
90 | "repo": "flake-utils",
91 | "type": "github"
92 | }
93 | }
94 | },
95 | "root": "root",
96 | "version": 7
97 | }
98 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__wildcard_mimes.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/*`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/*`: mpv.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding brave.desktop to list of handlers for `video/webm`
11 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `brave.desktop` is invalid: No such file or directory (os error 2)
12 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
13 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/webm`: brave.desktop;
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
15 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `video/mp4` in mimeapps.list Default Associations: mpv.desktop;
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
17 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `video/mp4` in mimeapps.list Default Associations
19 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `video/asdf` in mimeapps.list Default Associations: mpv.desktop;
20 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
21 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
22 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `video/asdf` in mimeapps.list Default Associations
23 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `video/webm` in mimeapps.list Default Associations: brave.desktop;
24 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
26 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `video/webm` in mimeapps.list Default Associations
27 |
--------------------------------------------------------------------------------
/tests/assets/vlc.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Version=1.0
3 | Name=VLC media player
4 | Comment=Read, capture, broadcast your multimedia streams
5 | Name[bn]=VLC মিডিয়া প্লেয়ার
6 | Comment[bn]=আপনার মাল্টিমিডিয়া স্ট্রীম পড়ুন, ধরে রাখুন এবং ছড়িয়ে দিন
7 | Name[ca]=Reproductor multimèdia VLC
8 | Comment[ca]=Reproduïu, captureu i difoneu fluxos multimèdia
9 | Name[de]=VLC Media Player
10 | Comment[de]=Wiedergabe, Aufnahme und Verbreitung Ihrer Multimedia-Streams
11 | Name[es]=Reproductor multimedia VLC
12 | Comment[es]=Lea, capture y emita sus contenidos multimedia
13 | Name[et]=VLC meediaesitaja
14 | Comment[et]=Multimeediafailide taasesitamine, lindistamine ja edastamine
15 | Name[fi]=VLC-mediasoitin
16 | Comment[fi]=Toista, tallenna ja lähetä multimediaa
17 | Name[fr]=Lecteur multimédia VLC
18 | Comment[fr]=Lire, capturer, diffuser vos flux multimedia
19 | Name[gl]=Reprodutor multimedia VLC
20 | Comment[gl]=Lea, capture e emita os seus fluxos multimedia
21 | Name[hu]=VLC médialejátszó
22 | Comment[hu]=Multimédiás adatfolyamok olvasása, mentése, szórása
23 | Name[it]=Lettore multimediale VLC
24 | Comment[it]=Legge, acquisisce e trasmette i tuoi flussi multimediali
25 | Name[km]=កម្មវិធីចាក់មេឌៀ VLC
26 | Comment[km]=អាន ចាប់យក ប្រកាសស្ទ្រីមពហុមេឌៀរបស់អ្នក
27 | Name[nl]=VLC Mediaspeler
28 | Comment[nl]=Uw multimediastreams afspelen, opnemen en uitzenden
29 | Name[nn]=VLC mediespelar
30 | Comment[nn]=Spel av, ta opp og send ut multimedia
31 | Name[pa]=VLC ਮੀਡਿਆ ਪਲੇਅਰ
32 | Comment[pa]=ਆਪਣੀ ਮਲਟੀਮੀਡਿਆ ਸਟਰੀਮ ਪੜ੍ਹੋ, ਕੈਪਚਰ ਤੇ ਬਰਾਡਕਾਸਟ ਕਰੋ
33 | Name[pt_BR]=Reprodutor de Mídias VLC
34 | Comment[pt_BR]=Reproduza, capture e transmita os seus fluxos multimídia
35 | Name[sv]=Mediaspelaren VLC
36 | Comment[sv]=Allmän uppspelare av film och musik
37 | Name[sk]=VLC media player
38 | Comment[sk]=Naèítavajte, zaznamenávajte, vysielajte svoje multimediálne streamy
39 | Name[ru]=Медиаплеер VLC
40 | Comment[ru]=Универсальный проигрыватель видео и аудио
41 | Name[pl]=VLC media player - odtwarzacz multimedialny
42 | Comment[pl]=Odczytywanie, przechwytywanie i nadawanie strumieni multimedialnych
43 | Name[lt]=VLC grotuvas
44 | Comment[lt]=Groti, įrašyti, siųsti daugialypės terpės kūrinius
45 | Name[ja]=VLCメディアプレイヤー
46 | Comment[ja]=マルチメディアストリームの読み込み、キャプチャー、ブロードキャスト
47 | Name[wa]=Djouweu d' media VLC
48 | Comment[wa]=Lét, egaloye, evoye vos floûs multimedia
49 | Name[zh_CN]=VLC media player
50 | Comment[zh_CN]=为您读取、捕获或发送多媒体流
51 | Exec=vlc %U
52 | Icon=vlc
53 | Terminal=false
54 | Type=Application
55 | Categories=AudioVideo;Player;
56 | MimeType=video/dv;video/mpeg;video/x-mpeg;video/msvideo;video/quicktime;video/x-anim;video/x-avi;video/x-ms-asf;video/x-ms-wmv;video/x-msvideo;video/x-nsv;video/x-flc;video/x-fli;video/x-flv;video/vnd.rn-realvideo;video/mp4;video/mp4v-es;video/mp2t;application/ogg;application/x-ogg;application/x-matroska;audio/x-mp3;audio/x-mpeg;audio/mpeg;audio/x-wav;audio/x-mpegurl;audio/x-scpls;audio/x-m4a;audio/x-ms-asf;audio/x-ms-asx;audio/x-ms-wax;application/vnd.rn-realmedia;audio/x-real-audio;audio/x-pn-realaudio;application/x-flac;audio/x-flac;application/x-shockwave-flash;misc/ultravox;audio/vnd.rn-realaudio;audio/x-pn-aiff;audio/x-pn-au;audio/x-pn-wav;audio/x-pn-windows-acm;image/vnd.rn-realpix;audio/x-pn-realaudio-plugin;application/x-extension-mp4;audio/mp4;x-content/video-vcd;x-content/video-svcd;x-content/video-dvd;x-content/audio-cdda;x-content/audio-player;
57 |
--------------------------------------------------------------------------------
/tests/assets/Helix.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Helix
3 | GenericName=Text Editor
4 | GenericName[ar]=مُحَرِّرُ نُصُوص
5 | GenericName[de]=Texteditor
6 | GenericName[fr]=Éditeur de texte
7 | GenericName[ru]=Текстовый редактор
8 | GenericName[sr]=Едитор текст
9 | GenericName[tr]=Metin Düzenleyici
10 | Comment=Edit text files
11 | Comment[af]=Redigeer tekslêers
12 | Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
13 | Comment[ar]=مُحَرِّرُ مِلَفَّاتٍ نَصِّيَّة
14 | Comment[az]=Mətn fayllarını redaktə edin
15 | Comment[be]=Рэдагаваньне тэкставых файлаў
16 | Comment[bg]=Редактиране на текстови файлове
17 | Comment[bn]=টেক্স্ট ফাইল এডিট করুন
18 | Comment[bs]=Izmijeni tekstualne datoteke
19 | Comment[ca]=Edita fitxers de text
20 | Comment[cs]=Úprava textových souborů
21 | Comment[cy]=Golygu ffeiliau testun
22 | Comment[da]=Redigér tekstfiler
23 | Comment[de]=Textdateien bearbeiten
24 | Comment[el]=Επεξεργασία αρχείων κειμένου
25 | Comment[en_CA]=Edit text files
26 | Comment[en_GB]=Edit text files
27 | Comment[es]=Edita archivos de texto
28 | Comment[et]=Redigeeri tekstifaile
29 | Comment[eu]=Editatu testu-fitxategiak
30 | Comment[fa]=ویرایش پروندههای متنی
31 | Comment[fi]=Muokkaa tekstitiedostoja
32 | Comment[fr]=Éditer des fichiers texte
33 | Comment[ga]=Eagar comhad Téacs
34 | Comment[gu]=લખાણ ફાઇલોમાં ફેરફાર કરો
35 | Comment[he]=ערוך קבצי טקסט
36 | Comment[hi]=पाठ फ़ाइलें संपादित करें
37 | Comment[hr]=Uređivanje tekstualne datoteke
38 | Comment[hu]=Szövegfájlok szerkesztése
39 | Comment[id]=Edit file teks
40 | Comment[it]=Modifica file di testo
41 | Comment[ja]=テキストファイルを編集します
42 | Comment[kn]=ಪಠ್ಯ ಕಡತಗಳನ್ನು ಸಂಪಾದಿಸು
43 | Comment[ko]=텍스트 파일을 편집합니다
44 | Comment[lt]=Redaguoti tekstines bylas
45 | Comment[lv]=Rediģēt teksta failus
46 | Comment[mk]=Уреди текстуални фајлови
47 | Comment[ml]=വാചക രചനകള് തിരുത്തുക
48 | Comment[mn]=Текст файл боловсруулах
49 | Comment[mr]=गद्य फाइल संपादित करा
50 | Comment[ms]=Edit fail teks
51 | Comment[nb]=Rediger tekstfiler
52 | Comment[ne]=पाठ फाइललाई संशोधन गर्नुहोस्
53 | Comment[nl]=Tekstbestanden bewerken
54 | Comment[nn]=Rediger tekstfiler
55 | Comment[no]=Rediger tekstfiler
56 | Comment[or]=ପାଠ୍ଯ ଫାଇଲଗୁଡ଼ିକୁ ସମ୍ପାଦନ କରନ୍ତୁ
57 | Comment[pa]=ਪਾਠ ਫਾਇਲਾਂ ਸੰਪਾਦਨ
58 | Comment[pl]=Edytor plików tekstowych
59 | Comment[pt]=Editar ficheiros de texto
60 | Comment[pt_BR]=Edite arquivos de texto
61 | Comment[ro]=Editare fişiere text
62 | Comment[ru]=Редактирование текстовых файлов
63 | Comment[sk]=Úprava textových súborov
64 | Comment[sl]=Urejanje datotek z besedili
65 | Comment[sq]=Përpuno files teksti
66 | Comment[sr]=Уређујте текст фајлове
67 | Comment[sr@Latn]=Izmeni tekstualne datoteke
68 | Comment[sv]=Redigera textfiler
69 | Comment[ta]=உரை கோப்புகளை தொகுக்கவும்
70 | Comment[th]=แก้ไขแฟ้มข้อความ
71 | Comment[tk]=Metin faýllary editle
72 | Comment[tr]=Metin dosyaları düzenleyin
73 | Comment[uk]=Редактор текстових файлів
74 | Comment[vi]=Soạn thảo tập tin văn bản
75 | Comment[wa]=Asspougnî des fitchîs tecses
76 | Comment[zh_CN]=编辑文本文件
77 | Comment[zh_TW]=編輯文字檔
78 | TryExec=hx
79 | Exec=hx %F
80 | Terminal=true
81 | Type=Application
82 | Keywords=Text;editor;
83 | Keywords[ar]=نص;نصوص;محرر;
84 | Keywords[fr]=Texte;éditeur;
85 | Keywords[ru]=текст;текстовый редактор;
86 | Keywords[sr]=Текст;едитор;
87 | Keywords[tr]=Metin;düzenleyici;
88 | Icon=helix
89 | Categories=Utility;TextEditor;
90 | StartupNotify=false
91 | MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
92 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__set_and_unset_handlers.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Setting `Helix.desktop` as handler for `text/plain`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `Helix.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: Helix.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished setting handler
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: Helix.desktop;
11 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
12 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
13 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Setting `nvim.desktop` as handler for `text/plain`
15 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: nvim.desktop;
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished setting handler
19 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: nvim.desktop;
20 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
21 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
22 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
23 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Unsetting handler for `text/plain`
24 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`:
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished unsetting handler
26 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m No handlers configured for `text/plain` in mimeapps.list Default associations
27 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No match for `text/plain` in mimeapps.list Default Associations
28 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching entries for `text/plain` in mimeapps.list Added Associations
29 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::system[0m[2m:[0m No installed handlers found for `text/plain`
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching installed handlers found for `text/plain`
31 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__add_and_unset_handlers.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding Helix.desktop to list of handlers for `text/plain`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `Helix.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: Helix.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: Helix.desktop;
11 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
12 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
13 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
15 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: Helix.desktop;nvim.desktop;
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
19 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: Helix.desktop;nvim.desktop;
20 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 2
21 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
22 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
23 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Unsetting handler for `text/plain`
24 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`:
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished unsetting handler
26 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m No handlers configured for `text/plain` in mimeapps.list Default associations
27 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No match for `text/plain` in mimeapps.list Default Associations
28 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching entries for `text/plain` in mimeapps.list Added Associations
29 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::system[0m[2m:[0m No installed handlers found for `text/plain`
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching installed handlers found for `text/plain`
31 |
--------------------------------------------------------------------------------
/src/apps/snapshots/handlr__apps__user__tests__set_handlers_expand_wildcards.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/apps/user.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | [Default Applications]
6 | application/vnd.oasis.opendocument.chart=startcenter.desktop;
7 | application/vnd.oasis.opendocument.chart-template=startcenter.desktop;
8 | application/vnd.oasis.opendocument.database=startcenter.desktop;
9 | application/vnd.oasis.opendocument.formula=startcenter.desktop;
10 | application/vnd.oasis.opendocument.formula-template=startcenter.desktop;
11 | application/vnd.oasis.opendocument.graphics=startcenter.desktop;
12 | application/vnd.oasis.opendocument.graphics-template=startcenter.desktop;
13 | application/vnd.oasis.opendocument.image=startcenter.desktop;
14 | application/vnd.oasis.opendocument.image-template=startcenter.desktop;
15 | application/vnd.oasis.opendocument.presentation=startcenter.desktop;
16 | application/vnd.oasis.opendocument.presentation-template=startcenter.desktop;
17 | application/vnd.oasis.opendocument.spreadsheet=startcenter.desktop;
18 | application/vnd.oasis.opendocument.spreadsheet-template=startcenter.desktop;
19 | application/vnd.oasis.opendocument.text=startcenter.desktop;
20 | application/vnd.oasis.opendocument.text-master=startcenter.desktop;
21 | application/vnd.oasis.opendocument.text-template=startcenter.desktop;
22 | application/vnd.oasis.opendocument.text-web=startcenter.desktop;
23 | text/cache-manifest=Helix.desktop;
24 | text/calendar=Helix.desktop;
25 | text/coffeescript=Helix.desktop;
26 | text/css=Helix.desktop;
27 | text/csv=Helix.desktop;
28 | text/html=Helix.desktop;
29 | text/jade=Helix.desktop;
30 | text/javascript=Helix.desktop;
31 | text/jsx=Helix.desktop;
32 | text/less=Helix.desktop;
33 | text/markdown=Helix.desktop;
34 | text/mathml=Helix.desktop;
35 | text/mdx=Helix.desktop;
36 | text/n3=Helix.desktop;
37 | text/plain=Helix.desktop;
38 | text/prs.lines.tag=Helix.desktop;
39 | text/richtext=Helix.desktop;
40 | text/rtf=Helix.desktop;
41 | text/sgml=Helix.desktop;
42 | text/shex=Helix.desktop;
43 | text/slim=Helix.desktop;
44 | text/spdx=Helix.desktop;
45 | text/stylus=Helix.desktop;
46 | text/tab-separated-values=Helix.desktop;
47 | text/troff=Helix.desktop;
48 | text/turtle=Helix.desktop;
49 | text/uri-list=Helix.desktop;
50 | text/vcard=Helix.desktop;
51 | text/vnd.curl=Helix.desktop;
52 | text/vnd.curl.dcurl=Helix.desktop;
53 | text/vnd.curl.mcurl=Helix.desktop;
54 | text/vnd.curl.scurl=Helix.desktop;
55 | text/vnd.dvb.subtitle=Helix.desktop;
56 | text/vnd.familysearch.gedcom=Helix.desktop;
57 | text/vnd.fly=Helix.desktop;
58 | text/vnd.fmi.flexstor=Helix.desktop;
59 | text/vnd.graphviz=Helix.desktop;
60 | text/vnd.in3d.3dml=Helix.desktop;
61 | text/vnd.in3d.spot=Helix.desktop;
62 | text/vnd.sun.j2me.app-descriptor=Helix.desktop;
63 | text/vnd.wap.wml=Helix.desktop;
64 | text/vnd.wap.wmlscript=Helix.desktop;
65 | text/vtt=Helix.desktop;
66 | text/wgsl=Helix.desktop;
67 | text/x-asm=Helix.desktop;
68 | text/x-c=Helix.desktop;
69 | text/x-component=Helix.desktop;
70 | text/x-fortran=Helix.desktop;
71 | text/x-handlebars-template=Helix.desktop;
72 | text/x-java-source=Helix.desktop;
73 | text/x-lua=Helix.desktop;
74 | text/x-markdown=Helix.desktop;
75 | text/x-nfo=Helix.desktop;
76 | text/x-opml=Helix.desktop;
77 | text/x-org=Helix.desktop;
78 | text/x-pascal=Helix.desktop;
79 | text/x-processing=Helix.desktop;
80 | text/x-sass=Helix.desktop;
81 | text/x-scss=Helix.desktop;
82 | text/x-setext=Helix.desktop;
83 | text/x-sfv=Helix.desktop;
84 | text/x-suse-ymp=Helix.desktop;
85 | text/x-uuencode=Helix.desktop;
86 | text/x-vcalendar=Helix.desktop;
87 | text/x-vcard=Helix.desktop;
88 | text/xml=Helix.desktop;
89 | text/yaml=Helix.desktop;
90 | video/mp4=mpv.desktop;
91 |
--------------------------------------------------------------------------------
/src/logging.rs:
--------------------------------------------------------------------------------
1 | use notify_rust::{Notification, Timeout, Urgency};
2 | use tracing::{field::Visit, Level};
3 | use tracing_appender::non_blocking::WorkerGuard;
4 | use tracing_subscriber::{filter, fmt, layer::SubscriberExt, Layer};
5 |
6 | use crate::{cli::Cli, error::Result};
7 |
8 | /// Init global tracing subscriber
9 | pub fn init_tracing(cli: &Cli) -> Result {
10 | let (file_writer, guard) =
11 | tracing_appender::non_blocking(tracing_appender::rolling::never(
12 | xdg::BaseDirectories::new()?.create_cache_directory("handlr")?,
13 | "handlr.log",
14 | ));
15 |
16 | // Filter logs based on `$RUST_LOG` and cli args
17 | let env_filter = std::env::var("RUST_LOG")
18 | .ok()
19 | .and_then(|var| var.parse::().ok())
20 | .unwrap_or_else(|| filter::Targets::new().with_default(cli.verbosity));
21 |
22 | tracing::subscriber::set_global_default(
23 | tracing_subscriber::registry()
24 | // Send filtered logs to stdout
25 | .with(
26 | fmt::Layer::new()
27 | .with_writer(std::io::stderr)
28 | .with_filter(env_filter.clone()),
29 | )
30 | // Send all logs to a log file
31 | .with(fmt::Layer::new().with_writer(file_writer).with_ansi(false))
32 | // Notify for filtered logs
33 | .with(
34 | cli.show_notifications()
35 | .then_some(NotificationLayer.with_filter(env_filter)),
36 | )
37 | // Never any logs from other crates so the user is not overwhelmed by the output
38 | .with(
39 | filter::Targets::new()
40 | .with_target("handlr", Level::TRACE)
41 | .with_target("tracing_unwrap", Level::WARN),
42 | ),
43 | )?;
44 |
45 | Ok(guard)
46 | }
47 |
48 | /// Custom tracing layer for running a notification on relevant events
49 | struct NotificationLayer;
50 |
51 | impl Layer for NotificationLayer
52 | where
53 | S: tracing::Subscriber,
54 | {
55 | #[mutants::skip] // Cannot test, relies on dbus
56 | fn on_event(
57 | &self,
58 | event: &tracing::Event,
59 | _ctx: tracing_subscriber::layer::Context<'_, S>,
60 | ) {
61 | let mut message = String::new();
62 | event.record(&mut NotificationVisitor(&mut message));
63 |
64 | // Just in case a message has no message, but some other information
65 | if message.is_empty() {
66 | message = "No message, see ~/.cache/handlr/handlr.log for details"
67 | .to_string()
68 | }
69 |
70 | let (level, icon, urgency) = match *event.metadata().level() {
71 | tracing::Level::ERROR => {
72 | ("error".to_string(), "dialog-error", Urgency::Critical)
73 | }
74 | tracing::Level::WARN => {
75 | ("warning".to_string(), "dialog-warning", Urgency::Normal)
76 | }
77 | l => (
78 | l.as_str().to_lowercase(),
79 | "dialog-information",
80 | Urgency::Low,
81 | ),
82 | };
83 |
84 | Notification::new()
85 | .summary(&format!("handlr {}", level))
86 | .body(&message)
87 | .icon(icon)
88 | .appname("handlr")
89 | .timeout(Timeout::Milliseconds(10_000))
90 | .urgency(urgency)
91 | .show()
92 | .expect("handlr error: Could not issue dbus notification");
93 | }
94 | }
95 |
96 | struct NotificationVisitor<'a>(&'a mut String);
97 |
98 | impl Visit for NotificationVisitor<'_> {
99 | #[mutants::skip] // Cannot test independently of NotificationLayer
100 | fn record_debug(
101 | &mut self,
102 | field: &tracing::field::Field,
103 | value: &dyn std::fmt::Debug,
104 | ) {
105 | if field.name() == "message" {
106 | self.0.push_str(&format!("{:?}", value));
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/common/snapshots/handlr__common__table__tests__terminal_output.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/common/table.rs
3 | expression: "render_table(&rows(LOREM_IPSUM), true)"
4 | ---
5 | ┌──────────────┬───────────────┐
6 | │[37m [39m[37mcol1[39m[37m [39m[37m [39m│[37m [39m[37mcol2[39m[37m [39m[37m [39m│
7 | ├──────────────┼───────────────┤
8 | │[40m [49m[40mLorem[49m[40m [49m[40m [49m│[40m [49m[40mipsum[49m[40m [49m[40m [49m│
9 | │[37m [39m[37mdolor[39m[37m [39m[37m [39m│[37m [39m[37msit[39m[37m [39m[37m [39m│
10 | │[40m [49m[40mamet,[49m[40m [49m[40m [49m│[40m [49m[40mconsectetur[49m[40m [49m[40m [49m│
11 | │[37m [39m[37madipiscing[39m[37m [39m[37m [39m│[37m [39m[37melit,[39m[37m [39m[37m [39m│
12 | │[40m [49m[40msed[49m[40m [49m[40m [49m│[40m [49m[40mdo[49m[40m [49m[40m [49m│
13 | │[37m [39m[37meiusmod[39m[37m [39m[37m [39m│[37m [39m[37mtempor[39m[37m [39m[37m [39m│
14 | │[40m [49m[40mincididunt[49m[40m [49m[40m [49m│[40m [49m[40mut[49m[40m [49m[40m [49m│
15 | │[37m [39m[37mlabore[39m[37m [39m[37m [39m│[37m [39m[37met[39m[37m [39m[37m [39m│
16 | │[40m [49m[40mdolore[49m[40m [49m[40m [49m│[40m [49m[40mmagna[49m[40m [49m[40m [49m│
17 | │[37m [39m[37maliqua.[39m[37m [39m[37m [39m│[37m [39m[37mUt[39m[37m [39m[37m [39m│
18 | │[40m [49m[40menim[49m[40m [49m[40m [49m│[40m [49m[40mad[49m[40m [49m[40m [49m│
19 | │[37m [39m[37mminim[39m[37m [39m[37m [39m│[37m [39m[37mveniam,[39m[37m [39m[37m [39m│
20 | │[40m [49m[40mquis[49m[40m [49m[40m [49m│[40m [49m[40mnostrud[49m[40m [49m[40m [49m│
21 | │[37m [39m[37mexercitation[39m[37m [39m│[37m [39m[37mullamco[39m[37m [39m[37m [39m│
22 | │[40m [49m[40mlaboris[49m[40m [49m[40m [49m│[40m [49m[40mnisi[49m[40m [49m[40m [49m│
23 | │[37m [39m[37mut[39m[37m [39m[37m [39m│[37m [39m[37maliquip[39m[37m [39m[37m [39m│
24 | │[40m [49m[40mex[49m[40m [49m[40m [49m│[40m [49m[40mea[49m[40m [49m[40m [49m│
25 | │[37m [39m[37mcommodo[39m[37m [39m[37m [39m│[37m [39m[37mconsequat.[39m[37m [39m[37m [39m│
26 | │[40m [49m[40mDuis[49m[40m [49m[40m [49m│[40m [49m[40maute[49m[40m [49m[40m [49m│
27 | │[37m [39m[37mirure[39m[37m [39m[37m [39m│[37m [39m[37mdolor[39m[37m [39m[37m [39m│
28 | │[40m [49m[40min[49m[40m [49m[40m [49m│[40m [49m[40mreprehenderit[49m[40m [49m│
29 | │[37m [39m[37min[39m[37m [39m[37m [39m│[37m [39m[37mvoluptate[39m[37m [39m[37m [39m│
30 | │[40m [49m[40mvelit[49m[40m [49m[40m [49m│[40m [49m[40messe[49m[40m [49m[40m [49m│
31 | │[37m [39m[37mcillum[39m[37m [39m[37m [39m│[37m [39m[37mdolore[39m[37m [39m[37m [39m│
32 | │[40m [49m[40meu[49m[40m [49m[40m [49m│[40m [49m[40mfugiat[49m[40m [49m[40m [49m│
33 | │[37m [39m[37mnulla[39m[37m [39m[37m [39m│[37m [39m[37mpariatur.[39m[37m [39m[37m [39m│
34 | │[40m [49m[40mExcepteur[49m[40m [49m[40m [49m│[40m [49m[40msint[49m[40m [49m[40m [49m│
35 | │[37m [39m[37moccaecat[39m[37m [39m[37m [39m│[37m [39m[37mcupidatat[39m[37m [39m[37m [39m│
36 | │[40m [49m[40mnon[49m[40m [49m[40m [49m│[40m [49m[40mproident,[49m[40m [49m[40m [49m│
37 | │[37m [39m[37msunt[39m[37m [39m[37m [39m│[37m [39m[37min[39m[37m [39m[37m [39m│
38 | │[40m [49m[40mculpa[49m[40m [49m[40m [49m│[40m [49m[40mqui[49m[40m [49m[40m [49m│
39 | │[37m [39m[37mofficia[39m[37m [39m[37m [39m│[37m [39m[37mdeserunt[39m[37m [39m[37m [39m│
40 | │[40m [49m[40mmollit[49m[40m [49m[40m [49m│[40m [49m[40manim[49m[40m [49m[40m [49m│
41 | │[37m [39m[37mid[39m[37m [39m[37m [39m│[37m [39m[37mest[39m[37m [39m[37m [39m│
42 | └──────────────┴───────────────┘
43 |
--------------------------------------------------------------------------------
/src/apps/snapshots/handlr__apps__user__tests__add_handlers_expand_wildcards.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/apps/user.rs
3 | expression: "String::from_utf8(buffer)?"
4 | ---
5 | [Default Applications]
6 | application/vnd.oasis.opendocument.chart=startcenter.desktop;
7 | application/vnd.oasis.opendocument.chart-template=startcenter.desktop;
8 | application/vnd.oasis.opendocument.database=startcenter.desktop;
9 | application/vnd.oasis.opendocument.formula=startcenter.desktop;
10 | application/vnd.oasis.opendocument.formula-template=startcenter.desktop;
11 | application/vnd.oasis.opendocument.graphics=startcenter.desktop;
12 | application/vnd.oasis.opendocument.graphics-template=startcenter.desktop;
13 | application/vnd.oasis.opendocument.image=startcenter.desktop;
14 | application/vnd.oasis.opendocument.image-template=startcenter.desktop;
15 | application/vnd.oasis.opendocument.presentation=startcenter.desktop;
16 | application/vnd.oasis.opendocument.presentation-template=startcenter.desktop;
17 | application/vnd.oasis.opendocument.spreadsheet=startcenter.desktop;
18 | application/vnd.oasis.opendocument.spreadsheet-template=startcenter.desktop;
19 | application/vnd.oasis.opendocument.text=startcenter.desktop;
20 | application/vnd.oasis.opendocument.text-master=startcenter.desktop;
21 | application/vnd.oasis.opendocument.text-template=startcenter.desktop;
22 | application/vnd.oasis.opendocument.text-web=startcenter.desktop;
23 | text/cache-manifest=Helix.desktop;nvim.desktop;
24 | text/calendar=Helix.desktop;nvim.desktop;
25 | text/coffeescript=Helix.desktop;nvim.desktop;
26 | text/css=Helix.desktop;nvim.desktop;
27 | text/csv=Helix.desktop;nvim.desktop;
28 | text/html=Helix.desktop;nvim.desktop;
29 | text/jade=Helix.desktop;nvim.desktop;
30 | text/javascript=Helix.desktop;nvim.desktop;
31 | text/jsx=Helix.desktop;nvim.desktop;
32 | text/less=Helix.desktop;nvim.desktop;
33 | text/markdown=Helix.desktop;nvim.desktop;
34 | text/mathml=Helix.desktop;nvim.desktop;
35 | text/mdx=Helix.desktop;nvim.desktop;
36 | text/n3=Helix.desktop;nvim.desktop;
37 | text/plain=Helix.desktop;nvim.desktop;
38 | text/prs.lines.tag=Helix.desktop;nvim.desktop;
39 | text/richtext=Helix.desktop;nvim.desktop;
40 | text/rtf=Helix.desktop;nvim.desktop;
41 | text/sgml=Helix.desktop;nvim.desktop;
42 | text/shex=Helix.desktop;nvim.desktop;
43 | text/slim=Helix.desktop;nvim.desktop;
44 | text/spdx=Helix.desktop;nvim.desktop;
45 | text/stylus=Helix.desktop;nvim.desktop;
46 | text/tab-separated-values=Helix.desktop;nvim.desktop;
47 | text/troff=Helix.desktop;nvim.desktop;
48 | text/turtle=Helix.desktop;nvim.desktop;
49 | text/uri-list=Helix.desktop;nvim.desktop;
50 | text/vcard=Helix.desktop;nvim.desktop;
51 | text/vnd.curl=Helix.desktop;nvim.desktop;
52 | text/vnd.curl.dcurl=Helix.desktop;nvim.desktop;
53 | text/vnd.curl.mcurl=Helix.desktop;nvim.desktop;
54 | text/vnd.curl.scurl=Helix.desktop;nvim.desktop;
55 | text/vnd.dvb.subtitle=Helix.desktop;nvim.desktop;
56 | text/vnd.familysearch.gedcom=Helix.desktop;nvim.desktop;
57 | text/vnd.fly=Helix.desktop;nvim.desktop;
58 | text/vnd.fmi.flexstor=Helix.desktop;nvim.desktop;
59 | text/vnd.graphviz=Helix.desktop;nvim.desktop;
60 | text/vnd.in3d.3dml=Helix.desktop;nvim.desktop;
61 | text/vnd.in3d.spot=Helix.desktop;nvim.desktop;
62 | text/vnd.sun.j2me.app-descriptor=Helix.desktop;nvim.desktop;
63 | text/vnd.wap.wml=Helix.desktop;nvim.desktop;
64 | text/vnd.wap.wmlscript=Helix.desktop;nvim.desktop;
65 | text/vtt=Helix.desktop;nvim.desktop;
66 | text/wgsl=Helix.desktop;nvim.desktop;
67 | text/x-asm=Helix.desktop;nvim.desktop;
68 | text/x-c=Helix.desktop;nvim.desktop;
69 | text/x-component=Helix.desktop;nvim.desktop;
70 | text/x-fortran=Helix.desktop;nvim.desktop;
71 | text/x-handlebars-template=Helix.desktop;nvim.desktop;
72 | text/x-java-source=Helix.desktop;nvim.desktop;
73 | text/x-lua=Helix.desktop;nvim.desktop;
74 | text/x-markdown=Helix.desktop;nvim.desktop;
75 | text/x-nfo=Helix.desktop;nvim.desktop;
76 | text/x-opml=Helix.desktop;nvim.desktop;
77 | text/x-org=Helix.desktop;nvim.desktop;
78 | text/x-pascal=Helix.desktop;nvim.desktop;
79 | text/x-processing=Helix.desktop;nvim.desktop;
80 | text/x-sass=Helix.desktop;nvim.desktop;
81 | text/x-scss=Helix.desktop;nvim.desktop;
82 | text/x-setext=Helix.desktop;nvim.desktop;
83 | text/x-sfv=Helix.desktop;nvim.desktop;
84 | text/x-suse-ymp=Helix.desktop;nvim.desktop;
85 | text/x-uuencode=Helix.desktop;nvim.desktop;
86 | text/x-vcalendar=Helix.desktop;nvim.desktop;
87 | text/x-vcard=Helix.desktop;nvim.desktop;
88 | text/xml=Helix.desktop;nvim.desktop;
89 | text/yaml=Helix.desktop;nvim.desktop;
90 | video/mp4=mpv.desktop;
91 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__set_and_remove_handlers.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Setting `Helix.desktop` as handler for `text/plain`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `Helix.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: Helix.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished setting handler
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: Helix.desktop;
11 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
12 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
13 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Setting `nvim.desktop` as handler for `text/plain`
15 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: nvim.desktop;
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished setting handler
19 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: nvim.desktop;
20 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
21 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
22 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
23 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Removing `Helix.desktop` from list of handlers for `text/plain`
24 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: nvim.desktop;
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished removing handler
26 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: nvim.desktop;
27 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
28 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
29 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Removing `nvim.desktop` from list of handlers for `text/plain`
31 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`:
32 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished removing handler
33 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: ;
34 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 0
35 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
36 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No match for `text/plain` in mimeapps.list Default Associations
37 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching entries for `text/plain` in mimeapps.list Added Associations
38 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::system[0m[2m:[0m No installed handlers found for `text/plain`
39 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching installed handlers found for `text/plain`
40 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__add_and_remove_handlers.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding Helix.desktop to list of handlers for `text/plain`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `Helix.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: Helix.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: Helix.desktop;
11 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
12 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
13 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
15 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: Helix.desktop;nvim.desktop;
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
19 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: Helix.desktop;nvim.desktop;
20 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 2
21 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
22 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
23 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Removing `Helix.desktop` from list of handlers for `text/plain`
24 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: nvim.desktop;
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished removing handler
26 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: nvim.desktop;
27 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
28 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
29 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `text/plain` in mimeapps.list Default Associations
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Removing `nvim.desktop` from list of handlers for `text/plain`
31 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`:
32 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished removing handler
33 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `text/plain` in mimeapps.list Default Associations: ;
34 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 0
35 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
36 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No match for `text/plain` in mimeapps.list Default Associations
37 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching entries for `text/plain` in mimeapps.list Added Associations
38 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::system[0m[2m:[0m No installed handlers found for `text/plain`
39 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching installed handlers found for `text/plain`
40 |
--------------------------------------------------------------------------------
/src/apps/system.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | apps::DesktopList,
3 | common::{DesktopEntry, DesktopHandler, Handleable},
4 | config::Languages,
5 | error::Result,
6 | };
7 | use mime::Mime;
8 | use std::{collections::BTreeMap, ffi::OsString};
9 | use tracing::debug;
10 |
11 | #[derive(Debug, Default, Clone)]
12 | pub struct SystemApps {
13 | /// Associations of mimes and lists of apps
14 | pub associations: BTreeMap,
15 | /// Apps with no associated mime
16 | unassociated: DesktopList,
17 | }
18 |
19 | impl SystemApps {
20 | /// Get the list of handlers associated with a given mime
21 | pub fn get_handlers(&self, mime: &Mime) -> Option {
22 | let associations = self.associations.get(mime);
23 |
24 | if associations.is_none() {
25 | debug!("No installed handlers found for `{}`", mime);
26 | } else {
27 | debug!(
28 | "Installed handlers found for `{}`: {}",
29 | mime, associations?
30 | );
31 | }
32 |
33 | Some(associations?.clone())
34 | }
35 |
36 | /// Get the primary of handler associated with a given mime
37 | pub fn get_handler(&self, mime: &Mime) -> Option {
38 | let handler = self.get_handlers(mime)?.front()?.clone();
39 | debug!("Installed handler chosen for `{}`: {}", mime, handler);
40 | Some(handler)
41 | }
42 |
43 | /// Get all system-level desktop entries on the system
44 | #[mutants::skip] // Cannot test directly, depends on system state
45 | pub fn get_entries(
46 | languages: &Languages,
47 | ) -> Result> {
48 | // ) -> Result + use<'_>> {
49 | Ok(xdg::BaseDirectories::new()?
50 | .list_data_files_once("applications")
51 | .into_iter()
52 | .filter(|p| {
53 | p.extension().and_then(|x| x.to_str()) == Some("desktop")
54 | })
55 | .filter_map(|p| {
56 | Some((
57 | p.file_name()?.to_owned(),
58 | DesktopEntry::parse_file(&p.clone(), languages).ok()?,
59 | ))
60 | })
61 | .collect())
62 | }
63 |
64 | /// Create a new instance of `SystemApps`
65 | #[mutants::skip] // Cannot test directly, depends on system state
66 | pub fn populate(languages: &Languages) -> Result {
67 | let mut associations = BTreeMap::::new();
68 | let mut unassociated = DesktopList::default();
69 |
70 | Self::get_entries(languages)?
71 | .into_iter()
72 | .for_each(|(_, entry)| {
73 | let (file_name, mimes) = (entry.file_name, entry.mime_type);
74 | let desktop_handler =
75 | DesktopHandler::assume_valid(file_name.to_owned());
76 |
77 | if mimes.is_empty() {
78 | unassociated.push_back(desktop_handler);
79 | } else {
80 | mimes.into_iter().for_each(|mime| {
81 | associations
82 | .entry(mime)
83 | .or_default()
84 | .push_back(desktop_handler.clone());
85 | });
86 | }
87 | });
88 |
89 | Ok(Self {
90 | associations,
91 | unassociated,
92 | })
93 | }
94 |
95 | /// Get an installed terminal emulator
96 | pub fn terminal_emulator(
97 | &self,
98 | languages: &Languages,
99 | ) -> Option {
100 | self.unassociated
101 | .iter()
102 | .filter_map(|h| h.get_entry(languages).ok())
103 | .find(|h| h.is_terminal_emulator())
104 | }
105 |
106 | #[cfg(test)]
107 | /// Internal helper function for testing
108 | pub fn add_unassociated(&mut self, handler: DesktopHandler) {
109 | self.unassociated.push_front(handler)
110 | }
111 | }
112 |
113 | #[cfg(test)]
114 | mod tests {
115 | use super::*;
116 |
117 | #[test]
118 | fn get_handlers() -> Result<()> {
119 | let mut expected_handlers = DesktopList::default();
120 | expected_handlers
121 | .push_back(DesktopHandler::assume_valid("helix.desktop".into()));
122 | expected_handlers
123 | .push_back(DesktopHandler::assume_valid("nvim.desktop".into()));
124 |
125 | let mut associations: BTreeMap = BTreeMap::new();
126 |
127 | associations.insert(mime::TEXT_PLAIN, expected_handlers.clone());
128 |
129 | let system_apps = SystemApps {
130 | associations,
131 | ..Default::default()
132 | };
133 |
134 | assert_eq!(
135 | system_apps
136 | .get_handler(&mime::TEXT_PLAIN)
137 | .expect("Could not get handler")
138 | .to_string(),
139 | "helix.desktop"
140 | );
141 | assert_eq!(
142 | system_apps
143 | .get_handlers(&mime::TEXT_PLAIN)
144 | .expect("Could not get handler"),
145 | expected_handlers
146 | );
147 |
148 | Ok(())
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/common/path.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | common::{render_table, MimeType},
3 | error::{Error, Result},
4 | };
5 | use itertools::Itertools;
6 | use mime::Mime;
7 | use serde::Serialize;
8 | use std::{
9 | convert::{TryFrom, TryInto},
10 | fmt::{Display, Formatter},
11 | io::Write,
12 | path::PathBuf,
13 | str::FromStr,
14 | };
15 | use tabled::Tabled;
16 | use tracing::{debug, info};
17 | use url::Url;
18 |
19 | #[derive(Debug, Clone)]
20 | pub enum UserPath {
21 | Url(Url),
22 | File(PathBuf),
23 | }
24 |
25 | impl UserPath {
26 | pub fn get_mime(&self) -> Result {
27 | Ok(match self {
28 | Self::Url(url) => Ok(url.try_into()?),
29 | Self::File(f) => MimeType::try_from(f.as_path()),
30 | }?
31 | .0)
32 | }
33 | }
34 |
35 | impl FromStr for UserPath {
36 | type Err = Error;
37 | fn from_str(s: &str) -> Result {
38 | let normalized = match url::Url::parse(s) {
39 | Ok(url) if url.scheme() == "file" => {
40 | let path = url
41 | .to_file_path()
42 | .map_err(|_| Error::BadPath(url.path().to_owned()))?;
43 |
44 | Self::File(path)
45 | }
46 | Ok(url) => Self::Url(url),
47 | _ => Self::File(PathBuf::from(s)),
48 | };
49 |
50 | Ok(normalized)
51 | }
52 | }
53 |
54 | impl Display for UserPath {
55 | fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
56 | match self {
57 | Self::File(f) => fmt.write_str(&f.to_string_lossy()),
58 | Self::Url(u) => fmt.write_str(u.as_ref()),
59 | }
60 | }
61 | }
62 |
63 | /// Internal helper struct for turning a UserPath into tabular data
64 | #[derive(Tabled, Serialize)]
65 | struct UserPathTable {
66 | path: String,
67 | mime: String,
68 | }
69 |
70 | impl UserPathTable {
71 | fn new(path: &UserPath) -> Result {
72 | Ok(Self {
73 | path: path.to_string(),
74 | mime: path.get_mime()?.essence_str().to_owned(),
75 | })
76 | }
77 | }
78 |
79 | /// Render a table of mime types from a list of paths
80 | /// and write it to the given writer
81 | pub fn mime_table(
82 | writer: &mut W,
83 | paths: &[UserPath],
84 | output_json: bool,
85 | terminal_output: bool,
86 | ) -> Result<()> {
87 | info!(
88 | "Printing mime information for paths: [{}]",
89 | paths
90 | .iter()
91 | .format_with(", ", |str, f| f(&format!("\"{}\"", str)))
92 | .to_string()
93 | );
94 | debug!("JSON output: {}", output_json);
95 |
96 | let rows = paths
97 | .iter()
98 | .map(UserPathTable::new)
99 | .collect::>>()?;
100 |
101 | let table = if output_json {
102 | serde_json::to_string(&rows)?
103 | } else {
104 | render_table(&rows, terminal_output)
105 | };
106 |
107 | writeln!(writer, "{table}")?;
108 |
109 | info!("Finished printing mime information");
110 | Ok(())
111 | }
112 |
113 | #[cfg(test)]
114 | mod tests {
115 | use super::*;
116 |
117 | // Helper function to create a vector of UserPaths for testing `mime_table`
118 | fn paths() -> Result> {
119 | [
120 | "tests/assets",
121 | "tests/assets/cat",
122 | "tests/assets/cmus.desktop",
123 | "tests/assets/empty.txt",
124 | "tests/assets/no_html_tags.html",
125 | "tests/assets/org.wezfurlong.wezterm.desktop",
126 | "tests/assets/p.html",
127 | "tests/assets/rust.vim",
128 | "tests/assets/SettingsWidgetFdoSecrets.ui",
129 | "https://duckduckgo.com",
130 | ".",
131 | "README.md",
132 | ]
133 | .iter()
134 | .map(|p| UserPath::from_str(p))
135 | .collect()
136 | }
137 |
138 | #[test]
139 | fn file_url() -> Result<()> {
140 | let path = UserPath::from_str("file:///test.txt")?;
141 | assert_eq!(path.to_string(), "/test.txt");
142 | Ok(())
143 | }
144 |
145 | #[test]
146 | fn mime_table_terminal() -> Result<()> {
147 | let mut buffer = Vec::new();
148 | mime_table(&mut buffer, &paths()?, false, true)?;
149 | insta::assert_snapshot!(String::from_utf8(buffer)?);
150 | Ok(())
151 | }
152 |
153 | #[test]
154 | fn test_mime_table_piped() -> Result<()> {
155 | let mut buffer = Vec::new();
156 | mime_table(&mut buffer, &paths()?, false, false)?;
157 | insta::assert_snapshot!(String::from_utf8(buffer)?);
158 | Ok(())
159 | }
160 |
161 | #[test]
162 | fn test_mime_table_json() -> Result<()> {
163 | //NOTE: both calls should have the same result
164 | // JSON output and terminal output
165 | let mut buffer = Vec::new();
166 | mime_table(&mut buffer, &paths()?, true, true)?;
167 | insta::assert_snapshot!(String::from_utf8(buffer)?);
168 |
169 | // JSON output and no terminal output
170 | let mut buffer = Vec::new();
171 | mime_table(&mut buffer, &paths()?, true, false)?;
172 | insta::assert_snapshot!(String::from_utf8(buffer)?);
173 |
174 | Ok(())
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_piped-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/mp4`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/mp4`: mpv.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/asdf`
11 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
12 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
13 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/asdf`: mpv.desktop;
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
15 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding brave.desktop to list of handlers for `video/webm`
16 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `brave.desktop` is invalid: No such file or directory (os error 2)
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
18 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/webm`: brave.desktop;
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
20 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding helix.desktop to list of handlers for `text/plain`
21 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `helix.desktop` is invalid: No such file or directory (os error 2)
22 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
23 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;
24 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
26 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
27 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
28 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;
29 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding kakoune.desktop to list of handlers for `text/plain`
31 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `kakoune.desktop` is invalid: No such file or directory (os error 2)
32 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
33 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;kakoune.desktop;
34 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
35 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.oasis.opendocument.*`
36 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
37 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
38 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.oasis.opendocument.*`: startcenter.desktop;
39 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
40 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.openxmlformats-officedocument.*`
41 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
42 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
43 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.openxmlformats-officedocument.*`: startcenter.desktop;
44 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
45 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Printing associations
46 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: false
47 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished printing associations
48 |
--------------------------------------------------------------------------------
/src/common/mime_types.rs:
--------------------------------------------------------------------------------
1 | use crate::error::{Error, Result};
2 | use derive_more::Deref;
3 | use mime::Mime;
4 | use std::{convert::TryFrom, path::Path, str::FromStr, sync::LazyLock};
5 | use tracing_unwrap::{OptionExt, ResultExt};
6 | use url::Url;
7 |
8 | /// A mime derived from a path or URL
9 | #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deref)]
10 | pub struct MimeType(pub Mime);
11 |
12 | impl MimeType {
13 | /// Gets the `Mime` from a given file extension
14 | fn from_ext(ext: &str) -> Result {
15 | match xdg_mime::SharedMimeInfo::new()
16 | .get_mime_types_from_file_name(ext)
17 | .into_iter()
18 | .nth(0)
19 | .expect_or_log("The function xdg_mime::get_mime_types_from_file_type should always return a non-empty Vec")
20 | {
21 | // If the file extension is ambiguous, then error
22 | // Otherwise, the user may not expect this mimetype being assigned
23 | mime if mime == mime::APPLICATION_OCTET_STREAM => {
24 | Err(Error::AmbiguousExtension(ext.into()))
25 | }
26 | mime => Ok(mime)
27 | }
28 | }
29 | }
30 |
31 | impl TryFrom<&Url> for MimeType {
32 | type Error = Error;
33 | fn try_from(url: &Url) -> Result {
34 | Ok(Self(
35 | format!("x-scheme-handler/{}", url.scheme()).parse::()?,
36 | ))
37 | }
38 | }
39 |
40 | impl TryFrom<&Path> for MimeType {
41 | type Error = Error;
42 | fn try_from(path: &Path) -> Result {
43 | if !path.try_exists()? {
44 | return Err(Error::NonexistentFile(
45 | path.to_string_lossy().to_string(),
46 | ));
47 | }
48 |
49 | let db = xdg_mime::SharedMimeInfo::new();
50 |
51 | let mut guess = db.guess_mime_type();
52 | guess.file_name(&path.to_string_lossy());
53 |
54 | let mut mime = guess.guess().mime_type().clone();
55 |
56 | static APPLICATION_X_ZEROSIZE: LazyLock = LazyLock::new(|| {
57 | "application/x-zerosize"
58 | .parse::()
59 | .expect_or_log("Hardcoded mime should be valid")
60 | });
61 |
62 | // TODO: remove this check once xdg-mime crate makes a new release (currently v0.4.0)
63 | if mime == *APPLICATION_X_ZEROSIZE {
64 | mime = guess.path(path).guess().mime_type().clone();
65 | }
66 |
67 | Ok(Self(mime.clone()))
68 | }
69 | }
70 |
71 | impl FromStr for MimeType {
72 | type Err = Error;
73 | fn from_str(s: &str) -> Result {
74 | let mime = if s.starts_with('.') {
75 | Self::from_ext(s)?
76 | } else {
77 | match Mime::from_str(s)? {
78 | m if m.subtype() == "" => return Err(Error::InvalidMime(m)),
79 | proper_mime => proper_mime,
80 | }
81 | };
82 |
83 | Ok(Self(mime))
84 | }
85 | }
86 |
87 | #[cfg(test)]
88 | mod tests {
89 | use super::*;
90 |
91 | #[test]
92 | fn user_input() -> Result<()> {
93 | assert_eq!(MimeType::from_str(".pdf")?.0, mime::APPLICATION_PDF);
94 | assert_eq!(MimeType::from_str("image/jpeg")?.0, mime::IMAGE_JPEG);
95 |
96 | assert!("image//jpg".parse::().is_err());
97 | assert!("image".parse::().is_err());
98 |
99 | Ok(())
100 | }
101 |
102 | #[test]
103 | fn from_path() -> Result<()> {
104 | assert_eq!(
105 | MimeType::try_from(Path::new("."))?.0.essence_str(),
106 | "inode/directory"
107 | );
108 | assert_eq!(
109 | MimeType::try_from(Path::new("./tests/assets/rust.vim"))?.0,
110 | "text/plain"
111 | );
112 | assert_eq!(
113 | MimeType::try_from(Path::new("./tests/assets/cat"))?.0,
114 | "application/x-shellscript"
115 | );
116 | assert_eq!(
117 | MimeType::try_from(Path::new(
118 | "./tests/assets/SettingsWidgetFdoSecrets.ui"
119 | ))?
120 | .0,
121 | "application/x-designer"
122 | );
123 | assert_eq!(
124 | MimeType::try_from(Path::new("./tests/assets/empty.txt"))?.0,
125 | "text/plain"
126 | );
127 | assert_eq!(
128 | MimeType::try_from(Path::new("./tests/assets/p.html"))?.0,
129 | "text/html"
130 | );
131 | assert_eq!(
132 | MimeType::try_from(Path::new("./tests/assets/no_html_tags.html"))?
133 | .0,
134 | "text/html"
135 | );
136 | assert_eq!(
137 | MimeType::try_from(Path::new("./tests/assets/empty"))?.0,
138 | "application/x-zerosize"
139 | );
140 | assert_eq!(
141 | MimeType::try_from(Path::new(
142 | "./tests/assets/nonsense_binary_data"
143 | ))?
144 | .0,
145 | "application/octet-stream"
146 | );
147 |
148 | Ok(())
149 | }
150 |
151 | #[test]
152 | fn from_str() -> Result<()> {
153 | assert_eq!(".mp3".parse::()?.0, "audio/mpeg");
154 | assert_eq!("audio/mpeg".parse::()?.0, "audio/mpeg");
155 | assert!(".".parse::().is_err());
156 | assert!("audio/".parse::().is_err());
157 | assert_eq!(
158 | "application/octet-stream".parse::()?.0,
159 | "application/octet-stream"
160 | );
161 |
162 | Ok(())
163 | }
164 |
165 | #[test]
166 | fn nonexistent_file() {
167 | assert!(MimeType::try_from(Path::new("nonexistent_file")).is_err());
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_default-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/mp4`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/mp4`: mpv.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/asdf`
11 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
12 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
13 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/asdf`: mpv.desktop;
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
15 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding brave.desktop to list of handlers for `video/webm`
16 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `brave.desktop` is invalid: No such file or directory (os error 2)
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
18 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/webm`: brave.desktop;
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
20 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding helix.desktop to list of handlers for `text/plain`
21 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `helix.desktop` is invalid: No such file or directory (os error 2)
22 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
23 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;
24 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
26 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
27 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
28 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;
29 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding kakoune.desktop to list of handlers for `text/plain`
31 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `kakoune.desktop` is invalid: No such file or directory (os error 2)
32 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
33 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;kakoune.desktop;
34 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
35 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.oasis.opendocument.*`
36 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
37 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
38 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.oasis.opendocument.*`: startcenter.desktop;
39 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
40 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.openxmlformats-officedocument.*`
41 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
42 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
43 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.openxmlformats-officedocument.*`: startcenter.desktop;
44 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
45 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Printing associations
46 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: false
47 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished printing associations
48 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_detailed-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/mp4`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/mp4`: mpv.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/asdf`
11 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
12 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
13 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/asdf`: mpv.desktop;
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
15 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding brave.desktop to list of handlers for `video/webm`
16 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `brave.desktop` is invalid: No such file or directory (os error 2)
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
18 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/webm`: brave.desktop;
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
20 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding helix.desktop to list of handlers for `text/plain`
21 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `helix.desktop` is invalid: No such file or directory (os error 2)
22 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
23 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;
24 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
26 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
27 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
28 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;
29 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding kakoune.desktop to list of handlers for `text/plain`
31 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `kakoune.desktop` is invalid: No such file or directory (os error 2)
32 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
33 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;kakoune.desktop;
34 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
35 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.oasis.opendocument.*`
36 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
37 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
38 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.oasis.opendocument.*`: startcenter.desktop;
39 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
40 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.openxmlformats-officedocument.*`
41 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
42 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
43 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.openxmlformats-officedocument.*`: startcenter.desktop;
44 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
45 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Printing associations
46 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: false
47 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished printing associations
48 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_detailed_piped-2.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/mp4`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/mp4`: mpv.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/asdf`
11 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
12 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
13 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/asdf`: mpv.desktop;
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
15 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding brave.desktop to list of handlers for `video/webm`
16 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `brave.desktop` is invalid: No such file or directory (os error 2)
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
18 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/webm`: brave.desktop;
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
20 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding helix.desktop to list of handlers for `text/plain`
21 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `helix.desktop` is invalid: No such file or directory (os error 2)
22 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
23 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;
24 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
26 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
27 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
28 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;
29 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding kakoune.desktop to list of handlers for `text/plain`
31 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `kakoune.desktop` is invalid: No such file or directory (os error 2)
32 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
33 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;kakoune.desktop;
34 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
35 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.oasis.opendocument.*`
36 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
37 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
38 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.oasis.opendocument.*`: startcenter.desktop;
39 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
40 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.openxmlformats-officedocument.*`
41 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
42 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
43 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.openxmlformats-officedocument.*`: startcenter.desktop;
44 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
45 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Printing associations
46 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: false
47 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished printing associations
48 |
--------------------------------------------------------------------------------
/tests/assets/SettingsWidgetFdoSecrets.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | SettingsWidgetFdoSecrets
4 |
5 |
6 |
7 | 0
8 | 0
9 | 525
10 | 457
11 |
12 |
13 |
14 | Options
15 |
16 |
17 |
18 | 0
19 |
20 |
21 | 0
22 |
23 |
24 | 0
25 |
26 |
27 | 0
28 |
29 | -
30 |
31 |
32 | -
33 |
34 |
35 | Enable KeepassXC Freedesktop.org Secret Service integration
36 |
37 |
38 |
39 | -
40 |
41 |
42 | 0
43 |
44 |
45 |
46 | General
47 |
48 |
49 |
-
50 |
51 |
52 | <html><head/><body><p>If enabled, clients can delete exposed entries without confirmation. Otherwise, attempts to delete an entry may need to be confirmed interactively (subject to global Security/Convenience settings).</p></body></html>
53 |
54 |
55 | Skip confirmation when entries are deleted by clients
56 |
57 |
58 |
59 | -
60 |
61 |
62 | Show notification when passwords are retrieved by clients
63 |
64 |
65 |
66 | -
67 |
68 |
69 | <html><head/><body><p>If enabled, any attempt to read a password must be confirmed. Otherwise, clients can read passwords without confirmation when the database is unlocked.</p><p>This option only covers the access to the password of an entry. Clients can always enumerate the items of exposed databases and query their attributes.</p></body></html>
70 |
71 |
72 | Confirm when passwords are retrieved by clients
73 |
74 |
75 |
76 | -
77 |
78 |
79 | Qt::Vertical
80 |
81 |
82 | QSizePolicy::Preferred
83 |
84 |
85 |
86 | 20
87 | 40
88 |
89 |
90 |
91 |
92 | -
93 |
94 |
95 | Exposed database groups:
96 |
97 |
98 |
99 | -
100 |
101 |
102 | Qt::NoFocus
103 |
104 |
105 | QAbstractItemView::NoEditTriggers
106 |
107 |
108 | QAbstractItemView::NoSelection
109 |
110 |
111 | QAbstractItemView::SelectRows
112 |
113 |
114 | false
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Authorization
123 |
124 |
125 | -
126 |
127 |
128 | These applications are currently connected:
129 |
130 |
131 |
132 | -
133 |
134 |
135 | Qt::NoFocus
136 |
137 |
138 | QAbstractItemView::NoEditTriggers
139 |
140 |
141 | QAbstractItemView::NoSelection
142 |
143 |
144 | QAbstractItemView::SelectRows
145 |
146 |
147 | false
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | MessageWidget
160 | QWidget
161 |
162 | 1
163 |
164 |
165 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/src/common/handler.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | common::{DesktopEntry, ExecMode, UserPath},
3 | config::{Config, Languages},
4 | error::{Error, Result},
5 | };
6 | use derive_more::{Deref, Display};
7 | use enum_dispatch::enum_dispatch;
8 | use itertools::Itertools;
9 | use serde::{Deserialize, Serialize};
10 | use serde_with::{serde_as, DisplayFromStr};
11 | use std::{
12 | ffi::OsString,
13 | fmt::Display,
14 | hash::{Hash, Hasher},
15 | path::PathBuf,
16 | str::FromStr,
17 | };
18 | use tracing::{debug, info, warn};
19 |
20 | /// Represents a program or command that is used to open a file
21 | #[enum_dispatch(Handleable)]
22 | #[derive(Display, Debug, PartialEq, Eq, Hash)]
23 | pub enum Handler {
24 | DesktopHandler,
25 | RegexHandler,
26 | }
27 |
28 | #[cfg(test)]
29 | impl Handler {
30 | /// Helper function for testing
31 | pub fn new(name: &str) -> Self {
32 | Handler::DesktopHandler(DesktopHandler::assume_valid(name.into()))
33 | }
34 | }
35 |
36 | /// Trait providing common functionality for handlers
37 | #[enum_dispatch]
38 | pub trait Handleable {
39 | /// Get the desktop entry associated with the handler
40 | fn get_entry(&self, languages: &Languages) -> Result;
41 | /// Open the given paths with the handler
42 | #[mutants::skip] // Cannot test directly, runs commands
43 | fn open(&self, config: &Config, args: Vec) -> Result<()> {
44 | self.get_entry(&config.languages)?
45 | .exec(config, ExecMode::Open, args)
46 | }
47 | }
48 |
49 | /// Represents a handler defined in a desktop file
50 | #[derive(
51 | Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
52 | )]
53 | pub struct DesktopHandler(OsString);
54 |
55 | impl Display for DesktopHandler {
56 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 | f.write_str(&self.0.to_string_lossy())
58 | }
59 | }
60 |
61 | impl FromStr for DesktopHandler {
62 | type Err = Error;
63 | fn from_str(s: &str) -> Result {
64 | Ok(DesktopHandler(s.into()))
65 | }
66 | }
67 |
68 | impl Handleable for DesktopHandler {
69 | fn get_entry(&self, languages: &Languages) -> Result {
70 | DesktopEntry::parse_file(&Self::get_path(&self.0)?, languages)
71 | }
72 | }
73 |
74 | impl DesktopHandler {
75 | /// Create a DesktopHandler, skipping validity checks
76 | pub fn assume_valid(name: OsString) -> Self {
77 | Self(name)
78 | }
79 |
80 | /// Get the path of a given desktop entry file
81 | pub fn get_path(name: &std::ffi::OsStr) -> Result {
82 | if cfg!(test) {
83 | Ok(PathBuf::from(name))
84 | } else {
85 | let mut path = PathBuf::from("applications");
86 | path.push(name);
87 | Ok(xdg::BaseDirectories::new()?
88 | .find_data_file(path)
89 | .ok_or_else(|| {
90 | Error::NotFound(name.to_string_lossy().into())
91 | })?)
92 | }
93 | }
94 |
95 | /// Launch a DesktopHandler's desktop entry
96 | #[mutants::skip] // Cannot test directly, runs command
97 | pub fn launch(&self, config: &Config, args: Vec) -> Result<()> {
98 | info!("Launching `{}` with args: {:?}", self, args);
99 | self.get_entry(&config.languages)?
100 | .exec(config, ExecMode::Launch, args)
101 | }
102 |
103 | /// Issue a warning if the given handler is invalid
104 | pub fn warn_if_invalid(&self) {
105 | // This is just checking that the handler is valid
106 | // so we can assume that access to the language settings is unecessary
107 | if let Err(e) = self.get_entry(&Vec::new()) {
108 | warn!("The desktop entry `{}` is invalid: {}", self, e);
109 | }
110 | }
111 | }
112 |
113 | /// Represents a regex handler from the config
114 | #[derive(Display, Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
115 | #[display(fmt = "\"{}\" (Regex Handler)", exec)]
116 | pub struct RegexHandler {
117 | exec: String,
118 | #[serde(default)]
119 | terminal: bool,
120 | regexes: Vec,
121 | }
122 |
123 | impl RegexHandler {
124 | /// Test if a given path matches the handler's regex
125 | fn is_match(&self, path: &str) -> bool {
126 | let matches = self
127 | .regexes
128 | .iter()
129 | .filter(|regex| regex.is_match(path))
130 | .collect_vec();
131 |
132 | let is_match = !matches.is_empty();
133 |
134 | if is_match {
135 | debug!(
136 | "Regex matches found in `{}` for `{}`: {:?}",
137 | self, path, matches
138 | );
139 | } else {
140 | debug!("No regex matches found in `{}` for `{}`", self, path);
141 | }
142 |
143 | is_match
144 | }
145 | }
146 |
147 | impl Handleable for RegexHandler {
148 | fn get_entry(&self, _languages: &Languages) -> Result {
149 | Ok(DesktopEntry::fake_entry(&self.exec, self.terminal))
150 | }
151 | }
152 |
153 | /// Wrapper struct needed because `Regex` does not implement Eq, Hash, or Deserialize
154 | #[serde_as]
155 | #[derive(Debug, Clone, Deref, Deserialize)]
156 | struct Regex(#[serde_as(as = "DisplayFromStr")] lazy_regex::Regex);
157 |
158 | impl PartialEq for Regex {
159 | #[mutants::skip] // Trivial
160 | fn eq(&self, other: &Self) -> bool {
161 | self.as_str() == other.as_str()
162 | }
163 | }
164 |
165 | impl Eq for Regex {}
166 |
167 | impl Hash for Regex {
168 | #[mutants::skip] // Trivial
169 | fn hash(&self, state: &mut H) {
170 | self.as_str().hash(state);
171 | }
172 | }
173 |
174 | /// A collection of all of the defined RegexHandlers
175 | #[derive(Debug, Clone, Default, Deserialize)]
176 | pub struct RegexApps(Vec);
177 |
178 | impl RegexApps {
179 | /// Get a handler matching a given path
180 | pub fn get_handler(&self, path: &UserPath) -> Result {
181 | Ok(self
182 | .0
183 | .iter()
184 | .find(|app| app.is_match(&path.to_string()))
185 | .ok_or_else(|| Error::NotFound(path.to_string()))?
186 | .clone())
187 | }
188 | }
189 |
190 | #[cfg(test)]
191 | mod tests {
192 | use super::*;
193 | use crate::common::DesktopEntry;
194 | use url::Url;
195 |
196 | crate::logs_snapshot_test!(regex_handlers, {
197 | let exec: &str = "freetube %u";
198 | let regex_handler = RegexHandler {
199 | exec: String::from(exec),
200 | terminal: false,
201 | regexes: [String::from(
202 | r"(https://)?(www\.)?youtu(be\.com|\.be)/*",
203 | )]
204 | .iter()
205 | .map(|s| {
206 | Regex(
207 | lazy_regex::Regex::new(s)
208 | .expect("Hardcoded regex should be valid"),
209 | )
210 | })
211 | .collect(),
212 | };
213 |
214 | println!("{:#?}", regex_handler);
215 |
216 | let regex_apps = RegexApps(vec![regex_handler.clone()]);
217 |
218 | assert_eq!(
219 | regex_apps
220 | .get_handler(&UserPath::Url(Url::parse(
221 | "https://youtu.be/dQw4w9WgXcQ"
222 | )?))?
223 | .get_entry(&Vec::new())?,
224 | DesktopEntry {
225 | exec: exec.to_string(),
226 | terminal: false,
227 | ..Default::default()
228 | }
229 | );
230 |
231 | assert!(regex_apps
232 | .get_handler(&UserPath::Url(Url::parse(
233 | "https://en.wikipedia.org",
234 | )?))
235 | .is_err());
236 | });
237 | }
238 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__properly_assign_files_to_handlers.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding swayimg.desktop to list of handlers for `image/png`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `swayimg.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `image/png`: swayimg.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mupdf.desktop to list of handlers for `application/pdf`
11 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mupdf.desktop` is invalid: No such file or directory (os error 2)
12 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
13 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/pdf`: mupdf.desktop;
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
15 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/a.png`
16 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `image/png` in mimeapps.list Default Associations: swayimg.desktop;
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
18 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `image/png` in mimeapps.list Default Associations
20 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/a.pdf`
21 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `application/pdf` in mimeapps.list Default Associations: mupdf.desktop;
22 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
23 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
24 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `application/pdf` in mimeapps.list Default Associations
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/a.pdf`
26 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `application/pdf` in mimeapps.list Default Associations: mupdf.desktop;
27 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
28 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
29 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `application/pdf` in mimeapps.list Default Associations
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/a.png`
31 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `image/png` in mimeapps.list Default Associations: swayimg.desktop;
32 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
33 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
34 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `image/png` in mimeapps.list Default Associations
35 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/a.png`
36 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `image/png` in mimeapps.list Default Associations: swayimg.desktop;
37 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
38 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
39 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `image/png` in mimeapps.list Default Associations
40 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/b.png`
41 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `image/png` in mimeapps.list Default Associations: swayimg.desktop;
42 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
43 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
44 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `image/png` in mimeapps.list Default Associations
45 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/a.pdf`
46 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `application/pdf` in mimeapps.list Default Associations: mupdf.desktop;
47 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
48 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
49 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `application/pdf` in mimeapps.list Default Associations
50 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/a.pdf`
51 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `application/pdf` in mimeapps.list Default Associations: mupdf.desktop;
52 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
53 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
54 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `application/pdf` in mimeapps.list Default Associations
55 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/a.png`
56 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `image/png` in mimeapps.list Default Associations: swayimg.desktop;
57 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
58 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
59 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `image/png` in mimeapps.list Default Associations
60 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m No matching regex handlers found for `tests/assets/b.png`
61 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Configured handlers for `image/png` in mimeapps.list Default Associations: swayimg.desktop;
62 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Selector enabled: false, number of set handlers: 1
63 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::apps::user[0m[2m:[0m Not running selector, choosing first handler
64 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Match found for `image/png` in mimeapps.list Default Associations
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
handlr-regex
2 |
3 | Manage your default applications with ease using `handlr`! Now with the power of [regular expressions](#setting-regex-handlers)!
4 |
5 | Fork of the original [handlr](https://github.com/chmln/handlr)
6 |
7 | ## Features
8 |
9 | - Set default handler by extension or mime-type
10 | - Set arbitrary commands as handlers based on regular expressions
11 | - Intelligent mime type detection from files based on extension and content
12 | - Open multiple files at once
13 | - Set multiple handlers for mime/extension and use `rofi`/`dmenu` to pick one
14 | - Optional wildcard support like `text/*`
15 | - Automatically removes invalid/wrong `.desktop` entries from `mimeapps.list`
16 | - Helper commands like `launch`, `get --json`, `mime --json` for your scripting needs
17 | - Unnecessarily fast (written in Rust)
18 | - Single compiled binary with no dependencies
19 |
20 | ## Usage
21 |
22 | ```sh
23 | # Open a file/URL
24 | handlr open ~/.dotfiles/pacman/packages.txt
25 | handlr open https://google.ca
26 |
27 | # Set default handler for png files
28 | handlr set .png feh.desktop
29 |
30 | # Set wildcard handler for all text files
31 | handlr set 'text/*' nvim.desktop
32 | # Set wildcard handler for all OpenDocument formats
33 | # NOTE: startcenter.desktop is usually LibreOffice's main desktop entry
34 | handlr set 'application/vnd.oasis.opendocument.*' startcenter.desktop
35 |
36 | # Set default handler based on mime
37 | handlr set application/pdf evince.desktop
38 |
39 | # List default apps
40 | handlr list
41 |
42 | # Get the handler for a mime/extension
43 | $ handlr get .png
44 | feh.desktop
45 |
46 | # Launch a handler with given path/URL
47 | handlr launch x-scheme-handler/https -- https://google.ca
48 |
49 | # Get the mimetypes of given paths/URLs
50 | handlr mime https://duckduckgo.com . README.md
51 | ```
52 |
53 | ## Compared to `xdg-utils`
54 |
55 | - Can open multiple files/URLs at once
56 | - Can have multiple handlers and use rofi/dmenu to pick one at runtime
57 | - Far easier to use with simple commands like `get`, `set`, `list`
58 | - Can operate on extensions, **no need to look up or remember mime types**
59 | - useful for common tasks like setting a handler for png/docx/etc files
60 | - Superb autocomplete (currently fish, zsh and bash), including mimes, extensions, and `.desktop` files
61 | - Optional json output for scripting
62 | - Properly supports `Terminal=true` entries
63 |
64 | ## Setting default terminal
65 |
66 | Unfortunately, there isn't an XDG spec and thus a standardized way for `handlr` to get your default terminal emulator to run `Terminal=true` desktop entries. There was a proposal floating around a few years ago to use `x-scheme-handler/terminal` for this purpose. It seems to me the least worst option, compared to handling quirks of N+1 distros or using a handlr-specific config option.
67 |
68 | Now if `x-scheme-handler/terminal` is present, `handlr` will use it.
69 |
70 | Otherwise, `handlr` will find an app with `TerminalEmulator` category and use it instead.
71 |
72 | On the upside, `Terminal=true` entries will now work outside of interactive terminals, unlike `xdg-utils`.
73 |
74 | > [!NOTE]
75 | > For `handlr-regex` v0.11.2 and older, when `x-scheme-handler/terminal` is not present, this process is used instead:
76 | >
77 | > 1. Find an app with `TerminalEmulator` category
78 | > 2. Set it as the default for `x-scheme-handler/terminal`
79 | > 3. Send you a notification to let you know it guessed your terminal and provide instructions to change it if necessary
80 | >
81 | > This was changed in order to match how other mimetypes are handled and also so that `mimeapps.list` is not edited without the user's permission.
82 |
83 |
84 | ### Terminal emulator compatibility
85 | `handlr` should work with pretty much any terminal emulator.
86 |
87 | However, some slight configuration may be necessary depending on what command line arguments your terminal emulator supports or required to directly run a command in it.
88 |
89 | If it uses/supports `-e` (i.e. `xterm`, `xfce4-terminal`, `foot`, etc.) then you do not need to do anything.
90 |
91 | If it requires something else, then set `term_exec_args` in `~/.config/handlr/handlr.toml` to the necessary arguments like so:
92 |
93 | ```
94 | // Replace 'run' with whatever arguments you need
95 | term_exec_args = 'run'
96 | ```
97 |
98 | If it does not require any arguments or if its arguments are already included in its .desktop file, but it does not use `-e`, (i.e. `wezterm`, `kitty`, etc.) set `term_exec_args` to `''`.
99 |
100 | Feel free to open an issue or pull request if there's a better way to handle this.
101 |
102 | ## Setting multiple handlers
103 |
104 | 1) Open `~/.config/handlr/handlr.toml` and set `enable_selector = true`. Optionally, you can also tweak the `selector` to your selector command (using e.g. rofi or dmenu).
105 |
106 | 2) Add a second/third/whatever handler using `handlr add`, for example
107 | ```
108 | handlr add x-scheme-handler/https firefox-developer-edition.desktop
109 | ```
110 |
111 | 3) Now in this example when you open a URL, you will be prompted to select the desired application.
112 |
113 | 
114 |
115 | ## Setting regex handlers
116 |
117 | Inspired by a similar feature in [mimeo](https://xyne.dev/projects/mimeo/)
118 |
119 | Open `~/.config/handlr/handlr.toml` and add something like this:
120 | ```
121 | [[handlers]]
122 | exec = "freetube %u" # Uses desktop entry field codes
123 | terminal = false # Set to true for terminal apps, false for GUI apps (optional; defaults to false)
124 | regexes = ['(https://)?(www\.)?youtu(be\.com|\.be)/*.'] # Use single-quote literal strings
125 | ```
126 |
127 | For more information:
128 | * [desktop entry field codes](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables)
129 | * [regex reference](https://docs.rs/regex/latest/regex/#syntax)
130 |
131 | ## Smart table output
132 |
133 | Starting with v0.10.0, commands with table output (i.e. `handlr list` and `handlr mime`) switch to outputting tab-separated values when piped for use with commands like `cut`.
134 |
135 | ## Optional wildcards
136 |
137 | When `expand_wildcards` is set to `true` in `~/.config/handlr/handlr.toml`, rather than wildcard mimes being saved directly to `mimeapps.list`, they will be expanded into all matching mimetypes.
138 |
139 | This is off by default and will not automatically expand wildcards already present in `mimeapps.list` when enabled. Simply use `handlr remove` and `handlr add` to manually expand them.
140 |
141 | In addition, regardless of settings, literal wildcards are preferred when using `handlr remove` and `handlr unset`. (e.g. When using `handlr remove text/*`, if `text/*` is present, it will be removed, but `text/plain`, etc. will not be.)
142 |
143 | ## Completion scripts
144 |
145 | To generate a shell completion script, run `COMPLETE= handlr`, where `` is the name of the target shell (e.g. bash, zsh, fish, elvish, powershell, etc.). Note that this will only print it to stdout rather than creating a file or installing the script automatically.
146 |
147 | If you usually install `handlr-regex` from your distribution's repository, and you are not involved with packaging it, you probably do not need to worry about this.
148 |
149 | See [`clap_complete`'s documentation](https://docs.rs/clap_complete/latest/clap_complete/aot/enum.Shell.html) for the list of currently supported shells.
150 |
151 | > [!NOTE]
152 | > This currently relies on unstable features of the `clap_complete` crate and may potentially change in the future.
153 |
154 | ## Logging
155 |
156 | Since v0.13.0, `handlr-regex` supports logs to help with troubleshooting.
157 |
158 | Logs are output in three ways:
159 | - stderr
160 | - notifications (only when neither running in an interactive terminal nor with `--disable-notifications` active)
161 | - log file at `$XDG_CACHE_HOME/handlr/handlr.log`
162 |
163 | By default, for stderr and notifications, only warning and error logs are shown. This can be configured with `--verbose` and `--quiet` as well as with `$RUST_LOG`. All logs are printed to the log file regardless.
164 |
165 | For more information on the syntax for `$RUST_LOG`, see [the documentation for `tracing_subscriber::Envfilter`](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)
166 |
167 | ## Screenshots
168 |
169 |
170 |
171 | |
172 |
173 | |
174 |
175 | ## Installation
176 |
177 | ### Arch Linux
178 |
179 | ```sh
180 | sudo pacman -S handlr-regex
181 | ```
182 |
183 | Optionally, you can also use `handlr-regex` as a replacement for `xdg-open` by shadowing it with a script in `$HOME/.local/bin/` (or any other user-scoped `$PATH` directory). Use the following script as an example:
184 |
185 | ```sh
186 | #!/bin/sh
187 |
188 | handlr open "$@"
189 | ```
190 |
191 | ### Rust/Cargo
192 |
193 | ```sh
194 | cargo install handlr-regex
195 | ```
196 |
197 | ### Binaries
198 |
199 | 1. Download the latest [release binary](https://github.com/Anomalocaridid/handlr/releases) and put it somewhere in `$PATH`
200 | 2. Download completions for fish:
201 | ```sh
202 | curl https://raw.githubusercontent.com/Anomalocaridid/handlr/master/completions/handlr.fish --create-dirs -o ~/.config/fish/completions/handlr.fish
203 | ```
204 |
205 | ## Attribution
206 | Icons made by Eucalyp from www.flaticon.com
207 |
208 | Cover photo by [creativebloq.com](https://creativebloq.com)
209 |
--------------------------------------------------------------------------------
/src/common/desktop_entry.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | config::{Config, Languages},
3 | error::{Error, Result},
4 | };
5 | use freedesktop_entry_parser::Entry;
6 | use itertools::Itertools;
7 | use mime::Mime;
8 | use std::{ffi::OsString, path::Path, process::Stdio, str::FromStr};
9 | use tracing::debug;
10 |
11 | /// Represents a desktop entry file for an application
12 | #[derive(Debug, Clone, Default, PartialEq, Eq)]
13 | pub struct DesktopEntry {
14 | /// Name of the application
15 | pub name: String,
16 | /// Command to execute
17 | pub exec: String,
18 | /// Name of the desktop entry file
19 | pub file_name: OsString,
20 | /// Whether the program runs in a terminal window
21 | pub terminal: bool,
22 | /// The MIME type(s) supported by this application
23 | pub mime_type: Vec,
24 | /// Categories in which the entry should be shown in a menu
25 | pub categories: Vec,
26 | }
27 |
28 | /// Modes for running a DesktopFile's `exec` command
29 | #[derive(PartialEq, Eq, Copy, Clone)]
30 | pub enum Mode {
31 | /// Launch the command directly, passing arguments given to `handlr`
32 | Launch,
33 | /// Open files/urls passed to `handler` with the command
34 | Open,
35 | }
36 |
37 | impl DesktopEntry {
38 | /// Execute the command in `exec` in the given mode and with the given arguments
39 | #[mutants::skip] // Cannot test directly, runs external command
40 | pub fn exec(
41 | &self,
42 | config: &Config,
43 | mode: Mode,
44 | arguments: Vec,
45 | ) -> Result<()> {
46 | let supports_multiple =
47 | self.exec.contains("%F") || self.exec.contains("%U");
48 | if arguments.is_empty() {
49 | self.exec_inner(config, vec![])?
50 | } else if supports_multiple || mode == Mode::Launch {
51 | self.exec_inner(config, arguments)?;
52 | } else {
53 | for arg in arguments {
54 | self.exec_inner(config, vec![arg])?;
55 | }
56 | };
57 |
58 | Ok(())
59 | }
60 |
61 | /// Internal helper function for `exec`
62 | #[mutants::skip] // Cannot test directly, runs command
63 | fn exec_inner(&self, config: &Config, args: Vec) -> Result<()> {
64 | let cmd = self.get_cmd(config, args)?;
65 | debug!("Executing command: \"{}\"", cmd);
66 |
67 | let mut cmd = execute::command(cmd);
68 |
69 | if self.terminal && config.terminal_output {
70 | cmd.spawn()?.wait()?;
71 | } else {
72 | cmd.stdout(Stdio::null()).stderr(Stdio::null()).spawn()?;
73 | }
74 |
75 | Ok(())
76 | }
77 |
78 | /// Get the `exec` command, formatted with given arguments
79 | pub fn get_cmd(
80 | &self,
81 | config: &Config,
82 | args: Vec,
83 | ) -> Result {
84 | let special = lazy_regex::regex!("%(f|u)"i);
85 |
86 | let mut exec = self.exec.clone();
87 | let args = args.join(" ");
88 |
89 | if special.is_match(&exec) {
90 | exec = special.replace_all(&exec, args).to_string();
91 | } else {
92 | // The desktop entry doesn't contain arguments - we make best effort and append them at the end
93 | exec.push(' ');
94 | exec.push_str(&args);
95 | }
96 |
97 | // If the entry expects a terminal (emulator), but this process is not running in one, we launch a new one.
98 | if self.terminal && !config.terminal_output {
99 | let mut term_cmd = config.terminal()?;
100 | term_cmd.push(' ');
101 | term_cmd.push_str(&exec);
102 | exec = term_cmd;
103 | }
104 |
105 | Ok(exec.trim().to_string())
106 | }
107 |
108 | /// Parse a desktop entry file, given a path
109 | pub fn parse_file(
110 | path: &Path,
111 | languages: &Languages,
112 | ) -> Result {
113 | let fd_entry = Entry::parse_file(path)?;
114 | let fd_entry = fd_entry.section("Desktop Entry");
115 |
116 | let entry_error = |field_name: &str| -> Error {
117 | Error::BadEntry(path.to_path_buf(), field_name.to_string())
118 | };
119 |
120 | let entry = DesktopEntry {
121 | name: languages
122 | .iter()
123 | .filter_map(|lang| fd_entry.attr_with_param("Name", lang))
124 | .next()
125 | .or_else(|| fd_entry.attr("Name"))
126 | .ok_or(entry_error("Name"))?
127 | .to_string(),
128 | exec: fd_entry
129 | .attr("Exec")
130 | .ok_or(entry_error("Exec"))?
131 | .to_string(),
132 | file_name: path.file_name().unwrap_or_default().to_owned(),
133 | terminal: fd_entry
134 | .attr("Terminal")
135 | .and_then(|t| t.parse().ok())
136 | .unwrap_or(false),
137 | mime_type: fd_entry
138 | .attr("MimeType")
139 | .map(|m| {
140 | m.split(';')
141 | .filter(|s| !s.is_empty()) // Account for ending/duplicated semicolons
142 | .unique() // Remove duplicate entries
143 | .filter_map(|m| Mime::from_str(m).ok())
144 | .collect_vec()
145 | })
146 | .unwrap_or_default(),
147 | categories: fd_entry
148 | .attr("Categories")
149 | .map(|c| {
150 | c.split(';')
151 | .filter(|s| !s.is_empty()) // Account for ending/duplicated semicolons
152 | .unique() // Remove duplicate entries
153 | .map(|c| c.to_string())
154 | .collect_vec()
155 | })
156 | .unwrap_or_default(),
157 | };
158 |
159 | if entry.name.is_empty() {
160 | Err(entry_error("Name"))
161 | } else if entry.exec.is_empty() {
162 | Err(entry_error("Exec"))
163 | } else {
164 | Ok(entry)
165 | }
166 | }
167 |
168 | /// Make a fake DesktopEntry given only a value for exec and terminal.
169 | /// All other keys will have default values.
170 | pub fn fake_entry(exec: &str, terminal: bool) -> DesktopEntry {
171 | DesktopEntry {
172 | exec: exec.to_owned(),
173 | terminal,
174 | ..Default::default()
175 | }
176 | }
177 |
178 | /// Check if the given desktop entry represents a terminal emulator
179 | pub fn is_terminal_emulator(&self) -> bool {
180 | self.categories.contains(&"TerminalEmulator".to_string())
181 | }
182 | }
183 |
184 | #[cfg(test)]
185 | mod tests {
186 | use std::path::PathBuf;
187 |
188 | use super::*;
189 | use crate::common::DesktopHandler;
190 | use similar_asserts::assert_eq;
191 |
192 | // Helper function to test getting the command from the Exec field
193 | fn test_get_cmd(
194 | entry: &DesktopEntry,
195 | config: &Config,
196 | expected_command: &str,
197 | ) -> Result<()> {
198 | assert_eq!(
199 | entry.get_cmd(config, vec!["test".to_string()])?,
200 | expected_command
201 | );
202 | Ok(())
203 | }
204 |
205 | #[test]
206 | fn complex_exec() -> Result<()> {
207 | // Note that this entry also has no category key
208 | let entry = DesktopEntry::parse_file(
209 | &PathBuf::from("tests/assets/cmus.desktop"),
210 | &Vec::new(),
211 | )?;
212 | assert_eq!(entry.mime_type.len(), 2);
213 | assert_eq!(entry.mime_type[0].essence_str(), "audio/mp3");
214 | assert_eq!(entry.mime_type[1].essence_str(), "audio/ogg");
215 | assert!(!entry.is_terminal_emulator());
216 |
217 | test_get_cmd(
218 | &entry,
219 | &Config::default(),
220 | "bash -c \"(! pgrep cmus && tilix -e cmus && tilix -a session-add-down -e cava); sleep 0.1 && cmus-remote -q test\""
221 | )
222 | }
223 |
224 | #[test]
225 | fn terminal_emulator() -> Result<()> {
226 | let entry = DesktopEntry::parse_file(
227 | &PathBuf::from("tests/assets/org.wezfurlong.wezterm.desktop"),
228 | &Vec::new(),
229 | )?;
230 | assert!(entry.mime_type.is_empty());
231 | assert!(entry.is_terminal_emulator());
232 |
233 | test_get_cmd(&entry, &Config::default(), "wezterm start --cwd . test")
234 | }
235 |
236 | #[test]
237 | fn invalid_desktop_entries() -> Result<()> {
238 | let languages = Vec::new();
239 |
240 | let empty_name = DesktopEntry::parse_file(
241 | &PathBuf::from("tests/assets/empty_name.desktop"),
242 | &languages,
243 | );
244 |
245 | assert!(empty_name.is_err());
246 |
247 | let empty_exec = DesktopEntry::parse_file(
248 | &PathBuf::from("tests/assets/empty_exec.desktop"),
249 | &languages,
250 | );
251 |
252 | assert!(empty_exec.is_err());
253 |
254 | Ok(())
255 | }
256 |
257 | #[test]
258 | fn terminal_application_command() -> Result<()> {
259 | let mut config = Config::default();
260 |
261 | config.terminal_output = false;
262 |
263 | config.add_handler(
264 | &Mime::from_str("x-scheme-handler/terminal")?,
265 | &DesktopHandler::assume_valid(
266 | "tests/assets/org.wezfurlong.wezterm.desktop".into(),
267 | ),
268 | )?;
269 |
270 | let entry = DesktopEntry::parse_file(
271 | &PathBuf::from("tests/assets/Helix.desktop"),
272 | &Vec::new(),
273 | )?;
274 |
275 | test_get_cmd(&entry, &config, "wezterm start --cwd . -e hx test")
276 | }
277 |
278 | /// Helper function for testing language support
279 | fn lang_test(languages: &[&str], expected_name: &str) -> Result<()> {
280 | let entry = DesktopEntry::parse_file(
281 | &PathBuf::from("tests/assets/vlc.desktop"),
282 | &languages.iter().map(|s| s.to_string()).collect_vec(),
283 | )?;
284 |
285 | assert_eq!(entry.name, expected_name);
286 |
287 | Ok(())
288 | }
289 |
290 | #[test]
291 | fn language_support() -> Result<()> {
292 | // No languages
293 | lang_test(&[], "VLC media player")?;
294 |
295 | // Just one language
296 | lang_test(&["es"], "Reproductor multimedia VLC")?;
297 |
298 | // Multiple languages
299 | lang_test(&["ja", "fr", "nl"], "VLCメディアプレイヤー")?;
300 |
301 | // No valid languages
302 | lang_test(&["qwert", "yuiop"], "VLC media player")?;
303 |
304 | // Some invalid languages
305 | lang_test(&["asdfg", "hjkl?;", "bn", "hu"], "VLC মিডিয়া প্লেয়ার")?;
306 | lang_test(&["zxcv", "pa", "it", "ru"], "VLC ਮੀਡਿਆ ਪਲੇਅਰ")?;
307 |
308 | Ok(())
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_json-3.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/mp4`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/mp4`: mpv.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/asdf`
11 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
12 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
13 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/asdf`: mpv.desktop;
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
15 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding brave.desktop to list of handlers for `video/webm`
16 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `brave.desktop` is invalid: No such file or directory (os error 2)
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
18 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/webm`: brave.desktop;
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
20 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding helix.desktop to list of handlers for `text/plain`
21 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `helix.desktop` is invalid: No such file or directory (os error 2)
22 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
23 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;
24 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
26 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
27 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
28 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;
29 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding kakoune.desktop to list of handlers for `text/plain`
31 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `kakoune.desktop` is invalid: No such file or directory (os error 2)
32 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
33 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;kakoune.desktop;
34 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
35 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.oasis.opendocument.*`
36 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
37 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
38 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.oasis.opendocument.*`: startcenter.desktop;
39 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
40 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.openxmlformats-officedocument.*`
41 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
42 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
43 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.openxmlformats-officedocument.*`: startcenter.desktop;
44 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
45 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Printing associations
46 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: true
47 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished printing associations
48 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/mp4`
49 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
50 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
51 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/mp4`: mpv.desktop;
52 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
53 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/asdf`
54 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
55 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
56 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/asdf`: mpv.desktop;
57 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
58 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding brave.desktop to list of handlers for `video/webm`
59 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `brave.desktop` is invalid: No such file or directory (os error 2)
60 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
61 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/webm`: brave.desktop;
62 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
63 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding helix.desktop to list of handlers for `text/plain`
64 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `helix.desktop` is invalid: No such file or directory (os error 2)
65 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
66 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;
67 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
68 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
69 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
70 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
71 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;
72 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
73 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding kakoune.desktop to list of handlers for `text/plain`
74 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `kakoune.desktop` is invalid: No such file or directory (os error 2)
75 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
76 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;kakoune.desktop;
77 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
78 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.oasis.opendocument.*`
79 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
80 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
81 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.oasis.opendocument.*`: startcenter.desktop;
82 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
83 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.openxmlformats-officedocument.*`
84 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
85 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
86 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.openxmlformats-officedocument.*`: startcenter.desktop;
87 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
88 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Printing associations
89 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: true
90 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished printing associations
91 |
--------------------------------------------------------------------------------
/src/config/snapshots/handlr__config__main_config__tests__print_handlers_detailed_json-3.snap:
--------------------------------------------------------------------------------
1 | ---
2 | source: src/config/main_config.rs
3 | expression: "String :: from_utf8(buffer).expect(\"Buffer is invalid utf8\")"
4 | ---
5 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/mp4`
6 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
7 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
8 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/mp4`: mpv.desktop;
9 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
10 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/asdf`
11 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
12 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
13 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/asdf`: mpv.desktop;
14 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
15 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding brave.desktop to list of handlers for `video/webm`
16 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `brave.desktop` is invalid: No such file or directory (os error 2)
17 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
18 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/webm`: brave.desktop;
19 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
20 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding helix.desktop to list of handlers for `text/plain`
21 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `helix.desktop` is invalid: No such file or directory (os error 2)
22 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
23 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;
24 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
25 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
26 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
27 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
28 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;
29 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
30 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding kakoune.desktop to list of handlers for `text/plain`
31 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `kakoune.desktop` is invalid: No such file or directory (os error 2)
32 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
33 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;kakoune.desktop;
34 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
35 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.oasis.opendocument.*`
36 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
37 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
38 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.oasis.opendocument.*`: startcenter.desktop;
39 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
40 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.openxmlformats-officedocument.*`
41 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
42 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
43 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.openxmlformats-officedocument.*`: startcenter.desktop;
44 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
45 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Printing associations
46 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: true
47 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished printing associations
48 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/mp4`
49 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
50 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
51 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/mp4`: mpv.desktop;
52 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
53 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding mpv.desktop to list of handlers for `video/asdf`
54 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `mpv.desktop` is invalid: No such file or directory (os error 2)
55 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
56 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/asdf`: mpv.desktop;
57 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
58 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding brave.desktop to list of handlers for `video/webm`
59 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `brave.desktop` is invalid: No such file or directory (os error 2)
60 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
61 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `video/webm`: brave.desktop;
62 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
63 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding helix.desktop to list of handlers for `text/plain`
64 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `helix.desktop` is invalid: No such file or directory (os error 2)
65 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
66 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;
67 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
68 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding nvim.desktop to list of handlers for `text/plain`
69 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `nvim.desktop` is invalid: No such file or directory (os error 2)
70 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
71 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;
72 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
73 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding kakoune.desktop to list of handlers for `text/plain`
74 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `kakoune.desktop` is invalid: No such file or directory (os error 2)
75 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
76 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `text/plain`: helix.desktop;nvim.desktop;kakoune.desktop;
77 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
78 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.oasis.opendocument.*`
79 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
80 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
81 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.oasis.opendocument.*`: startcenter.desktop;
82 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
83 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Adding startcenter.desktop to list of handlers for `application/vnd.openxmlformats-officedocument.*`
84 | [2m[TIMESTAMP][0m [33m WARN[0m [2mhandlr::common::handler[0m[2m:[0m The desktop entry `startcenter.desktop` is invalid: No such file or directory (os error 2)
85 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m Expanding wildcards in mimeapps.list: false
86 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::apps::user[0m[2m:[0m New handlers for `application/vnd.openxmlformats-officedocument.*`: startcenter.desktop;
87 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished adding handler
88 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Printing associations
89 | [2m[TIMESTAMP][0m [34mDEBUG[0m [2mhandlr::config::main_config[0m[2m:[0m JSON output: true
90 | [2m[TIMESTAMP][0m [32m INFO[0m [2mhandlr::config::main_config[0m[2m:[0m Finished printing associations
91 |
--------------------------------------------------------------------------------