Merge pull request #43 from Voultapher/tealdeer

This commit is contained in:
Danilo Bargen 2018-09-16 00:42:41 +02:00
commit 278a2bbac9
17 changed files with 540 additions and 56 deletions

7
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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/)
(Or `tealdeer-git` if you prefer the current development version.)
## 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:
<img src="screenshot-custom.png" alt="Screenshot of customized version" width="600">
## Bash Autocompletion

View file

@ -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)

BIN
screenshot-custom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
screenshot-default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

255
src/config.rs Normal file
View file

@ -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);
}

View file

@ -3,6 +3,7 @@ use curl::Error as CurlError;
#[derive(Debug)]
pub enum TealdeerError {
CacheError(String),
ConfigError(String),
UpdateError(String),
}

View file

@ -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),
}
}

View file

@ -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);
}
}

17
tests/config.toml Normal file
View file

@ -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

View file

@ -20,7 +20,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:

View file

@ -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:

View file

@ -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:

View file

@ -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.
Open an SVG file in the Inkscape GUI:
inkscape filename.svg
Export an SVG file into a bitmap with the default format (PNG) and the default resolution (90 DPI):
inkscape filename.svg -e filename.png
Export an SVG file into a bitmap of 600x400 pixels (aspect ratio distortion may occur):
inkscape filename.svg -e filename.png -w 600 -h 400
Export a single object, given its ID, into a bitmap:
inkscape filename.svg -i id -e object.png
Export an SVG document to PDF, converting all texts to paths:
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:
inkscape filename.svg --select=path123 --verb=EditDuplicate --verb=ObjectRotate90 --verb=FileSave --verb=FileQuit

View file

@ -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().contains("Cache not found. Please run `tldr --update`.")
.stderr().contains("Cache not found. Please run `tldr --update`.")
.unwrap();
}
@ -48,7 +52,7 @@ fn test_update_cache() {
testenv.assert()
.with_args(&["sl"])
.fails()
.stdout().contains("Cache not found. Please run `tldr --update`.")
.stderr().contains("Cache not found. Please run `tldr --update`.")
.unwrap();
testenv.assert()
@ -63,6 +67,17 @@ fn test_update_cache() {
.unwrap();
}
#[test]
fn test_setup_seed_config() {
let testenv = TestEnv::new();
testenv.assert()
.with_args(&["--seed-config"])
.succeeds()
.stdout().contains("Successfully created seed config file")
.unwrap();
}
fn _test_correct_rendering(input_file: &str, filename: &str) {
let testenv = TestEnv::new();
@ -73,7 +88,7 @@ fn _test_correct_rendering(input_file: &str, filename: &str) {
file.write_all(input_file.as_bytes()).unwrap();
// Load expected output
let expected = include_str!("inkscape.expected");
let expected = include_str!("inkscape-default.expected");
testenv.assert()
.with_args(&["-f", &file_path.to_str().unwrap()])
@ -93,3 +108,33 @@ fn test_correct_rendering_v1() {
fn test_correct_rendering_v2() {
_test_correct_rendering(include_str!("inkscape-v2.md"), "inkscape-v2.md");
}
/// An end-to-end integration test for rendering with constom syntax config.
#[test]
fn test_correct_rendering_with_config() {
let testenv = TestEnv::new();
// Setup config file
// TODO should be config::CONFIG_FILE_NAME
let config_file_path = testenv.config_dir.path().join("config.toml");
println!("Config path: {:?}", &config_file_path);
let mut config_file = File::create(&config_file_path).unwrap();
config_file.write(include_str!("config.toml").as_bytes()).unwrap();
// Create input file
let file_path = testenv.input_dir.path().join("inkscape-v2.md");
println!("Testfile path: {:?}", &file_path);
let mut file = File::create(&file_path).unwrap();
file.write_all(include_str!("inkscape-v2.md").as_bytes()).unwrap();
// Load expected output
let expected = include_str!("inkscape-with-config.expected");
testenv.assert()
.with_args(&["-f", &file_path.to_str().unwrap()])
.succeeds()
.stdout().is(expected)
.unwrap();
}