From 7fb2b53463fdfd19972c62fd6c6ac604e594fc19 Mon Sep 17 00:00:00 2001 From: Denis Isidoro Date: Sat, 17 Apr 2021 10:17:22 -0300 Subject: [PATCH] Add support for config file (#518) --- Cargo.lock | 55 ++++++ Cargo.toml | 2 + src/actor.rs | 32 ++-- src/bin/main.rs | 2 +- src/cheat_variable.rs | 14 ++ src/{structures/config.rs => config/cli.rs} | 103 ++++-------- src/config/mod.rs | 136 +++++++++++++++ src/config/yaml.rs | 176 ++++++++++++++++++++ src/env_var.rs | 10 +- src/filesystem.rs | 12 +- src/finder/mod.rs | 22 ++- src/finder/structures.rs | 27 +++ src/fs.rs | 16 +- src/handler/core.rs | 40 ++--- src/handler/func.rs | 66 +------- src/handler/info.rs | 6 +- src/handler/mod.rs | 28 ++-- src/handler/preview.rs | 9 +- src/handler/preview_var.rs | 12 +- src/handler/{repo.rs => repo_add.rs} | 53 +----- src/handler/repo_browse.rs | 56 +++++++ src/lib.rs | 5 +- src/shell.rs | 50 +++++- src/structures/mod.rs | 1 - src/terminal.rs | 49 ++++-- src/ui.rs | 38 ++--- src/welcome.rs | 20 +++ src/writer.rs | 9 +- 28 files changed, 718 insertions(+), 331 deletions(-) create mode 100644 src/cheat_variable.rs rename src/{structures/config.rs => config/cli.rs} (74%) create mode 100644 src/config/mod.rs create mode 100644 src/config/yaml.rs rename src/handler/{repo.rs => repo_add.rs} (69%) create mode 100644 src/handler/repo_browse.rs diff --git a/Cargo.lock b/Cargo.lock index 2a01866..cfdb070 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + [[package]] name = "edit" version = "0.1.3" @@ -251,6 +257,12 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + [[package]] name = "lock_api" version = "0.4.3" @@ -318,6 +330,8 @@ dependencies = [ "lazy_static", "regex", "remove_dir_all 0.7.0", + "serde", + "serde_yaml", "shellwords", "strip-ansi-escapes", "thiserror", @@ -560,6 +574,38 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + [[package]] name = "shellwords" version = "1.1.0" @@ -776,3 +822,12 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index 2328cf4..6bd24bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,8 @@ thiserror = "1.0.24" strip-ansi-escapes = "0.1.0" edit = "0.1.3" remove_dir_all = "0.7.0" +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.8" [lib] name = "navi" diff --git a/src/actor.rs b/src/actor.rs index 42e62f0..d09ad4d 100644 --- a/src/actor.rs +++ b/src/actor.rs @@ -1,4 +1,7 @@ use crate::clipboard; +use crate::config::Action; + +use crate::config::CONFIG; use crate::env_var; use crate::extractor; use crate::finder::structures::{Opts as FinderOpts, SuggestionType}; @@ -6,8 +9,6 @@ use crate::finder::Finder; use crate::shell; use crate::shell::ShellSpawnError; use crate::structures::cheat::{Suggestion, VariableMap}; -use crate::structures::config::Action; -use crate::structures::config::Config; use crate::writer; use anyhow::Context; use anyhow::Result; @@ -17,7 +18,6 @@ use std::process::Stdio; fn prompt_finder( variable_name: &str, - config: &Config, suggestion: Option<&Suggestion>, variable_count: usize, ) -> Result { @@ -66,7 +66,7 @@ fn prompt_finder( }; let overrides = { - let mut o = config.fzf_overrides.clone(); + let mut o = CONFIG.fzf_overrides_var(); if let Some(io) = initial_opts { if io.overrides.is_some() { o = io.overrides.clone() @@ -110,8 +110,8 @@ NAVIEOF opts.suggestion_type = SuggestionType::Disabled; }; - let (output, _, _) = config - .finder + let (output, _, _) = CONFIG + .finder() .call(opts, |stdin, _| { stdin .write_all(suggestions.as_bytes()) @@ -130,12 +130,7 @@ fn unique_result_count(results: &[&str]) -> usize { vars.len() } -fn replace_variables_from_snippet( - snippet: &str, - tags: &str, - variables: VariableMap, - config: &Config, -) -> Result { +fn replace_variables_from_snippet(snippet: &str, tags: &str, variables: VariableMap) -> Result { let mut interpolated_snippet = String::from(snippet); let variables_found: Vec<&str> = writer::VAR_REGEX.find_iter(snippet).map(|m| m.as_str()).collect(); let variable_count = unique_result_count(&variables_found); @@ -150,11 +145,10 @@ fn replace_variables_from_snippet( e } else if let Some(suggestion) = variables.get_suggestion(&tags, &variable_name) { let mut new_suggestion = suggestion.clone(); - new_suggestion.0 = - replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone(), config)?; - prompt_finder(variable_name, &config, Some(&new_suggestion), variable_count)? + new_suggestion.0 = replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone())?; + prompt_finder(variable_name, Some(&new_suggestion), variable_count)? } else { - prompt_finder(variable_name, &config, None, variable_count)? + prompt_finder(variable_name, None, variable_count)? }; env_var::set(env_variable_name, &value); @@ -172,7 +166,6 @@ fn replace_variables_from_snippet( // TODO: make it depend on less inputs pub fn act( extractions: Result, - config: Config, files: Vec, variables: Option, ) -> Result<()> { @@ -180,7 +173,7 @@ pub fn act( if key == "ctrl-o" { edit::edit_file(Path::new(&files[file_index.expect("No files found")])) - .expect("Cound not open file in external editor"); + .expect("Could not open file in external editor"); return Ok(()); } @@ -193,12 +186,11 @@ pub fn act( snippet, tags, variables.expect("No variables received from finder"), - &config, ) .context("Failed to replace variables from snippet")?, ); - match config.action() { + match CONFIG.action() { Action::Print => { println!("{}", interpolated_snippet); } diff --git a/src/bin/main.rs b/src/bin/main.rs index 08cb4d0..ad67e71 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -25,5 +25,5 @@ impl FileAnIssue { } fn main() -> Result<(), anyhow::Error> { - navi::handle_config(navi::config_from_env()).map_err(|e| FileAnIssue::new(e).into()) + navi::handle().map_err(|e| FileAnIssue::new(e).into()) } diff --git a/src/cheat_variable.rs b/src/cheat_variable.rs new file mode 100644 index 0000000..ca50545 --- /dev/null +++ b/src/cheat_variable.rs @@ -0,0 +1,14 @@ +use crate::shell::{self, ShellSpawnError}; + +use anyhow::Result; + +pub fn map_expand() -> Result<()> { + let cmd = r#"sed -e 's/^.*$/"&"/' | tr '\n' ' '"#; + shell::command() + .arg("-c") + .arg(cmd) + .spawn() + .map_err(|e| ShellSpawnError::new(cmd, e))? + .wait()?; + Ok(()) +} diff --git a/src/structures/config.rs b/src/config/cli.rs similarity index 74% rename from src/structures/config.rs rename to src/config/cli.rs index 0755c87..6110e19 100644 --- a/src/structures/config.rs +++ b/src/config/cli.rs @@ -3,25 +3,15 @@ use crate::finder::FinderChoice; use crate::handler::func::Func; use crate::handler::info::Info; use crate::shell::Shell; + use clap::{crate_version, AppSettings, Clap}; + use std::str::FromStr; const FINDER_POSSIBLE_VALUES: &[&str] = &[&"fzf", &"skim"]; -const SHELL_POSSIBLE_VALUES: &[&str] = &[&"bash", &"zsh", &"fish"]; +const WIDGET_POSSIBLE_VALUES: &[&str] = &[&"bash", &"zsh", &"fish"]; const FUNC_POSSIBLE_VALUES: &[&str] = &[&"url::open", &"welcome", &"widget::last_command", &"map::expand"]; -const INFO_POSSIBLE_VALUES: &[&str] = &[&"cheats-path"]; - -impl FromStr for FinderChoice { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s { - "fzf" => Ok(FinderChoice::Fzf), - "skim" => Ok(FinderChoice::Skim), - _ => Err("no match"), - } - } -} +const INFO_POSSIBLE_VALUES: &[&str] = &[&"cheats-path", "config-path"]; impl FromStr for Shell { type Err = &'static str; @@ -56,6 +46,7 @@ impl FromStr for Info { fn from_str(s: &str) -> Result { match s { "cheats-path" => Ok(Info::CheatsPath), + "config-path" => Ok(Info::ConfigPath), _ => Err("no match"), } } @@ -94,14 +85,14 @@ EXAMPLES: #[clap(setting = AppSettings::ColoredHelp)] #[clap(setting = AppSettings::AllowLeadingHyphen)] #[clap(version = crate_version!())] -pub struct Config { - /// List of :-separated paths containing .cheat files +pub(super) struct ClapConfig { + /// Colon-separated list of paths containing .cheat files #[clap(short, long, env = env_var::PATH)] pub path: Option, /// Instead of executing a snippet, prints it to stdout #[clap(long)] - print: bool, + pub print: bool, /// Returns the best match #[clap(long)] @@ -109,36 +100,42 @@ pub struct Config { /// Search for cheatsheets using the tldr-pages repository #[clap(long)] - tldr: Option, + pub tldr: Option, /// [Experimental] Comma-separated list that acts as filter for tags. Parts starting with ! represent negation #[clap(long)] - tag_rules: Option, + pub tag_rules: Option, /// Search for cheatsheets using the cheat.sh repository #[clap(long)] - cheatsh: Option, + pub cheatsh: Option, /// Query #[clap(short, long)] - query: Option, + pub query: Option, - /// finder overrides for cheat selection - #[clap(long, env = env_var::FZF_OVERRIDES)] + /// Finder overrides for snippet selection + #[clap(long)] pub fzf_overrides: Option, - /// finder overrides for variable selection - #[clap(long, env = env_var::FZF_OVERRIDES_VAR)] + /// Finder overrides for variable selection + #[clap(long)] pub fzf_overrides_var: Option, - /// which finder application to use - #[clap(long, env = env_var::FINDER, default_value = "fzf", possible_values = FINDER_POSSIBLE_VALUES, case_insensitive = true)] - pub finder: FinderChoice, + /// Finder application to use + #[clap(long, possible_values = FINDER_POSSIBLE_VALUES, case_insensitive = true)] + pub finder: Option, #[clap(subcommand)] pub cmd: Option, } +impl ClapConfig { + pub fn new() -> Self { + Self::parse() + } +} + #[derive(Debug, Clap)] pub enum Command { /// [Experimental] Performs ad-hoc, internal functions provided by navi @@ -172,7 +169,7 @@ pub enum Command { }, /// Outputs shell widget source code Widget { - #[clap(possible_values = SHELL_POSSIBLE_VALUES, case_insensitive = true, default_value = "bash")] + #[clap(possible_values = WIDGET_POSSIBLE_VALUES, case_insensitive = true, default_value = "bash")] shell: Shell, }, /// Shows info @@ -204,57 +201,13 @@ pub enum Action { Execute, } -impl Config { - pub fn source(&self) -> Source { - if let Some(query) = self.tldr.clone() { - Source::Tldr(query) - } else if let Some(query) = self.cheatsh.clone() { - Source::Cheats(query) - } else { - Source::Filesystem(self.path.clone(), self.tag_rules.clone()) - } - } - - pub fn action(&self) -> Action { - if self.print { - Action::Print - } else { - Action::Execute - } - } - - pub fn get_query(&self) -> Option { - let q = self.query.clone(); - if q.is_some() { - return q; - } - if self.best_match { - match self.source() { - Source::Tldr(q) => Some(q), - Source::Cheats(q) => Some(q), - _ => Some(String::from("")), - } - } else { - None - } - } -} - -pub fn config_from_env() -> Config { - Config::parse() -} - -pub fn config_from_iter(args: Vec<&str>) -> Config { - Config::parse_from(args) -} - #[cfg(test)] mod tests { use super::*; #[test] - fn test_shell_possible_values() { - for v in SHELL_POSSIBLE_VALUES { + fn test_widget_possible_values() { + for v in WIDGET_POSSIBLE_VALUES { assert_eq!(true, Shell::from_str(v).is_ok()) } } diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..f69bae0 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,136 @@ +mod cli; +mod yaml; + +use crate::finder::FinderChoice; + +use crate::terminal::style::Color; +pub use cli::*; +use std::process; + +use yaml::YamlConfig; + +lazy_static! { + pub static ref CONFIG: Config = Config::new(); +} +pub struct Config { + yaml: YamlConfig, + clap: ClapConfig, +} + +impl Config { + pub fn new() -> Self { + match YamlConfig::get() { + Ok(yaml) => Self { + yaml, + clap: ClapConfig::new(), + }, + Err(e) => { + eprintln!("Error parsing config file: {}", e); + process::exit(42) + } + } + } + + pub fn best_match(&self) -> bool { + self.clap.best_match + } + + pub fn cmd(&self) -> Option<&Command> { + self.clap.cmd.as_ref() + } + + pub fn source(&self) -> Source { + if let Some(query) = self.clap.tldr.clone() { + Source::Tldr(query) + } else if let Some(query) = self.clap.cheatsh.clone() { + Source::Cheats(query) + } else { + Source::Filesystem(self.path(), self.tag_rules()) + } + } + + pub fn path(&self) -> Option { + self.clap.path.clone().or_else(|| self.yaml.cheats.path.clone()) + } + + pub fn finder(&self) -> FinderChoice { + self.clap.finder.unwrap_or(self.yaml.finder.command) + } + + pub fn fzf_overrides(&self) -> Option { + self.clap + .fzf_overrides + .clone() + .or_else(|| self.yaml.finder.overrides.clone()) + } + + pub fn fzf_overrides_var(&self) -> Option { + self.clap + .fzf_overrides_var + .clone() + .or_else(|| self.yaml.finder.overrides_var.clone()) + } + + pub fn shell(&self) -> String { + self.yaml.shell.command.clone() + } + + pub fn tag_rules(&self) -> Option { + self.clap + .tag_rules + .clone() + .or_else(|| self.yaml.search.tags.clone()) + } + + pub fn tag_color(&self) -> Color { + self.yaml.style.tag.color.get() + } + + pub fn comment_color(&self) -> Color { + self.yaml.style.comment.color.get() + } + + pub fn snippet_color(&self) -> Color { + self.yaml.style.snippet.color.get() + } + + pub fn tag_width_percentage(&self) -> u16 { + self.yaml.style.tag.width_percentage + } + + pub fn comment_width_percentage(&self) -> u16 { + self.yaml.style.comment.width_percentage + } + + pub fn tag_min_width(&self) -> u16 { + self.yaml.style.tag.min_width + } + + pub fn comment_min_width(&self) -> u16 { + self.yaml.style.comment.min_width + } + + pub fn action(&self) -> Action { + if self.clap.print { + Action::Print + } else { + Action::Execute + } + } + + pub fn get_query(&self) -> Option { + let q = self.clap.query.clone(); + if q.is_some() { + return q; + } + if self.best_match() { + match self.source() { + Source::Tldr(q) => Some(q), + Source::Cheats(q) => Some(q), + _ => Some(String::from("")), + } + } else { + None + } + } +} diff --git a/src/config/yaml.rs b/src/config/yaml.rs new file mode 100644 index 0000000..2c423dd --- /dev/null +++ b/src/config/yaml.rs @@ -0,0 +1,176 @@ +use crate::env_var; +use crate::filesystem::default_config_pathbuf; +use crate::finder::FinderChoice; +use crate::fs; +use crate::terminal::style; +use anyhow::Result; +use serde::{de, Deserialize}; +use std::io::BufReader; +use std::path::Path; +use std::path::PathBuf; +use std::str::FromStr; + +#[derive(Deserialize)] +pub struct Color(#[serde(deserialize_with = "color_deserialize")] style::Color); + +impl Color { + pub fn from_str(color: &str) -> Self { + Self(style::Color::from_str(color).unwrap_or(style::Color::White)) + } + + pub fn get(&self) -> style::Color { + self.0 + } +} + +fn color_deserialize<'de, D>(deserializer: D) -> Result +where + D: de::Deserializer<'de>, +{ + let s: String = Deserialize::deserialize(deserializer)?; + style::Color::from_str(&s).map_err(|_| de::Error::custom(format!("Failed to deserialize color: {}", s))) +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct ColorWidth { + pub color: Color, + pub width_percentage: u16, + pub min_width: u16, +} +#[derive(Deserialize)] +#[serde(default)] +pub struct Style { + pub tag: ColorWidth, + pub comment: ColorWidth, + pub snippet: ColorWidth, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct Finder { + pub command: FinderChoice, + pub overrides: Option, + pub overrides_var: Option, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct Cheats { + pub path: Option, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct Search { + pub tags: Option, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct Shell { + pub command: String, +} + +#[derive(Deserialize, Default)] +#[serde(default)] +pub struct YamlConfig { + pub style: Style, + pub finder: Finder, + pub cheats: Cheats, + pub search: Search, + pub shell: Shell, +} + +impl YamlConfig { + fn from_str(text: &str) -> Result { + serde_yaml::from_str(&text).map_err(|e| e.into()) + } + + fn from_path(path: &Path) -> Result { + let file = fs::open(path)?; + let reader = BufReader::new(file); + serde_yaml::from_reader(reader).map_err(|e| e.into()) + } + + pub fn get() -> Result { + if let Ok(yaml) = env_var::get(env_var::CONFIG_YAML) { + return Self::from_str(&yaml); + } + if let Ok(path_str) = env_var::get(env_var::CONFIG) { + let p = PathBuf::from(path_str); + return YamlConfig::from_path(&p); + } + if let Ok(p) = default_config_pathbuf() { + if p.exists() { + return YamlConfig::from_path(&p); + } + } + Ok(YamlConfig::default()) + } +} + +impl Default for ColorWidth { + fn default() -> Self { + Self { + color: Color::from_str("white"), + width_percentage: 26, + min_width: 20, + } + } +} + +impl Default for Style { + fn default() -> Self { + Self { + tag: ColorWidth { + color: Color::from_str("cyan"), + width_percentage: 26, + min_width: 20, + }, + comment: ColorWidth { + color: Color::from_str("blue"), + width_percentage: 42, + min_width: 45, + }, + snippet: Default::default(), + } + } +} + +impl Default for Finder { + fn default() -> Self { + Self { + command: env_var::get(env_var::FINDER) + .ok() + .and_then(|x| FinderChoice::from_str(&x).ok()) + .unwrap_or(FinderChoice::Fzf), + overrides: env_var::get(env_var::FZF_OVERRIDES).ok(), + overrides_var: env_var::get(env_var::FZF_OVERRIDES_VAR).ok(), + } + } +} + +impl Default for Cheats { + fn default() -> Self { + Self { + path: env_var::get(env_var::PATH).ok(), + } + } +} + +impl Default for Search { + fn default() -> Self { + Self { tags: None } + } +} + +impl Default for Shell { + fn default() -> Self { + Self { + command: env_var::get(env_var::SHELL) + .ok() + .unwrap_or_else(|| "bash".to_string()), + } + } +} diff --git a/src/env_var.rs b/src/env_var.rs index e33821b..2eb1dca 100644 --- a/src/env_var.rs +++ b/src/env_var.rs @@ -11,13 +11,6 @@ pub const PREVIEW_COLUMN: &str = "NAVI_PREVIEW_COLUMN"; pub const PREVIEW_DELIMITER: &str = "NAVI_PREVIEW_DELIMITER"; pub const PREVIEW_MAP: &str = "NAVI_PREVIEW_MAP"; -pub const TAG_COLOR: &str = "NAVI_TAG_COLOR"; -pub const COMMENT_COLOR: &str = "NAVI_COMMENT_COLOR"; -pub const SNIPPET_COLOR: &str = "NAVI_SNIPPET_COLOR"; - -pub const TAG_WIDTH: &str = "NAVI_TAG_WIDTH"; -pub const COMMENT_WIDTH: &str = "NAVI_COMMENT_WIDTH"; - pub const PATH: &str = "NAVI_PATH"; pub const FZF_OVERRIDES: &str = "NAVI_FZF_OVERRIDES"; pub const FZF_OVERRIDES_VAR: &str = "NAVI_FZF_OVERRIDES_VAR"; @@ -25,6 +18,9 @@ pub const FINDER: &str = "NAVI_FINDER"; pub const SHELL: &str = "NAVI_SHELL"; +pub const CONFIG: &str = "NAVI_CONFIG"; +pub const CONFIG_YAML: &str = "NAVI_CONFIG_YAML"; + pub fn parse(varname: &str) -> Option { if let Ok(x) = env::var(varname) { x.parse::().ok() diff --git a/src/filesystem.rs b/src/filesystem.rs index 58d28fa..a8b543f 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -33,6 +33,15 @@ pub fn default_cheat_pathbuf() -> Result { Ok(pathbuf) } +pub fn default_config_pathbuf() -> Result { + let base_dirs = BaseDirs::new().ok_or_else(|| anyhow!("Unable to get base dirs"))?; + + let mut pathbuf = PathBuf::from(base_dirs.config_dir()); + pathbuf.push("navi"); + pathbuf.push("config.yaml"); + Ok(pathbuf) +} + pub fn cheat_paths(path: Option) -> Result { if let Some(p) = path { Ok(p) @@ -124,7 +133,8 @@ impl fetcher::Fetcher for Fetcher { files.push(file.clone()); let index = files.len() - 1; let read_file_result = { - let lines = read_lines(&file)?; + let path = PathBuf::from(&file); + let lines = read_lines(&path)?; parser::read_lines( lines, &file, diff --git a/src/finder/mod.rs b/src/finder/mod.rs index cc9731b..b331d3b 100644 --- a/src/finder/mod.rs +++ b/src/finder/mod.rs @@ -1,24 +1,36 @@ -use crate::shell; +use crate::config::CONFIG; use crate::structures::cheat::VariableMap; use crate::writer; use anyhow::Context; use anyhow::Result; use std::process::{self, Output}; use std::process::{Command, Stdio}; - mod post; pub mod structures; - pub use post::process; +use serde::Deserialize; +use std::str::FromStr; use structures::Opts; use structures::SuggestionType; -#[derive(Debug)] +#[derive(Debug, Clone, Copy, Deserialize)] pub enum FinderChoice { Fzf, Skim, } +impl FromStr for FinderChoice { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "fzf" => Ok(FinderChoice::Fzf), + "skim" => Ok(FinderChoice::Skim), + _ => Err("no match"), + } + } +} + pub trait Finder { fn call(&self, opts: Opts, stdin_fn: F) -> Result<(String, Option, Vec)> where @@ -142,7 +154,7 @@ impl Finder for FinderChoice { } let child = command - .env("SHELL", &*shell::SHELL) + .env("SHELL", CONFIG.shell()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn(); diff --git a/src/finder/structures.rs b/src/finder/structures.rs index 24bddfb..1a44d5f 100644 --- a/src/finder/structures.rs +++ b/src/finder/structures.rs @@ -1,3 +1,7 @@ +use crate::config::Config; +use crate::filesystem; +use anyhow::Result; + #[derive(Debug, PartialEq, Clone)] pub struct Opts { pub query: Option, @@ -46,3 +50,26 @@ pub enum SuggestionType { /// initial snippet selection SnippetSelection, } + +impl Opts { + pub fn from_config(config: &Config) -> Result { + let opts = Opts { + preview: Some(format!("{} preview {{}}", filesystem::exe_string()?)), + overrides: config.fzf_overrides(), + suggestion_type: SuggestionType::SnippetSelection, + query: if config.best_match() { + None + } else { + config.get_query() + }, + filter: if config.best_match() { + config.get_query() + } else { + None + }, + ..Default::default() + }; + + Ok(opts) + } +} diff --git a/src/fs.rs b/src/fs.rs index 451c0e8..f627446 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Error, Result}; -use core::fmt::Display; + use remove_dir_all::remove_dir_all; use std::fmt::Debug; use std::fs::{self, create_dir_all, File}; @@ -19,11 +19,15 @@ pub struct UnreadableDir { source: anyhow::Error, } -pub fn read_lines

(filename: P) -> Result>> -where - P: AsRef + Display + Copy, -{ - let file = File::open(filename).with_context(|| format!("Failed to open file {}", filename))?; +pub fn open(filename: &Path) -> Result { + File::open(filename).with_context(|| { + let x = pathbuf_to_string(filename).unwrap_or_else(|e| format!("Unable to get path string: {}", e)); + format!("Failed to open file {}", &x) + }) +} + +pub fn read_lines(filename: &Path) -> Result>> { + let file = open(filename)?; Ok(io::BufReader::new(file) .lines() .map(|line| line.map_err(Error::from))) diff --git a/src/handler/core.rs b/src/handler/core.rs index 7fa393b..508ccdf 100644 --- a/src/handler/core.rs +++ b/src/handler/core.rs @@ -1,44 +1,24 @@ use crate::actor; use crate::cheatsh; +use crate::config::Source; +use crate::config::CONFIG; use crate::extractor; use crate::filesystem; -use crate::finder::structures::{Opts as FinderOpts, SuggestionType}; +use crate::finder::structures::Opts as FinderOpts; use crate::finder::Finder; use crate::structures::cheat::VariableMap; -use crate::structures::config::Config; -use crate::structures::config::Source; use crate::structures::fetcher::Fetcher; use crate::tldr; use crate::welcome; use anyhow::Context; use anyhow::Result; -fn gen_core_finder_opts(config: &Config) -> Result { - let opts = FinderOpts { - preview: Some(format!("{} preview {{}}", filesystem::exe_string()?)), - overrides: config.fzf_overrides.clone(), - suggestion_type: SuggestionType::SnippetSelection, - query: if config.best_match { - None - } else { - config.get_query() - }, - filter: if config.best_match { - config.get_query() - } else { - None - }, - ..Default::default() - }; - - Ok(opts) -} - -pub fn main(config: Config) -> Result<()> { - let opts = gen_core_finder_opts(&config).context("Failed to generate finder options")?; +pub fn main() -> Result<()> { + let config = &CONFIG; + let opts = FinderOpts::from_config(&config)?; let (raw_selection, variables, files) = config - .finder + .finder() .call(opts, |stdin, files| { let fetcher: Box = match config.source() { Source::Cheats(query) => Box::new(cheatsh::Fetcher::new(query)), @@ -59,13 +39,13 @@ pub fn main(config: Config) -> Result<()> { }) .context("Failed getting selection and variables from finder")?; - let extractions = extractor::extract_from_selections(&raw_selection, config.best_match); + let extractions = extractor::extract_from_selections(&raw_selection, config.best_match()); if extractions.is_err() { - return main(config); + return main(); } - actor::act(extractions, config, files, variables)?; + actor::act(extractions, files, variables)?; Ok(()) } diff --git a/src/handler/func.rs b/src/handler/func.rs index a0d6f22..686ee7d 100644 --- a/src/handler/func.rs +++ b/src/handler/func.rs @@ -1,9 +1,11 @@ -use crate::handler; -use crate::shell::{self, ShellSpawnError}; -use crate::structures::config; +use crate::cheat_variable; + +use crate::shell::{self}; + use crate::url; +use crate::welcome; + use anyhow::Result; -use std::io::{self, Read}; #[derive(Debug)] pub enum Func { @@ -16,58 +18,8 @@ pub enum Func { pub fn main(func: &Func, args: Vec) -> Result<()> { match func { Func::UrlOpen => url::open(args), - Func::Welcome => handler::handle_config(config::config_from_iter( - "navi --path /tmp/navi/irrelevant".split(' ').collect(), - )), - Func::WidgetLastCommand => widget_last_command(), - Func::MapExpand => map_expand(), + Func::Welcome => welcome::main(), + Func::WidgetLastCommand => shell::widget_last_command(), + Func::MapExpand => cheat_variable::map_expand(), } } - -fn map_expand() -> Result<()> { - let cmd = r#"sed -e 's/^.*$/"&"/' | tr '\n' ' '"#; - shell::command() - .arg("-c") - .arg(cmd) - .spawn() - .map_err(|e| ShellSpawnError::new(cmd, e))? - .wait()?; - Ok(()) -} - -fn widget_last_command() -> Result<()> { - let mut text = String::new(); - io::stdin().read_to_string(&mut text)?; - - let replacements = vec![("|", "ඛ"), ("||", "ග"), ("&&", "ඝ")]; - - let parts = shellwords::split(&text).unwrap_or_else(|_| text.split('|').map(|s| s.to_string()).collect()); - - for p in parts { - for (pattern, escaped) in replacements.clone() { - if p.contains(pattern) && p != pattern { - let replacement = p.replace(pattern, escaped); - text = text.replace(&p, &replacement); - } - } - } - - let mut extracted = text.clone(); - for (pattern, _) in replacements.clone() { - let mut new_parts = text.rsplit(pattern); - if let Some(extracted_attempt) = new_parts.next() { - if extracted_attempt.len() <= extracted.len() { - extracted = extracted_attempt.to_string(); - } - } - } - - for (pattern, escaped) in replacements.clone() { - text = text.replace(&escaped, &pattern); - extracted = extracted.replace(&escaped, &pattern); - } - - println!("{}", extracted.trim_start()); - - Ok(()) -} diff --git a/src/handler/info.rs b/src/handler/info.rs index 7a77d5c..8e96b17 100644 --- a/src/handler/info.rs +++ b/src/handler/info.rs @@ -1,15 +1,17 @@ -use crate::filesystem::default_cheat_pathbuf; +use crate::filesystem; use crate::fs::pathbuf_to_string; use anyhow::Result; #[derive(Debug)] pub enum Info { CheatsPath, + ConfigPath, } pub fn main(info: &Info) -> Result<()> { match info { - Info::CheatsPath => println!("{}", pathbuf_to_string(&default_cheat_pathbuf()?)?), + Info::CheatsPath => println!("{}", pathbuf_to_string(&filesystem::default_cheat_pathbuf()?)?), + Info::ConfigPath => println!("{}", pathbuf_to_string(&filesystem::default_config_pathbuf()?)?), } Ok(()) } diff --git a/src/handler/mod.rs b/src/handler/mod.rs index 80c6b0f..203625b 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -3,27 +3,28 @@ pub mod func; pub mod info; pub mod preview; pub mod preview_var; -pub mod repo; +pub mod repo_add; +pub mod repo_browse; pub mod shell; +use crate::config::Command::{Fn, Info, Preview, PreviewVar, Repo, Widget}; +use crate::config::{RepoCommand, CONFIG}; use crate::handler; -use crate::structures::config::Command::{Fn, Info, Preview, PreviewVar, Repo, Widget}; -use crate::structures::config::{Config, RepoCommand}; use anyhow::Context; use anyhow::Result; -pub fn handle_config(config: Config) -> Result<()> { - match config.cmd.as_ref() { - None => handler::core::main(config), +pub fn handle() -> Result<()> { + match CONFIG.cmd() { + None => handler::core::main(), Some(c) => match c { - Preview { line } => handler::preview::main(&line), + Preview { line } => handler::preview::main(line), PreviewVar { selection, query, variable, - } => handler::preview_var::main(&selection, &query, &variable), + } => handler::preview_var::main(selection, query, variable), Widget { shell } => handler::shell::main(shell).context("Failed to print shell widget code"), @@ -36,13 +37,16 @@ pub fn handle_config(config: Config) -> Result<()> { Repo { cmd } => match cmd { RepoCommand::Add { uri } => { - handler::repo::add(uri.clone(), &config.finder) + handler::repo_add::main(uri.clone()) .with_context(|| format!("Failed to import cheatsheets from `{}`", uri))?; - handler::core::main(config) + handler::core::main() } RepoCommand::Browse => { - handler::repo::browse(&config.finder).context("Failed to browse featured cheatsheets")?; - handler::core::main(config) + let repo = + handler::repo_browse::main().context("Failed to browse featured cheatsheets")?; + handler::repo_add::main(repo.clone()) + .with_context(|| format!("Failed to import cheatsheets from `{}`", repo))?; + handler::core::main() } }, }, diff --git a/src/handler/preview.rs b/src/handler/preview.rs index c19ba99..2403f6e 100644 --- a/src/handler/preview.rs +++ b/src/handler/preview.rs @@ -1,3 +1,4 @@ +use crate::config::CONFIG; use crate::ui; use crate::writer; use anyhow::Result; @@ -12,13 +13,15 @@ fn extract_elements(argstr: &str) -> (&str, &str, &str) { } pub fn main(line: &str) -> Result<()> { + // dbg!(CONFIG.comment_color()); + let (tags, comment, snippet) = extract_elements(line); println!( "{comment} {tags} \n{snippet}", - comment = ui::style(comment).with(*ui::COMMENT_COLOR), - tags = ui::style(format!("[{}]", tags)).with(*ui::TAG_COLOR), - snippet = ui::style(writer::fix_newlines(snippet)).with(*ui::SNIPPET_COLOR), + comment = ui::style(comment).with(CONFIG.comment_color()), + tags = ui::style(format!("[{}]", tags)).with(CONFIG.tag_color()), + snippet = ui::style(writer::fix_newlines(snippet)).with(CONFIG.snippet_color()), ); process::exit(0) diff --git a/src/handler/preview_var.rs b/src/handler/preview_var.rs index 6814b36..02467fc 100644 --- a/src/handler/preview_var.rs +++ b/src/handler/preview_var.rs @@ -1,11 +1,9 @@ +use crate::config::CONFIG; use crate::env_var; use crate::finder; - use crate::terminal::style::style; -use crate::ui; use crate::writer; use anyhow::Result; - use std::collections::HashSet; use std::iter; use std::process; @@ -18,8 +16,8 @@ pub fn main(selection: &str, query: &str, variable: &str) -> Result<()> { let delimiter = env_var::get(env_var::PREVIEW_DELIMITER).ok(); let map = env_var::get(env_var::PREVIEW_MAP).ok(); - let active_color = *ui::TAG_COLOR; - let inactive_color = *ui::COMMENT_COLOR; + let active_color = CONFIG.tag_color(); + let inactive_color = CONFIG.comment_color(); let mut colored_snippet = String::from(&snippet); let mut visited_vars: HashSet<&str> = HashSet::new(); @@ -28,8 +26,8 @@ pub fn main(selection: &str, query: &str, variable: &str) -> Result<()> { println!( "{comment} {tags}", - comment = style(comment).with(*ui::COMMENT_COLOR), - tags = style(format!("[{}]", tags)).with(*ui::TAG_COLOR), + comment = style(comment).with(CONFIG.comment_color()), + tags = style(format!("[{}]", tags)).with(CONFIG.tag_color()), ); let bracketed_current_variable = format!("<{}>", variable); diff --git a/src/handler/repo.rs b/src/handler/repo_add.rs similarity index 69% rename from src/handler/repo.rs rename to src/handler/repo_add.rs index de47dea..8125da2 100644 --- a/src/handler/repo.rs +++ b/src/handler/repo_add.rs @@ -1,3 +1,4 @@ +use crate::config::CONFIG; use crate::filesystem; use crate::finder::structures::{Opts as FinderOpts, SuggestionType}; use crate::finder::{Finder, FinderChoice}; @@ -9,51 +10,7 @@ use std::fs; use std::io::Write; use std::path; -pub fn browse(finder: &FinderChoice) -> Result<()> { - let repo_pathbuf = { - let mut p = filesystem::tmp_pathbuf()?; - p.push("featured"); - p - }; - - let repo_path_str = pathbuf_to_string(&repo_pathbuf)?; - - let _ = filesystem::remove_dir(&repo_pathbuf); - filesystem::create_dir(&repo_pathbuf)?; - - let (repo_url, _, _) = git::meta("denisidoro/cheats"); - git::shallow_clone(repo_url.as_str(), &repo_path_str) - .with_context(|| format!("Failed to clone `{}`", repo_url))?; - - let feature_repos_file = { - let mut p = repo_pathbuf.clone(); - p.push("featured_repos.txt"); - p - }; - - let repos = fs::read_to_string(&feature_repos_file).context("Unable to fetch featured repositories")?; - - let opts = FinderOpts { - column: Some(1), - suggestion_type: SuggestionType::SingleSelection, - ..Default::default() - }; - - let (repo, _, _) = finder - .call(opts, |stdin, _| { - stdin - .write_all(repos.as_bytes()) - .context("Unable to prompt featured repositories")?; - Ok(None) - }) - .context("Failed to get repo URL from finder")?; - - filesystem::remove_dir(&repo_pathbuf)?; - - add(repo, finder) -} - -pub fn ask_if_should_import_all(finder: &FinderChoice) -> Result { +fn ask_if_should_import_all(finder: &FinderChoice) -> Result { let opts = FinderOpts { column: Some(1), header: Some("Do you want to import all files from this repo?".to_string()), @@ -76,8 +33,10 @@ pub fn ask_if_should_import_all(finder: &FinderChoice) -> Result { } } -pub fn add(uri: String, finder: &FinderChoice) -> Result<()> { - let should_import_all = ask_if_should_import_all(finder).unwrap_or(false); +pub fn main(uri: String) -> Result<()> { + let finder = CONFIG.finder(); + + let should_import_all = ask_if_should_import_all(&finder).unwrap_or(false); let (actual_uri, user, repo) = git::meta(uri.as_str()); let cheat_pathbuf = filesystem::default_cheat_pathbuf()?; diff --git a/src/handler/repo_browse.rs b/src/handler/repo_browse.rs new file mode 100644 index 0000000..aeb2aed --- /dev/null +++ b/src/handler/repo_browse.rs @@ -0,0 +1,56 @@ +use crate::config::CONFIG; +use crate::filesystem; +use crate::finder::structures::{Opts as FinderOpts, SuggestionType}; +use crate::finder::Finder; +use crate::fs::pathbuf_to_string; +use crate::git; +use anyhow::Context; +use anyhow::Result; +use std::fs; +use std::io::Write; + +pub fn main() -> Result { + let finder = CONFIG.finder(); + + let repo_pathbuf = { + let mut p = filesystem::tmp_pathbuf()?; + p.push("featured"); + p + }; + + let repo_path_str = pathbuf_to_string(&repo_pathbuf)?; + + let _ = filesystem::remove_dir(&repo_pathbuf); + filesystem::create_dir(&repo_pathbuf)?; + + let (repo_url, _, _) = git::meta("denisidoro/cheats"); + git::shallow_clone(repo_url.as_str(), &repo_path_str) + .with_context(|| format!("Failed to clone `{}`", repo_url))?; + + let feature_repos_file = { + let mut p = repo_pathbuf.clone(); + p.push("featured_repos.txt"); + p + }; + + let repos = fs::read_to_string(&feature_repos_file).context("Unable to fetch featured repositories")?; + + let opts = FinderOpts { + column: Some(1), + suggestion_type: SuggestionType::SingleSelection, + ..Default::default() + }; + + let (repo, _, _) = finder + .call(opts, |stdin, _| { + stdin + .write_all(repos.as_bytes()) + .context("Unable to prompt featured repositories")?; + Ok(None) + }) + .context("Failed to get repo URL from finder")?; + + filesystem::remove_dir(&repo_pathbuf)?; + + Ok(repo) +} diff --git a/src/lib.rs b/src/lib.rs index a3b11ed..93dec3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,10 @@ extern crate lazy_static; extern crate anyhow; mod actor; +mod cheat_variable; mod cheatsh; mod clipboard; +mod config; mod env_var; mod extractor; mod filesystem; @@ -24,5 +26,4 @@ mod url; mod welcome; mod writer; -pub use handler::handle_config; -pub use structures::config::{config_from_env, config_from_iter}; +pub use handler::handle; diff --git a/src/shell.rs b/src/shell.rs index 1870e5b..67c326b 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -1,15 +1,10 @@ -use crate::env_var; +use crate::config::CONFIG; +use anyhow::Result; use std::fmt::Debug; +use std::io::{self, Read}; use std::process::Command; use thiserror::Error; -lazy_static! { - pub static ref IS_FISH: bool = env_var::get("SHELL") - .unwrap_or_else(|_| "".to_string()) - .contains(&"fish"); - pub static ref SHELL: String = env_var::get(env_var::SHELL).unwrap_or_else(|_| "bash".to_string()); -} - #[derive(Debug)] pub enum Shell { Bash, @@ -38,5 +33,42 @@ impl ShellSpawnError { } pub fn command() -> Command { - Command::new(&*SHELL) + Command::new(CONFIG.shell()) +} + +pub fn widget_last_command() -> Result<()> { + let mut text = String::new(); + io::stdin().read_to_string(&mut text)?; + + let replacements = vec![("|", "ඛ"), ("||", "ග"), ("&&", "ඝ")]; + + let parts = shellwords::split(&text).unwrap_or_else(|_| text.split('|').map(|s| s.to_string()).collect()); + + for p in parts { + for (pattern, escaped) in replacements.clone() { + if p.contains(pattern) && p != pattern { + let replacement = p.replace(pattern, escaped); + text = text.replace(&p, &replacement); + } + } + } + + let mut extracted = text.clone(); + for (pattern, _) in replacements.clone() { + let mut new_parts = text.rsplit(pattern); + if let Some(extracted_attempt) = new_parts.next() { + if extracted_attempt.len() <= extracted.len() { + extracted = extracted_attempt.to_string(); + } + } + } + + for (pattern, escaped) in replacements.clone() { + text = text.replace(&escaped, &pattern); + extracted = extracted.replace(&escaped, &pattern); + } + + println!("{}", extracted.trim_start()); + + Ok(()) } diff --git a/src/structures/mod.rs b/src/structures/mod.rs index 543dac8..a233a27 100644 --- a/src/structures/mod.rs +++ b/src/structures/mod.rs @@ -1,4 +1,3 @@ pub mod cheat; -pub mod config; pub mod fetcher; pub mod item; diff --git a/src/terminal.rs b/src/terminal.rs index 45f9b0f..780ab9c 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,9 +1,11 @@ +use anyhow::Result; pub use crossterm::style; use crossterm::terminal; +use std::str::FromStr; const FALLBACK_WIDTH: u16 = 80; -fn width_with_shell_out() -> u16 { +fn width_with_shell_out() -> Result { use std::process::Command; use std::process::Stdio; @@ -13,40 +15,53 @@ fn width_with_shell_out() -> u16 { .arg("/dev/stderr") .arg("size") .stderr(Stdio::inherit()) - .output() - .expect("Failed to execute stty") + .output()? } else { Command::new("stty") .arg("size") .arg("-F") .arg("/dev/stderr") .stderr(Stdio::inherit()) - .output() - .expect("Failed to execute stty") + .output()? }; - match output.status.code() { - Some(0) => { - let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty"); - let mut data = stdout.split_whitespace(); - data.next(); - data.next() - .expect("Not enough data") - .parse::() - .expect("Invalid base-10 number") - } - _ => FALLBACK_WIDTH, + if let Some(0) = output.status.code() { + let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty"); + let mut data = stdout.split_whitespace(); + data.next(); + return data + .next() + .expect("Not enough data") + .parse::() + .map_err(|_| anyhow!("Invalid width")); } + + Err(anyhow!("Invalid status code")) } pub fn width() -> u16 { if let Ok((w, _)) = terminal::size() { w } else { - width_with_shell_out() + width_with_shell_out().unwrap_or(FALLBACK_WIDTH) } } pub fn parse_ansi(ansi: &str) -> Option { style::Color::parse_ansi(&format!("5;{}", ansi)) } + +#[derive(Debug, Clone)] +pub struct Color(pub style::Color); + +impl FromStr for Color { + type Err = &'static str; + + fn from_str(ansi: &str) -> Result { + if let Some(c) = parse_ansi(ansi) { + Ok(Color(c)) + } else { + Err("Invalid color") + } + } +} diff --git a/src/ui.rs b/src/ui.rs index d0d5d4b..ed311b2 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,32 +1,20 @@ -use crate::env_var; - +use crate::config::CONFIG; use crate::terminal; pub use crate::terminal::style::style; -use crate::terminal::style::Color; - use std::cmp::max; -fn parse_ansi(varname: &str, default: Color) -> Color { - let value: Option = env_var::parse(varname); - if let Some(v) = value { - if let Some(a) = terminal::parse_ansi(&v) { - return a; - } - } - default -} - -lazy_static! { - pub static ref TAG_COLOR: Color = parse_ansi(env_var::TAG_COLOR, Color::Cyan); - pub static ref COMMENT_COLOR: Color = parse_ansi(env_var::COMMENT_COLOR, Color::Blue); - pub static ref SNIPPET_COLOR: Color = parse_ansi(env_var::SNIPPET_COLOR, Color::White); - pub static ref TAG_WIDTH_PERCENTAGE: u16 = env_var::parse(env_var::TAG_WIDTH).unwrap_or(26); - pub static ref COMMENT_WIDTH_PERCENTAGE: u16 = env_var::parse(env_var::COMMENT_WIDTH).unwrap_or(42); -} - pub fn get_widths() -> (usize, usize) { let width = terminal::width(); - let tag_width = max(20, width * *TAG_WIDTH_PERCENTAGE / 100); - let comment_width = max(45, width * *COMMENT_WIDTH_PERCENTAGE / 100); - (usize::from(tag_width), usize::from(comment_width)) + let tag_width_percentage = max( + CONFIG.tag_min_width(), + width * CONFIG.tag_width_percentage() / 100, + ); + let comment_width_percentage = max( + CONFIG.comment_min_width(), + width * CONFIG.comment_width_percentage() / 100, + ); + ( + usize::from(tag_width_percentage), + usize::from(comment_width_percentage), + ) } diff --git a/src/welcome.rs b/src/welcome.rs index da3a6e9..edbcb05 100644 --- a/src/welcome.rs +++ b/src/welcome.rs @@ -1,7 +1,27 @@ +use crate::config::CONFIG; +use crate::finder::structures::Opts as FinderOpts; +use crate::finder::Finder; + +use crate::structures::cheat::VariableMap; use crate::structures::item::Item; use crate::writer; +use anyhow::Context; +use anyhow::Result; use std::io::Write; +pub fn main() -> Result<()> { + let config = &CONFIG; + let opts = FinderOpts::from_config(&config)?; + let _ = config + .finder() + .call(opts, |stdin, _| { + populate_cheatsheet(stdin); + Ok(Some(VariableMap::new())) + }) + .context("Failed getting selection and variables from finder")?; + Ok(()) +} + fn add_msg(tags: &str, comment: &str, snippet: &str, stdin: &mut std::process::ChildStdin) { let item = Item { tags: tags.to_string(), diff --git a/src/writer.rs b/src/writer.rs index 56bbc14..895596c 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -1,3 +1,4 @@ +use crate::config::CONFIG; use crate::structures::item::Item; use crate::ui; use regex::Regex; @@ -35,12 +36,12 @@ fn limit_str(text: &str, length: usize) -> String { } pub fn write(item: &Item) -> String { - let (tag_width, comment_width) = *COLUMN_WIDTHS; + let (tag_width_percentage, comment_width_percentage) = *COLUMN_WIDTHS; format!( "{tags_short}{delimiter}{comment_short}{delimiter}{snippet_short}{delimiter}{tags}{delimiter}{comment}{delimiter}{snippet}{delimiter}{file_index}{delimiter}\n", - tags_short = ui::style(limit_str(&item.tags, tag_width)).with(*ui::TAG_COLOR), - comment_short = ui::style(limit_str(&item.comment, comment_width)).with(*ui::COMMENT_COLOR), - snippet_short = ui::style(fix_newlines(&item.snippet)).with(*ui::SNIPPET_COLOR), + tags_short = ui::style(limit_str(&item.tags, tag_width_percentage)).with(CONFIG.tag_color()), + comment_short = ui::style(limit_str(&item.comment, comment_width_percentage)).with(CONFIG.comment_color()), + snippet_short = ui::style(fix_newlines(&item.snippet)).with(CONFIG.snippet_color()), tags = item.tags, comment = item.comment, delimiter = DELIMITER,