doing-plugins 0.3.1

Export and import plugins for the doing CLI
Documentation
use doing_config::Config;
use doing_taskpaper::Entry;
use doing_template::renderer::RenderOptions;

use crate::{ExportPlugin, Plugin, PluginSettings};

/// Export plugin that renders entries in TaskPaper format.
///
/// Sections are written as top-level headers, entries as `- title @tag @date(value)` lines,
/// and notes are indented beneath their entry.
pub struct TaskPaperExport;

impl ExportPlugin for TaskPaperExport {
  fn render(&self, entries: &[Entry], options: &RenderOptions, _config: &Config) -> String {
    let mut out = String::new();

    for entry in entries {
      out.push_str("- ");
      out.push_str(entry.title());

      if !entry.tags().is_empty() {
        out.push(' ');
        out.push_str(&entry.tags().to_string());
      }

      out.push_str(" @date(");
      out.push_str(&entry.date().format(&options.date_format).to_string());
      out.push(')');

      if !entry.note().is_empty() {
        for line in entry.note().lines() {
          out.push_str("\n\t");
          out.push_str(line);
        }
      }

      out.push('\n');
    }

    out
  }
}

impl Plugin for TaskPaperExport {
  fn name(&self) -> &str {
    "taskpaper"
  }

  fn settings(&self) -> PluginSettings {
    PluginSettings {
      trigger: "task(?:paper)?|tp".into(),
    }
  }
}

#[cfg(test)]
mod test {
  use doing_taskpaper::{Note, Tag, Tags};

  use super::*;
  use crate::test_helpers::{sample_date, sample_options};

  mod taskpaper_export_name {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_taskpaper() {
      assert_eq!(TaskPaperExport.name(), "taskpaper");
    }
  }

  mod taskpaper_export_render {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_renders_empty_entries() {
      let config = Config::default();
      let options = sample_options();

      let output = TaskPaperExport.render(&[], &options, &config);

      assert_eq!(output, "");
    }

    #[test]
    fn it_renders_flat_entry_with_tags_and_date() {
      let config = Config::default();
      let options = sample_options();
      let entry = Entry::new(
        sample_date(17, 14, 30),
        "Working on project",
        Tags::from_iter(vec![Tag::new("coding", None::<String>)]),
        Note::new(),
        "Currently",
        None::<String>,
      );

      let output = TaskPaperExport.render(&[entry], &options, &config);

      assert_eq!(output, "- Working on project @coding @date(2024-03-17 14:30)\n");
    }

    #[test]
    fn it_renders_entry_with_note() {
      let config = Config::default();
      let options = sample_options();
      let entry = Entry::new(
        sample_date(17, 14, 30),
        "Task",
        Tags::new(),
        Note::from_text("Note line 1\nNote line 2"),
        "Currently",
        None::<String>,
      );

      let output = TaskPaperExport.render(&[entry], &options, &config);

      assert!(output.contains("\tNote line 1\n\tNote line 2"));
    }

    #[test]
    fn it_renders_flat_list_without_section_headers() {
      let config = Config::default();
      let options = sample_options();
      let entries = vec![
        Entry::new(
          sample_date(17, 14, 0),
          "A",
          Tags::new(),
          Note::new(),
          "Currently",
          None::<String>,
        ),
        Entry::new(
          sample_date(17, 15, 0),
          "B",
          Tags::new(),
          Note::new(),
          "Archive",
          None::<String>,
        ),
      ];

      let output = TaskPaperExport.render(&entries, &options, &config);

      assert!(!output.contains("Currently:"));
      assert!(!output.contains("Archive:"));
      assert!(output.starts_with("- A"));
    }
  }

  mod taskpaper_export_settings {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_taskpaper_trigger() {
      let settings = TaskPaperExport.settings();

      assert_eq!(settings.trigger, "task(?:paper)?|tp");
    }
  }
}