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 29, 2021
1 parent 3358b07 commit 62980ac
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 20 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ dirs-next = { version = "2.0.0", optional = true }
grep-cli = { version = "0.1.6", optional = true }
regex = { version = "1.0", optional = true }
walkdir = { version = "2.0", optional = true }
time = { version = "0.3.5", features = ["formatting"] }
bytesize = {version = "1.1.0", features = ["serde"]}

[dependencies.git2]
version = "0.13"
Expand Down
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)
}
}
32 changes: 31 additions & 1 deletion src/input.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::convert::TryFrom;
use std::fs;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Read};
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::time::SystemTime;

use clircle::{Clircle, Identifier};
use content_inspector::{self, ContentType};
Expand Down Expand Up @@ -84,9 +87,17 @@ impl<'a> InputKind<'a> {
}
}

#[derive(Clone)]
pub(crate) struct InputPermissions {
pub(crate) mode: u32,
}

#[derive(Clone, Default)]
pub(crate) struct InputMetadata {
pub(crate) user_provided_name: Option<PathBuf>,
pub(crate) size: Option<u64>,
pub(crate) permissions: Option<InputPermissions>,
pub(crate) modified: Option<SystemTime>,
}

pub struct Input<'a> {
Expand Down Expand Up @@ -130,9 +141,28 @@ impl<'a> Input<'a> {

fn _ordinary_file(path: &Path) -> Self {
let kind = InputKind::OrdinaryFile(path.to_path_buf());
let metadata = match fs::metadata(path.to_path_buf()) {
Ok(meta) => {
let size = meta.len();
let modified = meta.modified().ok();
let perm = meta.permissions();
InputMetadata {
size: Some(size),
modified: modified,
permissions: Some(InputPermissions {
// the 3 digits from right are the familiar mode bits
// we are looking for
mode: perm.mode() & 0o777,
}),
..InputMetadata::default()
}
}
Err(_) => InputMetadata::default(),
};

Input {
description: kind.description(),
metadata: InputMetadata::default(),
metadata: metadata,
kind,
}
}
Expand Down
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
Loading

0 comments on commit 62980ac

Please sign in to comment.