mirror of
https://github.com/chmln/handlr.git
synced 2025-04-22 16:43:03 +02:00
Refactor
This commit is contained in:
parent
11db7ab94e
commit
b5468c1ec7
11 changed files with 124 additions and 76 deletions
|
@ -91,10 +91,12 @@ impl MimeApps {
|
||||||
let handler = self.get_handler(mime)?;
|
let handler = self.get_handler(mime)?;
|
||||||
let output = if output_json {
|
let output = if output_json {
|
||||||
let entry = handler.get_entry()?;
|
let entry = handler.get_entry()?;
|
||||||
|
let cmd = entry.get_cmd(vec![])?;
|
||||||
|
|
||||||
(json::object! {
|
(json::object! {
|
||||||
handler: handler.to_string(),
|
handler: handler.to_string(),
|
||||||
name: entry.name.as_str(),
|
name: entry.name.as_str(),
|
||||||
cmd: entry.get_cmd(vec![])?.0
|
cmd: cmd.0 + " " + &cmd.1.join(" "),
|
||||||
})
|
})
|
||||||
.to_string()
|
.to_string()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::common::{Handler, MimeOrExtension};
|
use crate::common::{Handler, MimeOrExtension, UserPath};
|
||||||
|
|
||||||
#[derive(clap::Clap)]
|
#[derive(clap::Clap)]
|
||||||
#[clap(global_setting = clap::AppSettings::DeriveDisplayOrder)]
|
#[clap(global_setting = clap::AppSettings::DeriveDisplayOrder)]
|
||||||
|
@ -14,7 +14,7 @@ pub enum Cmd {
|
||||||
/// Open a path/URL with its default handler
|
/// Open a path/URL with its default handler
|
||||||
Open {
|
Open {
|
||||||
#[clap(required = true)]
|
#[clap(required = true)]
|
||||||
paths: Vec<String>,
|
paths: Vec<UserPath>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Set the default handler for mime/extension
|
/// Set the default handler for mime/extension
|
||||||
|
|
|
@ -4,6 +4,7 @@ static CUSTOM_MIMES: &[&'static str] = &[
|
||||||
"inode/directory",
|
"inode/directory",
|
||||||
"x-scheme-handler/http",
|
"x-scheme-handler/http",
|
||||||
"x-scheme-handler/https",
|
"x-scheme-handler/https",
|
||||||
|
"x-scheme-handler/terminal",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn autocomplete() -> Result<()> {
|
pub fn autocomplete() -> Result<()> {
|
||||||
|
|
|
@ -59,38 +59,50 @@ impl DesktopEntry {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn get_cmd(&self, args: Vec<String>) -> Result<(String, Vec<String>)> {
|
pub fn get_cmd(&self, args: Vec<String>) -> Result<(String, Vec<String>)> {
|
||||||
let cmd_patterns = &["%f", "%F", "%u", "%U"];
|
let special =
|
||||||
let special = AhoCorasick::new_auto_configured(cmd_patterns);
|
AhoCorasick::new_auto_configured(&["%f", "%F", "%u", "%U"]);
|
||||||
|
|
||||||
let mut split = shlex::split(&self.exec)
|
let mut exec = shlex::split(&self.exec).unwrap();
|
||||||
.unwrap()
|
|
||||||
|
// The desktop entry doesn't contain arguments - we make best effort and append them at
|
||||||
|
// the end
|
||||||
|
if special.is_match(&self.exec) {
|
||||||
|
exec = exec
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|s| match s.as_str() {
|
.flat_map(|s| match s.as_str() {
|
||||||
"%f" | "%F" | "%u" | "%U" => args.clone(),
|
"%f" | "%F" | "%u" | "%U" => args.clone(),
|
||||||
s if special.is_match(s) => vec![{
|
s if special.is_match(s) => vec![{
|
||||||
let mut replaced = String::with_capacity(s.len());
|
let mut replaced =
|
||||||
special.replace_all_with(s, &mut replaced, |_, _, dst| {
|
String::with_capacity(s.len() + args.len() * 2);
|
||||||
|
special.replace_all_with(
|
||||||
|
s,
|
||||||
|
&mut replaced,
|
||||||
|
|_, _, dst| {
|
||||||
dst.push_str(args.clone().join(" ").as_str());
|
dst.push_str(args.clone().join(" ").as_str());
|
||||||
false
|
false
|
||||||
});
|
},
|
||||||
|
);
|
||||||
replaced
|
replaced
|
||||||
}],
|
}],
|
||||||
_ => vec![s],
|
_ => vec![s],
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect()
|
||||||
|
} else {
|
||||||
|
exec.extend_from_slice(&args);
|
||||||
|
}
|
||||||
|
|
||||||
// If the entry expects a terminal (emulator), but this process is not running in one, we
|
// If the entry expects a terminal (emulator), but this process is not running in one, we
|
||||||
// launch a new one.
|
// launch a new one.
|
||||||
if self.terminal && !atty::is(atty::Stream::Stdout) {
|
if self.terminal && !atty::is(atty::Stream::Stdout) {
|
||||||
split = shlex::split(&crate::config::Config::terminal()?)
|
exec = shlex::split(&crate::config::Config::terminal()?)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(vec!["-e".to_owned()])
|
.chain(vec!["-e".to_owned()])
|
||||||
.chain(split.into_iter())
|
.chain(exec)
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((split.remove(0), split))
|
Ok((exec.remove(0), exec))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,18 @@ use crate::{
|
||||||
common::{DesktopEntry, ExecMode},
|
common::{DesktopEntry, ExecMode},
|
||||||
Error, Result,
|
Error, Result,
|
||||||
};
|
};
|
||||||
use std::{convert::TryFrom, ffi::OsString, path::PathBuf};
|
use std::{fmt::Display, convert::TryFrom, ffi::OsString, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Handler(OsString);
|
pub struct Handler(OsString);
|
||||||
|
|
||||||
impl std::fmt::Display for Handler {
|
impl Display for Handler {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(&self.0.to_string_lossy())
|
f.write_str(&self.0.to_string_lossy())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Handler {
|
impl FromStr for Handler {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Self::resolve(s.into())
|
Self::resolve(s.into())
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use std::{
|
use std::{convert::TryFrom, path::Path, str::FromStr};
|
||||||
convert::TryFrom,
|
use url::Url;
|
||||||
path::{Path},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
// A mime derived from a path or URL
|
// A mime derived from a path or URL
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
@ -24,22 +21,27 @@ impl MimeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for MimeType {
|
impl From<&Url> for MimeType {
|
||||||
type Error = Error;
|
fn from(url: &Url) -> Self {
|
||||||
|
Self(
|
||||||
fn try_from(arg: &str) -> Result<Self> {
|
|
||||||
match url::Url::parse(arg) {
|
|
||||||
Ok(url) if url.scheme() == "file" => {
|
|
||||||
Self::try_from(Path::new(url.path()))
|
|
||||||
}
|
|
||||||
Ok(url) => Ok(Self(
|
|
||||||
format!("x-scheme-handler/{}", url.scheme())
|
format!("x-scheme-handler/{}", url.scheme())
|
||||||
.parse::<Mime>()
|
.parse::<Mime>()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)),
|
)
|
||||||
Err(_) => Self::try_from(Path::new(arg)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Path> for MimeType {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(path: &Path) -> Result<Self> {
|
||||||
|
let db = xdg_mime::SharedMimeInfo::new();
|
||||||
|
let guess = db.guess_mime_type().path(&path).guess();
|
||||||
|
|
||||||
|
let mime = mime_to_option(guess.mime_type().clone())
|
||||||
|
.ok_or_else(|| Error::Ambiguous(path.to_owned()))?;
|
||||||
|
|
||||||
|
Ok(Self(mime))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mime_to_option(mime: Mime) -> Option<Mime> {
|
fn mime_to_option(mime: Mime) -> Option<Mime> {
|
||||||
|
@ -50,20 +52,6 @@ fn mime_to_option(mime: Mime) -> Option<Mime> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&Path> for MimeType {
|
|
||||||
type Error = Error;
|
|
||||||
fn try_from(path: &Path) -> Result<Self> {
|
|
||||||
let db = xdg_mime::SharedMimeInfo::new();
|
|
||||||
|
|
||||||
let guess = db.guess_mime_type().path(&path).guess();
|
|
||||||
|
|
||||||
let mime = mime_to_option(guess.mime_type().clone())
|
|
||||||
.ok_or_else(|| Error::Ambiguous(path.to_owned()))?;
|
|
||||||
|
|
||||||
Ok(Self(mime))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mime derived from user input: extension(.pdf) or type like image/jpg
|
// Mime derived from user input: extension(.pdf) or type like image/jpg
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MimeOrExtension(pub Mime);
|
pub struct MimeOrExtension(pub Mime);
|
||||||
|
|
|
@ -2,8 +2,10 @@ mod db;
|
||||||
mod desktop_entry;
|
mod desktop_entry;
|
||||||
mod handler;
|
mod handler;
|
||||||
mod mime_types;
|
mod mime_types;
|
||||||
|
mod path;
|
||||||
|
|
||||||
pub use self::db::autocomplete as db_autocomplete;
|
pub use self::db::autocomplete as db_autocomplete;
|
||||||
pub use desktop_entry::{DesktopEntry, Mode as ExecMode};
|
pub use desktop_entry::{DesktopEntry, Mode as ExecMode};
|
||||||
pub use handler::Handler;
|
pub use handler::Handler;
|
||||||
pub use mime_types::{MimeOrExtension, MimeType};
|
pub use mime_types::{MimeOrExtension, MimeType};
|
||||||
|
pub use path::UserPath;
|
||||||
|
|
51
src/common/path.rs
Normal file
51
src/common/path.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::{common::MimeType, Error, Result};
|
||||||
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
path::PathBuf,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum UserPath {
|
||||||
|
Url(Url),
|
||||||
|
File(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserPath {
|
||||||
|
pub fn get_mime(&self) -> Result<MimeType> {
|
||||||
|
match self {
|
||||||
|
Self::Url(url) => Ok(url.into()),
|
||||||
|
Self::File(f) => MimeType::try_from(f.as_path()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for UserPath {
|
||||||
|
type Err = Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let normalized = match url::Url::parse(&s) {
|
||||||
|
Ok(url) if url.scheme() == "file" => {
|
||||||
|
let path = url
|
||||||
|
.to_file_path()
|
||||||
|
.map_err(|_| Error::BadPath(url.path().to_owned()))?;
|
||||||
|
|
||||||
|
Self::File(path)
|
||||||
|
}
|
||||||
|
Ok(url) => Self::Url(url),
|
||||||
|
_ => Self::File(PathBuf::from(s)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(normalized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UserPath {
|
||||||
|
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
|
match self {
|
||||||
|
Self::File(f) => fmt.write_str(&f.to_string_lossy().to_string()),
|
||||||
|
Self::Url(u) => fmt.write_str(&u.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,8 +54,6 @@ impl Config {
|
||||||
);
|
);
|
||||||
apps.save().ok()?;
|
apps.save().ok()?;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Some(entry.1)
|
Some(entry.1)
|
||||||
})
|
})
|
||||||
.map(|e| e.exec)
|
.map(|e| e.exec)
|
||||||
|
|
|
@ -22,9 +22,10 @@ pub enum Error {
|
||||||
Selector(String),
|
Selector(String),
|
||||||
#[error("selection cancelled")]
|
#[error("selection cancelled")]
|
||||||
Cancelled,
|
Cancelled,
|
||||||
|
|
||||||
#[error("Please specify the default terminal with handlr set x-scheme-handler/terminal")]
|
#[error("Please specify the default terminal with handlr set x-scheme-handler/terminal")]
|
||||||
NoTerminal,
|
NoTerminal,
|
||||||
|
#[error("Bad path: {0}")]
|
||||||
|
BadPath(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -12,8 +12,8 @@ mod utils;
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
use cli::Cmd;
|
use cli::Cmd;
|
||||||
use common::{Handler, MimeType};
|
use common::{Handler};
|
||||||
use std::{collections::HashMap, convert::TryFrom};
|
use std::{collections::HashMap};
|
||||||
|
|
||||||
// create config if it doesn't exist
|
// create config if it doesn't exist
|
||||||
Lazy::force(&CONFIG);
|
Lazy::force(&CONFIG);
|
||||||
|
@ -41,17 +41,10 @@ fn main() -> Result<()> {
|
||||||
HashMap::new();
|
HashMap::new();
|
||||||
|
|
||||||
for path in paths.into_iter() {
|
for path in paths.into_iter() {
|
||||||
let path = match url::Url::parse(&path) {
|
handlers
|
||||||
Ok(url) if url.scheme() == "file" => {
|
.entry(apps.get_handler(&path.get_mime()?.0)?)
|
||||||
url.path().to_owned()
|
.or_default()
|
||||||
}
|
.push(path.to_string());
|
||||||
_ => path,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mime = MimeType::try_from(path.as_str())?.0;
|
|
||||||
let handler = apps.get_handler(&mime)?;
|
|
||||||
|
|
||||||
handlers.entry(handler).or_default().push(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (handler, paths) in handlers.into_iter() {
|
for (handler, paths) in handlers.into_iter() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue