aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_cli')
-rw-r--r--mingling_cli/help/help.txt1
-rw-r--r--mingling_cli/src/config.rs24
-rw-r--r--mingling_cli/src/config/cmd_cfg.rs110
3 files changed, 129 insertions, 6 deletions
diff --git a/mingling_cli/help/help.txt b/mingling_cli/help/help.txt
index 91a7a7e..dd6ea3d 100644
--- a/mingling_cli/help/help.txt
+++ b/mingling_cli/help/help.txt
@@ -34,4 +34,5 @@ __ pkg-disable <NAME> *Disable the specified package*
__ **CONFIG:**
__ cfg <KEY> <VALUE> *Edit the value of a configuration item*
__ cfg <KEY> *Print the value of the configuration item; print nothing if it does not exist*
+__ cfg <KEY> --pair *Output in the form of `"key" = "value"`*
__ cfg *Print all configuration items, can be used with grep for searching*
diff --git a/mingling_cli/src/config.rs b/mingling_cli/src/config.rs
index 834b98c..32a0994 100644
--- a/mingling_cli/src/config.rs
+++ b/mingling_cli/src/config.rs
@@ -1,14 +1,20 @@
-use std::collections::HashMap;
-
use mingling::{LazyInit, Program, macros::program_setup};
+use std::collections::HashMap;
use crate::ThisProgram;
+pub mod cmd_cfg;
+
#[derive(Debug, Default, Clone)]
pub struct ResMlingConfig {
kvp: HashMap<String, String>,
}
+/// Get the path to the config file.
+fn config_path() -> Option<std::path::PathBuf> {
+ dirs::data_dir().map(|data_dir| data_dir.join("mingling").join("mling-cfg.json"))
+}
+
impl ResMlingConfig {
/// Edit a KVP entry. Empty string removes the key.
pub fn edit(&mut self, key: &str, value: &str) {
@@ -43,8 +49,10 @@ impl ResMlingConfig {
/// Read the config from disk, defaulting to empty if not present.
pub fn read() -> Self {
- let path = std::path::Path::new("");
- Self::read_from_path(path)
+ match config_path() {
+ Some(path) => Self::read_from_path(&path),
+ None => Self::default(),
+ }
}
/// Read the config from a file at the given path.
@@ -81,11 +89,15 @@ impl ResMlingConfig {
/// Write the config to the default data directory path.
pub fn write(&self) {
- if let Some(data_dir) = dirs::data_dir() {
- let path = data_dir.join("mingling").join("mling-cfg.json");
+ if let Some(path) = config_path() {
self.write_to_path(&path);
}
}
+
+ /// Get the underlying hash map as a reference.
+ pub fn get_hash_map(&self) -> &HashMap<String, String> {
+ &self.kvp
+ }
}
impl From<HashMap<String, String>> for ResMlingConfig {
diff --git a/mingling_cli/src/config/cmd_cfg.rs b/mingling_cli/src/config/cmd_cfg.rs
new file mode 100644
index 0000000..f0e4c2b
--- /dev/null
+++ b/mingling_cli/src/config/cmd_cfg.rs
@@ -0,0 +1,110 @@
+use mingling::{
+ LazyRes, Routable, ShellContext, Suggest,
+ macros::{arg, buffer, chain, command, completion, pack, r_println, renderer, suggest},
+ picker::{EntryPicker, PickerArg, value::Flag},
+};
+
+use crate::{Entry, Next, config::ResMlingConfig};
+
+const FLAG_PAIR: PickerArg<Flag> = arg![pair: Flag];
+
+pack!(StateConfigEdit = (String, String));
+pack!(ResultConfigKeyValuePair = String);
+pack!(ResultConfigValue = String);
+pack!(ResultConfig = ());
+
+#[command]
+pub fn cfg(args: Entry) -> Next {
+ let (key, value, show_pair) = args
+ .pick(&arg![Option<String>])
+ .pick(&arg![Option<String>])
+ .pick(&FLAG_PAIR)
+ .unwrap();
+
+ match (key, value) {
+ (Some(k), Some(v)) => {
+ // Edit
+ StateConfigEdit::new((k, v)).into()
+ }
+ (Some(k), None) => {
+ // Display
+ if *show_pair {
+ ResultConfigKeyValuePair::new(k).to_render()
+ } else {
+ ResultConfigValue::new(k).to_render()
+ }
+ }
+ (None, None) => {
+ // List
+ ResultConfig::new(()).to_render()
+ }
+ _ => {
+ unreachable!("This path is unreachable given the positional parsing done by arg-picker")
+ }
+ }
+}
+
+#[chain]
+pub fn handle_state_config_edit(kv: StateConfigEdit, config: &mut LazyRes<ResMlingConfig>) {
+ let config = config.get_mut();
+ config.edit(&kv.0, &kv.1);
+}
+
+#[renderer(buffer)]
+pub fn render_config_kvp(r: ResultConfigKeyValuePair, config: &mut LazyRes<ResMlingConfig>) {
+ let config = config.get_ref();
+ let key = r.inner;
+ let value = config.get(&key);
+ r_println!(
+ "\"{}\" = \"{}\"",
+ escape_config_value(&key),
+ escape_config_value(value)
+ )
+}
+
+#[renderer(buffer)]
+pub fn render_config_value(r: ResultConfigValue, config: &mut LazyRes<ResMlingConfig>) {
+ let config = config.get_ref();
+ let key = r.inner;
+ let value = config.get(&key);
+ r_println!("{}", value)
+}
+
+#[renderer(buffer)]
+pub fn render_config(_: ResultConfig, config: &mut LazyRes<ResMlingConfig>) {
+ let config = config.get_ref();
+ for (k, v) in config.get_hash_map().iter() {
+ r_println!(
+ "\"{}\" = \"{}\"",
+ escape_config_value(k),
+ escape_config_value(v)
+ )
+ }
+}
+
+/// Utility function: escapes `\t`, `\n`, `\b`, `\r` in the string to their
+/// literal representations `\\t`, `\\n`, `\\b`, `\\r`, then trims leading and
+/// trailing whitespace, and escapes `"` to `\"`.
+pub fn escape_config_value(input: &str) -> String {
+ let mut result = String::with_capacity(input.len());
+ for ch in input.trim().chars() {
+ match ch {
+ '\t' => result.push_str("\\t"),
+ '\n' => result.push_str("\\n"),
+ '\u{0008}' => result.push_str("\\b"),
+ '\r' => result.push_str("\\r"),
+ '"' => result.push_str("\\\""),
+ _ => result.push(ch),
+ }
+ }
+ result
+}
+
+#[completion(EntryCfg)]
+pub fn complete_config(_ctx: &ShellContext, config: &mut LazyRes<ResMlingConfig>) -> Suggest {
+ let config = config.get_ref();
+ let keys = config.get_hash_map().keys().cloned().collect::<Vec<_>>();
+ Suggest::from(keys).combine(suggest! {
+ FLAG_PAIR
+ })
+}