use std::path::PathBuf; use clap::{Arg, ArgAction, ArgMatches, Command, value_parser}; use log::{debug, info}; use rusqlite::Connection; use serde::Serialize; use simple_logger::SimpleLogger; use crate::migration::apply_migrations; use crate::securities::list_securities; mod migration; mod users; mod securities; mod account; #[derive(Debug, Serialize)] struct Output { status: String, result: T, } impl Output { fn success(shit: T) -> Output { return Output { status: "success".to_string(), result: shit }; } } trait CommandLineUserLogic { fn create_user(&mut self, conn: &mut Connection, name: &String, public_key: &String); fn list_user(&mut self, conn: &Connection); fn list_securities(&mut self, conn: &Connection); } fn verbosity_arg() -> Arg { return Arg::new("verbosity") .short('v') .action(ArgAction::Count) .default_value("0"); } fn get_verbosity(m: &ArgMatches) -> log::LevelFilter { match m.get_one::("verbosity") { None => log::LevelFilter::Error, Some(1) => log::LevelFilter::Warn, Some(2) => log::LevelFilter::Info, Some(3) => log::LevelFilter::Debug, Some(4) => log::LevelFilter::Trace, _ => log::LevelFilter::Error, } } fn db_path_arg() -> Arg { return Arg::new("db-path") .long("db-path") .value_name("FILE") .value_parser(value_parser!(PathBuf)) .default_value( "ledger.sqlite"); } fn get_db_path(m: & ArgMatches) -> & PathBuf{ return m.get_one::("db-path").unwrap(); } fn new_user_command_line_parser() -> Command { return Command::new("new") .long_flag("new") .about("Create new user.") .arg(Arg::new("name").long("name").required(true).help("name of new user.").action(ArgAction::Set)) .arg(Arg::new("public_key").required(true).long("public-key").help("public key of users.").action(ArgAction::Set)); } fn ls_user_command_line_parser() -> Command { return Command::new("ls") .long_flag("ls") .about("List all users."); } fn user_command_line_parser() -> Command { return Command::new("users") .subcommand(new_user_command_line_parser()) .subcommand(ls_user_command_line_parser()); } fn ls_securities_command_line_parser() -> Command { return Command::new("ls") .long_flag("ls") .about("List all securities."); } fn securities_command_line_parser() -> Command { return Command::new("securities") .subcommand(ls_securities_command_line_parser()); } fn command_line_parser() -> Command { return Command::new("prog") .arg(verbosity_arg()) .arg(db_path_arg()) .subcommand(user_command_line_parser()) .subcommand(securities_command_line_parser()); } fn process_new_user_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing new user command line {:?}.", m); let name = m.get_one::("name").unwrap(); let pk = m.get_one::("public_key").unwrap(); logic.create_user(conn, name, pk); } fn process_list_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing list users command line {:?}.", m); logic.list_user(conn); } fn process_user_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing user command line {:?}.", m); match m.subcommand() { Some(("new", new_user_match)) => { process_new_user_command_line(conn, new_user_match, logic); } Some(("ls", list_user_match)) => { process_list_user_command_line(conn, list_user_match, logic); } _ => unreachable!(), } } fn process_list_securities_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing list securities command line {:?}.", m); logic.list_securities(conn); } fn process_securities_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing securities command line {:?}.", m); match m.subcommand() { Some(("ls", list_securities_match)) => { process_list_securities_command_line(conn, list_securities_match, logic); } _ => unreachable!(), } } fn process_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing command line {:?}.", m); match m.subcommand() { Some(("users", user_match)) => { process_user_command_line(conn, user_match, logic); } Some(("securities", securities_match)) => { process_securities_command_line(conn, securities_match, logic); } _ => { println!("Received unparsable command. {:?}", m.subcommand()); unreachable!(); }, } } #[cfg(test)] mod test { use super::*; struct TestCommandLineLogic { list_user_count: usize, create_user_count: usize, list_securities_count: usize, } impl TestCommandLineLogic { fn new() -> TestCommandLineLogic { return TestCommandLineLogic { list_user_count: 0, create_user_count: 0, list_securities_count: 0, }; } } impl CommandLineUserLogic for TestCommandLineLogic { fn create_user(&mut self, _conn: &mut Connection, _name: &String, _public_key: &String) { self.create_user_count += 1; } fn list_user(&mut self, _conn: &Connection) { self.list_user_count += 1; } fn list_securities(&mut self, _conn: &Connection) { self.list_securities_count += 1; } } #[test] fn verbosity() { { let cmd = Command::new("test").arg(verbosity_arg()); let m = cmd.get_matches_from(vec!["test", "-vv"]); assert_eq!(get_verbosity(&m), log::LevelFilter::Info); } { let cmd = Command::new("test").arg(verbosity_arg()); let m = cmd.get_matches_from(vec!["test"]); assert_eq!(get_verbosity(&m), log::LevelFilter::Error); } } #[test] fn db_path() { { let cmd = Command::new("test").arg(db_path_arg()); let m = cmd.get_matches_from(vec!["test", "--db-path", "/foo/bar"]); let expt = PathBuf::from("/foo/bar"); assert_eq!(get_db_path(&m), &expt); } { let cmd = Command::new("test").arg(db_path_arg()); let m = cmd.get_matches_from(vec!["test"]); let expt = PathBuf::from("ledger.sqlite"); assert_eq!(get_db_path(&m), &expt); } } #[test] fn create_user() { let cmd = command_line_parser(); let m = cmd.get_matches_from(vec!["test", "users", "new", "--name", "paul", "--public-key", "123abc"]); let mut logic = TestCommandLineLogic::new(); let mut conn = Connection::open_in_memory().unwrap(); process_command_line(&mut conn, &m, &mut logic); assert_eq!(logic.create_user_count, 1); } #[test] fn list_users() { let cmd = command_line_parser(); let m = cmd.get_matches_from(vec!["test", "users", "ls"]); let mut logic = TestCommandLineLogic::new(); let mut conn = Connection::open_in_memory().unwrap(); process_command_line(&mut conn, &m, &mut logic); assert_eq!(logic.list_user_count, 1); } #[test] fn list_securities() { let cmd = command_line_parser(); let m = cmd.get_matches_from(vec!["test", "securities", "ls"]); let mut logic = TestCommandLineLogic::new(); let mut conn = Connection::open_in_memory().unwrap(); process_command_line(&mut conn, &m, &mut logic); assert_eq!(logic.list_securities_count, 1); } } struct ProCommandLineLogic {} impl ProCommandLineLogic { fn new() -> ProCommandLineLogic { return ProCommandLineLogic {}; } } impl CommandLineUserLogic for ProCommandLineLogic { fn create_user(&mut self, conn: &mut Connection, name: &String, public_key: &String) { let tx = conn.transaction().unwrap(); users::create_user(& tx, name, public_key); tx.commit().unwrap(); } fn list_user(&mut self, conn: &Connection) { let users = users::list_users(conn); match users { Ok(u) => { let out = Output::success(u); println!("{}", serde_json::to_string(&out).unwrap()); } Err(e) => { println!("{:?}", e); } } } fn list_securities(&mut self, conn: & Connection) { let securities = list_securities(conn); match securities { Ok(s) => { let out = Output::success(s); println!("{}", serde_json::to_string(&out).unwrap()); }, Err(e) => { println!("{:?}", e); } }; } } fn get_connection(m: &ArgMatches) -> Connection { let path = get_db_path(&m); info!("Sqlite database location is {:?}.", path); return Connection::open(path).unwrap(); } fn set_logger(m: &ArgMatches) { SimpleLogger::new().with_level(get_verbosity(&m)).init().unwrap(); } fn main() { let command = command_line_parser(); let m = command.get_matches(); set_logger(&m); let mut conn = get_connection(&m); apply_migrations(&mut conn).unwrap(); let mut logic = ProCommandLineLogic::new(); process_command_line(&mut conn, &m, &mut logic); }