mirror of
https://github.com/chmln/handlr.git
synced 2024-11-14 13:39:29 +01: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 output = if output_json {
|
||||
let entry = handler.get_entry()?;
|
||||
let cmd = entry.get_cmd(vec![])?;
|
||||
|
||||
(json::object! {
|
||||
handler: handler.to_string(),
|
||||
name: entry.name.as_str(),
|
||||
cmd: entry.get_cmd(vec![])?.0
|
||||
cmd: cmd.0 + " " + &cmd.1.join(" "),
|
||||
})
|
||||
.to_string()
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::{Handler, MimeOrExtension};
|
||||
use crate::common::{Handler, MimeOrExtension, UserPath};
|
||||
|
||||
#[derive(clap::Clap)]
|
||||
#[clap(global_setting = clap::AppSettings::DeriveDisplayOrder)]
|
||||
|
@ -14,7 +14,7 @@ pub enum Cmd {
|
|||
/// Open a path/URL with its default handler
|
||||
Open {
|
||||
#[clap(required = true)]
|
||||
paths: Vec<String>,
|
||||
paths: Vec<UserPath>,
|
||||
},
|
||||
|
||||
/// Set the default handler for mime/extension
|
||||
|
|
|
@ -4,6 +4,7 @@ static CUSTOM_MIMES: &[&'static str] = &[
|
|||
"inode/directory",
|
||||
"x-scheme-handler/http",
|
||||
"x-scheme-handler/https",
|
||||
"x-scheme-handler/terminal",
|
||||
];
|
||||
|
||||
pub fn autocomplete() -> Result<()> {
|
||||
|
|
|
@ -59,38 +59,50 @@ impl DesktopEntry {
|
|||
Ok(())
|
||||
}
|
||||
pub fn get_cmd(&self, args: Vec<String>) -> Result<(String, Vec<String>)> {
|
||||
let cmd_patterns = &["%f", "%F", "%u", "%U"];
|
||||
let special = AhoCorasick::new_auto_configured(cmd_patterns);
|
||||
let special =
|
||||
AhoCorasick::new_auto_configured(&["%f", "%F", "%u", "%U"]);
|
||||
|
||||
let mut split = shlex::split(&self.exec)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.flat_map(|s| match s.as_str() {
|
||||
"%f" | "%F" | "%u" | "%U" => args.clone(),
|
||||
s if special.is_match(s) => vec![{
|
||||
let mut replaced = String::with_capacity(s.len());
|
||||
special.replace_all_with(s, &mut replaced, |_, _, dst| {
|
||||
dst.push_str(args.clone().join(" ").as_str());
|
||||
false
|
||||
});
|
||||
replaced
|
||||
}],
|
||||
_ => vec![s],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut exec = shlex::split(&self.exec).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()
|
||||
.flat_map(|s| match s.as_str() {
|
||||
"%f" | "%F" | "%u" | "%U" => args.clone(),
|
||||
s if special.is_match(s) => vec![{
|
||||
let mut replaced =
|
||||
String::with_capacity(s.len() + args.len() * 2);
|
||||
special.replace_all_with(
|
||||
s,
|
||||
&mut replaced,
|
||||
|_, _, dst| {
|
||||
dst.push_str(args.clone().join(" ").as_str());
|
||||
false
|
||||
},
|
||||
);
|
||||
replaced
|
||||
}],
|
||||
_ => vec![s],
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
exec.extend_from_slice(&args);
|
||||
}
|
||||
|
||||
// If the entry expects a terminal (emulator), but this process is not running in one, we
|
||||
// launch a new one.
|
||||
if self.terminal && !atty::is(atty::Stream::Stdout) {
|
||||
split = shlex::split(&crate::config::Config::terminal()?)
|
||||
exec = shlex::split(&crate::config::Config::terminal()?)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.chain(vec!["-e".to_owned()])
|
||||
.chain(split.into_iter())
|
||||
.chain(exec)
|
||||
.collect();
|
||||
}
|
||||
|
||||
Ok((split.remove(0), split))
|
||||
Ok((exec.remove(0), exec))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,18 +2,18 @@ use crate::{
|
|||
common::{DesktopEntry, ExecMode},
|
||||
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)]
|
||||
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 {
|
||||
f.write_str(&self.0.to_string_lossy())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Handler {
|
||||
impl FromStr for Handler {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::resolve(s.into())
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use crate::{Error, Result};
|
||||
use mime::Mime;
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
path::{Path},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::{convert::TryFrom, path::Path, str::FromStr};
|
||||
use url::Url;
|
||||
|
||||
// A mime derived from a path or URL
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
@ -24,21 +21,26 @@ impl MimeType {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for MimeType {
|
||||
type Error = Error;
|
||||
impl From<&Url> for MimeType {
|
||||
fn from(url: &Url) -> Self {
|
||||
Self(
|
||||
format!("x-scheme-handler/{}", url.scheme())
|
||||
.parse::<Mime>()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
.parse::<Mime>()
|
||||
.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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
#[derive(Debug)]
|
||||
pub struct MimeOrExtension(pub Mime);
|
||||
|
|
|
@ -2,8 +2,10 @@ mod db;
|
|||
mod desktop_entry;
|
||||
mod handler;
|
||||
mod mime_types;
|
||||
mod path;
|
||||
|
||||
pub use self::db::autocomplete as db_autocomplete;
|
||||
pub use desktop_entry::{DesktopEntry, Mode as ExecMode};
|
||||
pub use handler::Handler;
|
||||
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()?;
|
||||
|
||||
|
||||
|
||||
Some(entry.1)
|
||||
})
|
||||
.map(|e| e.exec)
|
||||
|
|
|
@ -22,9 +22,10 @@ pub enum Error {
|
|||
Selector(String),
|
||||
#[error("selection cancelled")]
|
||||
Cancelled,
|
||||
|
||||
#[error("Please specify the default terminal with handlr set x-scheme-handler/terminal")]
|
||||
NoTerminal,
|
||||
#[error("Bad path: {0}")]
|
||||
BadPath(String),
|
||||
}
|
||||
|
||||
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<()> {
|
||||
use clap::Clap;
|
||||
use cli::Cmd;
|
||||
use common::{Handler, MimeType};
|
||||
use std::{collections::HashMap, convert::TryFrom};
|
||||
use common::{Handler};
|
||||
use std::{collections::HashMap};
|
||||
|
||||
// create config if it doesn't exist
|
||||
Lazy::force(&CONFIG);
|
||||
|
@ -41,17 +41,10 @@ fn main() -> Result<()> {
|
|||
HashMap::new();
|
||||
|
||||
for path in paths.into_iter() {
|
||||
let path = match url::Url::parse(&path) {
|
||||
Ok(url) if url.scheme() == "file" => {
|
||||
url.path().to_owned()
|
||||
}
|
||||
_ => path,
|
||||
};
|
||||
|
||||
let mime = MimeType::try_from(path.as_str())?.0;
|
||||
let handler = apps.get_handler(&mime)?;
|
||||
|
||||
handlers.entry(handler).or_default().push(path);
|
||||
handlers
|
||||
.entry(apps.get_handler(&path.get_mime()?.0)?)
|
||||
.or_default()
|
||||
.push(path.to_string());
|
||||
}
|
||||
|
||||
for (handler, paths) in handlers.into_iter() {
|
||||
|
|
Loading…
Reference in a new issue