use std::convert::TryInto; use std::process::exit; use std::io; use clap::{App, Arg, SubCommand, AppSettings, crate_version, ArgMatches}; use chrono::Utc; use regex::Regex; use tiempo::error; use tiempo::database::SqliteDatabase; use tiempo::config::Config; use tiempo::commands::{ Command, r#in::InCommand, display::DisplayCommand, sheet::SheetCommand, today::TodayCommand, yesterday::YesterdayCommand, week::WeekCommand, month::MonthCommand, list::ListCommand, out::OutCommand, resume::ResumeCommand, backend::BackendCommand, }; fn error_trap(matches: ArgMatches) -> error::Result<()> { let config = Config::read()?; if let Some(_matches) = matches.subcommand_matches("backend") { return BackendCommand::handle(&config); } let mut conn = SqliteDatabase::from_path_or_create(&config.database_file)?; let mut out = io::stdout(); let mut err = io::stderr(); let now = Utc::now(); match matches.subcommand() { ("in", Some(matches)) => InCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("out", Some(matches)) => OutCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("resume", Some(matches)) => ResumeCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("display", Some(matches)) => DisplayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("today", Some(matches)) => TodayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("yesterday", Some(matches)) => YesterdayCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("week", Some(matches)) => WeekCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("month", Some(matches)) => MonthCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("sheet", Some(matches)) => SheetCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), ("list", Some(matches)) => ListCommand::handle(matches.try_into()?, &mut conn, &mut out, &mut err, &config, now), (cmd, _) => Err(error::Error::UnimplementedCommand(cmd.into())), } } fn main() { // Lets first declar some args that repeat here and there let start_arg = Arg::with_name("start") .long("start").short("s") .takes_value(true).value_name("TIME") .help("Include entries that start on this date or later"); let end_arg = Arg::with_name("end") .long("end").short("e") .takes_value(true).value_name("TIME") .help("Include entries that start on this date or earlier"); let ids_arg = Arg::with_name("ids") .short("v").long("ids") .help("Print database ids (for use with edit)"); let grep_arg = Arg::with_name("grep") .long("grep").short("g") .takes_value(true).value_name("REGEXP") .help("Include entries where the note matches this regexp."); let format_arg = Arg::with_name("format") .short("f").long("format") .takes_value(true).value_name("FORMAT").default_value("text") .help( "The output format. Valid built-in formats are ical, csv, json, \ ids, and text. Documentation on defining custom formats can be \ found at https://gitlab.com/categulario/tiempo" ); let sheet_arg = Arg::with_name("sheet") .takes_value(true).value_name("SHEET") .help( "The sheet to display. Pass 'all' to see entries from all sheets \ or 'full' to see hidden entries" ); let at_arg = Arg::with_name("at") .long("at") .takes_value(true).value_name("TIME") .help("Use this time instead of now"); let num_re = Regex::new(r"^\d+$").unwrap(); let id_arg = Arg::with_name("id") .long("id") .takes_value(true).value_name("ID") .validator(move |v| if num_re.is_match(&v) { Ok(()) } else { Err(format!("the --id arg must be a number. '{}' is not a valid number", v)) }) .help("Use entry with ID instead of the last entry"); // Now declar this app's cli let matches = App::new("Tiempo") .name("t") .setting(AppSettings::SubcommandRequired) .version(crate_version!()) .author("Abraham Toriz ") .about("Tiempo helps you keep track of time spent in different activities") .subcommand(SubCommand::with_name("archive") .visible_alias("a") .about("Move entries to a hidden sheet (by default named '_[SHEET]') so they're out of the way.") .arg(start_arg.clone()) .arg(end_arg.clone()) .arg(grep_arg.clone()) ) .subcommand(SubCommand::with_name("backend") .visible_alias("b") .about("Open an sqlite shell to the database.") ) .subcommand(SubCommand::with_name("configure") .visible_alias("c") .about("Configure tiempo. Print path to config file.") .arg(Arg::with_name("round_in_seconds") .long("round-in-seconds") .takes_value(true) .value_name("SECONDS") .help("The duration of time to use for rounding with the -r flag")) .arg(Arg::with_name("database_file") .long("database-file") .takes_value(true) .value_name("PATH") .help("The file path of the sqlite database")) .arg(Arg::with_name("append_notes_delimiter") .long("append-notes-delimiter") .takes_value(true) .value_name("DELIMITER") .help("delimiter used when appending notes via t edit --append")) .arg(Arg::with_name("formatter_search_paths") .long("formatter-search-paths") .takes_value(true) .value_name("PATHS") .help("comma separated directories to search for user defined fomatter classes")) .arg(Arg::with_name("default_formatter") .long("default-formatter") .takes_value(true) .value_name("FORMATTER") .help("The format to use when display is invoked without a `--format` option")) .arg(Arg::with_name("require_note") .long("require-note") .help("Prompt for a note if one isn't provided when checking in")) .arg(Arg::with_name("no_require_note") .long("no-require-note") .help("Prompt for a note if one isn't provided when checking in")) .arg(Arg::with_name("note_editor") .long("note-editor") .takes_value(true) .value_name("EDITOR") .help("Command to launch notes editor or false if no editor use.")) .arg(Arg::with_name("week_start") .long("week-start") .takes_value(true) .value_name("DAY") .help("The day of the week to use as the start of the week for t week.")) ) .subcommand(SubCommand::with_name("display") .visible_alias("d") .about( "Display the current timesheet or a specific. Pass `all' as \ SHEET to display all unarchived sheets or `full' to display \ archived and unarchived sheets.") .arg(ids_arg.clone()) .arg(start_arg.clone()) .arg(end_arg.clone()) .arg(format_arg.clone()) .arg(grep_arg.clone()) .arg(sheet_arg.clone()) ) .subcommand(SubCommand::with_name("today") .visible_alias("t") .about("Display entries that started today") .arg(ids_arg.clone()) .arg(end_arg.clone()) .arg(format_arg.clone()) .arg(grep_arg.clone()) .arg(sheet_arg.clone()) ) .subcommand(SubCommand::with_name("yesterday") .visible_alias("y") .about("Display entries that started yesterday") .arg(ids_arg.clone()) .arg(format_arg.clone()) .arg(grep_arg.clone()) .arg(sheet_arg.clone()) ) .subcommand(SubCommand::with_name("week") .visible_alias("w") .about("Display entries starting last monday or later") .arg(ids_arg.clone()) .arg(end_arg.clone()) .arg(format_arg.clone()) .arg(grep_arg.clone()) .arg(sheet_arg.clone()) ) .subcommand(SubCommand::with_name("month") .visible_alias("m") .about("Display entries starting this month") .arg(ids_arg.clone()) .arg(format_arg.clone()) .arg(grep_arg.clone()) .arg(sheet_arg.clone()) .arg(Arg::with_name("month") .long("month").short("m") .takes_value(true).value_name("TIME") .aliases(&["s", "start"]) .possible_values(&[ "this", "current", "last", "jan", "january", "feb", "february", "mar", "march", "apr", "april", "may", "jun", "june", "jul", "july", "aug", "august", "sep", "september", "oct", "october", "nov", "november", "dic", "december", ]) .hide_possible_values(true) .help( "Include entries of the specified month instead of the \ current month" ) ) ) .subcommand(SubCommand::with_name("in") .visible_alias("i") .about("Start an activity in the current timesheet") .arg(at_arg.clone()) .arg(Arg::with_name("note") .takes_value(true) .value_name("NOTE") .help("Text describing the activity to start")) ) .subcommand(SubCommand::with_name("resume") .visible_alias("r") .about("Restart the timer for an entry. Defaults to the last active entry") .arg(at_arg.clone()) .arg(id_arg.clone()) ) .subcommand(SubCommand::with_name("out") .visible_alias("o") .about("end the active entry in the current timesheet") ) .subcommand(SubCommand::with_name("sheet") .visible_alias("s") .about("Change active timesheet or list existing timesheets") .arg(sheet_arg.clone()) ) .subcommand(SubCommand::with_name("list") .visible_alias("l") .about("List existing sheets") .arg(Arg::with_name("all") .short("a").long("all") .help("List archive sheets also")) ) .get_matches(); if let Err(e) = error_trap(matches) { eprintln!("{}", e); exit(1); } }