summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Zhao <jeff.no.zhao@gmail.com>2022-09-11 09:50:09 -0400
committerJeff Zhao <jeff.no.zhao@gmail.com>2022-09-11 09:51:01 -0400
commitf117ddaccb996942774d9efee9c5807fe7272ded (patch)
tree21a0958d9502a149064ee99a2827c49d3d7294e2
parent1908ff94b7e5f981a4254ca697056a299b2e6b51 (diff)
add mimetype support via file command
-rw-r--r--config/mimetype.toml10
-rw-r--r--docs/configuration/mimetype.toml.md17
-rw-r--r--src/commands/open_file.rs37
-rw-r--r--src/config/mimetype/entry.rs33
-rw-r--r--src/config/mimetype/list.rs66
-rw-r--r--src/config/mimetype/mod.rs6
-rw-r--r--src/config/mimetype/registry.rs80
-rw-r--r--src/main.rs4
-rw-r--r--src/util/mimetype.rs57
-rw-r--r--src/util/mod.rs1
-rw-r--r--src/util/process.rs6
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>,