diff options
author | Danilo Bargen <mail@dbrgn.ch> | 2018-09-16 00:42:41 +0200 |
---|---|---|
committer | Danilo Bargen <mail@dbrgn.ch> | 2018-09-16 00:42:41 +0200 |
commit | 278a2bbac9ea8434f546df017fc935e1d4013200 (patch) | |
tree | d08d2d1b4e25cd373f46bdaf5e41e6d952f76388 | |
parent | 9e86f2ee73d84a1d992c5d1dcdbf7305c4dd6a1f (diff) | |
parent | f3c80ad744cd7784cdc1645eeb382a382e10f5cc (diff) |
Merge pull request #43 from Voultapher/tealdeer
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | README.md | 70 | ||||
-rw-r--r-- | bash_tealdeer | 2 | ||||
-rw-r--r-- | screenshot-custom.png | bin | 0 -> 60027 bytes | |||
-rw-r--r-- | screenshot-default.png | bin | 0 -> 58268 bytes | |||
-rw-r--r-- | screenshot.png | bin | 60230 -> 0 bytes | |||
-rw-r--r-- | src/config.rs | 255 | ||||
-rw-r--r-- | src/error.rs | 1 | ||||
-rw-r--r-- | src/formatter.rs | 65 | ||||
-rw-r--r-- | src/main.rs | 85 | ||||
-rw-r--r-- | tests/config.toml | 17 | ||||
-rw-r--r-- | tests/inkscape-default.expected (renamed from tests/inkscape.expected) | 2 | ||||
-rw-r--r-- | tests/inkscape-v1.md | 2 | ||||
-rw-r--r-- | tests/inkscape-v2.md | 2 | ||||
-rw-r--r-- | tests/inkscape-with-config.expected | 28 | ||||
-rw-r--r-- | tests/lib.rs | 59 |
17 files changed, 540 insertions, 56 deletions
@@ -136,7 +136,7 @@ dependencies = [ "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -672,6 +672,7 @@ dependencies = [ "tar 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -724,7 +725,7 @@ dependencies = [ [[package]] name = "toml" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", @@ -937,7 +938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" "checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" -"checksum toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7540f4ffc193e0d3c94121edb19b055670d369f77d5804db11ae053a45b6e7e" +"checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" @@ -25,6 +25,7 @@ serde = "1.0.21" serde_derive = "1.0.21" tar = "0.4.14" time = "0.1.38" +toml = "0.4.6" walkdir = "2.0.1" xdg = "2.1.0" @@ -9,13 +9,14 @@ A very fast implementation of [tldr](https://github.com/tldr-pages/tldr) in Rust: Simplified, example based and community-driven man pages. -![screenshot](screenshot.png) +<img src="screenshot-default.png" alt="Screenshot of tldr command" width="600"> If you pronounce "tldr" in English, it sounds somewhat like "tealdeer". Hence the project name :) In case you're in a hurry and just want to quickly try tealdeer, you can find static binaries on the [GitHub releases page](https://github.com/dbrgn/tealdeer/releases/)! + ## Goals High level project goals: @@ -54,6 +55,7 @@ These are the clients I tried but failed to compile or run: [Go client by anoopengineer](https://github.com/anoopengineer/tldr/), [PHP client](https://github.com/BrainMaestro/tldr-php). + ## Usage tldr [options] <command> @@ -61,13 +63,15 @@ These are the clients I tried but failed to compile or run: Options: - -h --help Show this screen - -v --version Show version information - -l --list List all commands in the cache - -f --render <file> Render a specific markdown file - -o --os <type> Override the operating system [linux, osx, sunos] - -u --update Update the local cache - -c --clear-cache Clear the local cache + -h --help Show this screen + -v --version Show version information + -l --list List all commands in the cache + -f --render <file> Render a specific markdown file + -o --os <type> Override the operating system [linux, osx, sunos] + -u --update Update the local cache + -c --clear-cache Clear the local cache + --config-path Show config file path + --seed-config Create a basic config Examples: @@ -116,14 +120,58 @@ To enable the log output, set the `RUST_LOG` env variable: $ export RUST_LOG=tldr=debug - ### From AUR (Arch Linux) If you're an Arch Linux user, you can also install the package from the AUR: - $ yaourt -S tealdeer +- [tealdeer](https://aur.archlinux.org/packages/tealdeer/) +- [tealdeer-git](https://aur.archlinux.org/packages/tealdeer-git/) + + +## Configuration + +The tldr command can be customized with a config file called `config.toml`. +Creating the config file can be done manually or with the help of tldr: + + $ tldr --seed-config + +The configuration file path follows OS conventions. It can be queried with the following command: + + $ tldr --config-path + +### Style + +Using the config file, the style (e.g. colors or underlines) can be customized. + +Possible styles: + +- `description`: The initial description text +- `command_name`: The command name as part of the example code +- `example_text`: The text that describes an example +- `example_code`: The example itself, except the `command_name` and `example_variable` +- `example_variable`: The variables in the example + +Currently supported attributes: + +- `foreground` (color string, see below) +- `background` (color string, see below) +- `underline` (`true` or `false`) +- `bold` (`true` or `false`) + +The currently supported colors are: + +- `black` +- `red` +- `green` +- `yellow` +- `blue` +- `purple` +- `cyan` +- `white` + +Example customization: -(Or `tealdeer-git` if you prefer the current development version.) +<img src="screenshot-custom.png" alt="Screenshot of customized version" width="600"> ## Bash Autocompletion diff --git a/bash_tealdeer b/bash_tealdeer index 315aee5..05d298e 100644 --- a/bash_tealdeer +++ b/bash_tealdeer @@ -6,7 +6,7 @@ _tealdeer() _init_completion || return case $prev in - -h|--help|-v|--version|-l|--list|-u|--update|-c|--clear-cache) + -h|--help|-v|--version|-l|--list|-u|--update|-c|--clear-cache|--config-path|--seed-config) return ;; -f|--render) diff --git a/screenshot-custom.png b/screenshot-custom.png Binary files differnew file mode 100644 index 0000000..ff0c78d --- /dev/null +++ b/screenshot-custom.png diff --git a/screenshot-default.png b/screenshot-default.png Binary files differnew file mode 100644 index 0000000..8a73de7 --- /dev/null +++ b/screenshot-default.png diff --git a/screenshot.png b/screenshot.png Binary files differdeleted file mode 100644 index 2aa51e8..0000000 --- a/screenshot.png +++ /dev/null diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..bbfc2d7 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,255 @@ +use std::env; +use std::fs; +use std::io::{Error as IoError, Read, Write}; +use std::path::PathBuf; + +use ansi_term::{Color, Style}; +use toml; +use xdg::BaseDirectories; + +use error::TealdeerError::{self, ConfigError}; + +pub const CONFIG_FILE_NAME: &'static str = "config.toml"; + +fn default_underline() -> bool { + false +} + +fn default_bold() -> bool { + false +} + +#[serde(rename_all = "lowercase")] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub enum RawColor { + Black, + Red, + Green, + Yellow, + Blue, + Purple, + Cyan, + White, +} + +impl From<RawColor> for Color { + fn from(raw_color: RawColor) -> Color { + match raw_color { + RawColor::Black => Color::Black, + RawColor::Red => Color::Red, + RawColor::Green => Color::Green, + RawColor::Yellow => Color::Yellow, + RawColor::Blue => Color::Blue, + RawColor::Purple => Color::Purple, + RawColor::Cyan => Color::Cyan, + RawColor::White => Color::White, + } + } +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +struct RawStyle { + pub foreground: Option<RawColor>, + pub background: Option<RawColor>, + #[serde(default = "default_underline")] + pub underline: bool, + #[serde(default = "default_bold")] + pub bold: bool, +} + +impl Default for RawStyle { + fn default() -> RawStyle { + RawStyle{ + foreground: None, + background: None, + underline: false, + bold: false, + } + } +} // impl RawStyle + +impl From<RawStyle> for Style { + fn from(raw_style: RawStyle) -> Style { + let mut style = Style::default(); + + if let Some(foreground) = raw_style.foreground { + style = style.fg(Color::from(foreground)); + } + + if let Some(background) = raw_style.background { + style = style.on(Color::from(background)); + } + + if raw_style.underline { + style = style.underline(); + } + + if raw_style.bold { + style = style.bold(); + } + + style + } +} + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct RawStyleConfig { + #[serde(default)] + pub description: RawStyle, + #[serde(default)] + pub command_name: RawStyle, + #[serde(default)] + pub example_text: RawStyle, + #[serde(default)] + pub example_code: RawStyle, + #[serde(default)] + pub example_variable: RawStyle, +} + + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct RawConfig { + style: RawStyleConfig, +} + +impl RawConfig { + fn new() -> RawConfig { + let mut raw_config = RawConfig::default(); + + // Set default config + raw_config.style.example_text.foreground = Some(RawColor::Green); + raw_config.style.command_name.foreground = Some(RawColor::Cyan); + raw_config.style.example_code.foreground = Some(RawColor::Cyan); + raw_config.style.example_variable.foreground = Some(RawColor::Cyan); + raw_config.style.example_variable.underline = true; + + raw_config + } +} // impl RawConfig + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct StyleConfig { + pub description: Style, + pub command_name: Style, + pub example_text: Style, + pub example_code: Style, + pub example_variable: Style, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Config { + pub style: StyleConfig, +} + +impl From<RawConfig> for Config { + fn from(raw_config: RawConfig) -> Config { + Config{ + style: StyleConfig{ + command_name: raw_config.style.command_name.into(), + description: raw_config.style.description.into(), + example_text: raw_config.style.example_text.into(), + example_code: raw_config.style.example_code.into(), + example_variable: raw_config.style.example_variable.into(), + } + } + } +} + +fn map_io_err_to_config_err(e: IoError) -> TealdeerError { + ConfigError(format!("Io Error: {}", e)) +} + +impl Config { + pub fn load() -> Result<Config, TealdeerError> { + let raw_config = match get_config_path() { + Ok(config_file_path) => { + let mut config_file = fs::File::open(config_file_path) + .map_err(map_io_err_to_config_err)?; + let mut contents = String::new(); + let _rc = config_file.read_to_string(&mut contents) + .map_err(map_io_err_to_config_err)?; + + toml::from_str(&contents).map_err(|err| ConfigError(format!("Failed to parse config file: {}", err)))? + } + Err(ConfigError(_)) => RawConfig::new(), + Err(_) => { + return Err(ConfigError("Unknown error while looking up config path".into())); + } + }; + + Ok(Config::from(raw_config)) + } +} // impl Config + +/// Return the path to the config directory. +pub fn get_config_dir() -> Result<PathBuf, TealdeerError> { + // Allow overriding the config directory by setting the + // $TEALDEER_CONFIG_DIR env variable. + if let Ok(value) = env::var("TEALDEER_CONFIG_DIR") { + let path = PathBuf::from(value); + + if path.exists() && path.is_dir() { + return Ok(path) + } else { + return Err(ConfigError( + "Path specified by $TEALDEER_CONFIG_DIR \ + does not exist or is not a directory.".into() + )); + } + }; + + // Otherwise, fall back to $XDG_CONFIG_HOME/tealdeer. + let xdg_dirs = match BaseDirectories::with_prefix(::NAME) { + Ok(dirs) => dirs, + Err(_) => return Err(ConfigError("Could not determine XDG base directory.".into())), + }; + Ok(xdg_dirs.get_config_home()) +} + +/// Return the path to the config file. +pub fn get_config_path() -> Result<PathBuf, TealdeerError> { + let config_dir = get_config_dir()?; + let config_file_path = config_dir.join(CONFIG_FILE_NAME); + + if config_file_path.is_file() { + Ok(config_file_path) + } else { + Err(ConfigError(format!("{} is not a file path", config_file_path.to_str().unwrap()))) + } +} + +/// Create default config file. +pub fn make_default_config() -> Result<PathBuf, TealdeerError> { + let config_dir = get_config_dir()?; + if !config_dir.is_dir() { + if let Err(e) = fs::create_dir_all(&config_dir) { + return Err(ConfigError(format!("Could not create config directory: {}", e))); + } + } + + let config_file_path = config_dir.join(CONFIG_FILE_NAME); + if config_file_path.is_file() { + return Err(ConfigError(format!( + "A configuration file already exists at {}, no action was taken.", + config_file_path.to_str().unwrap() + ))); + } + + let serialized_config = toml::to_string(&RawConfig::new()) + .map_err(|err| ConfigError(format!("Failed to serialize default config: {}", err)))?; + + let mut config_file = fs::File::create(&config_file_path) + .map_err(map_io_err_to_config_err)?; + let _wc = config_file.write(serialized_config.as_bytes()) + .map_err(map_io_err_to_config_err)?; + + Ok(config_file_path) +} + +#[test] +fn test_serialize_deserialize() { + let raw_config = RawConfig::new(); + let serialized = toml::to_string(&raw_config).unwrap(); + let deserialized: RawConfig = toml::from_str(&serialized).unwrap(); + assert_eq!(raw_config, deserialized); +} diff --git a/src/error.rs b/src/error.rs index f099eb2..8274008 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,6 +3,7 @@ use curl::Error as CurlError; #[derive(Debug)] pub enum TealdeerError { CacheError(String), + ConfigError(String), UpdateError(String), } diff --git a/src/formatter.rs b/src/formatter.rs index 4031132..2be62d1 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -2,35 +2,64 @@ use std::io::BufRead; -use ansi_term::{Colour, ANSIStrings}; +use ansi_term::{ANSIString, ANSIStrings}; +use config::Config; use tokenizer::Tokenizer; use types::LineType; -/// Provide formatting for {{ curly braces }} in ExampleCode lines -fn format_braces(text: &str) -> String { - let parts = text.split("{{").flat_map(|s| s.split("}}")) - .enumerate() - .map(|(i, v)| { - if i % 2 == 0 { - Colour::Cyan.paint(v) - } else { - Colour::Cyan.underline().paint(v) - } - }) - .collect::<Vec<_>>(); +fn highlight_command<'a>( + command: &'a str, + example_code: &'a str, + config: &Config, + parts: &mut Vec<ANSIString<'a>> +) { + let mut code_part_end_pos = 0; + while let Some(command_start) = example_code[code_part_end_pos..].find(&command) { + let code_part = &example_code[code_part_end_pos..code_part_end_pos + command_start]; + parts.push(config.style.example_code.paint(code_part)); + parts.push(config.style.command_name.paint(command)); + + code_part_end_pos += command_start + command.len(); + } + parts.push(config.style.example_code.paint(&example_code[code_part_end_pos..])); +} + +/// Format and highlight code examples including variables in {{ curly braces }}. +fn format_code(command: &str, text: &str, config: &Config) -> String { + let mut parts = Vec::new(); + for between_variables in text.split("}}") { + if let Some(variable_start) = between_variables.find("{{") { + let example_code = &between_variables[..variable_start]; + let example_variable = &between_variables[variable_start + 2..]; + + highlight_command(&command, &example_code, &config, &mut parts); + parts.push(config.style.example_variable.paint(example_variable)); + } else { + highlight_command(&command, &between_variables, &config, &mut parts); + } + } + ANSIStrings(&parts).to_string() } /// Print a token stream to an ANSI terminal. -pub fn print_lines<R>(tokenizer: &mut Tokenizer<R>) where R: BufRead { +pub fn print_lines<R>(tokenizer: &mut Tokenizer<R>, config: &Config) where R: BufRead { + let mut command = String::new(); while let Some(token) = tokenizer.next_token() { match token { LineType::Empty => println!(""), - LineType::Title(_) => debug!("Ignoring title"), - LineType::Description(text) => println!(" {}", text), - LineType::ExampleText(text) => println!(" {}", Colour::Green.paint(text)), - LineType::ExampleCode(text) => println!(" {}", &format_braces(&text)), + LineType::Title(title) => { + debug!("Ignoring title"); + + // This is safe as long as the parsed title is only the command, + // and tokenizer yields values in order of appearance. + command = title; + debug!("Detected command name: {}", &command); + }, + LineType::Description(text) => println!(" {}", config.style.description.paint(text)), + LineType::ExampleText(text) => println!(" {}", config.style.example_text.paint(text)), + LineType::ExampleCode(text) => println!(" {}", &format_code(&command, &text, &config)), LineType::Other(text) => debug!("Unknown line type: {:?}", text), } } diff --git a/src/main.rs b/src/main.rs index c40e62f..9ce2d32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ extern crate tar; extern crate xdg; extern crate curl; extern crate time; +extern crate toml; extern crate walkdir; #[macro_use] extern crate serde_derive; @@ -41,17 +42,19 @@ use std::path::{Path, PathBuf}; use std::process; use docopt::Docopt; -use ansi_term::Colour; +use ansi_term::Color; mod types; mod tokenizer; mod formatter; mod cache; +mod config; mod error; use tokenizer::Tokenizer; use cache::Cache; -use error::TealdeerError::{UpdateError, CacheError}; +use config::{get_config_path, make_default_config, Config}; +use error::TealdeerError::{CacheError, ConfigError, UpdateError}; use formatter::print_lines; use types::OsType; @@ -72,6 +75,8 @@ Options: -o --os <type> Override the operating system [linux, osx, sunos] -u --update Update the local cache -c --clear-cache Clear the local cache + --config-path Show config file path + --seed-config Create a basic config Examples: @@ -100,6 +105,8 @@ struct Args { flag_os: Option<OsType>, flag_update: bool, flag_clear_cache: bool, + flag_config_path: bool, + flag_seed_config: bool, } /// Print page by path @@ -110,9 +117,22 @@ fn print_page(path: &Path) -> Result<(), String> { ); let reader = BufReader::new(file); + // Look up config file, if none is found fall back to default config. + let config = match Config::load() { + Ok(config) => config, + Err(ConfigError(msg)) => { + eprintln!("Failed to create config: {}", msg); + process::exit(1); + } + Err(_) => { + eprintln!("Unknown error while creating config"); + process::exit(1); + } + }; + // Create tokenizer and print output let mut tokenizer = Tokenizer::new(reader); - print_lines(&mut tokenizer); + print_lines(&mut tokenizer, &config); Ok(()) } @@ -122,14 +142,14 @@ fn check_cache(args: &Args, cache: &Cache) { if !args.flag_update { match cache.last_update() { Some(ago) if ago > MAX_CACHE_AGE => { - println!("{}", Colour::Red.paint(format!( + println!("{}", Color::Red.paint(format!( "Cache wasn't updated in {} days.\n\ - You should probably run `tldr --update` soon.\n", + You should probably run `tldr --update` soon.", MAX_CACHE_AGE / 24 / 3600 ))); }, None => { - println!("Cache not found. Please run `tldr --update`."); + eprintln!("Cache not found. Please run `tldr --update`."); process::exit(1); }, _ => {}, @@ -182,7 +202,8 @@ fn main() { if args.flag_clear_cache { cache.clear().unwrap_or_else(|e| { match e { - UpdateError(msg) | CacheError(msg) => println!("Could not delete cache: {}", msg), + CacheError(msg) | ConfigError(msg) | UpdateError(msg) => + eprintln!("Could not delete cache: {}", msg), }; process::exit(1); }); @@ -193,7 +214,8 @@ fn main() { if args.flag_update { cache.update().unwrap_or_else(|e| { match e { - UpdateError(msg) | CacheError(msg) => println!("Could not update cache: {}", msg), + CacheError(msg) | ConfigError(msg) | UpdateError(msg) => + eprintln!("Could not update cache: {}", msg), }; process::exit(1); }); @@ -204,7 +226,7 @@ fn main() { if let Some(ref file) = args.flag_render { let path = PathBuf::from(file); if let Err(msg) = print_page(&path) { - println!("{}", msg); + eprintln!("{}", msg); process::exit(1); } else { process::exit(0); @@ -219,7 +241,8 @@ fn main() { // Get list of pages let pages = cache.list_pages().unwrap_or_else(|e| { match e { - UpdateError(msg) | CacheError(msg) => println!("Could not get list of pages: {}", msg), + CacheError(msg) | ConfigError(msg) | UpdateError(msg) => + eprintln!("Could not get list of pages: {}", msg), } process::exit(1); }); @@ -237,7 +260,7 @@ fn main() { // Search for command in cache if let Some(path) = cache.find_page(&command) { if let Err(msg) = print_page(&path) { - println!("{}", msg); + eprintln!("{}", msg); process::exit(1); } else { process::exit(0); @@ -245,14 +268,50 @@ fn main() { } else { println!("Page {} not found in cache", &command); println!("Try updating with `tldr --update`, or submit a pull request to:"); - println!("https://github.com/tldr-pages/tldr"); + eprintln!("https://github.com/tldr-pages/tldr"); process::exit(1); } } + // Show config file and path and exit + if args.flag_config_path { + match get_config_path() { + Ok(config_file_path) => { + println!("Config path is: {}", config_file_path.to_str().unwrap()); + process::exit(0); + }, + Err(ConfigError(msg)) => { + eprintln!("Could not look up config_path: {}", msg); + process::exit(1); + }, + Err(_) => { + eprintln!("Unknown error"); + process::exit(1); + }, + } + } + + // Create a basic config and exit + if args.flag_seed_config { + match make_default_config() { + Ok(config_file_path) => { + println!("Successfully created seed config file here: {}", config_file_path.to_str().unwrap()); + process::exit(0); + }, + Err(ConfigError(msg)) => { + eprintln!("Could not create seed config: {}", msg); + process::exit(1); + }, + Err(_) => { + eprintln!("Unkown error"); + process::exit(1); + }, + } + } + // Some flags can be run without a command. if !(args.flag_update || args.flag_clear_cache) { - println!("{}", USAGE); + eprintln!("{}", USAGE); process::exit(1); } } diff --git a/tests/config.toml b/tests/config.toml new file mode 100644 index 0000000..7440a82 --- /dev/null +++ b/tests/config.toml @@ -0,0 +1,17 @@ +[style.highlight] +foreground = "green" +underline = false +bold = false + +[style.description] +underline = false +bold = false + +[style.example_text] +foreground = "black" +background = "blue" +underline = false + +[style.example_variable] +underline = true +bold = false diff --git a/tests/inkscape.expected b/tests/inkscape-default.expected index 7aed257..c7061b3 100644 --- a/tests/inkscape.expected +++ b/tests/inkscape-default.expected @@ -20,7 +20,7 @@ [32mExport an SVG document to PDF, converting all texts to paths:[0m - [36minkscape [4mfilename.svg[0m[36m --export-pdf=[4mfilename.pdf[0m[36m --export-text-to-path[0m + [36minkscape [4mfilename.svg[0m[36m | inkscape | inkscape --export-pdf=[4minkscape.pdf[0m[36m | inkscape | inkscape --export-text-to-path[0m [32mDuplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape:[0m diff --git a/tests/inkscape-v1.md b/tests/inkscape-v1.md index dfa30dc..4071196 100644 --- a/tests/inkscape-v1.md +++ b/tests/inkscape-v1.md @@ -21,7 +21,7 @@ - Export an SVG document to PDF, converting all texts to paths: -`inkscape {{filename.svg}} --export-pdf={{filename.pdf}} --export-text-to-path` +`inkscape {{filename.svg}} | inkscape | inkscape --export-pdf={{inkscape.pdf}} | inkscape | inkscape --export-text-to-path` - Duplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape: diff --git a/tests/inkscape-v2.md b/tests/inkscape-v2.md index 5772c89..3051867 100644 --- a/tests/inkscape-v2.md +++ b/tests/inkscape-v2.md @@ -22,7 +22,7 @@ Export a single object, given its ID, into a bitmap: Export an SVG document to PDF, converting all texts to paths: - inkscape {{filename.svg}} --export-pdf={{filename.pdf}} --export-text-to-path + inkscape {{filename.svg}} | inkscape | inkscape --export-pdf={{inkscape.pdf}} | inkscape | inkscape --export-text-to-path Duplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape: diff --git a/tests/inkscape-with-config.expected b/tests/inkscape-with-config.expected new file mode 100644 index 0000000..313e140 --- /dev/null +++ b/tests/inkscape-with-config.expected @@ -0,0 +1,28 @@ + + An SVG (Scalable Vector Graphics) editing program. + Use -z to not open the GUI and only process files in the console. + + [44;30mOpen an SVG file in the Inkscape GUI:[0m + + inkscape [4mfilename.svg[0m + + [44;30mExport an SVG file into a bitmap with the default format (PNG) and the default resolution (90 DPI):[0m + + inkscape [4mfilename.svg[0m -e [4mfilename.png[0m + + [44;30mExport an SVG file into a bitmap of 600x400 pixels (aspect ratio distortion may occur):[0m + + inkscape [4mfilename.svg[0m -e [4mfilename.png[0m -w [4m600[0m -h [4m400[0m + + [44;30mExport a single object, given its ID, into a bitmap:[0m + + inkscape [4mfilename.svg[0m -i [4mid[0m -e [4mobject.png[0m + + [44;30mExport an SVG document to PDF, converting all texts to paths:[0m + + inkscape [4mfilename.svg[0m | inkscape | inkscape --export-pdf=[4minkscape.pdf[0m | inkscape | inkscape --export-text-to-path + + [44;30mDuplicate the object with id="path123", rotate the duplicate 90 degrees, save the file, and quit Inkscape:[0m + + inkscape [4mfilename.svg[0m --select=path123 --verb=EditDuplicate --verb=ObjectRotate90 --verb=FileSave --verb=FileQuit + diff --git a/tests/lib.rs b/tests/lib.rs index 79a12ec..6c3f51b 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -11,21 +11,25 @@ use tempdir::TempDir; struct TestEnv { pub cache_dir: TempDir, + pub config_dir: TempDir, pub input_dir: TempDir, } impl TestEnv { fn new() -> Self { - let cache_dir = TempDir::new(".tldr.test.cache").unwrap(); - let input_dir = TempDir::new(".tldr.test.input").unwrap(); - TestEnv { cache_dir, input_dir } + TestEnv { + cache_dir: TempDir::new(".tldr.test.cache").unwrap(), + config_dir: TempDir::new(".tldr.test.config").unwrap(), + input_dir: TempDir::new(".tldr.test.input").unwrap(), + } } /// Return a new [`Assert`](../assert_cli/struct.Assert.html) instance for /// the main binary with env vars set. fn assert(&self) -> Assert { let env = Environment::inherit() - .insert("TEALDEER_CACHE_DIR", self.cache_dir.path().to_str().unwrap()); + .insert("TEALDEER_CACHE_DIR", self.cache_dir.path().to_str().unwrap()) + .insert("TEALDEER_CONFIG_DIR", self.config_dir.path().to_str().unwrap()); Assert::main_binary() .with_env(env) } @@ -37,7 +41,7 @@ fn test_missing_cache() { .assert() .with_args(&["sl"]) .fails() - .stdout() |