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

Make --style parameter more flexible #74

Merged
merged 6 commits into from
May 10, 2018
Merged

Conversation

pitkley
Copy link
Contributor

@pitkley pitkley commented May 6, 2018

The --style parameter now accepts a comma-separated list of strings, where every element defines either a single output component (changes, grid, header, numbers) or a predefined style (full, line-numbers, plain).

If available, bat picks the first predefined style in the user-supplied style-list and ignores everything else. If no predefined style was requested, the other parameters that are simple output components will be used.

Examples:

  1. --style changes,full,numbers
    Will be reduced to only the predefined style full.

  2. --style plain,full
    Will be reduced to only the predefined style plain.

  3. --style changes,numbers
    Will not be reduced, because the list does not contain any predefined styles.

(Note: if grid is requested but no other parameters, bat still creates the left-most column with a width of PANEL_WIDTH. I didn't want to introduce further logic in this PR that drops or adapts the width of the left column.)

(This PR supersedes #63.)

@pitkley pitkley force-pushed the flexible-style branch 3 times, most recently from 617ec34 to ee06c2e Compare May 6, 2018 19:16
@sharkdp sharkdp self-requested a review May 6, 2018 20:44
@BrainMaestro BrainMaestro mentioned this pull request May 6, 2018
src/main.rs Outdated
fn from_str(s: &str) -> Result<Self> {
match s {
"full" => Ok(PredefinedStyle::Full),
"line-numbers" => Ok(PredefinedStyle::LineNumbers),
Copy link
Owner

Choose a reason for hiding this comment

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

Just a small heads-up: Do we actually need this combined/predefined style if it is equivalent to "numbers"?

(there is no need to maintain backwards-compatibility. The --style option has not been released)

@pitkley pitkley force-pushed the flexible-style branch 2 times, most recently from f64bb5b to df6ce07 Compare May 7, 2018 20:04
@pitkley
Copy link
Contributor Author

pitkley commented May 7, 2018

I have updated the PR to work with the new Printer. (I have also decreased visibility modifiers where possible to not publish too much of the API we don't want public. I kept most of it on a separate commit, in case this is not wanted.)

From my tests this seems to work correctly, but I'd appreciate if somebody could verify this! 👍

src/printer.rs Outdated
self.print_horizontal_line('┼')
} else {
writeln!(self.handle, "File {}", self.colors.filename.paint(filename))
.map_err(Into::into)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary? doesn't the ? operator suffice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since I am returning the result here, no. It would complain about getting a Result<_, io::Error> instead of Result<_, Error>. I can use ?; though and simply return Ok(()) at the end instead.

Let me know if you think that would be easier to understand!

src/printer.rs Outdated
);

// Print decorations
decorations
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm i'm not sure why this change was made. Is this better? There are more writes and a vector that isn't being used. Also, the changes don't seem related to the rest

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would assume that the write!s would be optimized extremely well, especially since we have stdout locked.

To me it felt wasteful allocating a Vec for every line, when we don't even have varying amount of elements in that vector. I'm fine with going back to a Vec on this, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After thinking about it a bit more, I have come up with the following solution:

let line_number_output = self.print_line_number(line_number).unwrap_or("".into());
let git_marker_output = self.print_git_marker(line_number).unwrap_or("".into());
let line_border_output = self.print_line_border().unwrap_or("".into());
let line = as_terminal_escaped(
    &regions,
    self.options.true_color,
    self.options.colored_output,
);

write!(
    self.handle,
    "{}{}{}{}",
    line_number_output, git_marker_output, line_border_output, line
).map_err(Into::into)

What do you think about that?

@@ -417,8 +479,7 @@ fn run() -> Result<()> {
.arg(
Arg::with_name("style")
.long("style")
.possible_values(&["auto", "plain", "line-numbers", "full"])
Copy link
Contributor

Choose a reason for hiding this comment

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

no more list of possible values?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have updated the handling of the comma-separated argument and could re-add the possible values! 👍

src/main.rs Outdated
match s {
"full" => Ok(PredefinedStyle::Full),
"plain" => Ok(PredefinedStyle::Plain),
_ => Err(ErrorKind::UnknownStyleName(s.to_owned()).into()),
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it fail or just default to full? The other options take this more forgiving style

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Defaulting to full happens in the clap-configuration: the default value for --style is full. Also, defaulting here would negate the selection-logic.

@BrainMaestro
Copy link
Contributor

Hey @pitkley, seems good, but the parsing logic seems a little complicated. Something like this that uses a HashSet could be another way. Let me know what you think.

@sharkdp sharkdp added this to the 0.3 milestone May 8, 2018
@sharkdp
Copy link
Owner

sharkdp commented May 8, 2018

I'm sorry for not being too active on this pull request, but unfortunately this is quite hard to review as there are many changes that are unrelated to the actual feature. While some of these changes are really good, it would be better to split these into separate pull requests (in the future).

I agree with @BrainMaestro, the parsing logic seems quite complex. Maybe things would also be simpler if we remove plain and full completely and simply parse a list of components? The default value would simply be a list of all decorations. What do you think?

@sharkdp sharkdp removed this from the 0.3 milestone May 8, 2018
pitkley added 2 commits May 9, 2018 20:56
The `--style` parameter now accepts a comma-separated list of strings,
where every element defines either a single output component (`changes`,
`grid`, `header`, `numbers`) or a predefined style (`full`,
`line-numbers`, `plain`).

If available, bat picks the first predefined style in the user-supplied
style-list and ignores everything else. If no predefined style was
requested, the other parameters that are simple output components will
be used.

Examples:

    --style changes,full,numbers

  Will internally be reduced to only the predefined style `full`.

    --style plain,full

  Will internally be reduced to only the predefined style `plain`.

    --style changes,numbers

  Will not be reduced, because the list does not contain any predefined
  styles.

(Note: if `grid` is requested but no other parameters, bat still creates
the left-most column with a width of `PANEL_WIDTH`. I didn't want to
introduce further logic in this PR that drops or adapts the width of the
left column.)
This commit tries to simply the change. Instead of separating an
`OutputComponent` and a `PredefinedStyle`, I have combined the two into
just `OutputComponent`.

To still have the styles work, I added an impl to `OutputComponent` with
a function `components` which returns the components related to the
specified component.

For a simple output component this is trivial, but for the predefined
styles this is a list of other components.

The evaluating of the command-line parameter simplified significantly,
making use of Claps `values_t!` macro to parse the supplied parameters
into a `Vec<OutputComponent>`. After that it is simply a task of
combining all supplied output components into one vector.

Important: this means that the styles are now additive, rather than one
of the predefined styles overruling other parameters supplied.
@pitkley
Copy link
Contributor Author

pitkley commented May 9, 2018

I have rebased the PR and performed a few changes:

  • I have tried to change less of the surroundings. (The Cow change in printer has to stay though if we don't want to needlessly allocate strings required for grid-handling.)

  • I have pushed another commit on top. This one combines the two enums OutputComponent and PredefinedStyle into one and significantly reduces the parsing logic.

    This is at the "expense" of the supplied styles now being additive, rather than one predefined style overruling. But this is probably more in line to what an end-user would expect.

    (For more details, see the commit message.)

Let me know if this change fits better, or if there are more open points! 👍

Removing the plain and full styles altogether would simplify the logic even further, but we'd probably have to discuss how the plain-style would be achieved then. (Not sure if --style "" is ergonomic from an end-user perspective.)

@@ -545,18 +598,17 @@ fn run() -> Result<()> {
})
.unwrap_or_else(|| vec![None]); // read from stdin (None) if no args are given

let output_components = values_t!(app_matches.values_of("style"), OutputComponent)?
Copy link
Contributor

Choose a reason for hiding this comment

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

very nice 👍

src/main.rs Outdated
let output_components = values_t!(app_matches.values_of("style"), OutputComponent)?
.into_iter()
.map(|style| style.components())
.fold(vec![], |mut acc, x| {
Copy link
Contributor

Choose a reason for hiding this comment

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

Vec::new() would be better since it's empty right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

vec![] is empty, too. I guess this is just preference, but results in the same code. (1, 2)

src/printer.rs Outdated
.output_components
.contains(&OutputComponent::Grid)
{
self.print_horizontal_line('┬')?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this function is only relevant for grid, you could put a check in the function itself to print nothing if grid is not present. it would simplify a lot. what do you think?

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 depends: the way it is implemented here right now, the header can also be shown regardless of if the grid is enabled or not. I suppose this is a decision to make: do we want to display a header if the grid is not requested and if so, how should it be formatted?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry if I wasn't clear, I was actually specifically talking about print_horizontal_line, but you're right about the header thing. 🤷‍♂️

src/printer.rs Outdated

self.print_horizontal_line('┼')
} else {
writeln!(
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not quite the same behavior. probably best to avoid the duplication

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, thanks for noticing. I'll leave this unchanged for the time being until we have a decision on whether header should have an effect without grid.

src/printer.rs Outdated
write!(
self.handle,
"{}",
decorations
.into_iter()
.filter_map(|dec| dec)
.filter_map(|dec| if grid_requested {
Some(dec.unwrap_or(" ".into()))
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm not sure I follow

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In a grid we have to at least have a space for a non-existing element to have the right spacing in the left column. Unfortunately I haven't found a cleaner way to express this spacing, without going back to the multiple writes.

@BrainMaestro
Copy link
Contributor

Nice, this is much easier to follow!

  • The Cow change, it probably has a negligible effect since we're still building strings after calling paint. Only 2-3 places where we have &str that can benefit while introducing many into calls.
  • As an extra suggestion, it would be nice if the checks could look like self.options.style.grid() and self.options.style.headers(). A simple tuple struct that wraps Vec<OutputComponent> would do this.
  • Also, wouldn't HashSet be better since there can be duplicates if the user passes --style=changes,full,header. Not that it affects anything

@pitkley
Copy link
Contributor Author

pitkley commented May 9, 2018

The Cow change, it probably has a negligible effect since we're still building strings after calling paint. Only 2-3 places where we have &str that can benefit while introducing many into calls.

Agreed. I'll go back to Option<String>!

As an extra suggestion, it would be nice if the checks could look like self.options.style.grid() and self.options.style.headers(). A simple tuple struct that wraps Vec would do this.

I was thinking about how to make this simpler. I like your suggestion and will give it a try!

Also, wouldn't HashSet be better since there can be duplicates if the user passes --style=changes,full,header. Not that it affects anything

Good point, I'll update that too.


Thanks for the thorough reviews, by the way. I really appreciate them!

@pitkley
Copy link
Contributor Author

pitkley commented May 9, 2018

Newest updates:

  • I have replace Cow by String. (It doesn't reduce as much noise since we need to convert &str to String, but it seems better anyway.)
  • I have replaced Vec by HashSet and wrapped the resulting HashSet<OutputComponent> into struct OutputComponents as suggested by @BrainMaestro. I kept the parameter name output_components to be in line with the struct, since renaming the struct to Style would have clashed with the Style from ansi_term. (If Style would be preferred though, I'd suggest going for something like use ansi_term::Style as AnsiStyle instead.)

src/printer.rs Outdated
Some(&LineChange::Modified) => self.colors.git_modified.paint("~"),
_ => Style::default().paint(" "),
}
fn print_git_marker<'s>(&self, line_number: usize) -> Option<String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

unused lifetime

src/printer.rs Outdated
fn print_line_border(&self) -> Option<String> {
if let OptionsStyle::Plain = self.options.style {
return None;
fn print_line_border<'s>(&self) -> Option<String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

@BrainMaestro
Copy link
Contributor

Awesome! You've been very patient. Thanks for that

Copy link
Owner

@sharkdp sharkdp left a comment

Choose a reason for hiding this comment

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

@pitkley I really like the new changes, thank you very much!

@BrainMaestro Thank you for reviewing!

@sharkdp sharkdp merged commit ea27053 into sharkdp:master May 10, 2018
@pitkley pitkley deleted the flexible-style branch November 1, 2020 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants