Combine mimes with extensions

This commit is contained in:
Gregory 2020-05-17 15:48:56 -04:00
parent a3aa736a0d
commit 5629d971f6
No known key found for this signature in database
GPG key ID: 2E44FAEEDC94B1E2
10 changed files with 161 additions and 98 deletions

52
Cargo.lock generated
View file

@ -431,14 +431,14 @@ dependencies = [
"itertools",
"json",
"mime-db",
"mime_guess",
"mime-sniffer",
"pest",
"pest_derive",
"rayon",
"shlex",
"structopt",
"thiserror",
"url",
"url 2.1.1",
]
[[package]]
@ -523,6 +523,17 @@ dependencies = [
"tokio-tls",
]
[[package]]
name = "idna"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna"
version = "0.2.0"
@ -664,6 +675,16 @@ dependencies = [
"tokio",
]
[[package]]
name = "mime-sniffer"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e98f7cfbbaf64674624e2aa35327d75e3de8e4d1b2555ef70dcf0c107a95490"
dependencies = [
"mime",
"url 1.7.2",
]
[[package]]
name = "mime_guess"
version = "2.0.3"
@ -783,6 +804,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "percent-encoding"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -1038,7 +1065,7 @@ dependencies = [
"mime",
"mime_guess",
"native-tls",
"percent-encoding",
"percent-encoding 2.1.0",
"pin-project-lite",
"serde",
"serde_json",
@ -1046,7 +1073,7 @@ dependencies = [
"time",
"tokio",
"tokio-tls",
"url",
"url 2.1.1",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@ -1150,7 +1177,7 @@ dependencies = [
"dtoa",
"itoa",
"serde",
"url",
"url 2.1.1",
]
[[package]]
@ -1411,15 +1438,26 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "url"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
dependencies = [
"idna 0.1.5",
"matches",
"percent-encoding 1.0.1",
]
[[package]]
name = "url"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
dependencies = [
"idna",
"idna 0.2.0",
"matches",
"percent-encoding",
"percent-encoding 2.1.0",
]
[[package]]

View file

@ -12,10 +12,10 @@ rayon = "1.3.0"
dashmap = "3.11.1"
structopt = "0.3.14"
url = "2.1.1"
mime_guess = "2.0.3"
itertools = "0.9.0"
json = "0.12.4"
shlex = "0.1.1"
thiserror = "1.0.18"
ascii_table = "3.0.0"
mime-db = "0.1.5"
mime-sniffer = "0.1.2"

View file

@ -10,19 +10,14 @@ function __handlr_autocomplete
end
function _set
complete -f -c handlr -n '__fish_seen_subcommand_from set' -s "em"
complete -f -c handlr -n '__fish_seen_subcommand_from set; __fish_prev_arg_in set' -a '-e -m'
complete -f -c handlr -n '__fish_seen_subcommand_from set; __fish_prev_arg_in "-e"' -a "(handlr autocomplete -e)"
complete -f -c handlr -n '__fish_seen_subcommand_from set; __fish_prev_arg_in "-m"' -a '(handlr autocomplete -m)'
complete -f -c handlr -n '__fish_seen_subcommand_from set; set -l last (commandline -pco)[-2]; [ "$last" = "-e" ]' -a '(handlr autocomplete -d)' -d "desc1 desc2"
complete -f -c handlr -n '__fish_seen_subcommand_from set; set -l last (commandline -pco)[-2]; [ "$last" = "-m" ]' -a '(handlr autocomplete -d)'
complete -f -c handlr -n '__fish_seen_subcommand_from set; __fish_prev_arg_in "set"' -a '(handlr autocomplete -m)'
complete -f -c handlr -n '__fish_seen_subcommand_from set; set -l last (commandline -pco)[-2]; [ "$last" = "set" ]' -a '(handlr autocomplete -d)'
end
subcommands
_set
complete -f -c handlr -n '__fish_seen_subcommand_from get' -l 'json' -a '(handlr autocomplete -m)'
complete -f -c handlr -n '__fish_seen_subcommand_from get' -a '(handlr autocomplete -m)'
complete -f -c handlr -n '__fish_seen_subcommand_from get' -l 'json'
complete -f -c handlr -n '__fish_seen_subcommand_from unset' -a '(handlr autocomplete -m)'
complete -f -c handlr -n '__fish_seen_subcommand_from launch; __fish_prev_arg_in launch' -a '(handlr autocomplete -m)'

View file

@ -1,26 +0,0 @@
use crate::Result;
pub fn extensions() -> Result<()> {
use std::io::Write;
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
mime_db::EXTENSIONS.iter().for_each(|(ext, _)| {
stdout.write_all(ext.as_bytes()).unwrap();
stdout.write_all(b"\n").unwrap();
});
Ok(())
}
pub fn mimes() -> Result<()> {
use std::io::Write;
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
mime_db::TYPES.iter().for_each(|(mime, _, _)| {
stdout.write_all(mime.as_bytes()).unwrap();
stdout.write_all(b"\n").unwrap();
});
Ok(())
}

View file

@ -1,17 +1,52 @@
use crate::{Error, Result};
use crate::{mime_types, Error, Result};
use pest::Parser;
use std::convert::TryFrom;
use std::path::PathBuf;
use std::{convert::TryFrom, fs, path::PathBuf};
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Mime(pub String);
impl std::str::FromStr for Mime {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_owned()))
impl Mime {
pub fn try_from_path(path: &str) -> Result<Self> {
if let Ok(url) = url::Url::parse(path) {
return Ok(Mime(format!("x-scheme-handler/{}", url.scheme())));
}
let path = PathBuf::from(path);
let mime = match path.extension().map(|e| e.to_str()).flatten() {
Some(extension) => {
mime_types::lookup_extension(extension)?.to_owned()
}
None => {
use mime_sniffer::MimeTypeSniffer;
if fs::metadata(&path)?.is_dir() {
"inode/directory".to_owned()
} else if let Some(mime) = fs::read(path)?.sniff_mime_type() {
mime.to_owned()
} else {
return Err(Error::Ambiguous);
}
}
};
Ok(Mime(mime))
}
}
impl std::str::FromStr for Mime {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mime = if s.starts_with(".") {
mime_types::lookup_extension(&s[1..])?
} else {
mime_types::verify(&s)?
};
Ok(Self(mime.to_owned()))
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Handler(String);
@ -107,17 +142,21 @@ impl TryFrom<PathBuf> for DesktopEntry {
let raw = std::fs::read_to_string(&p)?;
let file = Self::parse(Rule::file, &raw)?.next().unwrap();
let mut section: &str = Default::default();
let mut entry = Self::default();
entry.file_name = p.file_name().unwrap().to_str().unwrap().to_owned();
for line in file.into_inner() {
match line.as_rule() {
Rule::property => {
Rule::section => {
section = line.into_inner().as_str();
}
Rule::property if section == "Desktop Entry" => {
let mut inner_rules = line.into_inner(); // { name ~ "=" ~ value }
let name = inner_rules.next().unwrap().as_str();
match name {
"Name" => {
"Name" if entry.name.is_empty() => {
entry.name =
inner_rules.next().unwrap().as_str().into();
}

View file

@ -12,6 +12,10 @@ pub enum Error {
NoConfigDir,
#[error("could not figure out the mime type")]
Ambiguous,
#[error("could not figure out the mime type from extension .{0}")]
UnknownExtension(String),
#[error("invalid mime {0}")]
BadMime(String),
#[error("either mime (via -m) or extension (via -e) must be provided")]
MissingMimeOrExt,
}

View file

@ -1,4 +1,4 @@
section_char = { XID_CONTINUE | " " }
section_char = { XID_CONTINUE | " " | "-" }
name_char = {XID_CONTINUE | PUNCTUATION | "/" | "+" | "." | "-" | "%" | " " | "[" | "]" }
value_char = { PUNCTUATION | FORMAT | NUMBER | MARK | GRAPHEME_BASE}

View file

@ -1,9 +1,9 @@
use error::{Error, Result};
use structopt::StructOpt;
mod autocomplete;
mod common;
mod error;
mod mime_types;
mod mimeapps;
pub use common::{DesktopEntry, Handler, Mime};
@ -24,10 +24,7 @@ enum Cmd {
args: Vec<String>,
},
Set {
#[structopt(long, short)]
mime: Option<Mime>,
#[structopt(long, short)]
ext: Option<String>,
mime: Mime,
handler: Handler,
},
Unset {
@ -39,8 +36,6 @@ enum Cmd {
desktop_files: bool,
#[structopt(short)]
mimes: bool,
#[structopt(short)]
extensions: bool,
},
}
@ -48,16 +43,7 @@ fn main() -> Result<()> {
let mut apps = mimeapps::MimeApps::read()?;
match Cmd::from_args() {
Cmd::Set { mime, ext, handler } => {
let mime = match ext {
Some(extension) => mime_guess::from_ext(&extension)
.first_raw()
.map(ToOwned::to_owned)
.map(Mime)
.ok_or(Error::Ambiguous)?,
None => mime.ok_or(Error::MissingMimeOrExt)?,
};
Cmd::Set { mime, handler } => {
apps.set_handler(mime, handler)?;
}
Cmd::Launch { mime, args } => {
@ -66,24 +52,9 @@ fn main() -> Result<()> {
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()));
apps.get_handler(&mime)?.open(path)?;
}
Err(_) => {
let mime = match mime_guess::from_path(&path).first_raw() {
Some(mime) => mime,
None if std::fs::metadata(&path)?.is_dir() => {
"inode/directory"
}
_ => {
return Err(Error::Ambiguous);
}
};
apps.get_handler(&Mime(mime.to_owned()))?.open(path)?;
}
},
Cmd::Open { path } => {
apps.get_handler(&Mime::try_from_path(&path)?)?.open(path)?;
}
Cmd::List => {
apps.print()?;
}
@ -93,14 +64,11 @@ fn main() -> Result<()> {
Cmd::Autocomplete {
desktop_files,
mimes,
extensions,
} => {
if desktop_files {
apps.list_handlers()?;
} else if mimes {
autocomplete::mimes()?;
} else if extensions {
autocomplete::extensions()?;
mime_types::list()?;
}
}
};

49
src/mime_types.rs Normal file
View file

@ -0,0 +1,49 @@
use crate::{Error, Result};
static CUSTOM_MIMES: &[&'static str] = &[
"inode/directory",
"x-scheme-handler/http",
"x-scheme-handler/https",
];
pub fn lookup_extension(ext: &str) -> Result<&str> {
mime_db::lookup(ext).ok_or(Error::UnknownExtension(ext.to_owned()))
}
pub fn verify(mime: &str) -> Result<&str> {
if mime.starts_with("x-scheme-handler/") || CUSTOM_MIMES.contains(&mime) {
return Ok(mime);
}
mime_db::TYPES
.iter()
.find(|(m, _, _)| m == &mime)
.ok_or(Error::BadMime(mime.to_owned()))?;
Ok(mime)
}
pub fn list() -> Result<()> {
use std::io::Write;
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
mime_db::EXTENSIONS.iter().for_each(|(ext, _)| {
stdout.write_all(b".").unwrap();
stdout.write_all(ext.as_bytes()).unwrap();
stdout.write_all(b"\n").unwrap();
});
CUSTOM_MIMES.iter().for_each(|mime| {
stdout.write_all(mime.as_bytes()).unwrap();
stdout.write_all(b"\n").unwrap();
});
mime_db::TYPES.iter().for_each(|(mime, _, _)| {
stdout.write_all(mime.as_bytes()).unwrap();
stdout.write_all(b"\n").unwrap();
});
Ok(())
}

View file

@ -161,10 +161,7 @@ impl MimeApps {
Ok(())
}
pub fn list_handlers(&self) -> Result<()> {
// use rayon::iter::ParallelBridge;
// use rayon::prelude::ParallelIterator;
use std::convert::TryFrom;
use std::io::Write;
use std::{convert::TryFrom, io::Write};
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
@ -199,8 +196,7 @@ impl SystemApps {
Some(self.get_handlers(mime)?.get(0).unwrap().clone())
}
pub fn populate() -> Result<Self> {
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
use rayon::{iter::ParallelBridge, prelude::ParallelIterator};
use std::convert::TryFrom;
let map = DashMap::<Mime, Vec<Handler>>::with_capacity(50);