From 9903c4eb1e28be4c5068c8af6e7b19f4a980c554 Mon Sep 17 00:00:00 2001 From: Ayose Date: Sun, 22 Aug 2021 10:21:20 +0100 Subject: [PATCH] Format options are set in the string itself. `header` and `table` options are now part of the format string, instead of fields of the builtin state. --- FORMAT.md | 18 ++++++ generator/src/doc.md | 18 ++++++ generator/src/doc.txt | 14 +++++ src/format/mod.rs | 2 + src/format/options.rs | 81 +++++++++++++++++++++++++++ src/lib.rs | 53 ++++-------------- src/tests/shell/change-config.test.sh | 2 - src/tests/shell/clear-history.test.sh | 2 + src/tests/shell/headers.test.sh | 3 +- src/tests/shell/tables.test.sh | 11 +++- 10 files changed, 154 insertions(+), 50 deletions(-) create mode 100644 src/format/options.rs diff --git a/FORMAT.md b/FORMAT.md index da44e53..830976d 100644 --- a/FORMAT.md +++ b/FORMAT.md @@ -43,6 +43,24 @@ The following resource specifiers are accepted in the format string: |`%x`
`%(status)` | Exit status of command. | |`%Z`
`%(page_size)` | Page size. | +## Options + +Options are surrounded by brackets at the beginning of the format string. There +are two valid options: + +* `header` + + Print a header containing the field labels. + +* `table` + + Render the history list as a table. Columns are separated by the tab + character. + +Example: + + [header,table]%n\t%e\t%C + ## Date/Time Format The syntax for the `%(time)` specifier is from the [chrono library]. diff --git a/generator/src/doc.md b/generator/src/doc.md index bfac692..2f19682 100644 --- a/generator/src/doc.md +++ b/generator/src/doc.md @@ -14,6 +14,24 @@ The following resource specifiers are accepted in the format string: |------------|-------------| %SPECS% +## Options + +Options are surrounded by brackets at the beginning of the format string. There +are two valid options: + +* `header` + + Print a header containing the field labels. + +* `table` + + Render the history list as a table. Columns are separated by the tab + character. + +Example: + + [header,table]%n\t%e\t%C + ## Date/Time Format The syntax for the `%(time)` specifier is from the [chrono library]. diff --git a/generator/src/doc.txt b/generator/src/doc.txt index 1ed9248..b8f71cf 100644 --- a/generator/src/doc.txt +++ b/generator/src/doc.txt @@ -12,6 +12,20 @@ SPECIFIERS %SPECS% +OPTIONS + + Options are surrounded by brackets at the beginning of the format string. + There are two valid options: + + header Print a header containing the field labels. + table Render the history list as a table. Columns are separated by + the tab character. + + Example: + + [header,table]%n\t%e\t%C + + DATE/TIME FORMAT The syntax for the %(time) specifier is from the [1]chrono library. diff --git a/src/format/mod.rs b/src/format/mod.rs index 0e1e183..be30a1e 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -6,12 +6,14 @@ use std::mem; use std::os::unix::ffi::OsStrExt; mod escapes; +mod options; mod tables; #[cfg(test)] mod tests; pub use escapes::EscapeArgument; +pub use options::FormatOptions; pub use tables::TableWriter; pub const HELP: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/doc.txt")); diff --git a/src/format/options.rs b/src/format/options.rs new file mode 100644 index 0000000..56f5c76 --- /dev/null +++ b/src/format/options.rs @@ -0,0 +1,81 @@ +//! Extract options from a format string. + +#[cfg_attr(test, derive(PartialEq, Debug))] +pub struct FormatOptions<'a> { + pub header: bool, + pub table: bool, + pub format: &'a str, +} + +impl FormatOptions<'_> { + pub fn parse(mut format: &str) -> FormatOptions { + let mut header = false; + let mut table = false; + + if format.starts_with('[') { + if let Some(end) = format.find(']') { + let (options, fmt) = format[1..].split_at(end - 1); + format = &fmt[1..]; + + for option in options.split(',') { + match option { + "header" => header = true, + "table" => table = true, + o => bash_builtins::warning!("'{}': invalid format option.", o), + } + } + } + } + + FormatOptions { + header, + table, + format, + } + } +} + +#[test] +fn parse_options() { + assert_eq!( + FormatOptions::parse("abc"), + FormatOptions { + header: false, + table: false, + format: "abc" + } + ); + + assert_eq!( + FormatOptions::parse("[header]abc"), + FormatOptions { + header: true, + table: false, + format: "abc" + } + ); + + assert_eq!( + FormatOptions::parse("[table,header]abc"), + FormatOptions { + header: true, + table: true, + format: "abc" + } + ); + + assert_eq!( + FormatOptions::parse("[]abc"), + FormatOptions { + header: false, + table: false, + format: "abc" + } + ); +} + +#[cfg(test)] +mod mock_bash_fns { + #[no_mangle] + extern "C" fn builtin_warning(_: *const libc::c_char) {} +} diff --git a/src/lib.rs b/src/lib.rs index 869d131..b58d5d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,9 +32,7 @@ builtin_metadata!( The following settings are available: 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. @@ -54,17 +52,11 @@ mod tests; use std::time::Duration; -const DEFAULT_FORMAT: &str = "%n\\t%(time:%X)\\t%P\\t%e\\t%C"; +const DEFAULT_FORMAT: &str = "[header,table]%n\\t%(time:%X)\\t%P\\t%e\\t%C"; struct TimeHistory { /// Default format to print history entries. default_format: String, - - /// Show header with field labels. - show_header: bool, - - /// Render lists as a table. - render_table: bool, } #[derive(BuiltinOptions)] @@ -115,8 +107,6 @@ impl TimeHistory { Ok(TimeHistory { default_format: DEFAULT_FORMAT.into(), - show_header: false, - render_table: false, }) } } @@ -176,22 +166,6 @@ impl Builtin for TimeHistory { 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() @@ -246,25 +220,22 @@ impl Builtin for TimeHistory { Some(Output::Json) => None, }; - // Use headers/tables. - let decorate = matches!(&output_format, None | Some(Output::Format(_))); + let format = format.map(format::FormatOptions::parse); // 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 let Some(options) = &format { + if options.table { + table_writer = format::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)?; + if options.header { + format::labels(options.format, &mut output)?; output.write_all(b"\n")?; - } else { - bash_builtins::warning!("header not available in JSON output."); } } - match (action, format) { + match (action, format.map(|f| f.format)) { (Action::List, None) => { let mut first = true; output.write_all(b"[\n")?; @@ -315,14 +286,10 @@ impl TimeHistory { &mut output, "\ format = {}\n\ - header = {}\n\ limit = {}\n\ - table = {}\n\ ", self.default_format, - self.show_header, history.size(), - self.render_table, )?; Ok(()) diff --git a/src/tests/shell/change-config.test.sh b/src/tests/shell/change-config.test.sh index 1d814de..f10fd96 100644 --- a/src/tests/shell/change-config.test.sh +++ b/src/tests/shell/change-config.test.sh @@ -9,9 +9,7 @@ ASSERT_OUTPUT \ "timehistory -s" \ <<-'ITEMS' format = %n\t%P\t%C - header = false limit = 5000 - table = false ITEMS timehistory -s format='> %C' diff --git a/src/tests/shell/clear-history.test.sh b/src/tests/shell/clear-history.test.sh index 2ba2984..868c8d8 100644 --- a/src/tests/shell/clear-history.test.sh +++ b/src/tests/shell/clear-history.test.sh @@ -2,6 +2,8 @@ load_builtin +timehistory -s format='%n' + /bin/true test -n "$(timehistory)" diff --git a/src/tests/shell/headers.test.sh b/src/tests/shell/headers.test.sh index adec295..d62986b 100644 --- a/src/tests/shell/headers.test.sh +++ b/src/tests/shell/headers.test.sh @@ -2,7 +2,6 @@ load_builtin -timehistory -s header=true ASSERT_OUTPUT \ - "timehistory -f '%n %C'" \ + "timehistory -f '[header]%n %C'" \ "NUMBER COMMAND" diff --git a/src/tests/shell/tables.test.sh b/src/tests/shell/tables.test.sh index 2149881..1e98900 100644 --- a/src/tests/shell/tables.test.sh +++ b/src/tests/shell/tables.test.sh @@ -5,12 +5,17 @@ load_builtin /bin/true 1 /bin/true 2 -timehistory -s header=true -s table=true - ASSERT_OUTPUT \ - "timehistory -f '%n\t%C'" \ + "timehistory -f '[header,table]%n\t%C'" \ <<-ITEMS NUMBER COMMAND 1 /bin/true 1 2 /bin/true 2 ITEMS + +ASSERT_OUTPUT \ + "timehistory -f '[table]%n\t%C'" \ + <<-ITEMS + 1 /bin/true 1 + 2 /bin/true 2 +ITEMS