This commit is contained in:
Gregory 2021-04-25 12:40:03 -04:00
parent 11db7ab94e
commit b5468c1ec7
No known key found for this signature in database
GPG key ID: 2E44FAEEDC94B1E2
11 changed files with 124 additions and 76 deletions

View file

@ -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 {

View file

@ -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

View file

@ -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<()> {

View file

@ -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))
}
}

View file

@ -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())

View file

@ -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);

View file

@ -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
View 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()),
}
}
}

View file

@ -54,8 +54,6 @@ impl Config {
);
apps.save().ok()?;
Some(entry.1)
})
.map(|e| e.exec)

View file

@ -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>;

View file

@ -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() {