Skip to content

Commit

Permalink
Add --header-info option to show more information about file in header
Browse files Browse the repository at this point in the history
Fixes #1701
  • Loading branch information
mdibaiee committed Dec 25, 2021
1 parent 3358b07 commit de79acb
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 19 deletions.
37 changes: 37 additions & 0 deletions src/bin/bat/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use bat::{
bat_warning,
config::{Config, VisibleLines},
error::*,
header::{HeaderComponent, HeaderComponents},
input::Input,
line_range::{HighlightedLineRanges, LineRange, LineRanges},
style::{StyleComponent, StyleComponents},
Expand Down Expand Up @@ -78,6 +79,7 @@ impl App {

pub fn config(&self, inputs: &[Input]) -> Result<Config> {
let style_components = self.style_components()?;
let header_components = self.header_components()?;

let paging_mode = match self.matches.value_of("paging") {
Some("always") => PagingMode::Always,
Expand Down Expand Up @@ -229,6 +231,7 @@ impl App {
),
},
style_components,
header_components,
syntax_mapping,
pager: self.matches.value_of("pager"),
use_italic_text: self.matches.value_of("italic-text") == Some("always"),
Expand Down Expand Up @@ -338,4 +341,38 @@ impl App {

Ok(styled_components)
}

fn header_components(&self) -> Result<HeaderComponents> {
let matches = &self.matches;
let header_components = HeaderComponents({
let env_header_components: Option<Vec<HeaderComponent>> = env::var("BAT_HEADER_INFO")
.ok()
.map(|header_str| {
header_str
.split(',')
.map(HeaderComponent::from_str)
.collect::<Result<Vec<HeaderComponent>>>()
})
.transpose()?;

matches
.values_of("header-info")
.map(|header| {
header
.map(|header| header.parse::<HeaderComponent>())
.filter_map(|header| header.ok())
.collect::<Vec<_>>()
})
.or(env_header_components)
.unwrap_or_else(|| vec![HeaderComponent::Full])
.into_iter()
.map(|header| header.components())
.fold(HashSet::new(), |mut acc, components| {
acc.extend(components.iter().cloned());
acc
})
});

Ok(header_components)
}
}
27 changes: 27 additions & 0 deletions src/bin/bat/clap_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,33 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
.help("Display all supported highlighting themes.")
.long_help("Display a list of supported themes for syntax highlighting."),
)
.arg(
Arg::with_name("header-info")
.long("header-info")
.value_name("components")
.use_delimiter(true)
.takes_value(true)
.possible_values(&["full", "auto", "filename", "size", "last-modified", "permissions"])
.help(
"Comma-separated list of header information elements to display \
(full, filename, size, last-modified, permissions).",
)
.long_help(
"Configure what information (filename, file size, last modification date, \
permissions, ..) to display in the header.\
The argument is a comma-separated list of \
components to display (e.g. 'filename,size,last-modified') or all of them ('full'). \
To set a default set of header information, add the \
'--header-info=\"..\"' option to the configuration file or export the \
BAT_HEADER_INFO environment variable (e.g.: export BAT_HEADER_INFO=\"..\").\n\n\
Possible values:\n\n \
* full: enables all available components (default).\n \
* filename: displays the file name.\n \
* size: displays the size of the file in human-readable format.\n \
* last-modified: displays the last modification timestamp of the file.\n \
* permissions: displays the file owner, group and mode.",
),
)
.arg(
Arg::with_name("style")
.long("style")
Expand Down
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::header::HeaderComponents;
use crate::line_range::{HighlightedLineRanges, LineRanges};
#[cfg(feature = "paging")]
use crate::paging::PagingMode;
Expand Down Expand Up @@ -58,6 +59,9 @@ pub struct Config<'a> {
/// Style elements (grid, line numbers, ...)
pub style_components: StyleComponents,

/// Header elements (filename, size, ...)
pub header_components: HeaderComponents,

/// If and how text should be wrapped
pub wrapping_mode: WrappingMode,

Expand Down
89 changes: 89 additions & 0 deletions src/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::collections::HashSet;
use std::fmt;
use std::str::FromStr;

use crate::error::*;

#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub enum HeaderComponent {
Filename,
Size,
Permissions,
LastModified,
Full,
}

impl fmt::Display for HeaderComponent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Write strictly the first element into the supplied output
// stream: `f`. Returns `fmt::Result` which indicates whether the
// operation succeeded or failed. Note that `write!` uses syntax which
// is very similar to `println!`.
let name = match self {
HeaderComponent::Filename => "filename",
HeaderComponent::Size => "size",
HeaderComponent::Permissions => "permissions",
HeaderComponent::LastModified => "last-modified",
HeaderComponent::Full => "full",
};

write!(f, "{}", name)
}
}

impl HeaderComponent {
pub fn components(self) -> &'static [HeaderComponent] {
match self {
HeaderComponent::Filename => &[HeaderComponent::Filename],
HeaderComponent::Size => &[HeaderComponent::Size],
HeaderComponent::Permissions => &[HeaderComponent::Permissions],
HeaderComponent::LastModified => &[HeaderComponent::LastModified],
HeaderComponent::Full => &[
HeaderComponent::Filename,
HeaderComponent::Size,
HeaderComponent::Permissions,
HeaderComponent::LastModified,
],
}
}
}

impl FromStr for HeaderComponent {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
match s {
"filename" => Ok(HeaderComponent::Filename),
"size" => Ok(HeaderComponent::Size),
"permissions" => Ok(HeaderComponent::Permissions),
"last-modified" => Ok(HeaderComponent::LastModified),
"full" => Ok(HeaderComponent::Full),
_ => Err(format!("Unknown header-info '{}'", s).into()),
}
}
}

#[derive(Debug, Clone, Default)]
pub struct HeaderComponents(pub HashSet<HeaderComponent>);

impl HeaderComponents {
pub fn new(components: &[HeaderComponent]) -> HeaderComponents {
HeaderComponents(components.iter().cloned().collect())
}

pub fn filename(&self) -> bool {
self.0.contains(&HeaderComponent::Filename)
}

pub fn size(&self) -> bool {
self.0.contains(&HeaderComponent::Size)
}

pub fn permissions(&self) -> bool {
self.0.contains(&HeaderComponent::Permissions)
}

pub fn last_modified(&self) -> bool {
self.0.contains(&HeaderComponent::LastModified)
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub mod controller;
mod decorations;
mod diff;
pub mod error;
pub mod header;
pub mod input;
mod less;
pub mod line_range;
Expand Down
63 changes: 44 additions & 19 deletions src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration}
#[cfg(feature = "git")]
use crate::diff::LineChanges;
use crate::error::*;
use crate::header::HeaderComponent;
use crate::input::OpenedInput;
use crate::line_range::RangeCheckResult;
use crate::preprocessor::{expand_tabs, replace_nonprintable};
Expand Down Expand Up @@ -289,15 +290,6 @@ impl<'a> Printer for InteractivePrinter<'a> {

if self.config.style_components.grid() {
self.print_horizontal_line(handle, '┬')?;

write!(
handle,
"{}{}",
" ".repeat(self.panel_width),
self.colors
.grid
.paint(if self.panel_width > 0 { "│ " } else { "" }),
)?;
} else {
// Only pad space between files, if we haven't already drawn a horizontal rule
if add_header_padding && !self.config.style_components.rule() {
Expand All @@ -316,16 +308,49 @@ impl<'a> Printer for InteractivePrinter<'a> {

let description = &input.description;

writeln!(
handle,
"{}{}{}",
description
.kind()
.map(|kind| format!("{}: ", kind))
.unwrap_or_else(|| "".into()),
self.colors.filename.paint(description.title()),
mode
)?;
self.config
.header_components
.0
.iter()
.try_for_each(|component| {
if self.config.style_components.grid() {
write!(
handle,
"{}{}",
" ".repeat(self.panel_width),
self.colors
.grid
.paint(if self.panel_width > 0 { "│ " } else { "" }),
)?;
};

match component {
HeaderComponent::Filename => writeln!(
handle,
"{}{}{}",
description
.kind()
.map(|kind| format!("{}: ", kind))
.unwrap_or_else(|| "".into()),
self.colors.filename.paint(description.title()),
mode
),

HeaderComponent::Size => writeln!(handle, "Size: {}", "3.3K"),

HeaderComponent::Permissions => writeln!(
handle,
"Permissions: {}:{} {}",
"mahdi", "mahdi", "rw-rw-rw"
),

HeaderComponent::LastModified => {
writeln!(handle, "Last Modified At: {}", "2021 Dec 25 00:20")
}

_ => Ok(()),
}
})?;

if self.config.style_components.grid() {
if self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable {
Expand Down

0 comments on commit de79acb

Please sign in to comment.