on master help sign in
Major refactoring and cleanup
commit9316f2a758d015ced23af421e1257f5b926f25c4 authorsharkdp <davidpeter@web.de> dateWed 22 Aug 2018, 22:29 (8 years ago) parentc884c3c
10 files changed+176−150
src/app.rs+40−105 src/assets.rs+1−1 src/decorations.rs−3 src/features.rs+18−7 src/line_range.rs+73 src/main.rs+29−26 src/output.rs+3−2 src/printer.rs+9−5 src/style.rs+2−1 src/terminal.rs+1
src/app.rs+40−105
@@ -1,15 +1,51 @@
1+use std::collections::HashSet;
2+use std::env;
3+
14use atty::{self, Stream};
5+
26use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand};
7+
38use console::Term;
4−use errors::*;
5−use std::collections::HashSet;
6−use std::env;
7−use style::{OutputComponent, OutputComponents, OutputWrap};
89
910#[cfg(windows)]
1011use ansi_term;
1112
1213use assets::BAT_THEME_DEFAULT;
14+use errors::*;
15+use line_range::LineRange;
16+use style::{OutputComponent, OutputComponents, OutputWrap};
17+
18+#[derive(Debug, Clone, Copy)]
19+pub enum PagingMode {
20+ Always,
21+ QuitIfOneScreen,
22+ Never,
23+}
24+
25+pub struct Config<'a> {
26+ pub true_color: bool,
27+ pub output_wrap: OutputWrap,
28+ pub output_components: OutputComponents,
29+ pub language: Option<&'a str>,
30+ pub colored_output: bool,
31+ pub paging_mode: PagingMode,
32+ pub term_width: usize,
33+ pub files: Vec<Option<&'a str>>,
34+ pub theme: String,
35+ pub line_range: Option<LineRange>,
36+}
37+
38+fn is_truecolor_terminal() -> bool {
39+ env::var("COLORTERM")
40+ .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
41+ .unwrap_or(false)
42+}
43+
44+/// Helper function that should might appear in Rust stable at some point
45+/// (https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.transpose)
46+fn transpose<T>(opt: Option<Result<T>>) -> Result<Option<T>> {
47+ opt.map_or(Ok(None), |res| res.map(Some))
48+}
1349
1450pub struct App {
1551 pub matches: ArgMatches<'static>,
@@ -319,104 +355,3 @@ impl App {
319355 }))
320356 }
321357}
322−
323−#[derive(Debug, Clone, Copy)]
324−pub enum PagingMode {
325− Always,
326− QuitIfOneScreen,
327− Never,
328−}
329−
330−pub struct Config<'a> {
331− pub true_color: bool,
332− pub output_wrap: OutputWrap,
333− pub output_components: OutputComponents,
334− pub language: Option<&'a str>,
335− pub colored_output: bool,
336− pub paging_mode: PagingMode,
337− pub term_width: usize,
338− pub files: Vec<Option<&'a str>>,
339− pub theme: String,
340− pub line_range: Option<LineRange>,
341−}
342−
343−fn is_truecolor_terminal() -> bool {
344− env::var("COLORTERM")
345− .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
346− .unwrap_or(false)
347−}
348−
349−pub struct LineRange {
350− pub lower: usize,
351− pub upper: usize,
352−}
353−
354−impl LineRange {
355− pub fn from(range_raw: &str) -> Result<LineRange> {
356− LineRange::parse_range(range_raw)
357− }
358−
359− pub fn new() -> LineRange {
360− LineRange {
361− lower: usize::min_value(),
362− upper: usize::max_value(),
363− }
364− }
365−
366− pub fn parse_range(range_raw: &str) -> Result<LineRange> {
367− let mut new_range = LineRange::new();
368−
369− if range_raw.bytes().nth(0).ok_or("Empty line range")? == b':' {
370− new_range.upper = range_raw[1..].parse()?;
371− return Ok(new_range);
372− } else if range_raw.bytes().last().ok_or("Empty line range")? == b':' {
373− new_range.lower = range_raw[..range_raw.len() - 1].parse()?;
374− return Ok(new_range);
375− }
376−
377− let line_numbers: Vec<&str> = range_raw.split(':').collect();
378− if line_numbers.len() == 2 {
379− new_range.lower = line_numbers[0].parse()?;
380− new_range.upper = line_numbers[1].parse()?;
381− return Ok(new_range);
382− }
383− Err("expected single ':' character".into())
384− }
385−}
386−
387−#[test]
388−fn test_parse_line_range_full() {
389− let range = LineRange::from("40:50").expect("Shouldn't fail on test!");
390− assert_eq!(40, range.lower);
391− assert_eq!(50, range.upper);
392−}
393−
394−#[test]
395−fn test_parse_line_range_partial_min() {
396− let range = LineRange::from(":50").expect("Shouldn't fail on test!");
397− assert_eq!(usize::min_value(), range.lower);
398− assert_eq!(50, range.upper);
399−}
400−
401−#[test]
402−fn test_parse_line_range_partial_max() {
403− let range = LineRange::from("40:").expect("Shouldn't fail on test!");
404− assert_eq!(40, range.lower);
405− assert_eq!(usize::max_value(), range.upper);
406−}
407−
408−#[test]
409−fn test_parse_line_range_fail() {
410− let range = LineRange::from("40:50:80");
411− assert!(range.is_err());
412− let range = LineRange::from("40::80");
413− assert!(range.is_err());
414− let range = LineRange::from(":40:");
415− assert!(range.is_err());
416− let range = LineRange::from("40");
417− assert!(range.is_err());
418−}
419−
420−fn transpose<T>(opt: Option<Result<T>>) -> Result<Option<T>> {
421− opt.map_or(Ok(None), |res| res.map(Some))
422−}
src/assets.rs+1−1
@@ -193,7 +193,7 @@ impl HighlightingAssets {
193193 }
194194}
195195
196−// TODO: ideally, this function would be part of syntect's `ThemeSet`.
196+// TODO: this function will soon be part of syntect's `ThemeSet`.
197197fn extend_theme_set<P: AsRef<Path>>(theme_set: &mut ThemeSet, folder: P) -> Result<()> {
198198 let paths = ThemeSet::discover_theme_paths(folder)?;
199199 for p in &paths {
src/decorations.rs−3
@@ -14,7 +14,6 @@ pub trait Decoration {
1414 fn width(&self) -> usize;
1515}
1616
17−// Line number decoration.
1817pub struct LineNumberDecoration {
1918 color: Style,
2019 cached_wrap: DecorationText,
@@ -65,7 +64,6 @@ impl Decoration for LineNumberDecoration {
6564 }
6665}
6766
68−// Line changes decoration.
6967pub struct LineChangesDecoration {
7068 cached_none: DecorationText,
7169 cached_added: DecorationText,
@@ -121,7 +119,6 @@ impl Decoration for LineChangesDecoration {
121119 }
122120}
123121
124−// Grid border decoration.
125122pub struct GridBorderDecoration {
126123 cached: DecorationText,
127124}
src/features.rs+18−7
@@ -1,15 +1,19 @@
1+use std::fs::File;
2+use std::io::{self, BufRead, BufReader};
3+
14use ansi_term::Colour::Green;
2−use app::{Config, LineRange};
5+
6+use syntect::easy::HighlightLines;
7+use syntect::highlighting::Theme;
8+use syntect::parsing::SyntaxDefinition;
9+
10+use app::Config;
311use assets::HighlightingAssets;
412use diff::get_git_diff;
513use errors::*;
14+use line_range::LineRange;
615use output::OutputType;
716use printer::Printer;
8−use std::fs::File;
9−use std::io::{self, BufRead, BufReader};
10−use syntect::easy::HighlightLines;
11−use syntect::highlighting::Theme;
12−use syntect::parsing::SyntaxDefinition;
1317
1418pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
1519 let mut languages = assets
@@ -56,6 +60,13 @@ pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
5660 }
5761}
5862
63+pub fn list_themes(assets: &HighlightingAssets) {
64+ let themes = &assets.theme_set.themes;
65+ for (theme, _) in themes.iter() {
66+ println!("{}", theme);
67+ }
68+}
69+
5970pub fn print_files(assets: &HighlightingAssets, config: &Config) -> Result<bool> {
6071 let theme = assets.get_theme(&config.theme);
6172
@@ -86,7 +97,7 @@ fn print_file(
8697 printer: &mut Printer,
8798 filename: Option<&str>,
8899) -> Result<()> {
89− let stdin = io::stdin(); // TODO: this is not always needed
100+ let stdin = io::stdin(); // TODO: this variable is not always needed
90101 {
91102 let reader: Box<BufRead> = match filename {
92103 None => Box::new(stdin.lock()),
src/line_range.rsnew file+73
@@ -0,0 +1,73 @@
1+use errors::*;
2+
3+pub struct LineRange {
4+ pub lower: usize,
5+ pub upper: usize,
6+}
7+
8+impl LineRange {
9+ pub fn from(range_raw: &str) -> Result<LineRange> {
10+ LineRange::parse_range(range_raw)
11+ }
12+
13+ pub fn new() -> LineRange {
14+ LineRange {
15+ lower: usize::min_value(),
16+ upper: usize::max_value(),
17+ }
18+ }
19+
20+ pub fn parse_range(range_raw: &str) -> Result<LineRange> {
21+ let mut new_range = LineRange::new();
22+
23+ if range_raw.bytes().nth(0).ok_or("Empty line range")? == b':' {
24+ new_range.upper = range_raw[1..].parse()?;
25+ return Ok(new_range);
26+ } else if range_raw.bytes().last().ok_or("Empty line range")? == b':' {
27+ new_range.lower = range_raw[..range_raw.len() - 1].parse()?;
28+ return Ok(new_range);
29+ }
30+
31+ let line_numbers: Vec<&str> = range_raw.split(':').collect();
32+ if line_numbers.len() == 2 {
33+ new_range.lower = line_numbers[0].parse()?;
34+ new_range.upper = line_numbers[1].parse()?;
35+ return Ok(new_range);
36+ }
37+
38+ Err("expected single ':' character".into())
39+ }
40+}
41+
42+#[test]
43+fn test_parse_full() {
44+ let range = LineRange::from("40:50").expect("Shouldn't fail on test!");
45+ assert_eq!(40, range.lower);
46+ assert_eq!(50, range.upper);
47+}
48+
49+#[test]
50+fn test_parse_partial_min() {
51+ let range = LineRange::from(":50").expect("Shouldn't fail on test!");
52+ assert_eq!(usize::min_value(), range.lower);
53+ assert_eq!(50, range.upper);
54+}
55+
56+#[test]
57+fn test_parse_partial_max() {
58+ let range = LineRange::from("40:").expect("Shouldn't fail on test!");
59+ assert_eq!(40, range.lower);
60+ assert_eq!(usize::max_value(), range.upper);
61+}
62+
63+#[test]
64+fn test_parse_fail() {
65+ let range = LineRange::from("40:50:80");
66+ assert!(range.is_err());
67+ let range = LineRange::from("40::80");
68+ assert!(range.is_err());
69+ let range = LineRange::from(":40:");
70+ assert!(range.is_err());
71+ let range = LineRange::from("40");
72+ assert!(range.is_err());
73+}
src/main.rs+29−26
@@ -22,6 +22,7 @@ mod assets;
2222mod decorations;
2323mod diff;
2424mod features;
25+mod line_range;
2526mod output;
2627mod printer;
2728mod style;
@@ -33,7 +34,7 @@ use std::process;
3334
3435use app::App;
3536use assets::{clear_assets, config_dir, HighlightingAssets};
36−use features::{list_languages, print_files};
37+use features::{list_languages, list_themes, print_files};
3738
3839mod errors {
3940 error_chain! {
@@ -62,6 +63,24 @@ mod errors {
6263
6364use errors::*;
6465
66+fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
67+ if matches.is_present("init") {
68+ let source_dir = matches.value_of("source").map(Path::new);
69+ let target_dir = matches.value_of("target").map(Path::new);
70+
71+ let blank = matches.is_present("blank");
72+
73+ let assets = HighlightingAssets::from_files(source_dir, blank)?;
74+ assets.save(target_dir)?;
75+ } else if matches.is_present("clear") {
76+ clear_assets();
77+ } else if matches.is_present("config-dir") {
78+ println!("{}", config_dir());
79+ }
80+
81+ Ok(())
82+}
83+
6584/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Some(true)` on full success and
6685/// `Some(false)` if any intermediate errors occurred (were printed).
6786fn run() -> Result<bool> {
@@ -69,21 +88,8 @@ fn run() -> Result<bool> {
6988
7089 match app.matches.subcommand() {
7190 ("cache", Some(cache_matches)) => {
72− if cache_matches.is_present("init") {
73− let source_dir = cache_matches.value_of("source").map(Path::new);
74− let target_dir = cache_matches.value_of("target").map(Path::new);
75−
76− let blank = cache_matches.is_present("blank");
77−
78− let assets = HighlightingAssets::from_files(source_dir, blank)?;
79− assets.save(target_dir)?;
80− } else if cache_matches.is_present("clear") {
81− clear_assets();
82− } else if cache_matches.is_present("config-dir") {
83− println!("{}", config_dir());
84− }
85−
86− return Ok(true);
91+ run_cache_subcommand(cache_matches)?;
92+ Ok(true)
8793 }
8894 _ => {
8995 let config = app.config()?;
@@ -91,18 +97,15 @@ fn run() -> Result<bool> {
9197
9298 if app.matches.is_present("list-languages") {
9399 list_languages(&assets, config.term_width);
94− return Ok(true);
95− }
96100
97− if app.matches.is_present("list-themes") {
98− let themes = &assets.theme_set.themes;
99− for (theme, _) in themes.iter() {
100− println!("{}", theme);
101− }
102− return Ok(true);
103− }
101+ Ok(true)
102+ } else if app.matches.is_present("list-themes") {
103+ list_themes(&assets);
104104
105− print_files(&assets, &config)
105+ Ok(true)
106+ } else {
107+ print_files(&assets, &config)
108+ }
106109 }
107110 }
108111}
src/output.rs+3−2
@@ -1,9 +1,10 @@
1−use app::PagingMode;
2−use errors::*;
31use std::env;
42use std::io::{self, Write};
53use std::process::{Child, Command, Stdio};
64
5+use app::PagingMode;
6+use errors::*;
7+
78pub enum OutputType {
89 Pager(Child),
910 Stdout(io::Stdout),
src/printer.rs+9−5
@@ -1,15 +1,19 @@
1+use std::boxed::Box;
2+use std::io::Write;
3+use std::vec::Vec;
4+
15use ansi_term::Colour::{Fixed, Green, Red, Yellow};
26use ansi_term::Style;
3−use app::Config;
7+
48use console::AnsiCodeIterator;
9+
10+use syntect::highlighting::{self, Theme};
11+
12+use app::Config;
513use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration};
614use diff::LineChanges;
715use errors::*;
8−use std::boxed::Box;
9−use std::io::Write;
10−use std::vec::Vec;
1116use style::OutputWrap;
12−use syntect::highlighting::{self, Theme};
1317use terminal::{as_terminal_escaped, to_ansi_color};
1418
1519pub struct Printer<'a> {
src/style.rs+2−1
@@ -1,7 +1,8 @@
1−use errors::*;
21use std::collections::HashSet;
32use std::str::FromStr;
43
4+use errors::*;
5+
56#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
67pub enum OutputComponent {
78 Auto,
src/terminal.rs+1
@@ -1,5 +1,6 @@
11use ansi_term::Colour::{Fixed, RGB};
22use ansi_term::{self, Style};
3+
34use syntect::highlighting::{self, FontStyle};
45
56/// Approximate a 24 bit color value by a 8 bit ANSI code