summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Nguyen <benjamin.van.nguyen@gmail.com>2023-06-25 17:48:13 -0700
committerBenjamin Nguyen <benjamin.van.nguyen@gmail.com>2023-06-25 17:48:13 -0700
commitb9d09c6831d270ce8a099ae6339a66af51f34c0f (patch)
tree9b68f4591a339c7495cb4600beafbede3af4de8e
parentd841f3daea3cd6e4fbe09d83a7b2376862721174 (diff)
support multi-config with toml config file
-rw-r--r--Cargo.lock303
-rw-r--r--Cargo.toml1
-rw-r--r--src/context/config/mod.rs22
-rw-r--r--src/context/config/rc.rs (renamed from src/context/config.rs)58
-rw-r--r--src/context/config/toml/error.rs19
-rw-r--r--src/context/config/toml/mod.rs222
-rw-r--r--src/context/config/toml/test.rs91
-rw-r--r--src/context/error.rs15
-rw-r--r--src/context/mod.rs47
-rw-r--r--src/context/test.rs37
-rw-r--r--tests/data/.erdtreerc3
11 files changed, 712 insertions, 106 deletions
diff --git a/Cargo.lock b/Cargo.lock
index efe1064..7d9fa5f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,17 @@
version = 3
[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -36,18 +47,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
+name = "async-trait"
+version = "0.1.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.10",
+]
+
+[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
name = "bstr"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -147,12 +184,40 @@ dependencies = [
]
[[package]]
+name = "config"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7"
+dependencies = [
+ "async-trait",
+ "json5",
+ "lazy_static",
+ "nom",
+ "pathdiff",
+ "ron",
+ "rust-ini",
+ "serde",
+ "serde_json",
+ "toml",
+ "yaml-rust",
+]
+
+[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
+name = "cpufeatures"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "crossterm"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -178,6 +243,16 @@ dependencies = [
]
[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
name = "cxx"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -222,6 +297,16 @@ dependencies = [
]
[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
name = "dirs"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -242,6 +327,12 @@ dependencies = [
]
[[package]]
+name = "dlv-list"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
+
+[[package]]
name = "erdtree"
version = "3.0.2"
dependencies = [
@@ -249,6 +340,7 @@ dependencies = [
"chrono",
"clap",
"clap_complete",
+ "config",
"crossterm",
"dirs",
"errno 0.3.1",
@@ -323,6 +415,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -347,6 +449,15 @@ dependencies = [
]
[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -444,6 +555,12 @@ dependencies = [
]
[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
+[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -453,6 +570,17 @@ dependencies = [
]
[[package]]
+name = "json5"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "serde",
+]
+
+[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -474,6 +602,12 @@ dependencies = [
]
[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -521,6 +655,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
name = "mio"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -533,6 +673,16 @@ dependencies = [
]
[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -568,6 +718,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
+name = "ordered-multimap"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
+dependencies = [
+ "dlv-list",
+ "hashbrown",
+]
+
+[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -603,6 +763,56 @@ dependencies = [
]
[[package]]
+name = "pathdiff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+
+[[package]]
+name = "pest"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16833386b02953ca926d19f64af613b9bf742c48dcd5e09b32fbfc9740bf84e2"
+dependencies = [
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7763190f9406839f99e5197afee8c9e759969f7dbfa40ad3b8dbee8757b745b5"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "249061b22e99973da1f5f5f1410284419e283bb60b79255bf5f42a94b66a2e00"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.10",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "457c310cfc9cf3f22bc58901cc7f0d3410ac5d6298e432a4f9a6138565cb6df6"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2",
+]
+
+[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -682,6 +892,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
+name = "ron"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
+dependencies = [
+ "base64",
+ "bitflags",
+ "serde",
+]
+
+[[package]]
+name = "rust-ini"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
+dependencies = [
+ "cfg-if",
+ "ordered-multimap",
+]
+
+[[package]]
name = "rustix"
version = "0.36.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -710,6 +941,12 @@ dependencies = [
]
[[package]]
+name = "ryu"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+
+[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -735,6 +972,42 @@ name = "serde"
version = "1.0.156"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.156"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
[[package]]
name = "signal-hook"
@@ -873,6 +1146,27 @@ dependencies = [
]
[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
+
+[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1179,3 +1473,12 @@ name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/Cargo.toml b/Cargo.toml
index c6eb1e4..3060e9a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,6 +28,7 @@ ansi_term = "0.12.1"
chrono = "0.4.24"
clap = { version = "4.1.1", features = ["derive"] }
clap_complete = "4.1.1"
+config = { version = "0.13.3", features = ["toml"] }
crossterm = "0.26.1"
dirs = "5.0"
errno = "0.3.1"
diff --git a/src/context/config/mod.rs b/src/context/config/mod.rs
new file mode 100644
index 0000000..c28c3a7
--- /dev/null
+++ b/src/context/config/mod.rs
@@ -0,0 +1,22 @@
+const ERDTREE_CONFIG_TOML: &str = ".erdtree.toml";
+const ERDTREE_TOML_PATH: &str = "ERDTREE_TOML_PATH";
+
+const ERDTREE_CONFIG_NAME: &str = ".erdtreerc";
+const ERDTREE_CONFIG_PATH: &str = "ERDTREE_CONFIG_PATH";
+
+const ERDTREE_DIR: &str = "erdtree";
+
+#[cfg(unix)]
+const CONFIG_DIR: &str = ".config";
+
+#[cfg(unix)]
+const HOME: &str = "HOME";
+
+#[cfg(unix)]
+const XDG_CONFIG_HOME: &str = "XDG_CONFIG_HOME";
+
+/// Concerned with loading `.erdtreerc`.
+pub mod rc;
+
+/// Concerned with loading `.erdtree.toml`.
+pub mod toml;
diff --git a/src/context/config.rs b/src/context/config/rc.rs
index 377d48b..7e230c9 100644
--- a/src/context/config.rs
+++ b/src/context/config/rc.rs
@@ -1,20 +1,4 @@
-use std::{
- env, fs,
- path::{Path, PathBuf},
-};
-
-const ERDTREE_CONFIG_NAME: &str = ".erdtreerc";
-const ERDTREE_CONFIG_PATH: &str = "ERDTREE_CONFIG_PATH";
-const ERDTREE_DIR: &str = "erdtree";
-
-#[cfg(unix)]
-const CONFIG_DIR: &str = ".config";
-
-#[cfg(unix)]
-const HOME: &str = "HOME";
-
-#[cfg(unix)]
-const XDG_CONFIG_HOME: &str = "XDG_CONFIG_HOME";
+use std::{env, fs, path::PathBuf};
/// Reads the config file into a `String` if there is one. When `None` is provided then the config
/// is looked for in the following locations in order:
@@ -25,25 +9,19 @@ const XDG_CONFIG_HOME: &str = "XDG_CONFIG_HOME";
/// - `$HOME/.config/erdtree/.erdtreerc`
/// - `$HOME/.erdtreerc`
#[cfg(unix)]
-pub fn read_config_to_string<T: AsRef<Path>>(path: Option<T>) -> Option<String> {
- path.map(fs::read_to_string)
- .and_then(Result::ok)
- .or_else(config_from_config_path)
+pub fn read_config_to_string() -> Option<String> {
+ config_from_config_path()
.or_else(config_from_xdg_path)
.or_else(config_from_home)
.map(|e| prepend_arg_prefix(&e))
}
-
-/// Reads the config file into a `String` if there is one. When `None` is provided then the config
/// is looked for in the following locations in order (Windows specific):
///
/// - `$ERDTREE_CONFIG_PATH`
/// - `%APPDATA%/erdtree/.erdtreerc`
#[cfg(windows)]
-pub fn read_config_to_string<T: AsRef<Path>>(path: Option<T>) -> Option<String> {
- path.map(fs::read_to_string)
- .and_then(Result::ok)
- .or_else(config_from_config_path)
+pub fn read_config_to_string() -> Option<String> {
+ config_from_config_path()
.or_else(config_from_appdata)
.map(|e| prepend_arg_prefix(&e))
}
@@ -61,13 +39,13 @@ pub fn parse<'a>(config: &'a str) -> Vec<&'a str> {
.next()
.map_or(true, |ch| ch != '#')
})
- .flat_map(str::split_ascii_whitespace)
+ .flat_map(str::split_whitespace)
.collect::<Vec<&'a str>>()
}
/// Try to read in config from `ERDTREE_CONFIG_PATH`.
fn config_from_config_path() -> Option<String> {
- env::var_os(ERDTREE_CONFIG_PATH)
+ env::var_os(super::ERDTREE_CONFIG_PATH)
.map(PathBuf::from)
.map(fs::read_to_string)
.and_then(Result::ok)
@@ -78,15 +56,15 @@ fn config_from_config_path() -> Option<String> {
/// - `$HOME/.erdtreerc`
#[cfg(not(windows))]
fn config_from_home() -> Option<String> {
- let home = env::var_os(HOME).map(PathBuf::from)?;
+ let home = env::var_os(super::HOME).map(PathBuf::from)?;
let config_path = home
- .join(CONFIG_DIR)
- .join(ERDTREE_DIR)
- .join(ERDTREE_CONFIG_NAME);
+ .join(super::CONFIG_DIR)
+ .join(super::ERDTREE_DIR)
+ .join(super::ERDTREE_CONFIG_NAME);
fs::read_to_string(config_path).ok().or_else(|| {
- let config_path = home.join(ERDTREE_CONFIG_NAME);
+ let config_path = home.join(super::ERDTREE_CONFIG_NAME);
fs::read_to_string(config_path).ok()
})
}
@@ -97,7 +75,9 @@ fn config_from_home() -> Option<String> {
fn config_from_appdata() -> Option<String> {
let app_data = dirs::config_dir()?;
- let config_path = app_data.join(ERDTREE_DIR).join(ERDTREE_CONFIG_NAME);
+ let config_path = app_data
+ .join(super::ERDTREE_DIR)
+ .join(super::ERDTREE_CONFIG_NAME);
fs::read_to_string(config_path).ok()
}
@@ -107,12 +87,14 @@ fn config_from_appdata() -> Option<String> {
/// - `$XDG_CONFIG_HOME/.erdtreerc`
#[cfg(unix)]
fn config_from_xdg_path() -> Option<String> {
- let xdg_config = env::var_os(XDG_CONFIG_HOME).map(PathBuf::from)?;
+ let xdg_config = env::var_os(super::XDG_CONFIG_HOME).map(PathBuf::from)?;
- let config_path = xdg_config.join(ERDTREE_DIR).join(ERDTREE_CONFIG_NAME);
+ let config_path = xdg_config
+ .join(super::ERDTREE_DIR)
+ .join(super::ERDTREE_CONFIG_NAME);
fs::read_to_string(config_path).ok().or_else(|| {
- let config_path = xdg_config.join(ERDTREE_CONFIG_NAME);
+ let config_path = xdg_config.join(super::ERDTREE_CONFIG_NAME);
fs::read_to_string(config_path).ok()
})
}
diff --git a/src/context/config/toml/error.rs b/src/context/config/toml/error.rs
new file mode 100644
index 0000000..c6a4be3
--- /dev/null
+++ b/src/context/config/toml/error.rs
@@ -0,0 +1,19 @@
+use config::ConfigError;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("Failed to load .erdtree.toml")]
+ LoadConfig,
+
+ #[error("The configuration file is improperly formatted")]
+ InvalidFormat(#[from] ConfigError),
+
+ #[error("Alternate configuration '{0}' was not found in '.erdtree.toml'")]
+ MissingAltConfig(String),
+
+ #[error("'#{0}' is required to be a pointer-sized unsigned integer type")]
+ InvalidInteger(String),
+
+ #[error("'#{0}' has a type that is invalid")]
+ InvalidArgument(String),
+}
diff --git a/src/context/config/toml/mod.rs b/src/context/config/toml/mod.rs
new file mode 100644
index 0000000..4ac6386
--- /dev/null
+++ b/src/context/config/toml/mod.rs
@@ -0,0 +1,222 @@
+use config::{Config, File, Value, ValueKind};
+use error::Error;
+use std::{env, ffi::OsString};
+
+/// Errors associated with loading and parsing the toml config file.
+pub mod error;
+
+/// Testing related to `.erdtree.toml`.
+pub mod test;
+
+/// Represents an instruction on how to handle a single key-value pair, which makes up a single
+/// command-line argument, when constructing the arguments vector.
+enum ArgInstructions {
+ /// Used for bool arguments such as `--icons`. When `icons = true` is set in `.erdtree.toml`,
+ /// we only want `--icons` to be pushed into the ultimate arguments vector.
+ PushKeyOnly,
+
+ /// Used for arguments such as `--threads 10`.
+ PushKeyValue { parsed_value: OsString },
+
+ /// If a bool field is set to false in `.erdtree.toml` (e.g. `icons = false`) then we want to
+ /// completely omit the key-value pair from the arguments that we ultimately use.
+ Pass,
+}
+
+/// Takes in a `Config` that is generated from [`load_toml`] returning a `Vec<OsString>` which
+/// represents command-line arguments from `.erdtree.toml`. If a `nested_table` is provided then
+/// the top-level table in `.erdtree.toml` is ignored and the configurations specified in the
+/// `nested_table` will be used instead.
+pub fn parse(config: Config, nested_table: Option<&str>) -> Result<Vec<OsString>, Error> {
+ let mut args_map = config.cache.into_table()?;
+
+ if let Some(table) = nested_table {
+ let new_conf = args_map
+ .get(table)
+ .and_then(|conf| conf.clone().into_table().ok())
+ .ok_or_else(|| Error::MissingAltConfig(table.to_owned()))?;
+
+ args_map = new_conf;
+ } else {
+ args_map.retain(|_k, v| !matches!(v.kind, ValueKind::Table(_)));
+ }
+
+ let mut parsed_args = vec![OsString::from("--")];
+
+ let process_key = |s| OsString::from(format!("--{s}").replace('_', "-"));
+
+ for (k, v) in &args_map {
+ match parse_argument(k, v)? {
+ ArgInstructions::PushKeyValue { parsed_value } => {
+ let fmt_key = process_key(k);
+ parsed_args.push(fmt_key);
+ parsed_args.push(parsed_value);
+ }
+
+ ArgInstructions::PushKeyOnly => {
+ let fmt_key = process_key(k);
+ parsed_args.push(fmt_key);
+ }
+
+ ArgInstructions::Pass => continue,
+ }
+ }
+
+ Ok(parsed_args)
+}
+
+/// Reads in `.erdtree.toml` file.
+pub fn load() -> Result<Config, Error> {
+ #[cfg(windows)]
+ return windows::load_toml().ok_or(Error::LoadConfig);
+
+ #[cfg(unix)]
+ unix::load_toml().ok_or(Error::LoadConfig)
+}
+
+/// Attempts to load in `.erdtree.toml` from `$ERDTREE_TOML_PATH`. Will return `None` for whatever
+/// reason.
+fn toml_from_env() -> Option<Config> {
+ let config = env::var_os(super::ERDTREE_TOML_PATH)
+ .map(OsString::into_string)
+ .and_then(Result::ok)?;
+
+ let file = config.strip_suffix(".toml").map(File::with_name)?;
+
+ Config::builder().add_source(file).build().ok()
+}
+
+/// Simple utility used to extract the underlying value from the [`Value`] enum that we get when
+/// loading in the values from `.erdtree.toml`, returning instructions on how the argument should
+/// be processed into the ultimate arguments vector.
+fn parse_argument(keyword: &str, arg: &Value) -> Result<ArgInstructions, Error> {
+ macro_rules! try_parse_num {
+ ($n:expr) => {
+ usize::try_from($n)
+ .map_err(|_e| Error::InvalidInteger(keyword.to_owned()))
+ .map(|num| {
+ let parsed = OsString::from(format!("{num}"));
+ ArgInstructions::PushKeyValue {
+ parsed_value: parsed,
+ }
+ })
+ };
+ }
+
+ match &arg.kind {
+ ValueKind::Boolean(val) => {
+ if *val {
+ Ok(ArgInstructions::PushKeyOnly)
+ } else {
+ Ok(ArgInstructions::Pass)
+ }
+ }
+ ValueKind::String(val) => Ok(ArgInstructions::PushKeyValue {
+ parsed_value: OsString::from(val),
+ }),
+ ValueKind::I64(val) => try_parse_num!(*val),
+ ValueKind::I128(val) => try_parse_num!(*val),
+ ValueKind::U64(val) => try_parse_num!(*val),
+ ValueKind::U128(val) => try_parse_num!(*val),
+ _ => Err(Error::InvalidArgument(keyword.to_owned())),
+ }
+}
+
+#[cfg(unix)]
+mod unix {
+ use super::super::{CONFIG_DIR, ERDTREE_CONFIG_TOML, ERDTREE_DIR, HOME, XDG_CONFIG_HOME};
+ use config::{Config, File};
+ use std::{env, path::PathBuf};
+
+ /// Looks for `.erdtree.toml` in the following locations in order:
+ ///
+ /// - `$ERDTREE_TOML_PATH`
+ /// - `$XDG_CONFIG_HOME/erdtree/.erdtree.toml`
+ /// - `$XDG_CONFIG_HOME/.erdtree.toml`
+ /// - `$HOME/.config/erdtree/.erdtree.toml`
+ /// - `$HOME/.erdtree.toml`
+ pub(super) fn load_toml() -> Option<Config> {
+ super::toml_from_env()
+ .or_else(toml_from_xdg_path)
+ .or_else(toml_from_home)
+ }
+
+ /// Looks for `.erdtree.toml` in the following locations in order:
+ ///
+ /// - `$XDG_CONFIG_HOME/erdtree/.erdtree.toml`
+ /// - `$XDG_CONFIG_HOME/.erdtree.toml`
+ fn toml_from_xdg_path() -> Option<Config> {
+ let config = env::var_os(XDG_CONFIG_HOME).map(PathBuf::from)?;
+
+ let mut file = config
+ .join(ERDTREE_DIR)
+ .join(ERDTREE_CONFIG_TOML)
+ .to_str()
+ .and_then(|s| s.strip_suffix(".toml"))
+ .map(File::with_name);
+
+ if file.is_none() {
+ file = config
+ .join(ERDTREE_CONFIG_TOML)
+ .to_str()
+ .and_then(|s| s.strip_suffix(".toml"))
+ .map(File::with_name);
+ }
+
+ Config::builder().add_source(file?).build().ok()
+ }
+
+ /// Looks for `.erdtree.toml` in the following locations in order:
+ ///
+ /// - `$HOME/.config/erdtree/.erdtree.toml`
+ /// - `$HOME/.erdtree.toml`
+ fn toml_from_home() -> Option<Config> {
+ let home = env::var_os(HOME).map(PathBuf::from)?;
+
+ let mut file = home
+ .join(CONFIG_DIR)
+ .join(ERDTREE_DIR)
+ .join(ERDTREE_CONFIG_TOML)
+ .to_str()
+ .and_then(|s| s.strip_suffix(".toml"))
+ .map(File::with_name);
+
+ if file.is_none() {
+ file = home
+ .join(ERDTREE_CONFIG_TOML)
+ .to_str()
+ .and_then(|s| s.strip_suffix(".toml"))
+ .map(File::with_name);
+ }
+
+ Config::builder().add_source(file?).build().ok()
+ }
+}
+
+#[cfg(windows)]
+mod windows {
+ use super::super::{ERDTREE_CONFIG_TOML, ERDTREE_DIR};
+ use config::{Config, File};
+ use std::{env, path::PathBuf};
+
+ /// Try to read in config from the following location:
+ /// - `%APPDATA%/erdtree/.erdtreerc`
+ pub(super) fn load_toml() -> Option<Config> {
+ super::toml_from_env().or_else(toml_from_appdata)
+ }
+
+ /// Try to read in config from the following location:
+ /// - `%APPDATA%/erdtree/.erdtreerc`
+ fn toml_from_appdata() -> Option<Config> {
+ let app_data = dirs::config_dir()?;
+
+ let file = app_data
+ .join(ERDTREE_DIR)
+ .join(ERDTREE_CONFIG_TOML)
+ .to_str()
+ .and_then(|s| s.strip_prefix(".toml"))
+ .map(File::with_name)?;
+
+ Config::builder().add_source(file).build().ok()
+ }
+}
diff --git a/src/context/config/toml/test.rs b/src/context/config/toml/test.rs
new file mode 100644
index 0000000..adba50d
--- /dev/null
+++ b/src/context/config/toml/test.rs
@@ -0,0 +1,91 @@
+#[test]
+fn parse_toml() -> Result<(), Box<dyn std::error::Error>> {
+ use config::{Config, File};
+ use std::{ffi::OsString, io::Write};
+ use tempfile::Builder;
+
+ let mut config_file = Builder::new()
+ .prefix(".erdtree")
+ .suffix(".toml")
+ .tempfile()?;
+
+ let toml_contents = r#"
+ icons = true
+ human = true
+ threads = 10
+
+ [grogoroth]
+ disk_usage = "block"
+ icons = true
+ human = false
+ threads = 10
+ "#;
+
+ config_file.write(toml_contents.as_bytes())?;
+
+ let file = config_file
+ .path()
+ .to_str()
+ .and_then(|s| s.strip_suffix(".toml"))
+ .map(File::with_name)
+ .unwrap();
+
+ let config = Config::builder().add_source(file).build()?;
+
+ // TOP-LEVEL TABLE
+ let mut toml = super::parse(config.clone(), None)?;
+
+ let expected = vec![
+ OsString::from("--"),
+ OsString::from("--icons"),
+ OsString::from("--human"),
+ OsString::from("--threads"),
+ OsString::from("10"),
+ ];
+
+ for (i, outer_item) in expected.iter().enumerate() {
+ for j in 0..toml.len() {
+ let inner_item = &toml[j];
+
+ if outer_item == inner_item {
+ toml.swap(i, j);
+ }
+ }
+ }
+
+ assert_eq!(toml.len(), expected.len());
+
+ for (lhs, rhs) in toml.iter().zip(expected.iter()) {
+ assert_eq!(lhs, rhs);
+ }
+
+ // NAMED-TABLE
+ let mut toml = super::parse(config, Some("grogoroth"))?;
+
+ let expected = vec![
+ OsString::from("--"),
+ OsString::from("--disk-usage"),
+ OsString::from("block"),
+ OsString::from("--icons"),
+ OsString::from("--threads"),
+ OsString::from("10"),
+ ];
+
+ for (i, outer_item) in expected.iter().enumerate() {
+ for j in 0..toml.len() {
+ let inner_item = &toml[j];
+
+ if outer_item == inner_item {
+ toml.swap(i, j);
+ }
+ }
+ }
+
+ assert_eq!(toml.len(), expected.len());
+
+ for (lhs, rhs) in toml.iter().zip(expected.iter()) {
+ assert_eq!(lhs, rhs);
+ }
+
+ Ok(())
+}
diff --git a/src/context/error.rs b/src/context/error.rs
index 518133f..056d9fb 100644
--- a/src/context/error.rs
+++ b/src/context/error.rs
@@ -1,14 +1,16 @@
+use super::config::toml::error::Error as TomlError;
use clap::Error as ClapError;
use ignore::Error as IgnoreError;
use regex::Error as RegexError;
+use std::convert::From;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}")]
- ArgParse(#[source] ClapError),
+ ArgParse(ClapError),
#[error("A configuration file was found but failed to parse: {0}")]
- Config(#[source] ClapError),
+ Config(ClapError),
#[error("No glob was provided")]
EmptyGlob,
@@ -21,4 +23,13 @@ pub enum Error {
#[error("Missing '--pattern' argument")]
PatternNotProvided,
+
+ #[error("{0}")]
+ ConfigError(TomlError),
+}
+
+impl From<TomlError> for Error {
+ fn from(value: TomlError) -> Self {
+ Self::ConfigError(value)
+ }
}
diff --git a/src/context/mod.rs b/src/context/mod.rs
index f524ad7..d15acf1 100644
--- a/src/context/mod.rs
+++ b/src/context/mod.rs
@@ -1,6 +1,6 @@
use super::disk_usage::{file_size::DiskUsage, units::PrefixKind};
use crate::tty;
-use clap::{parser::ValueSource, ArgMatches, CommandFactory, FromArgMatches, Id, Parser};
+use clap::{parser::ValueSource, ArgMatches, CommandFactory, FromArgMatches, Parser};
use color::Coloring;
use error::Error;
use ignore::{
@@ -12,7 +12,9 @@ use std::{
borrow::Borrow,
convert::From,
ffi::{OsStr, OsString},
+ num::NonZeroUsize,
path::{Pa