aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/config/cmd_cfg.rs
blob: dac360a20df44446421c5e2c7879b10e88899247 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use mingling::{
    LazyRes, Routable, ShellContext, Suggest,
    macros::{
        arg, buffer, chain, command, completion, metadata, pack, r_println, renderer, suggest,
    },
    metadata::Description,
    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
    })
}

#[metadata(EntryCfg)]
pub fn desc_cfg() -> Description {
    "View and edit Mling's user configuration file".into()
}