use anyhow::{Context, Error, Result, anyhow}; use clap::*; use clap::error::ErrorKind; use log::*; use directories::ProjectDirs; use keep::args::{Args, NumberOrString}; use keep::config::Settings; use keep::db; use keep::modes; /** * Main function to handle command-line arguments and execute the appropriate mode. */ fn main() -> Result<(), Error> { use std::fs; let proj_dirs = ProjectDirs::from("gt0.ca", "Andrew Phillips", "Keep"); let mut cmd = Args::command(); let args = Args::parse(); // Validate arguments based on mode if let Err(e) = args.validate() { cmd.error(ErrorKind::ValueValidation, e).exit(); } stderrlog::new() .module(module_path!()) .quiet(args.options.quiet) .verbosity(usize::from(args.options.verbose + 2)) //.timestamp(stderrlog::Timestamp::Second) .init() .unwrap(); debug!("MAIN: Start"); // Determine default data directory let default_dir = match proj_dirs { Some(ref proj_dirs) => proj_dirs.data_dir().to_path_buf(), None => return Err(anyhow!("Unable to determine data directory")), }; // Create unified settings using the new config system let settings = Settings::new(&args, default_dir)?; debug!("MAIN: Loaded settings: {:?}", settings); let ids = &mut Vec::new(); let tags = &mut Vec::new(); // For --info and --get modes, treat numeric strings as IDs for v in args.ids_or_tags.iter() { debug!("MAIN: Parsed value: {:?}", v); match v.clone() { NumberOrString::Number(num) => { debug!("MAIN: Adding to ids: {}", num); ids.push(num) }, NumberOrString::Str(str) => { // For --info and --get, try to parse strings as numbers to treat them as IDs if args.mode.info || args.mode.get { if let Ok(num) = str.parse::() { debug!("MAIN: Adding parsed string to ids: {}", num); ids.push(num); continue; } else if args.mode.info { // --info only accepts numeric IDs cmd.error( ErrorKind::InvalidValue, format!("--info requires numeric IDs, found: '{}'", str) ).exit(); } } // If not a number, or not using --info/--get, treat as tag debug!("MAIN: Adding to tags: {}", str); tags.push(str) }, } } tags.sort(); tags.dedup(); #[derive(PartialEq, Debug)] enum KeepModes { Unknown, Save, Get, Diff, List, Delete, Info, Status, Server, GenerateConfig, } let mut mode: KeepModes = KeepModes::Unknown; if args.mode.save { mode = KeepModes::Save; } else if args.mode.get { mode = KeepModes::Get; } else if args.mode.diff { mode = KeepModes::Diff; } else if args.mode.list { mode = KeepModes::List; } else if args.mode.delete { mode = KeepModes::Delete; } else if args.mode.info { mode = KeepModes::Info; } else if args.mode.status { mode = KeepModes::Status; } else if args.mode.server { mode = KeepModes::Server; } else if args.mode.generate_config { mode = KeepModes::GenerateConfig; } if mode == KeepModes::Unknown { if !ids.is_empty() { mode = KeepModes::Get; } else { mode = KeepModes::Save; } } // Validate output format usage if let Some(output_format_str) = &settings.output_format { if output_format_str != "table" && mode != KeepModes::Info && mode != KeepModes::Status && mode != KeepModes::List { cmd.error( ErrorKind::InvalidValue, "--output-format can only be used with --info, --status, or --list modes" ).exit(); } } // Validate human-readable usage if settings.human_readable && mode != KeepModes::List && mode != KeepModes::Info { cmd.error( ErrorKind::InvalidValue, "--human-readable can only be used with --list and --info modes" ).exit(); } // Validate server password usage if settings.server_password().is_some() && mode != KeepModes::Server { cmd.error( ErrorKind::InvalidValue, "--server-password can only be used with --server mode" ).exit(); } debug!("MAIN: args: {:?}", args); debug!("MAIN: ids: {:?}", ids); debug!("MAIN: tags: {:?}", tags); debug!("MAIN: mode: {:?}", mode); debug!("MAIN: settings: {:?}", settings); unsafe { libc::umask(0o077); } let data_path = settings.dir.clone(); let mut db_path = data_path.clone(); db_path.push("keep-1.db"); debug!("MAIN: Data directory: {:?}", data_path); debug!("MAIN: DB file: {:?}", db_path); // Ensure data directory exists fs::create_dir_all(&data_path) .with_context(|| format!("Unable to create data directory {:?}", data_path))?; // Initialize database let mut conn = db::open(db_path.clone())?; match mode { KeepModes::Save => modes::save::mode_save(&mut cmd, &settings, ids, tags, &mut conn, data_path), KeepModes::Get => modes::get::mode_get(&mut cmd, &settings, ids, tags, &mut conn, data_path), KeepModes::Diff => modes::diff::mode_diff(&mut cmd, &settings, &settings, ids, tags, &mut conn, data_path), KeepModes::List => modes::list::mode_list(&mut cmd, &settings, ids, tags, &mut conn, data_path), KeepModes::Delete => modes::delete::mode_delete(&mut cmd, &settings, &settings, ids, tags, &mut conn, data_path), KeepModes::Info => modes::info::mode_info(&mut cmd, &settings, ids, tags, &mut conn, data_path), KeepModes::Status => modes::status::mode_status(&mut cmd, &settings, data_path, db_path), KeepModes::Server => modes::server::mode_server(&mut cmd, &settings, &mut conn, data_path), KeepModes::GenerateConfig => modes::generate_config::mode_generate_config(&mut cmd, &settings), KeepModes::Unknown => unreachable!(), } }