proxide 0.2.3

Proxide is a debugging proxy for capturing HTTP/2 and gRPC traffic.
use crate::ui::state::UiContext;
use crate::ui::toast;
use chrono::prelude::*;
use clap::{App, Arg, ArgMatches, SubCommand};
use core::cell::RefCell;
use tui::backend::Backend;

use super::Executable;
use crate::session;
use crate::ui::state::HandleResult;

thread_local! {
    pub static CMD_API: RefCell<App<'static>> = RefCell::new(create_app());
}

pub fn create_app() -> App<'static>
{
    App::new("CMD")
        .setting(clap::AppSettings::NoBinaryName)
        .subcommand(SubCommand::with_name("quit").alias("q"))
        .subcommand(SubCommand::with_name("clear"))
        .subcommand(
            SubCommand::with_name("export")
                .alias("w")
                .arg(
                    Arg::with_name("file")
                        .index(1)
                        .value_name("file")
                        .required(false),
                )
                .arg(
                    Arg::with_name("format")
                        .short('f')
                        .long("format")
                        .takes_value(true)
                        .possible_values(["msgpack", "json"]),
                ),
        )
}

pub struct ColonCommand;
impl<B: Backend> Executable<B> for ColonCommand
{
    fn execute(&self, cmd: &str, ctx: &mut UiContext) -> Option<HandleResult<B>>
    {
        let words = match shell_words::split(cmd) {
            Ok(w) => w,
            Err(e) => {
                toast::show_error(format!("Failed to parse command:\n{}", e));
                return None;
            }
        };

        CMD_API.with(|api| {
            let mut cmd_borrow = api.borrow_mut();
            let matches = match cmd_borrow.get_matches_from_safe_borrow(words) {
                Ok(matches) => matches,
                Err(e) => {
                    toast::show_error(format!("Failed to parse command:\n{}", e));
                    return None;
                }
            };
            execute_matches(matches, ctx)
        })
    }
}

pub fn execute_matches<B: Backend>(s: ArgMatches, ctx: &mut UiContext) -> Option<HandleResult<B>>
{
    match s.subcommand() {
        Some(("quit", _)) => Some(HandleResult::Quit),
        Some(("clear", _)) => clear_session(ctx),
        Some(("export", m)) => export_session(ctx, m),
        Some((cmd, _)) => {
            toast::show_error(format!("Unknown command: {}", cmd));
            None
        }
        None => {
            toast::show_error("No command specified");
            None
        }
    }
}

pub fn clear_session<B: Backend>(ctx: &mut UiContext) -> Option<HandleResult<B>>
{
    ctx.data.requests = Default::default();
    ctx.data.connections = Default::default();
    Some(HandleResult::Update)
}

pub fn export_session<B: Backend>(ctx: &UiContext, matches: &ArgMatches)
    -> Option<HandleResult<B>>
{
    let filename = matches
        .value_of("file")
        .map(|f| f.to_string())
        .unwrap_or_else(|| format!("session-{}.bin", Local::now().format("%Y-%m-%d_%H%M%S")));

    let format = match matches.value_of("format") {
        Some("json") => session::serialization::OutputFormat::Json,
        _ => session::serialization::OutputFormat::MessagePack,
    };

    match ctx.data.write_to_file(&filename, format) {
        Ok(_) => toast::show_message(format!("Exported session to '{}'", filename)),
        Err(e) => toast::show_error(e.to_string()),
    }

    None
}