Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/improve colour contrast #43

Closed
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
49 changes: 41 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
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)
![screenshot](screenshot-default.png)

If you pronounce "tldr" in English, it sounds somewhat like "tealdeer". Hence the project name :)

Expand Down Expand Up @@ -61,13 +61,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
-d --display-config Show config directory path
-s --seed-config Create a basic config
Voultapher marked this conversation as resolved.
Show resolved Hide resolved

Examples:

Expand All @@ -83,6 +85,37 @@ These are the clients I tried but failed to compile or run:

$ tldr --render /path/to/file.md

### Style Customization

The tldr page syntax highlighting can be customized with a config file.
Creating the config file can be done manually or with the help of tldr.
```
tldr -s
```
Voultapher marked this conversation as resolved.
Show resolved Hide resolved

It should print the location where it created the config file.
Voultapher marked this conversation as resolved.
Show resolved Hide resolved
Example: `~/.config/tealdeer/syntax.toml`

The currently supported attributes are:

- `foreground` | optional
- `background` | optional
- `underline`
- `bold`

The currently supported colours are:

- `Black`
- `Red`
- `Green`
- `Yellow`
- `Blue`
- `Purple`
- `Cyan`
- `White`

Example customization:
![screenshot](screenshot-custom.png)

## Installing

Expand Down
Binary file added screenshot-custom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshot-default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed screenshot.png
Binary file not shown.
207 changes: 207 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
use std::env;
use std::fs;
use std::io::{Read, Write};
use std::path::PathBuf;

use ansi_term::{Colour, Style};
use toml;
use xdg::BaseDirectories;

use error::TealdeerError::{self, ConfigError};

pub const SYNTAX_CONFIG_FILE_NAME: &'static str = "syntax.toml";
Voultapher marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
Voultapher marked this conversation as resolved.
Show resolved Hide resolved
pub enum RawColour {
Voultapher marked this conversation as resolved.
Show resolved Hide resolved
Black,
Red,
Green,
Yellow,
Blue,
Purple,
Cyan,
White,
}

impl From<RawColour> for Colour {
Voultapher marked this conversation as resolved.
Show resolved Hide resolved
fn from(raw_colour: RawColour) -> Colour {
match raw_colour {
RawColour::Black => Colour::Black,
RawColour::Red => Colour::Red,
RawColour::Green => Colour::Green,
RawColour::Yellow => Colour::Yellow,
RawColour::Blue => Colour::Blue,
RawColour::Purple => Colour::Purple,
RawColour::Cyan => Colour::Cyan,
RawColour::White => Colour::White,
}
}
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
Voultapher marked this conversation as resolved.
Show resolved Hide resolved
struct RawStyle {
pub foreground: Option<RawColour>,
pub background: Option<RawColour>,
pub underline: bool,
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(Colour::from(foreground));
}

if let Some(background) = raw_style.background {
style = style.on(Colour::from(background));
}

if raw_style.underline {
style = style.underline();
}

if raw_style.bold {
style = style.bold();
}

style
}
}

#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
Voultapher marked this conversation as resolved.
Show resolved Hide resolved
struct RawConfig {
pub highlight_style: RawStyle,
pub description_style: RawStyle,
pub example_text_style: RawStyle,
pub example_code_style: RawStyle,
pub example_variable_style: RawStyle,
}

impl RawConfig {
fn new() -> RawConfig {
let mut raw_config = RawConfig::default();

raw_config.highlight_style.foreground = Some(RawColour::Red);
raw_config.example_variable_style.underline = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these defaults? Shouldn't this be the responsibility of the calling code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, what do we gain from having the caller specify them?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When creating a new RawConfig instance, I would expect it to be uninitialized. Otherwise the responsibility of the RawConfig type (encapulating style configuration) and the calling program (configuring the style of the output) are mixed -- the container all of a sudden sets styling. I'd separate those clearly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what default does, if we move it to the caller site we'd repeat the same thing 3 times.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's keep it for now, but then at least add a docstring indicating that some values will be initialized. Something like this:

/// Create a new `RawConfig` instance with some values initialized to certain default values:
///
/// - Foreground highlight style is "red"
/// - Example variable style is "underlined"


raw_config
}
} // impl RawConfig

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Config {
pub highlight_style: Style,
pub description_style: Style,
pub example_text_style: Style,
pub example_code_style: Style,
pub example_variable_style: Style,
}

impl From<RawConfig> for Config {
fn from(raw_config: RawConfig) -> Config {
Config{
highlight_style: Style::from(raw_config.highlight_style),
description_style: Style::from(raw_config.description_style),
example_text_style: Style::from(raw_config.example_text_style),
example_code_style: Style::from(raw_config.example_code_style),
example_variable_style: Style::from(raw_config.example_variable_style),
Voultapher marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

impl Config {
pub fn new() -> Result<Config, TealdeerError> {
Voultapher marked this conversation as resolved.
Show resolved Hide resolved
let raw_config = match get_syntax_config_path() {
Ok(syntax_config_file_path) => {
let mut syntax_config_file = fs::File::open(syntax_config_file_path)?;
let mut contents = String::new();
let _rc = syntax_config_file.read_to_string(&mut contents)?;

toml::from_str(&contents).map_err(|err| ConfigError(format!("Failed to parse syntax config file: {}", err)))?
}
Err(ConfigError(_)) => RawConfig::new(),
Err(_) => {
return Err(ConfigError("Unknown error while looking up syntax 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 syntax config file.
pub fn get_syntax_config_path() -> Result<PathBuf, TealdeerError> {
let config_dir = get_config_dir()?;
let syntax_config_file_path = config_dir.join(SYNTAX_CONFIG_FILE_NAME);

if syntax_config_file_path.is_file() {
Ok(syntax_config_file_path)
} else {
Err(ConfigError(format!("{} is not a file path", syntax_config_file_path.to_str().unwrap())))
}
}

/// Create default syntax config file.
pub fn make_default_syntax_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 serialized_syntax_config = toml::to_string(&RawConfig::new())
.map_err(|err| ConfigError(format!("Failed to serialize default syntax config: {}", err)))?;

let syntax_config_file_path = config_dir.join(SYNTAX_CONFIG_FILE_NAME);
let mut syntax_config_file = fs::File::create(&syntax_config_file_path)?;
let _wc = syntax_config_file.write(serialized_syntax_config.as_bytes())?;

Ok(syntax_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);
}
9 changes: 9 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::io::Error as IoError;

use curl::Error as CurlError;

#[derive(Debug)]
pub enum TealdeerError {
CacheError(String),
ConfigError(String),
UpdateError(String),
}

Expand All @@ -11,3 +14,9 @@ impl From<CurlError> for TealdeerError {
TealdeerError::UpdateError(format!("Curl error: {}", err.to_string()))
}
}

impl From<IoError> for TealdeerError {
fn from(err: IoError) -> TealdeerError {
TealdeerError::ConfigError(format!("Io error: {}", err))
}
}
Voultapher marked this conversation as resolved.
Show resolved Hide resolved
Loading