This commit is contained in:
Gregory 2020-05-16 03:19:50 -04:00
parent 5ce3e839e3
commit 6da612bee1
No known key found for this signature in database
GPG key ID: 2E44FAEEDC94B1E2
7 changed files with 941 additions and 14 deletions

815
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -19,3 +19,4 @@ json = "0.12.4"
shlex = "0.1.1"
thiserror = "1.0.14"
ascii_table = "3.0.0"
mime-db = "0.1.5"

31
completions/handlr.fish Normal file
View file

@ -0,0 +1,31 @@
function __handlr_autocomplete
function subcommands
set -l handlr_commands 'get help launch list open set unset'
complete -f -c handlr -n "not __fish_seen_subcommand_from $handlr_commands" -a "get" -d "Show handler for mime"
complete -f -c handlr -n "not __fish_seen_subcommand_from $handlr_commands" -a "launch" -d "Launch given handler with path/args"
complete -f -c handlr -n "not __fish_seen_subcommand_from $handlr_commands" -a "list" -d "Show handlers (default applications)"
complete -f -c handlr -n "not __fish_seen_subcommand_from $handlr_commands" -a "open" -d "Open path/URL with default handler (like xdg-open)"
complete -f -c handlr -n "not __fish_seen_subcommand_from $handlr_commands" -a "set" -d "Set handler for extension (e.g. pdf) or mime type"
complete -f -c handlr -n "not __fish_seen_subcommand_from $handlr_commands" -a "unset" -d "Unset handler"
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)'
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 unset' -a '(handlr autocomplete -m)'
complete -f -c handlr -n '__fish_seen_subcommand_from launch; __fish_prev_arg_in launch' -a '(handlr autocomplete -m)'
end
__handlr_autocomplete

26
src/autocomplete.rs Normal file
View file

@ -0,0 +1,26 @@
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

@ -4,14 +4,16 @@ pub enum Error {
Parse(#[from] pest::error::Error<crate::common::Rule>),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("handler not found")]
#[error("no handler defined for this mime/extension")]
NotFound,
#[error("Invalid desktop entry")]
#[error("badly-formatted desktop entry")]
BadCmd,
#[error("Could not find config dir")]
#[error("could not locate config dir")]
NoConfigDir,
#[error("could not guess mime type")]
#[error("could not figure out the mime from extension. please provide the mime type directly")]
Ambiguous,
#[error("either mime (via -m) or extension (via -e) must be provided")]
MissingMimeOrExt,
}
pub type Result<T, E = Error> = std::result::Result<T, E>;

View file

@ -1,6 +1,7 @@
use error::{Error, Result};
use structopt::StructOpt;
mod autocomplete;
mod common;
mod error;
mod mimeapps;
@ -23,19 +24,40 @@ enum Cmd {
args: Vec<String>,
},
Set {
mime: Mime,
#[structopt(long, short)]
mime: Option<Mime>,
#[structopt(long, short)]
ext: Option<String>,
handler: Handler,
},
Unset {
mime: Mime,
},
#[structopt(setting = structopt::clap::AppSettings::Hidden)]
Autocomplete {
#[structopt(short)]
desktop_files: bool,
#[structopt(short)]
mimes: bool,
#[structopt(short)]
extensions: bool,
},
}
fn main() -> Result<()> {
let mut apps = mimeapps::MimeApps::read()?;
match Cmd::from_args() {
Cmd::Set { mime, handler } => {
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)?,
};
apps.set_handler(mime, handler)?;
}
Cmd::Launch { mime, args } => {
@ -47,13 +69,14 @@ fn main() -> Result<()> {
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 guess = mime_guess::from_path(&path)
.first_raw()
.ok_or(Error::Ambiguous)?;
apps.get_handler(&Mime(guess.to_owned()))?.open(path)?;
.first_or_text_plain()
.to_string();
apps.get_handler(&Mime(guess))?.open(path)?;
}
},
Cmd::List => {
@ -62,6 +85,19 @@ fn main() -> Result<()> {
Cmd::Unset { mime } => {
apps.remove_handler(&mime)?;
}
Cmd::Autocomplete {
desktop_files,
mimes,
extensions,
} => {
if desktop_files {
apps.list_handlers()?;
} else if mimes {
autocomplete::mimes()?;
} else if extensions {
autocomplete::extensions()?;
}
}
};
Ok(())

View file

@ -158,6 +158,32 @@ impl MimeApps {
ascii_table::AsciiTable::default().print(rows);
Ok(())
}
pub fn list_handlers(&self) -> Result<()> {
// use rayon::iter::ParallelBridge;
// use rayon::prelude::ParallelIterator;
use std::convert::TryFrom;
use std::io::Write;
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
std::fs::read_dir("/usr/share/applications")?
.chain(std::fs::read_dir({
let mut dir = dirs::data_dir().ok_or(Error::NoConfigDir)?;
dir.push("applications");
dir
})?)
.filter_map(Result::ok)
.filter_map(|p| DesktopEntry::try_from(p.path()).ok())
.for_each(|e| {
stdout.write_all(e.file_name.as_bytes()).unwrap();
stdout.write_all(b"\t").unwrap();
stdout.write_all(e.name.as_bytes()).unwrap();
stdout.write_all(b"\n").unwrap();
});
Ok(())
}
}