display the text of long notes nicely
This commit is contained in:
parent
5b37b63e99
commit
e59211f1f9
|
@ -13,6 +13,15 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
|
@ -83,7 +92,7 @@ dependencies = [
|
|||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"textwrap 0.11.0",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
@ -312,6 +321,23 @@ dependencies = [
|
|||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.25.3"
|
||||
|
@ -366,6 +392,12 @@ version = "1.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
|
@ -392,6 +424,17 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"unicode-linebreak",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.25"
|
||||
|
@ -424,6 +467,7 @@ dependencies = [
|
|||
"rusqlite",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"textwrap 0.14.2",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
@ -448,6 +492,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05a31f45d18a3213b918019f78fe6a73a14ab896807f0aaf5622aa0684749455"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
|
|
|
@ -16,6 +16,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
serde_yaml = "0.8"
|
||||
toml = "0.5"
|
||||
itertools = "0.10"
|
||||
textwrap = "0.14"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.7.2"
|
||||
|
|
|
@ -76,6 +76,6 @@ impl<'a> Command<'a> for DisplayCommand {
|
|||
}
|
||||
};
|
||||
|
||||
args.format.print_formatted(sheets_to_display, out, Utc::now(), Local.offset_from_utc_datetime(&Utc::now().naive_utc()), args.ids)
|
||||
args.format.print_formatted(sheets_to_display, out, Utc::now(), Local.offset_from_utc_datetime(&Utc::now().naive_utc()), args.ids, 100)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
use std::io::Write;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use itertools::Itertools;
|
||||
|
@ -44,6 +45,11 @@ fn format_end(start: NaiveDateTime, end: NaiveDateTime) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
fn constrained_lines(text: &str, width: usize) -> Vec<Cow<'_, str>> {
|
||||
textwrap::wrap(text, width)
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Formatter {
|
||||
|
@ -58,9 +64,9 @@ impl Formatter {
|
|||
/// to the local timezone is given in `offset` to prevent this function from
|
||||
/// using a secondary effect to retrieve the time and conver dates. This
|
||||
/// also makes it easier to test.
|
||||
pub fn print_formatted<W: Write, O: Offset>(&self, entries: Vec<Entry>, out: &mut W, now: DateTime<Utc>, offset: O, ids: bool) -> error::Result<()> {
|
||||
pub fn print_formatted<W: Write, O: Offset>(&self, entries: Vec<Entry>, out: &mut W, now: DateTime<Utc>, offset: O, ids: bool, term_width: usize) -> error::Result<()> {
|
||||
match &self {
|
||||
Formatter::Text => self.print_formatted_text(entries, out, now, offset, ids)?,
|
||||
Formatter::Text => self.print_formatted_text(entries, out, now, offset, ids, term_width)?,
|
||||
Formatter::Custom(name) => {
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +76,7 @@ impl Formatter {
|
|||
|
||||
/// Print in the default text format. Assume entries are sorted by sheet and
|
||||
/// then by start
|
||||
fn print_formatted_text<W: Write, O: Offset>(&self, entries: Vec<Entry>, out: &mut W, now: DateTime<Utc>, offset: O, ids: bool) -> error::Result<()> {
|
||||
fn print_formatted_text<W: Write, O: Offset>(&self, entries: Vec<Entry>, out: &mut W, now: DateTime<Utc>, offset: O, ids: bool, term_width: usize) -> error::Result<()> {
|
||||
let grouped_entries = entries.into_iter().group_by(|e| e.sheet.to_string());
|
||||
|
||||
// Build a timezone based on the offset given to this function. This
|
||||
|
@ -138,17 +144,32 @@ impl Formatter {
|
|||
"Notes",
|
||||
)?;
|
||||
|
||||
let mut max_note_length = 0;
|
||||
|
||||
for [id, date, start, end, duration, note] in lines {
|
||||
writeln!(
|
||||
out,
|
||||
"{} {} {} {} {} {}",
|
||||
let first_line = format!(
|
||||
"{} {} {} {} {} ",
|
||||
lpad(&id, 3.max(lengths[0])),
|
||||
rpad(&date, 18),
|
||||
rpad(&start, 10),
|
||||
rpad(&end, 10.max(lengths[3])),
|
||||
lpad(&duration, 8.max(lengths[4])),
|
||||
note,
|
||||
)?;
|
||||
);
|
||||
|
||||
let space_left = term_width.saturating_sub(first_line.len()).max(40);
|
||||
let note_lines = constrained_lines(¬e, space_left);
|
||||
|
||||
for (i, note_line) in note_lines.into_iter().enumerate() {
|
||||
if i == 0 {
|
||||
writeln!(out, "{}{}", first_line, note_line)?;
|
||||
} else {
|
||||
writeln!(out, "{}{}", " ".repeat(first_line.len()), note_line)?;
|
||||
}
|
||||
|
||||
if note_line.len() > max_note_length {
|
||||
max_note_length = note_line.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(out,
|
||||
|
@ -158,7 +179,7 @@ impl Formatter {
|
|||
"-".repeat(10),
|
||||
"-".repeat(10.max(lengths[3])),
|
||||
"-".repeat(8.max(lengths[4])),
|
||||
"-".repeat(4.max(lengths[5])),
|
||||
"-".repeat(4.max(max_note_length)),
|
||||
)?;
|
||||
writeln!(out,
|
||||
"{} {} {} {} {}",
|
||||
|
@ -195,6 +216,8 @@ mod tests {
|
|||
use pretty_assertions::assert_eq;
|
||||
use chrono::TimeZone;
|
||||
|
||||
const LONG_NOTE: &'static str = "chatting with bob about upcoming task, district sharing of images, how the user settings currently works etc. Discussing the fingerprinting / cache busting issue with CKEDITOR, suggesting perhaps looking into forking the rubygem and seeing if we can work in our own changes, however hard that might be.";
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct PrettyString<'a>(pub &'a str);
|
||||
|
||||
|
@ -205,6 +228,20 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constrained_lines_long_text() {
|
||||
assert_eq!(constrained_lines(LONG_NOTE, 46), vec![
|
||||
"chatting with bob about upcoming task,",
|
||||
"district sharing of images, how the user",
|
||||
"settings currently works etc. Discussing the",
|
||||
"fingerprinting / cache busting issue with",
|
||||
"CKEDITOR, suggesting perhaps looking into",
|
||||
"forking the rubygem and seeing if we can work",
|
||||
"in our own changes, however hard that might",
|
||||
"be.",
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_output() {
|
||||
let formatter = Formatter::Text;
|
||||
|
@ -219,7 +256,7 @@ mod tests {
|
|||
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
|
||||
let offset = Utc;
|
||||
|
||||
formatter.print_formatted(entries, &mut output, now, offset, false).unwrap();
|
||||
formatter.print_formatted(entries, &mut output, now, offset, false, 100).unwrap();
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default
|
||||
Day Start End Duration Notes
|
||||
|
@ -245,7 +282,7 @@ mod tests {
|
|||
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
|
||||
let offset = Utc;
|
||||
|
||||
formatter.print_formatted(entries, &mut output, now, offset, false).unwrap();
|
||||
formatter.print_formatted(entries, &mut output, now, offset, false, 100).unwrap();
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default
|
||||
Day Start End Duration Notes
|
||||
|
@ -268,7 +305,7 @@ mod tests {
|
|||
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
|
||||
let offset = Utc;
|
||||
|
||||
formatter.print_formatted(entries, &mut output, now, offset, false).unwrap();
|
||||
formatter.print_formatted(entries, &mut output, now, offset, false, 100).unwrap();
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default
|
||||
Day Start End Duration Notes
|
||||
|
@ -295,7 +332,7 @@ mod tests {
|
|||
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
|
||||
let offset = Utc;
|
||||
|
||||
formatter.print_formatted(entries, &mut output, now, offset, true).unwrap();
|
||||
formatter.print_formatted(entries, &mut output, now, offset, true, 100).unwrap();
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default
|
||||
ID Day Start End Duration Notes
|
||||
|
@ -307,6 +344,105 @@ mod tests {
|
|||
4:00:00
|
||||
---------------------------------------------------------
|
||||
Total 8:00:00
|
||||
"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_output_long_note_default_with() {
|
||||
let formatter = Formatter::Text;
|
||||
let mut output = Vec::new();
|
||||
let entries = vec![
|
||||
Entry {
|
||||
id: 1,
|
||||
sheet: "default".into(),
|
||||
start: Utc.ymd(2008, 10, 5).and_hms(16, 0, 0),
|
||||
end: Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0)),
|
||||
note: LONG_NOTE.into(),
|
||||
},
|
||||
];
|
||||
|
||||
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
|
||||
let offset = Utc;
|
||||
|
||||
formatter.print_formatted(entries, &mut output, now, offset, false, 100).unwrap();
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default
|
||||
Day Start End Duration Notes
|
||||
Sun Oct 05, 2008 16:00:00 - 18:00:00 2:00:00 chatting with bob about upcoming task,
|
||||
district sharing of images, how the user
|
||||
settings currently works etc. Discussing the
|
||||
fingerprinting / cache busting issue with
|
||||
CKEDITOR, suggesting perhaps looking into
|
||||
forking the rubygem and seeing if we can work
|
||||
in our own changes, however hard that might
|
||||
be.
|
||||
2:00:00
|
||||
-----------------------------------------------------------------------------------------------
|
||||
Total 2:00:00
|
||||
"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_output_long_note_with_ids() {
|
||||
let formatter = Formatter::Text;
|
||||
let mut output = Vec::new();
|
||||
let entries = vec![
|
||||
Entry {
|
||||
id: 60000,
|
||||
sheet: "default".into(),
|
||||
start: Utc.ymd(2008, 10, 5).and_hms(16, 0, 0),
|
||||
end: Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0)),
|
||||
note: LONG_NOTE.into(),
|
||||
},
|
||||
];
|
||||
|
||||
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
|
||||
let offset = Utc;
|
||||
|
||||
formatter.print_formatted(entries, &mut output, now, offset, true, 100).unwrap();
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default
|
||||
ID Day Start End Duration Notes
|
||||
60000 Sun Oct 05, 2008 16:00:00 - 18:00:00 2:00:00 chatting with bob about upcoming task,
|
||||
district sharing of images, how the user
|
||||
settings currently works etc. Discussing the
|
||||
fingerprinting / cache busting issue with
|
||||
CKEDITOR, suggesting perhaps looking into
|
||||
forking the rubygem and seeing if we can
|
||||
work in our own changes, however hard that
|
||||
might be.
|
||||
2:00:00
|
||||
----------------------------------------------------------------------------------------------
|
||||
Total 2:00:00
|
||||
"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_output_note_with_line_breaks() {
|
||||
let formatter = Formatter::Text;
|
||||
let mut output = Vec::new();
|
||||
let entries = vec![
|
||||
Entry {
|
||||
id: 1,
|
||||
sheet: "default".into(),
|
||||
start: Utc.ymd(2008, 10, 5).and_hms(16, 0, 0),
|
||||
end: Some(Utc.ymd(2008, 10, 5).and_hms(18, 0, 0)),
|
||||
note: "first line\nand a second line".into(),
|
||||
},
|
||||
];
|
||||
|
||||
let now = Utc.ymd(2008, 10, 5).and_hms(20, 0, 0);
|
||||
let offset = Utc;
|
||||
|
||||
formatter.print_formatted(entries, &mut output, now, offset, false, 100).unwrap();
|
||||
|
||||
assert_eq!(PrettyString(&String::from_utf8_lossy(&output)), PrettyString("Timesheet: default
|
||||
Day Start End Duration Notes
|
||||
Sun Oct 05, 2008 16:00:00 - 18:00:00 2:00:00 first line
|
||||
and a second line
|
||||
2:00:00
|
||||
-------------------------------------------------------------------
|
||||
Total 2:00:00
|
||||
"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue