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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
use std::io::Write;
use std::io;
use std::collections::HashMap;
use std::rc::Rc;

use idea::{IdeaTree, Idea};
use error::{Result, Error};

use core_commands::core_commands;
use core_printers::core_printers;

pub enum CommandArgs {
    Zero,
    Amount(usize),
    Minimum(usize),
    Maximum(usize),
    Range { min: usize, max: usize },
    VarArgs,
}

impl CommandArgs {
    fn matches(&self, num_args: usize) -> bool {
        match self {
            CommandArgs::Zero => num_args == 0,
            CommandArgs::Amount(n) => num_args == *n,
            CommandArgs::Range { min, max } => *min <= num_args && num_args <= *max,
            CommandArgs::Minimum(min) => *min <= num_args,
            CommandArgs::Maximum(max) => *max >= num_args,

            CommandArgs::VarArgs => true,
        }
    }
}

// TODO this won't be pub after printing is moved out of core_commands.rs into repl.rs
pub type PrinterImplementation = Fn(&Idea, &IdeaTree) -> Result<()>;

pub struct IdeaPrinter {
    pub always_inherited: bool, 
    pub implementation: Box<PrinterImplementation>,
}

impl IdeaPrinter {
    pub fn new<C>(always_inherited: bool, implementation: C) -> Self
        where C: 'static + Fn(&Idea, &IdeaTree) -> Result<()>
    {
        IdeaPrinter {
            always_inherited,
            implementation: Box::new(implementation),
        }
    }
}



type CommandImplementation = Fn(&mut Repl, &mut IdeaTree, Vec<String>) -> Result<()>;

pub struct CommandHandler(CommandArgs, Rc<CommandImplementation>);

impl CommandHandler {
    pub fn new<C>(args: CommandArgs, implementation: C) -> Self
        where C: 'static + Fn(&mut Repl, &mut IdeaTree, Vec<String>) -> Result<()>
    {
        CommandHandler (args, Rc::new(implementation))
    }
}

pub struct HandlerList {
    pub delimiter: Option<String>,
    pub handlers: Vec<CommandHandler>,
}

pub struct Repl {
    pub selected_id: i64,
    commands: HashMap<String, HandlerList>,
    pub printers: HashMap<String, IdeaPrinter>,
}

impl Repl {
    pub fn new() -> Repl {
        let mut repl = Repl { 
            selected_id: 1,
            commands: HashMap::new(),
            printers: HashMap::new(),
        };

        repl.register_commands(core_commands());
        repl.register_printers(core_printers());
        repl
    }

    // add all the commands to this Repl's command map, and throw an
    // error if any of them are a duplicate command name
    pub fn register_commands(&mut self, commands: HashMap<String, HandlerList>) {
        for (command, handler_list) in commands {
            if self.commands.contains_key(&command) {
                println!("Error! Cannot add duplicate command with name '{}'", command);
            } else {
                self.commands.insert(command, handler_list);
            }
        }
    }

    pub fn register_printers(&mut self, printers: HashMap<String, IdeaPrinter>) {
        for (idea_type, printer) in printers {
            if self.printers.contains_key(&idea_type) {
                println!("Error! Cannot add duplicate printer for type '{}'", idea_type);
            } else {
                self.printers.insert(idea_type, printer);
            }
        }
    }

    // Allow the user to keep entering values for a prompt as many times
    // as they want until they type "exit"
    pub fn prompt<C>(prefix: &str, mut callback: C)
        where C: FnMut(&str) -> Result<bool>
    {
        loop {
            print!("{} ", prefix);
            io::stdout().flush().unwrap();
            let mut input = String::new();
            match io::stdin().read_line(&mut input) {
                Ok(_) => {
                    if input == "exit\n" {
                        break;
                    }
                    else {
                        match callback(&mut input.trim()) {
                            Ok(true) => { },
                            Ok(false) => break,
                            Err(e) => println!("Error processing console input: {:?}", e),
                        }
                    }
                }
                Err(e) => {
                    println!("Error getting console input: {:?}", e);
                    continue
                },
            };
        }
    }

    pub fn prompt_for_args(arg_names: Vec<&str>) -> Result<Vec<String>> {
        let mut arg_values = Vec::new();
        for arg_name in &arg_names {
            Repl::prompt(&format!(" {}", arg_name), |arg_value| {
                arg_values.push(arg_value.to_string());
                Ok(false)
            });
        }

        if arg_values.len() != arg_names.len() {
            return Err(Error::DaVinci(format!("User didn't supply all values for {:?}", arg_names)));
        }

        Ok(arg_values)
    }


    pub fn run(&mut self, tree: &mut IdeaTree) {
        self.run_command(tree, "select @".to_string());
        Repl::prompt("$", |input_line| { self.run_command(tree, input_line.to_string()); Ok(true) });
        // TODO confirm quitting Davincibot
    }

    // TODO Idea printing should be a function in this file like run_command,
    // not defined in core_commands.rs as it is

    pub fn run_command(&mut self, tree: &mut IdeaTree, input_line: String) {
        // An empty query is a no-op
        if input_line.len() == 0 {
            return;
        }

        // The first token of every input line should be a valid command name
        let mut parts = input_line.splitn(2, " ");
        let command = parts.next().unwrap();

        if self.commands.contains_key(command) {

            let args;
            let mut handler: Option<Rc<CommandImplementation>> = None;

            {
                let handler_list = &self.commands[command];
                args = match parts.next() {
                    Some(inputs) => match inputs.len() {
                        0 => Vec::new(),
                        _ => match handler_list.delimiter {
                            Some(ref delimiter) => inputs.split(delimiter.as_str()).map(|arg| arg.trim().to_string()).collect(),
                            None => vec![inputs.to_string()]
                        }
                    },
                    None => Vec::new()
                };
                // Check which of this command's handlers matches the number of
                // given inputs
                for possible_handler in &handler_list.handlers {
                    if possible_handler.0.matches((&args).len()) {
                        handler = Some(Rc::clone(&possible_handler.1));
                        break;
                    }
                }
            }

            match handler {
                Some(handler) => {
                    if let Err(e) = (*handler)(self, tree, args) {
                        println!("'{}' command returned an error: {:?}", command, e);
                    }
                },
                None => match &self.commands[command].delimiter {
                    Some(delimiter) => println!("Can't call '{}' command with {} arguments", command, args.len()),
                    None => println!("The '{}' command does not take arguments", command),
                }
            }
        }
        else {
            println!("There is no Da Vinci Bot command named {}", command);
        }
    }

    fn select_from_expression_internal(selected_id: i64, tree: &IdeaTree, expression: &str) -> Result<i64> {
        match expression {
            // @ is the operator for selecting the root Idea
            "@" => Ok(1),
            // ^ is the operator for selecting the parent Idea
            "^" => Ok(tree.get_idea(selected_id)?.parent_id.unwrap_or(1)),
            text => {
                let first_char = {
                    text.chars().next().unwrap()
                };
                match first_char {
                    '#' => {
                        let absolute_id: String = text.chars().skip(1).collect();
                        return Ok(absolute_id.parse::<i64>()?);
                    },
                    '0'...'9' => {
                        let child_index = text.parse::<usize>()?;
                        let child_ids = tree.get_child_ids(selected_id, false)?;
                        if child_ids.len() < child_index {
                            return Err(Error::DaVinci(format!("Tried to select child {} from an Idea that only has {} children", child_index, child_ids.len())));
                        }

                        Ok(child_ids[child_index-1])
                    },
                    _ => {
                        let child_ids = tree.get_child_ids(selected_id, true)?;
                        let mut selected_id: Option<i64> = None;

                        for child_id in child_ids {
                            if tree.get_name(child_id)? == text {
                                selected_id = Some(child_id);
                            }
                        }

                        if selected_id == None {
                            return Err(Error::DaVinci(format!("Selected Idea has no child named \"{}\"", text)));
                        }

                        Ok(selected_id.unwrap())
                    }
                }
            }
        }
    }

    pub fn select_from_expression(&self, tree: &IdeaTree, expression: &str) -> Result<i64> {
        let mut temp_selected = self.selected_id;
        for part in expression.split_terminator('/') {
            temp_selected = Repl::select_from_expression_internal(temp_selected, tree, part)?;
        }
        Ok(temp_selected)
    }
}