term_keep 1.2.1

Terminal-based Google Keep clone. Can be used as a note taking / To-Do list app on a server.
Documentation
use once_cell::sync::Lazy;

use colored::ColoredString;
use colored::Colorize;
use fancy_regex::Regex;

static CHECKBOX_REGEX: Lazy<Regex> =
  Lazy::new(|| Regex::new(r"(^\s*-\s+\[[xX\s]?\]\s+)(.*[^\s].*)").unwrap());
static CHECKED: Lazy<ColoredString> = Lazy::new(|| "[ ✔ ]".dimmed().bold());
static UNCHECKED: Lazy<ColoredString> = Lazy::new(|| "[   ]".dimmed().bold());

fn item_checked(item: &str) -> bool {
  item.contains('x') || item.contains('X')
}

pub fn checklist_completion(s: &str) -> (i32, i32) {
  let mut total = 0;
  let mut complete = 0;

  for line in s.lines() {
    let captures = CHECKBOX_REGEX
      .captures(line)
      .expect("Should attempt to parse");

    if let Some(c) = captures {
      total += 1;
      if item_checked(&c[1]) {
        complete += 1;
      }
    }
  }

  (complete, total)
}

fn format_line(line: &str) -> String {
  let captures = CHECKBOX_REGEX
    .captures(line)
    .expect("Should attempt to parse");

  captures.map_or_else(
    || line.to_owned(),
    |c| {
      if item_checked(&c[1]) {
        format!("{} {}", *CHECKED, &c[2].dimmed())
      } else {
        format!("{} {}", *UNCHECKED, &c[2])
      }
    },
  )
}

pub fn format_checklist(s: &str) -> String {
  s.lines()
    .map(format_line)
    .collect::<Vec<String>>()
    .join("\n")
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_format_checklist() {
    let y = CHECKED.to_string();
    let n = UNCHECKED.to_string();

    assert_eq!(format_checklist(" "), " ");
    assert_eq!(format_checklist("- [] "), "- [] ");
    assert_eq!(
      format_checklist("- [] \n - [] a"),
      format!("- [] \n{} a", n)
    );
    assert_eq!(format_checklist(" normal text "), " normal text ");
    assert_eq!(
      format_checklist("not task\n - [x] hello"),
      format!("not task\n{} {}", y, "hello".dimmed())
    );
    assert_eq!(
      format_checklist(" - [x] hello"),
      format!("{} {}", y, "hello".dimmed())
    );
    assert_eq!(
      format_checklist(" - [x] hello world\n  - [] bye world"),
      format!("{} {}\n{} bye world", y, "hello world".dimmed(), n)
    );
    assert_eq!(
      format_checklist(" - [ ] hello world and more words\n  - [X] bye world test"),
      format!(
        "{} hello world and more words\n{} {}",
        n,
        y,
        "bye world test".dimmed()
      )
    );
  }

  #[test]
  fn test_checklist_completion() {
    assert_eq!(checklist_completion("- [x] aaa"), (1, 1));
    assert_eq!(checklist_completion("- [] aaa"), (0, 1));
    assert_eq!(
      checklist_completion(
        "
      - [] task 1
      -   [] task 2
      -  [x]   task 3
    "
      ),
      (1, 3)
    );
    assert_eq!(
      checklist_completion(
        "
      - [] task 1
      -   [] task 2
      -  [x]   task 3
      -[] incorrect task
    "
      ),
      (1, 3)
    );
  }

  #[test]
  fn test_checklist_completion_empty() {
    let empty_checkbox = (0, 1);
    assert_eq!(checklist_completion("- [] x"), empty_checkbox);
    assert_eq!(checklist_completion("   -  [] x"), empty_checkbox);
    assert_eq!(checklist_completion("-  []   x"), empty_checkbox);
    assert_eq!(checklist_completion(" - []  x   "), empty_checkbox);
    assert_eq!(checklist_completion("- [] x"), empty_checkbox);
    assert_eq!(checklist_completion("   -  [ ] x"), empty_checkbox);
    assert_eq!(checklist_completion("-  [ ]   x"), empty_checkbox);
    assert_eq!(checklist_completion(" - [ ]  x   "), empty_checkbox);
  }

  #[test]
  fn test_checklist_completion_checked() {
    let checked_checkbox = (1, 1);
    assert_eq!(checklist_completion(" - [x]  x   "), checked_checkbox);
    assert_eq!(checklist_completion(" - [X]  x   "), checked_checkbox);
    assert_eq!(checklist_completion(" -   [x]   x   "), checked_checkbox);
    assert_eq!(checklist_completion("- [X]  x"), checked_checkbox);
    assert_eq!(checklist_completion(" - [x]  x   "), checked_checkbox);
    assert_eq!(checklist_completion(" - [X]  x   "), checked_checkbox);
    assert_eq!(checklist_completion(" -   [x]   x   "), checked_checkbox);
    assert_eq!(checklist_completion("- [X]  x"), checked_checkbox);
  }

  #[test]
  fn test_checklist_completion_invalid() {
    let not_checkbox = (0, 0);
    assert_eq!(checklist_completion("- []x"), not_checkbox);
    assert_eq!(checklist_completion("- []"), not_checkbox);
    assert_eq!(checklist_completion("- [] "), not_checkbox);
    assert_eq!(checklist_completion("- []"), not_checkbox);
    assert_eq!(checklist_completion("- [x]  "), not_checkbox);
    assert_eq!(checklist_completion("- [X]  "), not_checkbox);
    assert_eq!(checklist_completion("-[] s"), not_checkbox);
    assert_eq!(checklist_completion("[] s"), not_checkbox);
    assert_eq!(checklist_completion(" - [  ]  x   "), not_checkbox);
  }
}