Skip to content

Commit

Permalink
Setting to render list as a table.
Browse files Browse the repository at this point in the history
  • Loading branch information
ayosec committed Aug 26, 2021
1 parent fb71c64 commit 784cbef
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 15 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ once_cell = "1.8.0"
plthook = "0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
unicode-width = "0.1.8"

[build-dependencies]
generator = { path = "generator" }
Expand Down
2 changes: 2 additions & 0 deletions src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use std::io::{self, Write};
use std::os::unix::ffi::OsStrExt;
use std::{fmt, mem};

pub mod tables;

pub const HELP: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/doc.txt"));

/// Render a format string with data from a `Entry` instance.
Expand Down
87 changes: 87 additions & 0 deletions src/format/tables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Render a multi-line string as a table.
//!
//! Rows as separated by `\n`, and columns by `\t`.

use std::io::{self, Write};
use std::{mem, str};
use unicode_width::UnicodeWidthStr;

/// Padding between columns.
const PADDING: usize = 2;

pub struct TableWriter<T> {
output: T,
contents: Vec<u8>,
}

impl<T> TableWriter<T> {
pub fn new(output: T) -> Self {
TableWriter {
output,
contents: Vec::new(),
}
}
}

impl<T: Write> Write for TableWriter<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.contents.extend_from_slice(buf);
Ok(buf.len())
}

fn flush(&mut self) -> io::Result<()> {
let contents = mem::take(&mut self.contents);
let lines: Vec<&str> = match str::from_utf8(&contents) {
Ok(c) => c.trim_end().split('\n').collect(),

Err(_) => return self.output.write_all(&contents),
};

// Compute the width for every column.
let mut widths = Vec::new();

for line in &lines {
for (n, column) in line.split('\t').enumerate() {
let width = UnicodeWidthStr::width(column) + PADDING;
match widths.get_mut(n) {
Some(col) if *col < width => *col = width,
None => widths.push(width),
_ => (),
}
}
}

// The last column does not need the width value.
if let Some(last) = widths.last_mut() {
*last = 0;
}

// Print table.
for line in &lines {
for (column, width) in line.split('\t').zip(&widths) {
write!(self.output, "{:1$}", column, width)?;
}

self.output.write_all(&[b'\n'])?;
}

Ok(())
}
}

#[test]
fn render_test() {
let mut buf = vec![];
let mut table = TableWriter::new(&mut buf);

write!(&mut table, "aaa\tb\tcc\na\t\tc\na\tbbbb\na\tb\tcccc").unwrap();
table.flush().unwrap();

assert_eq!(
String::from_utf8(buf).unwrap(),
"aaa b cc\n\
a c\n\
a bbbb \n\
a b cccc\n"
);
}
55 changes: 44 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::io::{self, BufWriter, Write};
builtin_metadata!(
name = "timehistory",
try_create = TimeHistory::new,
short_doc = "timehistory [-f FMT | -v | -j] [<n> | +<n>] | -s SET | -R",
short_doc = "timehistory [-f FMT | -v | -j] [<n> | +<n>] | -s | -s SET | -R",
long_doc = "
Displays information about the resources used by programs executed in
the running shell.
Expand All @@ -34,11 +34,12 @@ builtin_metadata!(
format\tDefault format string.
header\tShow a header with the labels of every resource.
limit\tHistory limit.
table\tRender the history list as a table.
To change a setting, use '-s name=value', where 'name' is any of the
previous values. Use one '-s' for every setting to change.
To see the current values use '-c show'.
'-s' with no argument shows the current settings.
",
);

Expand All @@ -61,6 +62,9 @@ struct TimeHistory {

/// Show header with field labels.
show_header: bool,

/// Render lists as a table.
render_table: bool,
}

#[derive(BuiltinOptions)]
Expand All @@ -78,7 +82,7 @@ enum Opt<'a> {
Reset,

#[opt = 's']
Setting(&'a str),
Setting(Option<&'a str>),

#[cfg(feature = "option-for-panics")]
#[opt = 'P']
Expand Down Expand Up @@ -112,14 +116,16 @@ impl TimeHistory {
Ok(TimeHistory {
default_format: DEFAULT_FORMAT.into(),
show_header: false,
render_table: false,
})
}
}

impl Builtin for TimeHistory {
fn call(&mut self, args: &mut Args) -> BuiltinResult<()> {
let mut table_writer;
let stdout_handle = io::stdout();
let mut output = BufWriter::new(stdout_handle.lock());
let mut output = &mut BufWriter::new(stdout_handle.lock()) as &mut dyn Write;

let mut history = match crate::ipc::events::collect_events(true) {
Some(history) => history,
Expand Down Expand Up @@ -158,22 +164,34 @@ impl Builtin for TimeHistory {

Opt::Reset => action = Action::Reset,

Opt::Setting("show") => {
Opt::Setting(None) => {
self.print_config(&mut output, &history)?;
exit_after_options = true;
}

Opt::Setting(setting) => {
Opt::Setting(Some(setting)) => {
let mut parts = setting.splitn(2, '=');
match (parts.next(), parts.next()) {
(Some("limit"), Some(value)) => {
history.set_size(value.parse()?);
}

(Some("header"), None) => {
self.show_header = true;
}

(Some("header"), Some(value)) => {
self.show_header = value.parse()?;
}

(Some("table"), Some(value)) => {
self.render_table = value.parse()?;
}

(Some("table"), None) => {
self.render_table = true;
}

(Some("format"), Some(value)) => {
self.default_format = if value.is_empty() {
DEFAULT_FORMAT.into()
Expand Down Expand Up @@ -228,7 +246,16 @@ impl Builtin for TimeHistory {
Some(Output::Json) => None,
};

if self.show_header {
// Use headers/tables.
let decorate = matches!(&output_format, None | Some(Output::Format(_)));

// Render output as a table.
if decorate && self.render_table {
table_writer = format::tables::TableWriter::new(output);
output = &mut table_writer as &mut dyn Write;
}

if decorate && self.show_header {
if let Some(fmt) = &format {
format::labels(fmt, &mut output)?;
output.write_all(b"\n")?;
Expand Down Expand Up @@ -276,20 +303,26 @@ impl Builtin for TimeHistory {
}
}

output.flush()?;

Ok(())
}
}

impl TimeHistory {
fn print_config(&self, mut output: impl Write, history: &history::History) -> io::Result<()> {
writeln!(
write!(
&mut output,
"format={}\n\
header={}\n\
limit={}",
"\
format = {}\n\
header = {}\n\
limit = {}\n\
table = {}\n\
",
self.default_format,
self.show_header,
history.size(),
self.render_table,
)?;

Ok(())
Expand Down
9 changes: 5 additions & 4 deletions src/tests/shell/change-config.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ load_builtin
timehistory -s limit=5000 -s format='%n\t%P\t%C'

ASSERT_OUTPUT \
"timehistory -s show" \
"timehistory -s" \
<<-'ITEMS'
format=%n\t%P\t%C
header=false
limit=5000
format = %n\t%P\t%C
header = false
limit = 5000
table = false
ITEMS

timehistory -s format='> %C'
Expand Down
16 changes: 16 additions & 0 deletions src/tests/shell/tables.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Test to use the shell session after removing the builtin.

load_builtin

/bin/true 1
/bin/true 2

timehistory -s header=true -s table=true

ASSERT_OUTPUT \
"timehistory -f '%n\t%C'" \
<<-ITEMS
NUMBER COMMAND
1 /bin/true 1
2 /bin/true 2
ITEMS

0 comments on commit 784cbef

Please sign in to comment.