diff options
author | Sam Tay <sam.chong.tay@gmail.com> | 2020-07-10 22:06:36 -0700 |
---|---|---|
committer | Sam Tay <sam.chong.tay@gmail.com> | 2020-07-10 22:06:47 -0700 |
commit | 8319a6d405b6eaf2057bba1df34b971ff546448a (patch) | |
tree | bed7e1f9276d41ccea638e5916c4af4691eda02d | |
parent | 23420df92ea7f806350af2f652cd7c8c7e14a0a5 (diff) |
Refactor cli module
This allows testing the CLI interface
-rw-r--r-- | TODO.md | 8 | ||||
-rw-r--r-- | src/cli.rs | 116 | ||||
-rw-r--r-- | src/config.rs | 4 |
3 files changed, 113 insertions, 15 deletions
@@ -1,17 +1,18 @@ # TODO ### chores -1. Add test for clap app 2. Add significant cursive TUI test 3. Refactor layout handling (see TODO on `tui::views::LayoutView::relayout`) 4. Move to `directories 3.0`; optionally migrate existing macos configs? Not many people using this anyway... -5. Add github action to bump homebrew formula on tag push 6. Move to github actions ASAP, travis & appveyor are a PITA. See resources below. ### bugs -1. Shift+TAB should move focus backwards +[ ] Support lack of persistent configuration: + 1) If we can't write it, still continue with application defaults, and + 2) allow passing --config-path=PATH; +[ ] Shift+TAB should move focus backwards ### feature ideas - Add sort option, e.g. relevance|votes|date @@ -33,7 +34,6 @@ benefit of incorporating termimad features into a cursive view will not be felt. But, this is changing [soon](https://meta.stackexchange.com/q/348746). ### resources for later -- [GA - bump homebrew](https://github.com/mislav/bump-homebrew-formula-action) - [Github Actions example](https://github.com/extrawurst/gitui) - [another GA example](https://github.com/casey/just) - [logging example](https://deterministic.space/rust-cli-tips.html) @@ -1,9 +1,10 @@ -use clap::{App, AppSettings, Arg}; +use clap::{App, AppSettings, Arg, ArgMatches}; use crate::config::Config; use crate::error::Result; // TODO --add-site (in addition to defaults) +// TODO set_api_key should probably just be a bool, since we have config pub struct Opts { pub list_sites: bool, pub print_config_path: bool, @@ -13,13 +14,23 @@ pub struct Opts { pub config: Config, } -// TODO accept FnOnce() -> Result<Config> so I can test this +/// Get CLI opts and args, with defaults pulled from user configuration pub fn get_opts() -> Result<Opts> { - let config = Config::new()?; + get_opts_with(Config::new, |a| a.get_matches()) +} + +/// Get CLI opts, starting with defaults produced from `mk_config` and matching args with +/// `get_matches`. +fn get_opts_with<F, G>(mk_config: F, get_matches: G) -> Result<Opts> +where + F: FnOnce() -> Result<Config>, + G: for<'a> FnOnce(App<'a, '_>) -> ArgMatches<'a>, +{ + let config = mk_config()?; let limit = &config.limit.to_string(); let sites = &config.sites.join(";"); let engine = &config.search_engine.to_string(); - let matches = App::new("so") + let clapp = App::new("so") .setting(AppSettings::ColoredHelp) .version(clap::crate_version!()) .author(clap::crate_authors!()) @@ -104,8 +115,8 @@ pub fn get_opts() -> Result<Opts> { .possible_values(&["duckduckgo", "google", "stackexchange"]) .help("Use specified search engine") .next_line_help(true), - ) - .get_matches(); + ); + let matches = get_matches(clapp); let lucky = match (matches.is_present("lucky"), matches.is_present("no-lucky")) { (true, _) => true, (_, true) => false, @@ -139,6 +150,93 @@ pub fn get_opts() -> Result<Opts> { }) } -// TODO how can I test this App given -// https://users.rust-lang.org/t/help-with-idiomatic-rust-and-ownership-semantics/43880 -// Maybe pass get_opts a closure that produces the Config...? +#[cfg(test)] +mod tests { + use super::*; + use crate::config::SearchEngine; + + fn defaults() -> Config { + Config { + api_key: Some(String::from("my key")), + limit: 64, + lucky: false, + sites: vec![ + String::from("some"), + String::from("sites"), + String::from("yeah"), + ], + search_engine: SearchEngine::DuckDuckGo, + } + } + + fn mk_config() -> Result<Config> { + Ok(defaults()) + } + + #[test] + fn test_defaults() { + let opts = get_opts_with(mk_config, |a| { + a.get_matches_from(vec!["so", "how do I exit Vim"]) + }); + + assert_eq!(opts.unwrap().config, defaults()); + } + + #[test] + fn test_overrides() { + let opts = get_opts_with(mk_config, |a| { + a.get_matches_from(vec!["so", "-s", "english", "how do I exit Vim"]) + }); + + assert_eq!( + opts.unwrap().config, + Config { + sites: vec![String::from("english")], + ..defaults() + } + ); + + let opts = get_opts_with(mk_config, |a| { + a.get_matches_from(vec!["so", "-l", "5", "--lucky", "how do I exit Vim"]) + }); + + assert_eq!( + opts.unwrap().config, + Config { + limit: 5, + lucky: true, + ..defaults() + } + ); + } + + #[test] + fn test_set_api_key() { + let opts = get_opts_with(mk_config, |a| { + a.get_matches_from(vec!["so", "--set-api-key", "new key"]) + }) + .unwrap(); + + // Uses key in new config + assert_eq!( + opts.config, + Config { + api_key: Some(String::from("new key")), + ..defaults() + } + ); + + // Flags it in opts + assert_eq!(opts.set_api_key, Some(String::from("new key"))); + } + + #[test] + #[should_panic] + fn test_conflicts() { + get_opts_with(mk_config, |a| { + a.get_matches_from_safe(vec!["so", "--lucky", "--no-lucky"]) + .unwrap() + }) + .unwrap(); + } +} diff --git a/src/config.rs b/src/config.rs index 65907ba..d5dfe22 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; use crate::error::{Error, Result}; use crate::utils; -#[derive(Deserialize, Serialize, Debug, Clone)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] #[serde(rename_all = "lowercase")] pub enum SearchEngine { DuckDuckGo, @@ -16,7 +16,7 @@ pub enum SearchEngine { StackExchange, } -#[derive(Deserialize, Serialize, Debug, Clone)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] #[serde(default)] pub struct Config { pub api_key: Option<String>, |