mirror of
https://github.com/chmln/handlr.git
synced 2024-11-27 03:13:49 +01:00
Combine mimes with extensions
This commit is contained in:
parent
a3aa736a0d
commit
5629d971f6
10 changed files with 161 additions and 98 deletions
52
Cargo.lock
generated
52
Cargo.lock
generated
|
@ -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]]
|
||||
|
|
|
@ -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"
|
||||
|
|
13
completions/handlr.fish
vendored
13
completions/handlr.fish
vendored
|
@ -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)'
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
46
src/main.rs
46
src/main.rs
|
@ -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
49
src/mime_types.rs
Normal 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(())
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue