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 crate::services::tips;
use crate::util::env::get_bool;
use anyhow::{anyhow, bail, Result};
use colored::Colorize;
use std::fmt::Display;
use std::io::prelude::*;
use std::process::{Command, Stdio};

pub fn show_random_tip() {
  if let Some(t) = tips::random_tip() {
    println!();
    println!("💡 Tip: {t}");
  }
}

pub fn hide_logo() -> bool {
  get_bool("HIDE_LOGO", false)
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub enum TextInputMode {
  Stdin,
  Editor,
}

pub fn get_text_input(initial_text: &str) -> Result<(String, TextInputMode)> {
  Ok(if atty::is(atty::Stream::Stdin) {
    (edit::edit(initial_text)?, TextInputMode::Editor)
  } else {
    let mut buf = String::new();
    std::io::stdin().read_to_string(&mut buf)?;
    (buf, TextInputMode::Stdin)
  })
}

pub fn validate_text_input_mode(mode: TextInputMode, using_template: bool) -> Result<()> {
  if using_template && mode == TextInputMode::Stdin {
    Err(anyhow!(
      "Cannot get text from STDIN and use a template at the same time"
    ))
  } else {
    Ok(())
  }
}

pub fn abort_with_message<S: Display>(msg: S) -> ! {
  eprintln!("{}", color_danger(&format!("Error: {msg}")));
  std::process::exit(1);
}

fn less_aux(text: &str) -> Result<()> {
  let mut child = Command::new("less")
    .args(["-R"])
    .stdin(Stdio::piped())
    .spawn()?;

  match child.stdin.take() {
    None => bail!("cannot open stdin"),
    Some(mut s) => std::thread::spawn({
      let t = text.to_owned();
      move || {
        s.write_all(t.as_bytes()).expect("cannot write to stdin");
      }
    }),
  };

  child.wait()?;
  Ok(())
}

#[must_use]
pub fn color_primary(text: &str) -> String {
  text.blue().to_string()
}

#[must_use]
pub fn color_secondary(text: &str) -> String {
  text.dimmed().to_string()
}

#[must_use]
pub fn color_danger(text: &str) -> String {
  text.red().bold().to_string()
}

pub fn less(text: &str) {
  less_aux(text).unwrap_or_else(|e| abort_with_message(format!("Couldn't use 'less' ({e})")));
}

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

  #[test]
  fn test_validate_text_input_mode() {
    assert!(validate_text_input_mode(TextInputMode::Editor, false).is_ok());
    assert!(validate_text_input_mode(TextInputMode::Editor, true).is_ok());
    assert!(validate_text_input_mode(TextInputMode::Stdin, false).is_ok());
    assert!(validate_text_input_mode(TextInputMode::Stdin, true).is_err());
  }
}