summaryrefslogtreecommitdiffstats
path: root/src/verb/invocation_parser.rs
blob: 9f7b6c9bb8088ee568f726e42d6f01ffbf2f16cc (plain)
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
119
120
121
122
123
124
125
126
use {
    super::*,
    crate::{
        app::*,
        errors::ConfError,
        path::PathAnchor,
    },
    regex::Regex,
    std::{
        collections::HashMap,
        path::PathBuf,
    },
};


/// Definition of how the user input should be checked
/// and maybe parsed to provide the arguments used
/// for execution or description.
#[derive(Debug)]
pub struct InvocationParser {

    /// pattern of how the command is supposed to be typed in the input
    pub invocation_pattern: VerbInvocation,

    /// a regex to read the arguments in the user input
    args_parser: Option<Regex>,

    /// whether the path, when non absolute, should be interpreted
    /// as relative to the closest directory (which may be the selection)
    /// or to the parent of the selection
    pub arg_anchor: PathAnchor,

    /// contain the type of selection in case there's only one arg
    /// and it's a path (when it's not None, the user can type ctrl-P
    /// to select the argument in another panel)
    pub arg_selection_type: Option<SelectionType>,

}

impl InvocationParser {

    pub fn new(
        invocation_str: &str,
    ) -> Result<Self, ConfError> {
        let invocation_pattern = VerbInvocation::from(invocation_str);
        let mut args_parser = None;
        let mut arg_selection_type = None;
        let mut arg_anchor = PathAnchor::Unspecified;
        if let Some(args) = &invocation_pattern.args {
            let spec = GROUP.replace_all(args, r"(?P<$1>.+)");
            let spec = format!("^{}$", spec);
            args_parser = match Regex::new(&spec) {
                Ok(regex) => Some(regex),
                Err(_) => {
                    return Err(ConfError::InvalidVerbInvocation { invocation: spec });
                }
            };
            if let Some(group) = GROUP.find(args) {
                if group.start() == 0 && group.end() == args.len() {
                    // there's one group, covering the whole args
                    arg_selection_type = Some(SelectionType::Any);
                    let group_str = group.as_str();
                    if group_str.ends_with("path-from-parent}") {
                        arg_anchor = PathAnchor::Parent;
                    } else if group_str.ends_with("path-from-directory}") {
                        arg_anchor = PathAnchor::Directory;
                    }
                }
            }
        }
        Ok(Self {
            invocation_pattern,
            args_parser,
            arg_selection_type,
            arg_anchor,
        })
    }

    pub fn name(&self) -> &str {
        &self.invocation_pattern.name
    }

    /// Assuming the verb has been matched, check whether the arguments
    /// are OK according to the regex. Return none when there's no problem
    /// and return the error to display if arguments don't match
    pub fn check_args(
        &self,
        invocation: &VerbInvocation,
        _other_path: &Option<PathBuf>,
    ) -> Option<String> {
        match (&invocation.args, &self.args_parser) {
            (None, None) => None,
            (None, Some(ref regex)) => {
                if regex.is_match("") {
                    None
                } else {
                    Some(self.invocation_pattern.to_string_for_name(&invocation.name))
                }
            }
            (Some(ref s), Some(ref regex)) => {
                if regex.is_match(&s) {
                    None
                } else {
                    Some(self.invocation_pattern.to_string_for_name(&invocation.name))
                }
            }
            (Some(_), None) => Some(format!("{} doesn't take arguments", invocation.name)),
        }
    }

    pub fn parse(&self, args: &str) -> Option<HashMap<String, String>> {
        self.args_parser.as_ref()
            .map(|r| {
                let mut map = HashMap::new();
                if let Some(input_cap) = r.captures(&args) {
                    for name in r.capture_names().flatten() {
                        if let Some(c) = input_cap.name(name) {
                            map.insert(name.to_string(), c.as_str().to_string());
                        }
                    }
                }
                map
            })
    }

}