mp 0.1.0

Universal programming utility for GUI.
/* ------------------------------------------------------------
    Machine Pseudo-Code
    Project.Github: "https://github.com/kerryeon/mp"
---------------------------------------------------------------
    Author:
        Name: "kerryeon"
        Email: "besqer996@gnu.ac.kr"
        Github: "https://github.com/kerryeon"
    Generated:
        Date: "2/17/2019"
------------------------------------------------------------ */

use colored::*;
use std::io::{stdin, stdout};
use std::io::Write;
use std::str::SplitWhitespace;
use std::str::FromStr;
use super::core::MpCore;

type Command = Option<()>;
type MapHelp = (&'static str,  // Command
                &'static str,  // Arguments
                &'static str,  // Comment
                Option<MapFn>);
type MapFn = fn (&mut MpConsole,
                 command: &str,
                 arguments: &mut SplitWhitespace) -> Command;

const PROMPT: &'static str = "@ ";
const UNTITLED_PROJECT: &'static str = "untitled";

const MAP_HELP: [MapHelp; 5] = [
    ("add", "[type] [*name]", "Add a data.", Some(MpConsole::add)),
    ("revert", "[?steps]", "Cancel the previous command.", Some(MpConsole::revert)),
    ("help", "[?command]", "Print help.", Some(MpConsole::help)),
    ("source", "[path]", "Perform a pre-written script file.", Some(MpConsole::source)),
    ("exit", "", "Exit the console.", None),
];

struct MpConsole {
    project_name: &'static str,
    core: MpCore,
}

impl MpConsole {
    fn new(project_path: Option<&str>) -> MpConsole {
        init();
        match project_path {
            Some(path) => {
                // Set Project Name
                unimplemented!()
                // Load Project
            },
            None => MpConsole {
                project_name: UNTITLED_PROJECT,
                core: MpCore::new(),
            },
        }
    }
    pub fn begin_interactive(&mut self) -> Command {
        loop {
            write_prompt(self.project_name)?;

            // Get command & arguments
            let mut input = String::new();
            stdin().read_line(&mut input).unwrap();
            let mut args = input.trim().split_whitespace();
            let command = match args.next() {
                Some(c) => c,
                None => continue,
            };

            // Command Mapping
            let mut cmd_found = false;
            for (cmd, _, _, map_fn) in MAP_HELP.iter() {
                if *cmd == command {
                    match map_fn {
                        Some(map_fn) => map_fn(self, command, &mut args),
                        None => {
                            exit();
                            return Some(())
                        },
                    };
                    cmd_found = true;
                    break
                }
            }
            if ! cmd_found {
                command_not_found(command)
            }
        }
    }

    // -----------------------------------------------------------------
    //      Commands
    // -----------------------------------------------------------------

    fn add(&mut self, command: &str, args: &mut SplitWhitespace) -> Command {
        // Collecting arguments
        let t = args.next_str(command)?;
        let name = args.rest();
        args.finish(command)?;
        // Action
        println!(" * Type: {}", t);
        if name.len() > 0 {
            println!(" * Name: {}", name);
        }
        yet()
    }

    fn revert(&mut self, command: &str, args: &mut SplitWhitespace) -> Command {
        // Collecting arguments
        let steps = args.next_int(command)?;
        args.finish(command)?;
        // Action
        println!(" * Steps: {}", steps);
        yet()
    }

    // -----------------------------------------------------------------
    //      Extra Commands
    // -----------------------------------------------------------------

    fn help(&mut self, _: &str, args: &mut SplitWhitespace) -> Command {
        // Collecting arguments
        // Action
        help(args.next());
        None
    }
    fn source(&mut self, command: &str, args: &mut SplitWhitespace) -> Command {
        // Collecting arguments
        let path = args.next_str(command)?;
        args.finish(command)?;
        // Action
        yet()
    }

}

// -----------------------------------------------------------------
//      Utils
// -----------------------------------------------------------------

fn init() {
    println!();
    println!(" Machine Pseudo-Code");
    println!();
    println!(" * Copyright (C) 2019 {}", env!("CARGO_PKG_AUTHORS"));
    println!(" * Homepage: \"{}\"", env!("CARGO_PKG_HOMEPAGE"));
    println!(" * Version: {}", env!("CARGO_PKG_VERSION"));
    println!();
}

fn write_prompt(project_name: &str) -> Command {
    // PROMPT
    print!("{}{}", format!("[{}]", project_name).bright_green(), PROMPT);
    match stdout().flush() {
        Ok(_) => Some(()),
        Err(_) => None,
    }
}

fn help(command: Option<&str>) {
    match command {
        Some(cmd) => help_specific_command(cmd),
        None => {
            println!("Usage: <command> [options...]");
            for (command, args, desc, _) in &MAP_HELP {
                println!("  {:21} {}", format!("{} {}", command, args), desc)
            }
        }
    }
}
fn err_not_integer(value: &str) {
    println!("Value {:?} must be Integer.", value);
}
fn help_specific_command(command: &str) {
    // Search Command
    for (cmd, args, desc, _) in &MAP_HELP {
        if *cmd == command {
            println!("{}", desc);
            println!(" * Usage: {} {}", command, args);
            return
        }
    }
    command_not_found(command)
}

fn command_not_found(command: &str) {
    println!(" * Command not found: {:?}", command)
}

fn exit() {
    println!("Goodbye.")
}

pub fn begin_interactive(project_path: Option<&str>) {
    MpConsole::new(project_path).begin_interactive();
}

trait SplitWhitespaceForConsole<'a> {
    fn next_str(&mut self, command: &str) -> Option<&'a str>;
    fn next_int(&mut self, command: &str) -> Option<isize>;
    fn rest(&mut self) -> String;
    fn finish(&mut self, command: &str) -> Command;
}

impl<'a> SplitWhitespaceForConsole<'a> for SplitWhitespace<'a> {
    fn next_str(&mut self, command: &str) -> Option<&'a str> {
        match self.next() {
            Some(arg) => Some(arg),
            None => {
                help_specific_command(command);
                None
            },
        }
    }
    fn next_int(&mut self, command: &str) -> Option<isize> {
        match self.next_str(command) {
            Some(s) => match isize::from_str(s) {
                Ok(v) => Some(v),
                Err(_) => {
                    err_not_integer(s);
                    help_specific_command(command);
                    None
                },
            }
            None => None,
        }
    }
    fn rest(&mut self) -> String {
        let mut result = String::new();
        loop {
            match self.next() {
                Some(s) => {
                    result = if result.len() > 0 { result + " " + s }
                    else { result + s };
                },
                None => return result,
            }
        }
    }
    fn finish(&mut self, command: &str) -> Command {
        match self.next() {
            Some(arg) => {
                println!("Unexpected argument: {:?}", arg);
                help_specific_command(command);
                None
            },
            None => Some(()),
        }
    }
}

fn yet() -> Command {
    println!(" * Not implemented in this version.");
    println!(" * Please use nightly version.");
    None
}