use std::fmt::{Display, Formatter, Result as FmtResult};
use crate::Entry;
#[derive(Clone, Debug)]
pub struct Section {
entries: Vec<Entry>,
title: String,
trailing_content: Vec<String>,
}
impl Section {
pub fn new(title: impl Into<String>) -> Self {
Self {
entries: Vec::new(),
title: title.into(),
trailing_content: Vec::new(),
}
}
pub fn add_entry(&mut self, entry: Entry) {
self.entries.push(entry);
}
pub fn entries(&self) -> &[Entry] {
&self.entries
}
pub fn entries_mut(&mut self) -> &mut Vec<Entry> {
&mut self.entries
}
pub fn into_entries(self) -> Vec<Entry> {
self.entries
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn remove_entry(&mut self, id: &str) -> usize {
let before = self.entries.len();
self.entries.retain(|e| e.id() != id);
before - self.entries.len()
}
pub fn title(&self) -> &str {
&self.title
}
pub fn trailing_content(&self) -> &[String] {
&self.trailing_content
}
pub fn trailing_content_mut(&mut self) -> &mut Vec<String> {
&mut self.trailing_content
}
}
impl Display for Section {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}:", self.title)?;
for entry in &self.entries {
write!(f, "\n\t- {} | {}", entry.date().format("%Y-%m-%d %H:%M"), entry)?;
if !entry.note().is_empty() {
write!(f, "\n{}", entry.note())?;
}
}
for line in &self.trailing_content {
write!(f, "\n{line}")?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
mod display {
use chrono::Local;
use pretty_assertions::assert_eq;
use super::*;
use crate::{Note, Tags};
#[test]
fn it_formats_empty_section() {
let section = Section::new("Currently");
assert_eq!(format!("{section}"), "Currently:");
}
#[test]
fn it_formats_section_with_entries() {
let date = Local::now();
let formatted_date = date.format("%Y-%m-%d %H:%M");
let entry = Entry::new(
date,
"Working on feature",
Tags::new(),
Note::new(),
"Currently",
None::<String>,
);
let mut section = Section::new("Currently");
section.add_entry(entry.clone());
let output = format!("{section}");
assert!(output.starts_with("Currently:"));
assert!(output.contains(&format!("\t- {formatted_date} | Working on feature")));
}
#[test]
fn it_formats_section_with_notes() {
let date = Local::now();
let entry = Entry::new(
date,
"Working on feature",
Tags::new(),
Note::from_text("A note line"),
"Currently",
None::<String>,
);
let mut section = Section::new("Currently");
section.add_entry(entry);
let output = format!("{section}");
assert!(output.contains("\t\tA note line"));
}
}
mod is_empty {
use chrono::Local;
use super::*;
use crate::{Note, Tags};
#[test]
fn it_returns_true_when_empty() {
let section = Section::new("Currently");
assert!(section.is_empty());
}
#[test]
fn it_returns_false_when_not_empty() {
let mut section = Section::new("Currently");
section.add_entry(Entry::new(
Local::now(),
"Test",
Tags::new(),
Note::new(),
"Currently",
None::<String>,
));
assert!(!section.is_empty());
}
}
mod len {
use chrono::Local;
use pretty_assertions::assert_eq;
use super::*;
use crate::{Note, Tags};
#[test]
fn it_returns_entry_count() {
let mut section = Section::new("Currently");
assert_eq!(section.len(), 0);
section.add_entry(Entry::new(
Local::now(),
"First",
Tags::new(),
Note::new(),
"Currently",
None::<String>,
));
section.add_entry(Entry::new(
Local::now(),
"Second",
Tags::new(),
Note::new(),
"Currently",
None::<String>,
));
assert_eq!(section.len(), 2);
}
}
mod remove_entry {
use chrono::Local;
use pretty_assertions::assert_eq;
use super::*;
use crate::{Note, Tags};
#[test]
fn it_removes_matching_entry() {
let entry = Entry::new(
Local::now(),
"Test",
Tags::new(),
Note::new(),
"Currently",
None::<String>,
);
let id = entry.id().to_string();
let mut section = Section::new("Currently");
section.add_entry(entry);
let removed = section.remove_entry(&id);
assert_eq!(removed, 1);
assert_eq!(section.len(), 0);
}
#[test]
fn it_returns_zero_when_no_match() {
let mut section = Section::new("Currently");
section.add_entry(Entry::new(
Local::now(),
"Test",
Tags::new(),
Note::new(),
"Currently",
None::<String>,
));
let removed = section.remove_entry("nonexistent");
assert_eq!(removed, 0);
assert_eq!(section.len(), 1);
}
}
}