impl list

This commit is contained in:
Greg 2020-04-12 03:21:09 -04:00
parent 0da454a557
commit 04666c5fb7
No known key found for this signature in database
GPG key ID: 2E44FAEEDC94B1E2
8 changed files with 204 additions and 78 deletions

View file

@ -0,0 +1,2 @@
max_width = 80
merge-imports = true

83
Cargo.lock generated
View file

@ -18,12 +18,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "anyhow"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.6" version = "0.3.6"
@ -36,6 +30,12 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "ascii_table"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbcbc3963c670677e878a5253f5fef77577ef3821080cfac27779cadf224a9b5"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -288,6 +288,26 @@ dependencies = [
"wasi", "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]] [[package]]
name = "heck" name = "heck"
version = "0.3.1" version = "0.3.1"
@ -326,6 +346,12 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "json"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -365,23 +391,6 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "mime"
version = "0.1.0"
dependencies = [
"anyhow",
"dashmap",
"derive_more",
"dirs",
"itertools",
"mime_guess",
"pest",
"pest_derive",
"rayon",
"structopt",
"url",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.16" version = "0.3.16"
@ -394,7 +403,7 @@ version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
dependencies = [ dependencies = [
"mime 0.3.16", "mime",
"unicase", "unicase",
] ]
@ -584,6 +593,12 @@ dependencies = [
"opaque-debug", "opaque-debug",
] ]
[[package]]
name = "shlex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.2.0" version = "1.2.0"
@ -651,6 +666,26 @@ dependencies = [
"unicode-width", "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]] [[package]]
name = "typenum" name = "typenum"
version = "1.11.2" version = "1.11.2"

View file

@ -1,14 +1,11 @@
[package] [package]
name = "mime" name = "handlr"
version = "0.1.0" version = "0.1.0"
authors = ["greg <gregory.mkv@gmail.com>"] authors = ["greg <gregory.mkv@gmail.com>"]
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dirs = "2.0.2" dirs = "2.0.2"
anyhow = "1.0.28"
pest = "2.1.3" pest = "2.1.3"
pest_derive = "2.1.0" pest_derive = "2.1.0"
rayon = "1.3.0" rayon = "1.3.0"
@ -18,3 +15,7 @@ url = "2.1.1"
mime_guess = "2.0.3" mime_guess = "2.0.3"
itertools = "0.9.0" itertools = "0.9.0"
derive_more = {version = "0.99.5", default-features = false, features = ["display"] } 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"

View file

@ -1,4 +1,4 @@
use anyhow::Result; use crate::{Error, Result};
use pest::Parser; use pest::Parser;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::path::PathBuf; use std::path::PathBuf;
@ -12,11 +12,13 @@ impl std::str::FromStr for Mime {
Ok(Self(s.to_owned())) 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); pub struct Handler(String);
impl std::str::FromStr for Handler { impl std::str::FromStr for Handler {
type Err = anyhow::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.to_owned()) Self::resolve(s.to_owned())
} }
@ -41,21 +43,31 @@ impl Handler {
locally.or(system) locally.or(system)
} }
pub fn resolve(name: String) -> Result<Self> { pub fn resolve(name: String) -> Result<Self> {
let path = let path = Self::get_path(&name).ok_or(Error::NotFound)?;
Self::get_path(&name).ok_or_else(|| anyhow::Error::msg("Handler does not exist"))?;
DesktopEntry::try_from(path)?; DesktopEntry::try_from(path)?;
Ok(Self(name)) Ok(Self(name))
} }
pub fn get_entry(&self) -> Result<DesktopEntry> { pub fn get_entry(&self) -> Result<DesktopEntry> {
DesktopEntry::try_from(Self::get_path(&self.0).unwrap()) DesktopEntry::try_from(Self::get_path(&self.0).unwrap())
} }
pub fn run(&self, arg: &str) -> Result<()> { pub fn open(&self, arg: String) -> Result<()> {
std::process::Command::new("gtk-launch") let (cmd, args) = self.get_entry()?.get_cmd(Some(arg))?;
.args(&[self.0.as_str(), arg]) std::process::Command::new(cmd)
.args(args)
.stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null())
.spawn()?; .spawn()?;
Ok(()) 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)] #[derive(Debug, Clone, pest_derive::Parser, Default, PartialEq, Eq)]
@ -67,8 +79,22 @@ pub struct DesktopEntry {
pub(crate) mimes: Vec<Mime>, 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 { impl TryFrom<PathBuf> for DesktopEntry {
type Error = anyhow::Error; type Error = Error;
fn try_from(p: PathBuf) -> Result<DesktopEntry> { fn try_from(p: PathBuf) -> Result<DesktopEntry> {
let raw = std::fs::read_to_string(&p)?; let raw = std::fs::read_to_string(&p)?;
let file = Self::parse(Rule::file, &raw)?.next().unwrap(); 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(); let name = inner_rules.next().unwrap().as_str();
match name { match name {
"Name" => { "Name" => {
entry.name = inner_rules.next().unwrap().as_str().into(); entry.name =
inner_rules.next().unwrap().as_str().into();
} }
"Exec" => { "Exec" => {
entry.exec = inner_rules.next().unwrap().as_str().into(); entry.exec =
inner_rules.next().unwrap().as_str().into();
} }
"MimeType" => { "MimeType" => {
let mut mimes = inner_rules let mut mimes = inner_rules
@ -111,7 +139,7 @@ impl TryFrom<PathBuf> for DesktopEntry {
if !entry.name.is_empty() && !entry.exec.is_empty() { if !entry.name.is_empty() && !entry.exec.is_empty() {
Ok(entry) Ok(entry)
} else { } else {
Err(anyhow::Error::msg("Invalid desktop entry")) Err(Error::BadCmd)
} }
} }
} }

17
src/error.rs Normal file
View 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>;

View file

@ -1,44 +1,61 @@
use anyhow::Result; use error::{Error, Result};
use structopt::StructOpt; use structopt::StructOpt;
mod common; mod common;
mod error;
mod mimeapps; mod mimeapps;
pub use common::{DesktopEntry, Handler, Mime}; pub use common::{DesktopEntry, Handler, Mime};
#[derive(StructOpt)] #[derive(StructOpt)]
enum Options { enum Cmd {
List, List,
Open { path: String }, Open {
Get { mime: Mime }, path: String,
Set { mime: Mime, handler: Handler }, },
Get {
#[structopt(long)]
json: bool,
mime: Mime,
},
Launch {
mime: Mime,
args: Vec<String>,
},
Set {
mime: Mime,
handler: Handler,
},
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let cmd = Options::from_args(); let mut apps = mimeapps::MimeApps::read()?;
let mut user = mimeapps::MimeApps::read()?; match Cmd::from_args() {
Cmd::Set { mime, handler } => {
match cmd { apps.set_handler(mime, handler)?;
Options::Set { mime, handler } => {
user.set_handler(mime, handler)?;
} }
Options::Get { mime } => { Cmd::Launch { mime, args } => {
println!("{}", user.get_handler(&mime)?); 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) => { Ok(url) => {
let mime = Mime(format!("x-scheme-handler/{}", url.scheme())); let mime = Mime(format!("x-scheme-handler/{}", url.scheme()));
user.get_handler(&mime)?.run(&path)?; apps.get_handler(&mime)?.open(path)?;
} }
Err(_) => { Err(_) => {
let guess = mime_guess::from_path(&path) let guess = mime_guess::from_path(&path)
.first_raw() .first_raw()
.ok_or_else(|| anyhow::Error::msg("Could not determine mime type"))?; .ok_or(Error::Ambiguous)?;
user.get_handler(&Mime(guess.to_owned()))?.run(&path)?; apps.get_handler(&Mime(guess.to_owned()))?.open(path)?;
} }
}, },
_ => {} Cmd::List => {
apps.print()?;
}
}; };
Ok(()) Ok(())

View file

@ -1,9 +1,10 @@
use crate::{DesktopEntry, Handler, Mime}; use crate::{DesktopEntry, Error, Handler, Mime, Result};
use anyhow::Result;
use dashmap::DashMap; use dashmap::DashMap;
use pest::Parser; use pest::Parser;
use std::collections::{HashMap, VecDeque}; use std::{
use std::path::PathBuf; collections::{HashMap, VecDeque},
path::PathBuf,
};
#[derive(Debug, pest_derive::Parser)] #[derive(Debug, pest_derive::Parser)]
#[grammar = "ini.pest"] #[grammar = "ini.pest"]
@ -17,25 +18,40 @@ impl MimeApps {
pub fn set_handler(&mut self, mime: Mime, handler: Handler) -> Result<()> { pub fn set_handler(&mut self, mime: Mime, handler: Handler) -> Result<()> {
let handlers = self.default_apps.entry(mime).or_default(); let handlers = self.default_apps.entry(mime).or_default();
handlers.push_front(handler); handlers.push_front(handler);
self.print()?; self.save()?;
Ok(()) Ok(())
} }
pub fn get_handler(&self, mime: &Mime) -> Result<Handler> { pub fn get_handler(&self, mime: &Mime) -> Result<Handler> {
Ok(self self.default_apps
.default_apps
.get(mime) .get(mime)
.or_else(|| self.added_associations.get(mime)) .or_else(|| self.added_associations.get(mime))
.map(|hs| hs.get(0).unwrap().clone()) .map(|hs| hs.get(0).unwrap().clone())
.or_else(|| self.system_apps.get_handler(mime)) .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> { pub fn path() -> Result<PathBuf> {
dirs::config_dir() dirs::config_dir()
.map(|mut data_dir| { .map(|mut config_dir| {
data_dir.push("mimeapps.list"); config_dir.push("mimeapps.list");
data_dir config_dir
}) })
.ok_or_else(|| anyhow::Error::msg("Could not determine xdg data dir")) .ok_or(Error::NoConfigDir)
} }
pub fn read() -> Result<Self> { pub fn read() -> Result<Self> {
let raw_conf = std::fs::read_to_string(Self::path()?)?; let raw_conf = std::fs::read_to_string(Self::path()?)?;
@ -80,9 +96,9 @@ impl MimeApps {
"Added Associations" => conf "Added Associations" => conf
.added_associations .added_associations
.insert(Mime(name.to_owned()), handlers), .insert(Mime(name.to_owned()), handlers),
"Default Applications" => { "Default Applications" => conf
conf.default_apps.insert(Mime(name.to_owned()), handlers) .default_apps
} .insert(Mime(name.to_owned()), handlers),
_ => None, _ => None,
}; };
} }
@ -93,7 +109,7 @@ impl MimeApps {
Ok(conf) Ok(conf)
} }
pub fn print(&self) -> Result<()> { pub fn save(&self) -> Result<()> {
use itertools::Itertools; use itertools::Itertools;
use std::io::{prelude::*, BufWriter}; use std::io::{prelude::*, BufWriter};
@ -123,6 +139,20 @@ impl MimeApps {
writer.flush()?; writer.flush()?;
Ok(()) 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)] #[derive(Debug)]

View file

@ -1,4 +0,0 @@
use crate::{DesktopEntry, Handler, Mime};
use anyhow::Result;
use dashmap::DashMap;
use std::convert::TryFrom;