From 46e019ddf3b67a8307773edd109aa29d5be1a8b3 Mon Sep 17 00:00:00 2001 From: Sam Tay Date: Thu, 9 Jul 2020 22:24:49 -0700 Subject: Add hidden --print-config-path for debugging And refactor config to put functions under Config implementation; this seems more Rustic, based on my limited experience looking at other codebases. --- src/cli.rs | 18 ++++++-- src/config.rs | 95 ++++++++++++++++++++------------------ src/main.rs | 7 ++- src/stackexchange/local_storage.rs | 4 +- src/tui/app.rs | 6 +-- 5 files changed, 76 insertions(+), 54 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 92612ea..c8e4b41 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,12 +1,12 @@ use clap::{App, AppSettings, Arg}; -use crate::config; use crate::config::Config; use crate::error::Result; // TODO --add-site (in addition to defaults) pub struct Opts { pub list_sites: bool, + pub print_config_path: bool, pub update_sites: bool, pub set_api_key: Option, pub query: Option, @@ -14,7 +14,7 @@ pub struct Opts { } pub fn get_opts() -> Result { - let config = config::user_config()?; + let config = Config::new()?; let limit = &config.limit.to_string(); let sites = &config.sites.join(";"); let engine = &config.search_engine.to_string(); @@ -41,6 +41,12 @@ pub fn get_opts() -> Result { .value_name("key") .help("Set StackExchange API key"), ) + .arg( + Arg::with_name("print-config-path") + .long("print-config-path") + .help("Print path to config file") + .hidden(true), + ) .arg( Arg::with_name("site") .long("site") @@ -79,7 +85,12 @@ pub fn get_opts() -> Result { Arg::with_name("query") .multiple(true) .index(1) - .required_unless_one(&["list-sites", "update-sites", "set-api-key"]), + .required_unless_one(&[ + "list-sites", + "update-sites", + "set-api-key", + "print-config-path", + ]), ) .arg( Arg::with_name("search-engine") @@ -101,6 +112,7 @@ pub fn get_opts() -> Result { }; Ok(Opts { list_sites: matches.is_present("list-sites"), + print_config_path: matches.is_present("print-config-path"), update_sites: matches.is_present("update-sites"), set_api_key: matches.value_of("set-api-key").map(String::from), query: matches diff --git a/src/config.rs b/src/config.rs index 49209f6..65907ba 100644 --- a/src/config.rs +++ b/src/config.rs @@ -56,58 +56,63 @@ impl Default for Config { } } -/// Get user config (writes default if none found) -pub fn user_config() -> Result { - let project = project_dir()?; - let dir = project.config_dir(); - fs::create_dir_all(&dir)?; - let filename = config_file_name()?; +impl Config { + /// Get user config (writes default if none found) + pub fn new() -> Result { + let project = Self::project_dir()?; + let dir = project.config_dir(); + fs::create_dir_all(&dir)?; + let filename = Self::config_file_path()?; - match utils::open_file(&filename)? { - None => { - let def = Config::default(); - write_config(&def)?; - Ok(def) + match utils::open_file(&filename)? { + None => { + let def = Config::default(); + def.write()?; + Ok(def) + } + Some(file) => serde_yaml::from_reader(file) + .map_err(|_| Error::MalformedFile(filename.clone())) + .and_then(|cfg: Config| { + if cfg.sites.is_empty() { + Err(Error::MalformedFile(filename)) + } else { + Ok(cfg) + } + }), } - Some(file) => serde_yaml::from_reader(file) - .map_err(|_| Error::MalformedFile(filename.clone())) - .and_then(|cfg: Config| { - if cfg.sites.is_empty() { - Err(Error::MalformedFile(filename)) - } else { - Ok(cfg) - } - }), } -} -pub fn set_api_key(key: String) -> Result<()> { - let mut cfg = user_config()?; - cfg.api_key = Some(key); - write_config(&cfg) -} + // TODO This looks odd when refactoring to associate functions under Config; perhaps this + // shouldn't be a CLI opt? Maybe a generic --save-config based on current opts? + pub fn set_api_key(key: String) -> Result<()> { + let mut cfg = Self::new()?; + cfg.api_key = Some(key); + cfg.write() + } -/// Get project directory -pub fn project_dir() -> Result { - ProjectDirs::from("io", "Sam Tay", "so").ok_or_else(|| Error::ProjectDir) -} + /// Get project directory + pub fn project_dir() -> Result { + ProjectDirs::from("io", "Sam Tay", "so").ok_or_else(|| Error::ProjectDir) + } -pub fn theme_file_name() -> Result { - let name = project_dir()?.config_dir().join("colors.toml"); - if !name.as_path().exists() { - let mut file = utils::create_file(&name)?; - file.write_all(include_bytes!("../themes/default.toml"))?; + // TODO consider switching to .toml to be consistent with colors.toml + pub fn config_file_path() -> Result { + Ok(Self::project_dir()?.config_dir().join("config.yml")) } - Ok(name) -} -fn write_config(config: &Config) -> Result<()> { - let filename = config_file_name()?; - let file = utils::create_file(&filename)?; - Ok(serde_yaml::to_writer(file, config)?) -} + /// Get theme file path; if it doesn't exist yet, create it with defaults. + pub fn theme_file_path() -> Result { + let name = Self::project_dir()?.config_dir().join("colors.toml"); + if !name.as_path().exists() { + let mut file = utils::create_file(&name)?; + file.write_all(include_bytes!("../themes/default.toml"))?; + } + Ok(name) + } -// TODO consider switching to .toml to be consistent with colors.toml -fn config_file_name() -> Result { - Ok(project_dir()?.config_dir().join("config.yml")) + fn write(&self) -> Result<()> { + let filename = Self::config_file_path()?; + let file = utils::create_file(&filename)?; + Ok(serde_yaml::to_writer(file, &self)?) + } } diff --git a/src/main.rs b/src/main.rs index 2db6c99..48a73a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use termimad::{CompoundStyle, MadSkin}; use tokio::runtime::Runtime; use tokio::task; +use config::Config; use error::{Error, Result}; use stackexchange::{LocalStorage, Question, Search}; use tui::markdown::Markdown; @@ -49,7 +50,11 @@ async fn run(skin: &mut MadSkin) -> Result>>> { let ls = LocalStorage::new(opts.update_sites).await?; if let Some(key) = opts.set_api_key { - config::set_api_key(key)?; + Config::set_api_key(key)?; + } + + if opts.print_config_path { + println!("{}", Config::config_file_path()?.display()); } if opts.list_sites { diff --git a/src/stackexchange/local_storage.rs b/src/stackexchange/local_storage.rs index 8d009f8..d5adeb2 100644 --- a/src/stackexchange/local_storage.rs +++ b/src/stackexchange/local_storage.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::fs; use std::path::PathBuf; -use crate::config::project_dir; +use crate::config::Config; use crate::error::{Error, Result}; use crate::utils; @@ -40,7 +40,7 @@ impl LocalStorage { } pub async fn new(update: bool) -> Result { - let project = project_dir()?; + let project = Config::project_dir()?; let dir = project.cache_dir(); fs::create_dir_all(&dir)?; let sites_filename = dir.join("sites.json"); diff --git a/src/tui/app.rs b/src/tui/app.rs index 2763663..bdd4665 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -15,7 +15,7 @@ use super::views::{ LayoutView, ListView, MdView, Name, Vimable, NAME_ANSWER_LIST, NAME_ANSWER_VIEW, NAME_QUESTION_LIST, NAME_QUESTION_VIEW, }; -use crate::config; +use crate::config::Config; use crate::error::Result; use crate::stackexchange::{Answer, Question}; @@ -23,7 +23,7 @@ pub const NAME_HELP_VIEW: &str = "help_view"; pub fn run(qs: Vec>) -> Result<()> { let mut siv = cursive::default(); - siv.load_theme_file(config::theme_file_name()?).unwrap(); // TODO dont unwrap + siv.load_theme_file(Config::theme_file_path()?).unwrap(); // TODO dont unwrap let question_map: HashMap> = qs.clone().into_iter().map(|q| (q.id, q)).collect(); @@ -76,7 +76,7 @@ pub fn run(qs: Vec>) -> Result<()> { }); // Reload theme siv.add_global_callback(Event::CtrlChar('r'), |s| { - s.load_theme_file(config::theme_file_name().unwrap()) + s.load_theme_file(Config::theme_file_path().unwrap()) .unwrap() }); siv.run(); -- cgit v1.2.3