mirror of
https://github.com/chmln/handlr.git
synced 2024-11-23 17:51:46 +01:00
impl list
This commit is contained in:
parent
0da454a557
commit
04666c5fb7
8 changed files with 204 additions and 78 deletions
|
@ -0,0 +1,2 @@
|
|||
max_width = 80
|
||||
merge-imports = true
|
83
Cargo.lock
generated
83
Cargo.lock
generated
|
@ -18,12 +18,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.6"
|
||||
|
@ -36,6 +30,12 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||
|
||||
[[package]]
|
||||
name = "ascii_table"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbcbc3963c670677e878a5253f5fef77577ef3821080cfac27779cadf224a9b5"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
|
@ -288,6 +288,26 @@ dependencies = [
|
|||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "handlr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ascii_table",
|
||||
"dashmap",
|
||||
"derive_more",
|
||||
"dirs",
|
||||
"itertools",
|
||||
"json",
|
||||
"mime_guess",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"rayon",
|
||||
"shlex",
|
||||
"structopt",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.1"
|
||||
|
@ -326,6 +346,12 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -365,23 +391,6 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dashmap",
|
||||
"derive_more",
|
||||
"dirs",
|
||||
"itertools",
|
||||
"mime_guess",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"rayon",
|
||||
"structopt",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
|
@ -394,7 +403,7 @@ version = "2.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
|
||||
dependencies = [
|
||||
"mime 0.3.16",
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
|
@ -584,6 +593,12 @@ dependencies = [
|
|||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.2.0"
|
||||
|
@ -651,6 +666,26 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54b3d3d2ff68104100ab257bb6bb0cb26c901abe4bd4ba15961f3bf867924012"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca972988113b7715266f91250ddb98070d033c62a011fa0fcc57434a649310dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.11.2"
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
[package]
|
||||
name = "mime"
|
||||
name = "handlr"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <gregory.mkv@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dirs = "2.0.2"
|
||||
anyhow = "1.0.28"
|
||||
pest = "2.1.3"
|
||||
pest_derive = "2.1.0"
|
||||
rayon = "1.3.0"
|
||||
|
@ -18,3 +15,7 @@ url = "2.1.1"
|
|||
mime_guess = "2.0.3"
|
||||
itertools = "0.9.0"
|
||||
derive_more = {version = "0.99.5", default-features = false, features = ["display"] }
|
||||
json = "0.12.4"
|
||||
shlex = "0.1.1"
|
||||
thiserror = "1.0.14"
|
||||
ascii_table = "3.0.0"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use crate::{Error, Result};
|
||||
use pest::Parser;
|
||||
use std::convert::TryFrom;
|
||||
use std::path::PathBuf;
|
||||
|
@ -12,11 +12,13 @@ impl std::str::FromStr for Mime {
|
|||
Ok(Self(s.to_owned()))
|
||||
}
|
||||
}
|
||||
#[derive(Debug, derive_more::Display, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(
|
||||
Debug, derive_more::Display, Clone, Hash, PartialEq, Eq, PartialOrd, Ord,
|
||||
)]
|
||||
pub struct Handler(String);
|
||||
|
||||
impl std::str::FromStr for Handler {
|
||||
type Err = anyhow::Error;
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::resolve(s.to_owned())
|
||||
}
|
||||
|
@ -41,21 +43,31 @@ impl Handler {
|
|||
locally.or(system)
|
||||
}
|
||||
pub fn resolve(name: String) -> Result<Self> {
|
||||
let path =
|
||||
Self::get_path(&name).ok_or_else(|| anyhow::Error::msg("Handler does not exist"))?;
|
||||
let path = Self::get_path(&name).ok_or(Error::NotFound)?;
|
||||
DesktopEntry::try_from(path)?;
|
||||
Ok(Self(name))
|
||||
}
|
||||
pub fn get_entry(&self) -> Result<DesktopEntry> {
|
||||
DesktopEntry::try_from(Self::get_path(&self.0).unwrap())
|
||||
}
|
||||
pub fn run(&self, arg: &str) -> Result<()> {
|
||||
std::process::Command::new("gtk-launch")
|
||||
.args(&[self.0.as_str(), arg])
|
||||
pub fn open(&self, arg: String) -> Result<()> {
|
||||
let (cmd, args) = self.get_entry()?.get_cmd(Some(arg))?;
|
||||
std::process::Command::new(cmd)
|
||||
.args(args)
|
||||
.stdout(std::process::Stdio::null())
|
||||
.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn launch(&self, args: Vec<String>) -> Result<()> {
|
||||
let (cmd, mut base_args) = self.get_entry()?.get_cmd(None)?;
|
||||
base_args.extend_from_slice(&args);
|
||||
std::process::Command::new(cmd)
|
||||
.args(base_args)
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, pest_derive::Parser, Default, PartialEq, Eq)]
|
||||
|
@ -67,8 +79,22 @@ pub struct DesktopEntry {
|
|||
pub(crate) mimes: Vec<Mime>,
|
||||
}
|
||||
|
||||
impl DesktopEntry {
|
||||
pub fn get_cmd(
|
||||
&self,
|
||||
arg: Option<String>,
|
||||
) -> Result<(String, Vec<String>)> {
|
||||
let arg = arg.unwrap_or_default();
|
||||
let arg = shlex::quote(&arg);
|
||||
let replaced = self.exec.replace("%f", &arg).replace("%U", &arg);
|
||||
|
||||
let mut split = shlex::split(&replaced).ok_or(Error::BadCmd)?;
|
||||
Ok((split.remove(0), split))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PathBuf> for DesktopEntry {
|
||||
type Error = anyhow::Error;
|
||||
type Error = Error;
|
||||
fn try_from(p: PathBuf) -> Result<DesktopEntry> {
|
||||
let raw = std::fs::read_to_string(&p)?;
|
||||
let file = Self::parse(Rule::file, &raw)?.next().unwrap();
|
||||
|
@ -84,10 +110,12 @@ impl TryFrom<PathBuf> for DesktopEntry {
|
|||
let name = inner_rules.next().unwrap().as_str();
|
||||
match name {
|
||||
"Name" => {
|
||||
entry.name = inner_rules.next().unwrap().as_str().into();
|
||||
entry.name =
|
||||
inner_rules.next().unwrap().as_str().into();
|
||||
}
|
||||
"Exec" => {
|
||||
entry.exec = inner_rules.next().unwrap().as_str().into();
|
||||
entry.exec =
|
||||
inner_rules.next().unwrap().as_str().into();
|
||||
}
|
||||
"MimeType" => {
|
||||
let mut mimes = inner_rules
|
||||
|
@ -111,7 +139,7 @@ impl TryFrom<PathBuf> for DesktopEntry {
|
|||
if !entry.name.is_empty() && !entry.exec.is_empty() {
|
||||
Ok(entry)
|
||||
} else {
|
||||
Err(anyhow::Error::msg("Invalid desktop entry"))
|
||||
Err(Error::BadCmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
17
src/error.rs
Normal file
17
src/error.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Parse(#[from] pest::error::Error<crate::common::Rule>),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("handler not found")]
|
||||
NotFound,
|
||||
#[error("Invalid desktop entry")]
|
||||
BadCmd,
|
||||
#[error("Could not find config dir")]
|
||||
NoConfigDir,
|
||||
#[error("could not guess mime type")]
|
||||
Ambiguous,
|
||||
}
|
||||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
53
src/main.rs
53
src/main.rs
|
@ -1,44 +1,61 @@
|
|||
use anyhow::Result;
|
||||
use error::{Error, Result};
|
||||
use structopt::StructOpt;
|
||||
|
||||
mod common;
|
||||
mod error;
|
||||
mod mimeapps;
|
||||
|
||||
pub use common::{DesktopEntry, Handler, Mime};
|
||||
|
||||
#[derive(StructOpt)]
|
||||
enum Options {
|
||||
enum Cmd {
|
||||
List,
|
||||
Open { path: String },
|
||||
Get { mime: Mime },
|
||||
Set { mime: Mime, handler: Handler },
|
||||
Open {
|
||||
path: String,
|
||||
},
|
||||
Get {
|
||||
#[structopt(long)]
|
||||
json: bool,
|
||||
mime: Mime,
|
||||
},
|
||||
Launch {
|
||||
mime: Mime,
|
||||
args: Vec<String>,
|
||||
},
|
||||
Set {
|
||||
mime: Mime,
|
||||
handler: Handler,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cmd = Options::from_args();
|
||||
let mut apps = mimeapps::MimeApps::read()?;
|
||||
|
||||
let mut user = mimeapps::MimeApps::read()?;
|
||||
|
||||
match cmd {
|
||||
Options::Set { mime, handler } => {
|
||||
user.set_handler(mime, handler)?;
|
||||
match Cmd::from_args() {
|
||||
Cmd::Set { mime, handler } => {
|
||||
apps.set_handler(mime, handler)?;
|
||||
}
|
||||
Options::Get { mime } => {
|
||||
println!("{}", user.get_handler(&mime)?);
|
||||
Cmd::Launch { mime, args } => {
|
||||
apps.get_handler(&mime)?.launch(args)?;
|
||||
}
|
||||
Options::Open { path } => match url::Url::parse(&path) {
|
||||
Cmd::Get { mime, json } => {
|
||||
apps.show_handler(&mime, json)?;
|
||||
}
|
||||
Cmd::Open { path } => match url::Url::parse(&path) {
|
||||
Ok(url) => {
|
||||
let mime = Mime(format!("x-scheme-handler/{}", url.scheme()));
|
||||
user.get_handler(&mime)?.run(&path)?;
|
||||
apps.get_handler(&mime)?.open(path)?;
|
||||
}
|
||||
Err(_) => {
|
||||
let guess = mime_guess::from_path(&path)
|
||||
.first_raw()
|
||||
.ok_or_else(|| anyhow::Error::msg("Could not determine mime type"))?;
|
||||
user.get_handler(&Mime(guess.to_owned()))?.run(&path)?;
|
||||
.ok_or(Error::Ambiguous)?;
|
||||
apps.get_handler(&Mime(guess.to_owned()))?.open(path)?;
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
Cmd::List => {
|
||||
apps.print()?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::{DesktopEntry, Handler, Mime};
|
||||
use anyhow::Result;
|
||||
use crate::{DesktopEntry, Error, Handler, Mime, Result};
|
||||
use dashmap::DashMap;
|
||||
use pest::Parser;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
#[derive(Debug, pest_derive::Parser)]
|
||||
#[grammar = "ini.pest"]
|
||||
|
@ -17,25 +18,40 @@ impl MimeApps {
|
|||
pub fn set_handler(&mut self, mime: Mime, handler: Handler) -> Result<()> {
|
||||
let handlers = self.default_apps.entry(mime).or_default();
|
||||
handlers.push_front(handler);
|
||||
self.print()?;
|
||||
self.save()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_handler(&self, mime: &Mime) -> Result<Handler> {
|
||||
Ok(self
|
||||
.default_apps
|
||||
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(anyhow::Error::msg("No handlers found"))?)
|
||||
.ok_or(Error::NotFound)
|
||||
}
|
||||
pub fn show_handler(&self, mime: &Mime, output_json: bool) -> Result<()> {
|
||||
let handler = self.get_handler(mime)?;
|
||||
let output = if output_json {
|
||||
let entry = handler.get_entry()?;
|
||||
(json::object! {
|
||||
handler: handler.to_string(),
|
||||
name: entry.name.as_str(),
|
||||
cmd: entry.get_cmd(None)?.0
|
||||
})
|
||||
.to_string()
|
||||
} else {
|
||||
handler.to_string()
|
||||
};
|
||||
println!("{}", output);
|
||||
Ok(())
|
||||
}
|
||||
pub fn path() -> Result<PathBuf> {
|
||||
dirs::config_dir()
|
||||
.map(|mut data_dir| {
|
||||
data_dir.push("mimeapps.list");
|
||||
data_dir
|
||||
.map(|mut config_dir| {
|
||||
config_dir.push("mimeapps.list");
|
||||
config_dir
|
||||
})
|
||||
.ok_or_else(|| anyhow::Error::msg("Could not determine xdg data dir"))
|
||||
.ok_or(Error::NoConfigDir)
|
||||
}
|
||||
pub fn read() -> Result<Self> {
|
||||
let raw_conf = std::fs::read_to_string(Self::path()?)?;
|
||||
|
@ -80,9 +96,9 @@ impl MimeApps {
|
|||
"Added Associations" => conf
|
||||
.added_associations
|
||||
.insert(Mime(name.to_owned()), handlers),
|
||||
"Default Applications" => {
|
||||
conf.default_apps.insert(Mime(name.to_owned()), handlers)
|
||||
}
|
||||
"Default Applications" => conf
|
||||
.default_apps
|
||||
.insert(Mime(name.to_owned()), handlers),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
@ -93,7 +109,7 @@ impl MimeApps {
|
|||
|
||||
Ok(conf)
|
||||
}
|
||||
pub fn print(&self) -> Result<()> {
|
||||
pub fn save(&self) -> Result<()> {
|
||||
use itertools::Itertools;
|
||||
use std::io::{prelude::*, BufWriter};
|
||||
|
||||
|
@ -123,6 +139,20 @@ impl MimeApps {
|
|||
writer.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn print(&self) -> Result<()> {
|
||||
use itertools::Itertools;
|
||||
|
||||
let rows = self
|
||||
.default_apps
|
||||
.iter()
|
||||
.sorted()
|
||||
.map(|(k, v)| vec![k.0.clone(), v.iter().join(", ")])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
ascii_table::AsciiTable::default().print(rows);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
use crate::{DesktopEntry, Handler, Mime};
|
||||
use anyhow::Result;
|
||||
use dashmap::DashMap;
|
||||
use std::convert::TryFrom;
|
Loading…
Reference in a new issue