summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Tay <sam.chong.tay@gmail.com>2020-07-10 22:06:36 -0700
committerSam Tay <sam.chong.tay@gmail.com>2020-07-10 22:06:47 -0700
commit8319a6d405b6eaf2057bba1df34b971ff546448a (patch)
treebed7e1f9276d41ccea638e5916c4af4691eda02d
parent23420df92ea7f806350af2f652cd7c8c7e14a0a5 (diff)
Refactor cli module
This allows testing the CLI interface
-rw-r--r--TODO.md8
-rw-r--r--src/cli.rs116
-rw-r--r--src/config.rs4
3 files changed, 113 insertions, 15 deletions
diff --git a/TODO.md b/TODO.md
index 3ddd9f9..2ac0519 100644
--- a/TODO.md
+++ b/TODO.md
@@ -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)
diff --git a/src/cli.rs b/src/cli.rs
index af13f77..707ed3e 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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>,