diff --git a/src/cli.rs b/src/cli.rs index 1c91c0c..1bf0aa8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -8,11 +8,12 @@ use structopt::StructOpt; use crate::eval::{DateRange, EntryMode}; use crate::files::{self, Files}; -use self::error::{Error, Result}; +use self::error::Result; mod done; mod error; mod layout; +mod print; mod show; #[derive(Debug, StructOpt)] @@ -31,9 +32,6 @@ pub struct Opt { /// How many days to include after the current date #[structopt(short, long, default_value = "13")] after: u32, - /// Number of the entry to view or edit - // TODO Select multiple entries at once - entry: Option, #[structopt(subcommand)] command: Option, } @@ -41,11 +39,18 @@ pub struct Opt { #[derive(Debug, StructOpt)] pub enum Command { #[allow(rustdoc::broken_intra_doc_links)] - /// Shows entries in a range, or a single entry if one is specified - /// [default] - Show, - /// Marks an entry as done (requires entry) - Done, + /// Shows individual entries in detail + Show { + /// Entries to show + #[structopt(required = true)] + entries: Vec, + }, + /// Marks one or more entries as done + Done { + /// Entries to mark as done + #[structopt(required = true)] + entries: Vec, + }, /// Reformat all loaded files Fmt, } @@ -86,14 +91,13 @@ pub fn run() -> Result<()> { let layout = layout::layout(&files, &entries, range, now); match opt.command { - None | Some(Command::Show) => match opt.entry { - None => show::show_all(&layout), - Some(n) => show::show_entry(&files, &entries[layout.look_up_number(n)?])?, - }, - Some(Command::Done) => match opt.entry { - None => return Err(Error::NoNumber), - Some(n) => done::mark_done(&mut files, &entries, &layout, &[n], now)?, - }, + None => print::print(&layout), + Some(Command::Show { entries: numbers }) => { + show::show(&files, &entries, &layout, &numbers)? + } + Some(Command::Done { entries: numbers }) => { + done::done(&mut files, &entries, &layout, &numbers, now)? + } Some(Command::Fmt) => files.mark_all_dirty(), } diff --git a/src/cli/done.rs b/src/cli/done.rs index 7c32fff..379d6ce 100644 --- a/src/cli/done.rs +++ b/src/cli/done.rs @@ -9,7 +9,7 @@ use crate::files::Files; use super::error::{Error, Result}; use super::layout::line::LineLayout; -pub fn mark_done( +pub fn done( files: &mut Files, entries: &[Entry], layout: &LineLayout, diff --git a/src/cli/print.rs b/src/cli/print.rs new file mode 100644 index 0000000..cd04776 --- /dev/null +++ b/src/cli/print.rs @@ -0,0 +1,122 @@ +use std::cmp; + +use chrono::{Datelike, NaiveDate}; + +use crate::files::primitives::{Time, Weekday}; + +use super::layout::line::{LineEntry, LineLayout, SpanSegment, Times}; + +struct ShowLines { + num_width: usize, + span_width: usize, + result: String, +} + +impl ShowLines { + fn new(num_width: usize, span_width: usize) -> Self { + Self { + num_width, + span_width, + result: String::new(), + } + } + + fn display_line(&mut self, line: &LineEntry) { + match line { + LineEntry::Day { spans, date } => self.display_line_date(spans, *date), + LineEntry::Now { spans, time } => self.display_line_now(spans, *time), + LineEntry::Entry { + number, + spans, + time, + text, + } => self.display_line_entry(*number, spans, *time, text), + } + } + + fn display_line_date(&mut self, spans: &[Option], date: NaiveDate) { + let weekday: Weekday = date.weekday().into(); + let weekday = weekday.full_name(); + self.push(&format!( + "{:=>nw$}={:=nw$}\n", + "", + Self::display_spans(spans, '='), + weekday, + date, + "", + "", + nw = self.num_width, + sw = self.span_width + )); + } + + fn display_line_now(&mut self, spans: &[Option], time: Time) { + self.push(&format!( + "{:, + spans: &[Option], + time: Times, + text: &str, + ) { + let num = match number { + Some(n) => format!("{}", n), + None => "".to_string(), + }; + + let time = match time { + Times::Untimed => "".to_string(), + Times::At(t) => format!("{} ", t), + Times::FromTo(t1, t2) => format!("{}--{} ", t1, t2), + }; + + self.push(&format!( + "{:>nw$} {:sw$} {}{}\n", + num, + Self::display_spans(spans, ' '), + time, + text, + nw = self.num_width, + sw = self.span_width + )) + } + + fn display_spans(spans: &[Option], empty: char) -> String { + let mut result = String::new(); + for segment in spans { + result.push(match segment { + Some(SpanSegment::Start) => '┌', + Some(SpanSegment::Middle) => '│', + Some(SpanSegment::End) => '└', + None => empty, + }); + } + result + } + + fn push(&mut self, line: &str) { + self.result.push_str(line); + } + + fn result(self) -> String { + self.result + } +} + +pub fn print(layout: &LineLayout) { + let num_width = cmp::max(layout.num_width(), 3); // `now` is 3 chars wide + let mut show_lines = ShowLines::new(num_width, layout.span_width()); + for line in layout.lines() { + show_lines.display_line(line); + } + print!("{}", show_lines.result()); +} diff --git a/src/cli/show.rs b/src/cli/show.rs index 9d8262a..1b20413 100644 --- a/src/cli/show.rs +++ b/src/cli/show.rs @@ -1,130 +1,10 @@ -use std::cmp; - -use chrono::{Datelike, NaiveDate}; - use crate::eval::{Entry, EntryKind}; -use crate::files::primitives::{Time, Weekday}; use crate::files::Files; use super::error::Result; -use super::layout::line::{LineEntry, LineLayout, SpanSegment, Times}; +use super::layout::line::LineLayout; -struct ShowLines { - num_width: usize, - span_width: usize, - result: String, -} - -impl ShowLines { - fn new(num_width: usize, span_width: usize) -> Self { - Self { - num_width, - span_width, - result: String::new(), - } - } - - fn display_line(&mut self, line: &LineEntry) { - match line { - LineEntry::Day { spans, date } => self.display_line_date(spans, *date), - LineEntry::Now { spans, time } => self.display_line_now(spans, *time), - LineEntry::Entry { - number, - spans, - time, - text, - } => self.display_line_entry(*number, spans, *time, text), - } - } - - fn display_line_date(&mut self, spans: &[Option], date: NaiveDate) { - let weekday: Weekday = date.weekday().into(); - let weekday = weekday.full_name(); - self.push(&format!( - "{:=>nw$}={:=nw$}\n", - "", - Self::display_spans(spans, '='), - weekday, - date, - "", - "", - nw = self.num_width, - sw = self.span_width - )); - } - - fn display_line_now(&mut self, spans: &[Option], time: Time) { - self.push(&format!( - "{:, - spans: &[Option], - time: Times, - text: &str, - ) { - let num = match number { - Some(n) => format!("{}", n), - None => "".to_string(), - }; - - let time = match time { - Times::Untimed => "".to_string(), - Times::At(t) => format!("{} ", t), - Times::FromTo(t1, t2) => format!("{}--{} ", t1, t2), - }; - - self.push(&format!( - "{:>nw$} {:sw$} {}{}\n", - num, - Self::display_spans(spans, ' '), - time, - text, - nw = self.num_width, - sw = self.span_width - )) - } - - fn display_spans(spans: &[Option], empty: char) -> String { - let mut result = String::new(); - for segment in spans { - result.push(match segment { - Some(SpanSegment::Start) => '┌', - Some(SpanSegment::Middle) => '│', - Some(SpanSegment::End) => '└', - None => empty, - }); - } - result - } - - fn push(&mut self, line: &str) { - self.result.push_str(line); - } - - fn result(self) -> String { - self.result - } -} - -pub fn show_all(layout: &LineLayout) { - let num_width = cmp::max(layout.num_width(), 3); // `now` is 3 chars wide - let mut show_lines = ShowLines::new(num_width, layout.span_width()); - for line in layout.lines() { - show_lines.display_line(line); - } - print!("{}", show_lines.result()); -} - -pub fn show_entry(files: &Files, entry: &Entry) -> Result<()> { +fn show_entry(files: &Files, entry: &Entry) { let command = files.command(entry.source); match entry.kind { @@ -153,6 +33,29 @@ pub fn show_entry(files: &Files, entry: &Entry) -> Result<()> { for line in command.desc() { println!("# {}", line); } +} + +pub fn show( + files: &Files, + entries: &[Entry], + layout: &LineLayout, + numbers: &[usize], +) -> Result<()> { + if numbers.is_empty() { + // Nothing to do + return Ok(()); + } + + let indices = numbers + .iter() + .map(|n| layout.look_up_number(*n)) + .collect::>>()?; + + show_entry(files, &entries[indices[0]]); + for &index in indices.iter().skip(1) { + println!(); + show_entry(files, &entries[index]); + } Ok(()) }