diff options
-rw-r--r-- | config/mimetype.toml | 10 | ||||
-rw-r--r-- | docs/configuration/mimetype.toml.md | 17 | ||||
-rw-r--r-- | src/commands/open_file.rs | 37 | ||||
-rw-r--r-- | src/config/mimetype/entry.rs | 33 | ||||
-rw-r--r-- | src/config/mimetype/list.rs | 66 | ||||
-rw-r--r-- | src/config/mimetype/mod.rs | 6 | ||||
-rw-r--r-- | src/config/mimetype/registry.rs | 80 | ||||
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | src/util/mimetype.rs | 57 | ||||
-rw-r--r-- | src/util/mod.rs | 1 | ||||
-rw-r--r-- | src/util/process.rs | 6 |
11 files changed, 242 insertions, 75 deletions
diff --git a/config/mimetype.toml b/config/mimetype.toml index dc4a437..dc7179a 100644 --- a/config/mimetype.toml +++ b/config/mimetype.toml @@ -181,3 +181,13 @@ tex.app_list = [ torrent.app_list = [ { command = "transmission-gtk" } ] + +[mimetype] + +# text/* +[mimetype.text] +inherit = "text_default" + +# application/octet-stream +[mimetype.application.subtype.octet-stream] +inherit = "video_default" diff --git a/docs/configuration/mimetype.toml.md b/docs/configuration/mimetype.toml.md index 9e99fe6..6197fb2 100644 --- a/docs/configuration/mimetype.toml.md +++ b/docs/configuration/mimetype.toml.md @@ -2,9 +2,10 @@ This file tells joshuto what programs to use when opening files. There are currently 2 ways to configure opening files: - - via extension - - via mimetype (***CURRENTLY DOES NOT WORK***) - + - via extension (1st priority) + - via mimetype (2nd priority) + - must have `file` command available + - joshuto will use `file --mime-type -Lb` to determine the file's mimetype ## Class and inherit To alleviate the lack of variables and programmability in TOML, @@ -37,6 +38,16 @@ rs.app_list = [ { command = "micro" }, { command = "gedit", fork = true, silent = true }, { command = "bat", confirm_exit = true } ] + +[mimetype] + +# text/* +[mimetype.text] +inherit = "text_default" + +# application/octet-stream +[mimetype.application.subtype.octet-stream] +inherit = "video_default" ``` each extension has the following fields: diff --git a/src/commands/open_file.rs b/src/commands/open_file.rs index 030f6ed..eaa94ef 100644 --- a/src/commands/open_file.rs +++ b/src/commands/open_file.rs @@ -2,23 +2,40 @@ use std::io; use std::path; use crate::commands::{quit, reload}; -use crate::config::AppMimetypeEntry; +use crate::config::ProgramEntry; use crate::context::AppContext; use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult}; use crate::ui::views::TuiTextField; use crate::ui::AppBackend; +use crate::util::mimetype::get_mimetype; use crate::util::process::{execute_and_wait, fork_execute}; use super::change_directory; use crate::MIMETYPE_T; -fn _get_options<'a>(path: &path::Path) -> Vec<&'a AppMimetypeEntry> { - let mut options: Vec<&AppMimetypeEntry> = Vec::new(); - if let Some(file_ext) = path.extension() { - if let Some(file_ext) = file_ext.to_str() { - let ext_entries = MIMETYPE_T.app_list_for_ext(file_ext); - options.extend(ext_entries); +fn _get_options<'a>(path: &path::Path) -> Vec<&'a ProgramEntry> { + let mut options: Vec<&ProgramEntry> = Vec::new(); + + if let Some(file_ext) = path.extension().and_then(|ext| ext.to_str()) { + if let Some(entries) = MIMETYPE_T.app_list_for_ext(file_ext) { + options.extend(entries); + return options; + } + } + if let Ok(file_mimetype) = get_mimetype(path) { + if let Some(entry) = MIMETYPE_T.app_list_for_mimetype(file_mimetype.get_type()) { + match entry.subtypes().get(file_mimetype.get_subtype()) { + Some(entries) => { + options.extend(entries); + return options; + } + None => { + let entries = entry.app_list(); + options.extend(entries); + return options; + } + } } } options @@ -27,7 +44,7 @@ fn _get_options<'a>(path: &path::Path) -> Vec<&'a AppMimetypeEntry> { fn _open_with_entry<S>( context: &mut AppContext, backend: &mut AppBackend, - option: &AppMimetypeEntry, + option: &ProgramEntry, files: &[S], ) -> std::io::Result<()> where @@ -65,7 +82,7 @@ fn _open_with_xdg( fn _open_with_helper<S>( context: &mut AppContext, backend: &mut AppBackend, - options: Vec<&AppMimetypeEntry>, + options: Vec<&ProgramEntry>, files: &[S], ) -> std::io::Result<()> where @@ -107,7 +124,7 @@ where let mut args_iter = user_input.split_whitespace(); if let Some(cmd) = args_iter.next() { backend.terminal_drop(); - let mut option = AppMimetypeEntry::new(String::from(cmd)); + let mut option = ProgramEntry::new(String::from(cmd)); option.args(args_iter); let res = execute_and_wait(&option, files); backend.terminal_restore()?; diff --git a/src/config/mimetype/entry.rs b/src/config/mimetype/entry.rs index c6c0307..8a40d47 100644 --- a/src/config/mimetype/entry.rs +++ b/src/config/mimetype/entry.rs @@ -3,32 +3,7 @@ use std::env; use std::fmt; #[derive(Clone, Debug, Deserialize)] -pub struct AppList { - #[serde(default, rename = "inherit")] - _inherit: String, - #[serde(default, rename = "app_list")] - _app_list: Vec<AppMimetypeEntry>, -} - -impl AppList { - pub fn new(_inherit: String, _app_list: Vec<AppMimetypeEntry>) -> Self { - Self { - _inherit, - _app_list, - } - } - - pub fn parent(&self) -> &str { - self._inherit.as_str() - } - - pub fn app_list(&self) -> &[AppMimetypeEntry] { - self._app_list.as_slice() - } -} - -#[derive(Clone, Debug, Deserialize)] -pub struct AppMimetypeEntry { +pub struct ProgramEntry { #[serde(rename = "command")] _command: String, #[serde(default, rename = "args")] @@ -41,7 +16,7 @@ pub struct AppMimetypeEntry { _confirm_exit: bool, } -impl AppMimetypeEntry { +impl ProgramEntry { pub fn new(command: String) -> Self { Self { _command: command, @@ -114,7 +89,7 @@ impl AppMimetypeEntry { } } -impl std::default::Default for AppMimetypeEntry { +impl std::default::Default for ProgramEntry { fn default() -> Self { Self { _command: "".to_string(), @@ -126,7 +101,7 @@ impl std::default::Default for AppMimetypeEntry { } } -impl std::fmt::Display for AppMimetypeEntry { +impl std::fmt::Display for ProgramEntry { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.get_command()).unwrap(); self.get_args() diff --git a/src/config/mimetype/list.rs b/src/config/mimetype/list.rs new file mode 100644 index 0000000..67373e9 --- /dev/null +++ b/src/config/mimetype/list.rs @@ -0,0 +1,66 @@ +use serde_derive::Deserialize; +use std::collections::HashMap; + +use super::ProgramEntry; + +#[derive(Clone, Debug, Deserialize)] +pub struct ExtensionAppListRaw { + #[serde(default, rename = "inherit")] + pub inherit_class: String, + #[serde(default)] + pub app_list: Vec<ProgramEntry>, +} + +impl ExtensionAppListRaw { + pub fn parent(&self) -> &str { + self.inherit_class.as_str() + } + + pub fn app_list(&self) -> &[ProgramEntry] { + self.app_list.as_slice() + } +} + +pub type ExtensionAppList = Vec<ProgramEntry>; + +#[derive(Clone, Debug, Deserialize)] +pub struct MimetypeAppListRaw { + #[serde(default, rename = "inherit")] + pub inherit_class: String, + #[serde(default)] + pub app_list: Vec<ProgramEntry>, + #[serde(default)] + pub subtype: Option<HashMap<String, ExtensionAppListRaw>>, +} + +impl MimetypeAppListRaw { + pub fn parent(&self) -> &str { + self.inherit_class.as_str() + } + + pub fn app_list(&self) -> &[ProgramEntry] { + self.app_list.as_slice() + } +} + +#[derive(Clone, Debug)] +pub struct MimetypeAppList { + _app_list: Vec<ProgramEntry>, + _subtypes: HashMap<String, ExtensionAppList>, +} + +impl MimetypeAppList { + pub fn new(_app_list: Vec<ProgramEntry>, _subtypes: HashMap<String, ExtensionAppList>) -> Self { + Self { + _app_list, + _subtypes, + } + } + pub fn app_list(&self) -> &[ProgramEntry] { + self._app_list.as_slice() + } + + pub fn subtypes(&self) -> &HashMap<String, ExtensionAppList> { + &self._subtypes + } +} diff --git a/src/config/mimetype/mod.rs b/src/config/mimetype/mod.rs index 3ab8beb..ced4807 100644 --- a/src/config/mimetype/mod.rs +++ b/src/config/mimetype/mod.rs @@ -1,5 +1,7 @@ mod entry; +mod list; mod registry; -pub use self::entry::{AppList, AppMimetypeEntry}; -pub use self::registry::AppMimetypeRegistry; +pub use self::entry::*; +pub use self::list::*; +pub use self::registry::*; diff --git a/src/config/mimetype/registry.rs b/src/config/mimetype/registry.rs index 73c20ac..452bb93 100644 --- a/src/config/mimetype/registry.rs +++ b/src/config/mimetype/registry.rs @@ -3,60 +3,88 @@ use std::collections::HashMap; use crate::config::{parse_to_config_file, TomlConfigFile}; -use super::{AppList, AppMimetypeEntry}; +use super::{ + ExtensionAppList, ExtensionAppListRaw, MimetypeAppList, MimetypeAppListRaw, ProgramEntry, +}; -pub type MimetypeRegistry = HashMap<String, AppList>; +pub type ExtensionRegistryRaw = HashMap<String, ExtensionAppListRaw>; +pub type MimetypeRegistryRaw = HashMap<String, MimetypeAppListRaw>; + +pub type ExtensionRegistry = HashMap<String, ExtensionAppList>; +pub type MimetypeRegistry = HashMap<String, MimetypeAppList>; #[derive(Debug, Deserialize)] -pub struct AppMimetypeRegistryCrude { +pub struct AppProgramRegistryRaw { #[serde(default, rename = "class")] - pub _class: HashMap<String, Vec<AppMimetypeEntry>>, + pub _class: HashMap<String, Vec<ProgramEntry>>, #[serde(default, rename = "extension")] - pub _extension: MimetypeRegistry, + pub _extension: ExtensionRegistryRaw, + #[serde(default, rename = "mimetype")] + pub _mimetype: MimetypeRegistryRaw, } #[derive(Debug, Default)] -pub struct AppMimetypeRegistry { - // pub _class: HashMap<String, Vec<AppMimetypeEntry>>, - pub _extension: MimetypeRegistry, +pub struct AppProgramRegistry { + // pub _class: HashMap<String, Vec<ProgramEntry>>, + pub _extension: ExtensionRegistry, + pub _mimetype: MimetypeRegistry, } -pub const EMPTY_ARR: [AppMimetypeEntry; 0] = []; +impl AppProgramRegistry { + pub fn app_list_for_ext(&self, extension: &str) -> Option<&ExtensionAppList> { + self._extension.get(extension) + } -impl AppMimetypeRegistry { - pub fn app_list_for_ext(&self, extension: &str) -> &[AppMimetypeEntry] { - match self._extension.get(extension) { - Some(s) => s.app_list(), - None => &EMPTY_ARR, - } + pub fn app_list_for_mimetype(&self, mimetype: &str) -> Option<&MimetypeAppList> { + self._mimetype.get(mimetype) } } -impl From<AppMimetypeRegistryCrude> for AppMimetypeRegistry { - fn from(crude: AppMimetypeRegistryCrude) -> Self { - let mut registry = MimetypeRegistry::new(); - - for (ext, app_list) in crude._extension { +impl From<AppProgramRegistryRaw> for AppProgramRegistry { + fn from(raw: AppProgramRegistryRaw) -> Self { + let mut extension = ExtensionRegistry::new(); + for (ext, app_list) in raw._extension { let class = app_list.parent(); - let mut combined_app_list: Vec<AppMimetypeEntry> = crude + let mut combined_app_list: ExtensionAppList = raw ._class .get(class) .map(|v| (*v).clone()) .unwrap_or_default(); combined_app_list.extend_from_slice(app_list.app_list()); - let combined_app_list = AppList::new(class.to_string(), combined_app_list); - registry.insert(ext, combined_app_list); + + extension.insert(ext, combined_app_list); + } + + let mut mimetype = MimetypeRegistry::new(); + for (ttype, data) in raw._mimetype { + let class = data.parent(); + let mut combined_app_list: ExtensionAppList = raw + ._class + .get(class) + .map(|v| (*v).clone()) + .unwrap_or_default(); + combined_app_list.extend_from_slice(data.app_list()); + + let subtypes = data + .subtype + .unwrap_or_else(|| HashMap::new()) + .into_iter() + .map(|(k, v)| (k, v.app_list)) + .collect(); + let app_list = MimetypeAppList::new(combined_app_list, subtypes); + mimetype.insert(ttype, app_list); } Self { - _extension: registry, + _extension: extension, + _mimetype: mimetype, } } } -impl TomlConfigFile for AppMimetypeRegistry { +impl TomlConfigFile for AppProgramRegistry { fn get_config(file_name: &str) -> Self { - parse_to_config_file::<AppMimetypeRegistryCrude, AppMimetypeRegistry>(file_name) + parse_to_config_file::<AppProgramRegistryRaw, AppProgramRegistry>(file_name) .unwrap_or_default() } } diff --git a/src/main.rs b/src/main.rs index 2b3f171..1446474 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ use structopt::StructOpt; use crate::commands::quit::QuitAction; use crate::config::{ - AppConfig, AppKeyMapping, AppMimetypeRegistry, AppTheme, JoshutoPreview, TomlConfigFile, + AppConfig, AppKeyMapping, AppProgramRegistry, AppTheme, JoshutoPreview, TomlConfigFile, }; use crate::context::AppContext; use crate::error::JoshutoError; @@ -67,7 +67,7 @@ lazy_static! { config_dirs }; static ref THEME_T: AppTheme = AppTheme::get_config(THEME_FILE); - static ref MIMETYPE_T: AppMimetypeRegistry = AppMimetypeRegistry::get_config(MIMETYPE_FILE); + static ref MIMETYPE_T: AppProgramRegistry = AppProgramRegistry::get_config(MIMETYPE_FILE); static ref PREVIEW_T: JoshutoPreview = JoshutoPreview::get_config(PREVIEW_FILE); static ref HOME_DIR: Option<PathBuf> = dirs_next::home_dir(); diff --git a/src/util/mimetype.rs b/src/util/mimetype.rs new file mode 100644 index 0000000..423cb2f --- /dev/null +++ b/src/util/mimetype.rs @@ -0,0 +1,57 @@ +use std::path::Path; +use std::{io, process::Command}; + +use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult}; + +pub struct Mimetype { + _type: String, + _subtype: String, +} + +impl Mimetype { + pub fn new(ttype: String, subtype: String) -> Self { + Self { + _type: ttype, + _subtype: subtype, + } + } + + pub fn get_type(&self) -> &str { + &self._type + } + + pub fn get_subtype(&self) -> &str { + &&self._subtype + } +} + +pub fn get_mimetype(p: &Path) -> JoshutoResult<Mimetype> { + let res = Command::new("file") + .arg("--mime-type") + .arg("-Lb") + .arg(p) + .output(); + + let output = res?; + if !output.status.success() { + let stderr_msg = String::from_utf8_lossy(&output.stderr).to_string(); + + let error = JoshutoError::new( + JoshutoErrorKind::Io(io::ErrorKind::InvalidInput), + stderr_msg, + ); + return Err(error); + } + + let stdout_msg = String::from_utf8_lossy(&output.stdout).to_string(); + match stdout_msg.trim().split_once('/') { + Some((ttype, subtype)) => Ok(Mimetype::new(ttype.to_string(), subtype.to_string())), + None => { + let error = JoshutoError::new( + JoshutoErrorKind::Io(io::ErrorKind::InvalidInput), + "Unknown mimetype".to_string(), + ); + return Err(error); + } + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index c7ad758..562ec57 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -3,6 +3,7 @@ pub mod devicons; pub mod format; pub mod keyparse; +pub mod mimetype; pub mod name_resolution; pub mod process; pub mod search; diff --git a/src/util/process.rs b/src/util/process.rs index 4bf34fa..fba1cff 100644 --- a/src/util/process.rs +++ b/src/util/process.rs @@ -3,11 +3,11 @@ use std::process; use std::sync::mpsc; use std::thread; -use crate::config::AppMimetypeEntry; +use crate::config::ProgramEntry; use crate::event::AppEvent; pub fn fork_execute<I, S>( - entry: &AppMimetypeEntry, + entry: &ProgramEntry, paths: I, event_tx: mpsc::Sender<AppEvent>, ) -> std::io::Result<(u32, thread::JoinHandle<()>)> @@ -38,7 +38,7 @@ where Ok((child_id, handle)) } -pub fn execute_and_wait<I, S>(entry: &AppMimetypeEntry, paths: I) -> std::io::Result<()> +pub fn execute_and_wait<I, S>(entry: &ProgramEntry, paths: I) -> std::io::Result<()> where I: IntoIterator<Item = S>, S: AsRef<std::ffi::OsStr>, |