mirror of
https://github.com/chmln/handlr.git
synced 2024-11-23 09:41:45 +01:00
Implement config and multi-handler selection
This commit is contained in:
parent
935d1daa87
commit
9d726cb6af
9 changed files with 191 additions and 80 deletions
32
Cargo.lock
generated
32
Cargo.lock
generated
|
@ -185,6 +185,17 @@ dependencies = [
|
|||
"syn 1.0.30",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "confy"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2913470204e9e8498a0f31f17f90a0de801ae92c8c5ac18c49af4819e6786697"
|
||||
dependencies = [
|
||||
"directories",
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
|
@ -237,6 +248,16 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "1.0.5"
|
||||
|
@ -425,6 +446,7 @@ dependencies = [
|
|||
"ascii_table",
|
||||
"atty",
|
||||
"clap",
|
||||
"confy",
|
||||
"itertools",
|
||||
"json",
|
||||
"mime",
|
||||
|
@ -434,6 +456,7 @@ dependencies = [
|
|||
"pest",
|
||||
"pest_derive",
|
||||
"regex",
|
||||
"serde",
|
||||
"shlex",
|
||||
"thiserror",
|
||||
"url",
|
||||
|
@ -1506,6 +1529,15 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.0"
|
||||
|
|
|
@ -24,6 +24,8 @@ mime-db = "0.1.5"
|
|||
xdg-mime = { git = "https://github.com/ebassi/xdg-mime-rs" }
|
||||
atty = "0.2.14"
|
||||
notify-rust = "4.0.0-rc.1"
|
||||
confy = "0.4.0"
|
||||
serde = "1.0.111"
|
||||
|
||||
[profile.release]
|
||||
opt-level=3
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use crate::{apps::SystemApps, DesktopEntry, Error, Handler, Result};
|
||||
use crate::{
|
||||
apps::SystemApps,
|
||||
common::{DesktopEntry, Handler},
|
||||
Error, Result,
|
||||
};
|
||||
use mime::Mime;
|
||||
use pest::Parser;
|
||||
use std::{
|
||||
|
@ -39,12 +43,28 @@ impl MimeApps {
|
|||
Ok(())
|
||||
}
|
||||
pub fn get_handler(&self, mime: &Mime) -> Result<Handler> {
|
||||
self.default_apps
|
||||
.get(mime)
|
||||
.or_else(|| self.added_associations.get(mime))
|
||||
.map(|hs| hs.get(0).unwrap().clone())
|
||||
.or_else(|| self.system_apps.get_handler(mime))
|
||||
.ok_or(Error::NotFound(mime.to_string()))
|
||||
let config = crate::config::Config::load()?;
|
||||
|
||||
match self.default_apps.get(mime) {
|
||||
Some(handlers) if config.enable_selector && handlers.len() > 1 => {
|
||||
let handlers = handlers
|
||||
.into_iter()
|
||||
.map(|h| (h, h.get_entry().unwrap().name))
|
||||
.collect::<Vec<_>>();
|
||||
let selected =
|
||||
config.select(handlers.iter().map(|h| h.1.clone()))?;
|
||||
let selected =
|
||||
handlers.into_iter().find(|h| h.1 == selected).unwrap().0;
|
||||
Ok(selected.clone())
|
||||
}
|
||||
Some(handlers) => Ok(handlers.get(0).unwrap().clone()),
|
||||
None => self
|
||||
.added_associations
|
||||
.get(mime)
|
||||
.map(|h| h.get(0).unwrap().clone())
|
||||
.or_else(|| self.system_apps.get_handler(mime))
|
||||
.ok_or(Error::NotFound(mime.to_string())),
|
||||
}
|
||||
}
|
||||
pub fn show_handler(&self, mime: &Mime, output_json: bool) -> Result<()> {
|
||||
let handler = self.get_handler(mime)?;
|
||||
|
|
53
src/cli.rs
Normal file
53
src/cli.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use crate::common::{Handler, MimeOrExtension};
|
||||
|
||||
#[derive(clap::Clap)]
|
||||
#[clap(global_setting = clap::AppSettings::DeriveDisplayOrder)]
|
||||
#[clap(global_setting = clap::AppSettings::DisableHelpSubcommand)]
|
||||
#[clap(version = clap::crate_version!())]
|
||||
pub enum Cmd {
|
||||
/// List default apps and the associated handlers
|
||||
List,
|
||||
|
||||
/// Open a path/URL with its default handler
|
||||
Open {
|
||||
#[clap(required = true)]
|
||||
paths: Vec<String>,
|
||||
},
|
||||
|
||||
/// Set the default handler for mime/extension
|
||||
Set {
|
||||
mime: MimeOrExtension,
|
||||
handler: Handler,
|
||||
},
|
||||
|
||||
/// Unset the default handler for mime/extension
|
||||
Unset { mime: MimeOrExtension },
|
||||
|
||||
/// Launch the handler for specified extension/mime with optional arguments
|
||||
Launch {
|
||||
mime: MimeOrExtension,
|
||||
args: Vec<String>,
|
||||
},
|
||||
|
||||
/// Get handler for this mime/extension
|
||||
Get {
|
||||
#[clap(long)]
|
||||
json: bool,
|
||||
mime: MimeOrExtension,
|
||||
},
|
||||
|
||||
/// Add a handler for given mime/extension
|
||||
/// Note that the first handler is the default
|
||||
Add {
|
||||
mime: MimeOrExtension,
|
||||
handler: Handler,
|
||||
},
|
||||
|
||||
#[clap(setting = clap::AppSettings::Hidden)]
|
||||
Autocomplete {
|
||||
#[clap(short)]
|
||||
desktop_files: bool,
|
||||
#[clap(short)]
|
||||
mimes: bool,
|
||||
},
|
||||
}
|
|
@ -8,9 +8,9 @@ use std::{
|
|||
|
||||
// A mime derived from a path or URL
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FlexibleMime(pub Mime);
|
||||
pub struct MimeType(pub Mime);
|
||||
|
||||
impl TryFrom<&str> for FlexibleMime {
|
||||
impl TryFrom<&str> for MimeType {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(arg: &str) -> Result<Self> {
|
||||
|
@ -26,7 +26,7 @@ impl TryFrom<&str> for FlexibleMime {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Path> for FlexibleMime {
|
||||
impl TryFrom<&Path> for MimeType {
|
||||
type Error = Error;
|
||||
fn try_from(path: &Path) -> Result<Self> {
|
||||
let guess = SHARED_MIME_DB.guess_mime_type().path(path).guess();
|
||||
|
@ -47,7 +47,7 @@ impl FromStr for MimeOrExtension {
|
|||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if s.starts_with(".") {
|
||||
Ok(Self(FlexibleMime::try_from(s)?.0))
|
||||
Ok(Self(MimeType::try_from(s)?.0))
|
||||
} else {
|
||||
Ok(Self(Mime::from_str(s)?))
|
||||
}
|
||||
|
@ -69,11 +69,11 @@ mod tests {
|
|||
#[test]
|
||||
fn from_path_with_extension() {
|
||||
assert_eq!(
|
||||
FlexibleMime::try_from(".pdf").unwrap().0,
|
||||
MimeType::try_from(".pdf").unwrap().0,
|
||||
mime::APPLICATION_PDF
|
||||
);
|
||||
assert_eq!(
|
||||
FlexibleMime::try_from(".").unwrap().0.essence_str(),
|
||||
MimeType::try_from(".").unwrap().0.essence_str(),
|
||||
"inode/directory"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,4 +6,4 @@ mod mime_types;
|
|||
pub use self::db::{autocomplete as db_autocomplete, SHARED_MIME_DB};
|
||||
pub use desktop_entry::{DesktopEntry, Rule as PestRule};
|
||||
pub use handler::Handler;
|
||||
pub use mime_types::{FlexibleMime, MimeOrExtension};
|
||||
pub use mime_types::{MimeOrExtension, MimeType};
|
||||
|
|
55
src/config.rs
Normal file
55
src/config.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use crate::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub enable_selector: bool,
|
||||
pub selector: String,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
enable_selector: false,
|
||||
selector: "rofi -dmenu".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Self> {
|
||||
Ok(confy::load("handlr")?)
|
||||
}
|
||||
|
||||
pub fn select<O: Iterator<Item = String>>(
|
||||
&self,
|
||||
mut opts: O,
|
||||
) -> Result<String> {
|
||||
use itertools::Itertools;
|
||||
use std::{
|
||||
io::prelude::*,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
let process = {
|
||||
let mut split = shlex::split(&self.selector).unwrap();
|
||||
let (cmd, args) = (split.remove(0), split);
|
||||
Command::new(cmd)
|
||||
.args(args)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?
|
||||
};
|
||||
|
||||
process
|
||||
.stdin
|
||||
.unwrap()
|
||||
.write_all(opts.join("\n").as_bytes())?;
|
||||
|
||||
let mut output = String::with_capacity(24);
|
||||
process.stdout.unwrap().read_to_string(&mut output)?;
|
||||
let output = output.trim_end().to_owned();
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@ pub enum Error {
|
|||
Notify(#[from] notify_rust::error::Error),
|
||||
#[error(transparent)]
|
||||
Xdg(#[from] xdg::BaseDirectoriesError),
|
||||
#[error(transparent)]
|
||||
Config(#[from] confy::ConfyError),
|
||||
#[error("no handler defined for this mime/extension")]
|
||||
NotFound(String),
|
||||
#[error("could not figure out the mime type .{0}")]
|
||||
|
|
79
src/main.rs
79
src/main.rs
|
@ -1,68 +1,19 @@
|
|||
use clap::Clap;
|
||||
use error::{Error, Result};
|
||||
use notify_rust::Notification;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
mod apps;
|
||||
mod cli;
|
||||
mod common;
|
||||
mod config;
|
||||
mod error;
|
||||
|
||||
use common::{DesktopEntry, FlexibleMime, Handler, MimeOrExtension};
|
||||
|
||||
#[derive(Clap)]
|
||||
#[clap(global_setting = clap::AppSettings::DeriveDisplayOrder)]
|
||||
#[clap(global_setting = clap::AppSettings::DisableHelpSubcommand)]
|
||||
#[clap(version = clap::crate_version!())]
|
||||
enum Cmd {
|
||||
/// List default apps and the associated handlers
|
||||
List,
|
||||
|
||||
/// Open a path/URL with its default handler
|
||||
Open {
|
||||
#[clap(required = true)]
|
||||
path: Vec<String>,
|
||||
},
|
||||
|
||||
/// Set the default handler for mime/extension
|
||||
Set {
|
||||
mime: MimeOrExtension,
|
||||
handler: Handler,
|
||||
},
|
||||
|
||||
/// Unset the default handler for mime/extension
|
||||
Unset { mime: MimeOrExtension },
|
||||
|
||||
/// Launch the handler for specified extension/mime with optional arguments
|
||||
Launch {
|
||||
mime: MimeOrExtension,
|
||||
args: Vec<String>,
|
||||
},
|
||||
|
||||
/// Get handler for this mime/extension
|
||||
Get {
|
||||
#[clap(long)]
|
||||
json: bool,
|
||||
mime: MimeOrExtension,
|
||||
},
|
||||
|
||||
/// Add a handler for given mime/extension
|
||||
/// Note that the first handler is the default
|
||||
Add {
|
||||
mime: MimeOrExtension,
|
||||
handler: Handler,
|
||||
},
|
||||
|
||||
#[clap(setting = clap::AppSettings::Hidden)]
|
||||
Autocomplete {
|
||||
#[clap(short)]
|
||||
desktop_files: bool,
|
||||
#[clap(short)]
|
||||
mimes: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
use clap::Clap;
|
||||
use cli::Cmd;
|
||||
use common::MimeType;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
let mut apps = apps::MimeApps::read()?;
|
||||
crate::config::Config::load()?;
|
||||
|
||||
let res = || -> Result<()> {
|
||||
match Cmd::parse() {
|
||||
|
@ -78,14 +29,9 @@ fn main() -> Result<()> {
|
|||
Cmd::Get { mime, json } => {
|
||||
apps.show_handler(&mime.0, json)?;
|
||||
}
|
||||
Cmd::Open { path } => {
|
||||
std::process::Command::new("notify-send")
|
||||
.arg(&format!("{:?}", path))
|
||||
.spawn()?;
|
||||
apps.get_handler(
|
||||
&FlexibleMime::try_from(path.get(0).unwrap().as_str())?.0,
|
||||
)?
|
||||
.launch(path)?;
|
||||
Cmd::Open { paths } => {
|
||||
let mime = MimeType::try_from(paths[0].as_str())?.0;
|
||||
apps.get_handler(&mime)?.launch(paths)?;
|
||||
}
|
||||
Cmd::List => {
|
||||
apps.print()?;
|
||||
|
@ -110,12 +56,13 @@ fn main() -> Result<()> {
|
|||
match (res, atty::is(atty::Stream::Stdout)) {
|
||||
(Err(e), true) => eprintln!("{}", e),
|
||||
(Err(e), false) => {
|
||||
Notification::new()
|
||||
notify_rust::Notification::new()
|
||||
.summary("handlr error")
|
||||
.body(&e.to_string())
|
||||
.show()?;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue