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",
]
[[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"

View file

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

View file

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

View file

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

View file

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