on master help sign in
Allow for multiple line ranges

See #23

commit496e0bc04673b88bc6d9634ca45374b3c450557b authorsharkdp <davidpeter@web.de> dateSat 20 Oct 2018, 00:10 (8 years ago) parent7082fd0
5 files changed+142−21
src/app.rs+11−4 src/clap_app.rs+2−1 src/controller.rs+12−16 src/line_range.rs+106 tests/integration_tests.rs+11
src/app.rs+11−4
@@ -17,7 +17,7 @@ use assets::BAT_THEME_DEFAULT;
1717use config::{get_args_from_config_file, get_args_from_env_var};
1818use errors::*;
1919use inputfile::InputFile;
20−use line_range::LineRange;
20+use line_range::{LineRange, LineRanges};
2121use style::{OutputComponent, OutputComponents, OutputWrap};
2222use syntax_mapping::SyntaxMapping;
2323use util::transpose;
@@ -62,8 +62,8 @@ pub struct Config<'a> {
6262 /// Pager or STDOUT
6363 pub paging_mode: PagingMode,
6464
65− /// The range lines that should be printed, if specified
66− pub line_range: Option<LineRange>,
65+ /// Specifies the lines that should be printed
66+ pub line_ranges: LineRanges,
6767
6868 /// The syntax highlighting theme
6969 pub theme: String,
@@ -218,7 +218,14 @@ impl App {
218218 .map(String::from)
219219 .or_else(|| env::var("BAT_THEME").ok())
220220 .unwrap_or(String::from(BAT_THEME_DEFAULT)),
221− line_range: transpose(self.matches.value_of("line-range").map(LineRange::from))?,
221+ line_ranges: LineRanges::from(
222+ transpose(
223+ self.matches
224+ .values_of("line-range")
225+ .map(|vs| vs.map(LineRange::from).collect()),
226+ )?
227+ .unwrap_or(vec![]),
228+ ),
222229 output_components,
223230 syntax_mapping,
224231 })
src/clap_app.rs+2−1
@@ -146,8 +146,9 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
146146 .arg(
147147 Arg::with_name("line-range")
148148 .long("line-range")
149− .overrides_with("line-range")
149+ .multiple(true)
150150 .takes_value(true)
151+ .number_of_values(1)
151152 .value_name("N:M")
152153 .help("Only print the lines from N to M.")
153154 .long_help(
src/controller.rs+12−16
@@ -4,7 +4,7 @@ use app::Config;
44use assets::HighlightingAssets;
55use errors::*;
66use inputfile::{InputFile, InputFileReader};
7−use line_range::LineRange;
7+use line_range::{LineRanges, RangeCheckResult};
88use output::OutputType;
99use printer::{InteractivePrinter, Printer, SimplePrinter};
1010
@@ -64,7 +64,7 @@ impl<'b> Controller<'b> {
6464 input_file: InputFile<'a>,
6565 ) -> Result<()> {
6666 printer.print_header(writer, input_file)?;
67− self.print_file_ranges(printer, writer, reader, &self.config.line_range)?;
67+ self.print_file_ranges(printer, writer, reader, &self.config.line_ranges)?;
6868 printer.print_footer(writer)?;
6969
7070 Ok(())
@@ -75,29 +75,25 @@ impl<'b> Controller<'b> {
7575 printer: &mut P,
7676 writer: &mut Write,
7777 mut reader: InputFileReader,
78− line_ranges: &Option<LineRange>,
78+ line_ranges: &LineRanges,
7979 ) -> Result<()> {
8080 let mut line_buffer = Vec::new();
8181
8282 let mut line_number: usize = 1;
8383
8484 while reader.read_line(&mut line_buffer)? {
85− match line_ranges {
86− &Some(ref range) => {
87− if line_number < range.lower {
88− // Call the printer in case we need to call the syntax highlighter
89− // for this line. However, set `out_of_range` to `true`.
90− printer.print_line(true, writer, line_number, &line_buffer)?;
91− } else if line_number > range.upper {
92− // no more lines in range, exit early
93− break;
94− } else {
95− printer.print_line(false, writer, line_number, &line_buffer)?;
96− }
85+ match line_ranges.check(line_number) {
86+ RangeCheckResult::OutsideRange => {
87+ // Call the printer in case we need to call the syntax highlighter
88+ // for this line. However, set `out_of_range` to `true`.
89+ printer.print_line(true, writer, line_number, &line_buffer)?;
9790 }
98− &None => {
91+ RangeCheckResult::InRange => {
9992 printer.print_line(false, writer, line_number, &line_buffer)?;
10093 }
94+ RangeCheckResult::AfterLastRange => {
95+ break;
96+ }
10197 }
10298
10399 line_number += 1;
src/line_range.rs+106
@@ -38,6 +38,10 @@ impl LineRange {
3838
3939 Err("expected single ':' character".into())
4040 }
41+
42+ pub fn is_inside(&self, line: usize) -> bool {
43+ line >= self.lower && line <= self.upper
44+ }
4145}
4246
4347#[test]
@@ -72,3 +76,105 @@ fn test_parse_fail() {
7276 let range = LineRange::from("40");
7377 assert!(range.is_err());
7478}
79+
80+#[derive(Copy, Clone, Debug, PartialEq)]
81+pub enum RangeCheckResult {
82+ // Within one of the given ranges
83+ InRange,
84+
85+ // Before the first range or within two ranges
86+ OutsideRange,
87+
88+ // Line number is outside of all ranges and larger than the last range.
89+ AfterLastRange,
90+}
91+
92+#[derive(Clone)]
93+pub struct LineRanges {
94+ ranges: Vec<LineRange>,
95+ largest_upper_bound: usize,
96+}
97+
98+impl LineRanges {
99+ pub fn from(ranges: Vec<LineRange>) -> LineRanges {
100+ let largest_upper_bound = ranges
101+ .iter()
102+ .map(|r| r.upper)
103+ .max()
104+ .unwrap_or(usize::max_value());
105+ LineRanges {
106+ ranges,
107+ largest_upper_bound,
108+ }
109+ }
110+
111+ pub fn check(&self, line: usize) -> RangeCheckResult {
112+ if self.ranges.is_empty() {
113+ RangeCheckResult::InRange
114+ } else {
115+ if self.ranges.iter().any(|r| r.is_inside(line)) {
116+ RangeCheckResult::InRange
117+ } else {
118+ if line < self.largest_upper_bound {
119+ RangeCheckResult::OutsideRange
120+ } else {
121+ RangeCheckResult::AfterLastRange
122+ }
123+ }
124+ }
125+ }
126+}
127+
128+#[cfg(test)]
129+fn ranges(rs: &[&str]) -> LineRanges {
130+ LineRanges::from(rs.iter().map(|r| LineRange::from(r).unwrap()).collect())
131+}
132+
133+#[test]
134+fn test_ranges_simple() {
135+ let ranges = ranges(&["3:8"]);
136+
137+ assert_eq!(RangeCheckResult::OutsideRange, ranges.check(2));
138+ assert_eq!(RangeCheckResult::InRange, ranges.check(5));
139+ assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(9));
140+}
141+
142+#[test]
143+fn test_ranges_advanced() {
144+ let ranges = ranges(&["3:8", "11:20", "25:30"]);
145+
146+ assert_eq!(RangeCheckResult::OutsideRange, ranges.check(2));
147+ assert_eq!(RangeCheckResult::InRange, ranges.check(5));
148+ assert_eq!(RangeCheckResult::OutsideRange, ranges.check(9));
149+ assert_eq!(RangeCheckResult::InRange, ranges.check(11));
150+ assert_eq!(RangeCheckResult::OutsideRange, ranges.check(22));
151+ assert_eq!(RangeCheckResult::InRange, ranges.check(28));
152+ assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(31));
153+}
154+
155+#[test]
156+fn test_ranges_open_low() {
157+ let ranges = ranges(&["3:8", ":5"]);
158+
159+ assert_eq!(RangeCheckResult::InRange, ranges.check(1));
160+ assert_eq!(RangeCheckResult::InRange, ranges.check(3));
161+ assert_eq!(RangeCheckResult::InRange, ranges.check(7));
162+ assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(9));
163+}
164+
165+#[test]
166+fn test_ranges_open_high() {
167+ let ranges = ranges(&["3:", "2:5"]);
168+
169+ assert_eq!(RangeCheckResult::OutsideRange, ranges.check(1));
170+ assert_eq!(RangeCheckResult::InRange, ranges.check(3));
171+ assert_eq!(RangeCheckResult::InRange, ranges.check(5));
172+ assert_eq!(RangeCheckResult::InRange, ranges.check(9));
173+}
174+
175+#[test]
176+fn test_ranges_empty() {
177+ let ranges = ranges(&[]);
178+
179+ assert_eq!(RangeCheckResult::InRange, ranges.check(1));
180+}
tests/integration_tests.rs+11
@@ -96,6 +96,17 @@ fn line_range_last_3() {
9696 .stdout("line 2\nline 3\nline 4\n");
9797}
9898
99+#[test]
100+fn line_range_multiple() {
101+ bat()
102+ .arg("multiline.txt")
103+ .arg("--line-range=1:2")
104+ .arg("--line-range=4:4")
105+ .assert()
106+ .success()
107+ .stdout("line 1\nline 2\nline 4\n");
108+}
109+
99110#[test]
100111fn tabs_numbers() {
101112 bat()