aeruginous 3.0.1

The Aeruginous Open Source Development Toolbox.
Documentation
/*********************** GNU General Public License 3.0 ***********************\
|                                                                              |
|  Copyright (C) 2023 Kevin Matthes                                            |
|                                                                              |
|  This program is free software: you can redistribute it and/or modify        |
|  it under the terms of the GNU General Public License as published by        |
|  the Free Software Foundation, either version 3 of the License, or           |
|  (at your option) any later version.                                         |
|                                                                              |
|  This program is distributed in the hope that it will be useful,             |
|  but WITHOUT ANY WARRANTY; without even the implied warranty of              |
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               |
|  GNU General Public License for more details.                                |
|                                                                              |
|  You should have received a copy of the GNU General Public License           |
|  along with this program.  If not, see <https://www.gnu.org/licenses/>.      |
|                                                                              |
\******************************************************************************/

use crate::{AppendAsLine, RonlogReferences};
use std::collections::HashMap;
use sysexits::{ExitCode, Result};

/// The supported export formats.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExportFormat {
  /// Markdown.
  Md,

  /// Rusty Object Notation.
  Ron,

  /// reStructured Text.
  Rst,
}

crate::enum_trait!(ExportFormat {
  Md <-> "md",
  Ron <-> "ron",
  Rst <-> "rst"
});

/// The fragment data structure for exporting the harvested changes.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Fragment {
  /// The hyperlinks to references for further reading.
  references: RonlogReferences,

  /// The harvested changes.
  changes: HashMap<String, Vec<String>>,
}

impl Fragment {
  crate::getters!(@fn @ref
    references: RonlogReferences,
    changes: HashMap<String, Vec<String>>
  );

  /// Add another instance's contents to this one's.
  pub fn merge(&mut self, other: Self) {
    for (link, target) in other.references {
      self
        .references
        .entry(link)
        .and_modify(|t| *t = target.clone())
        .or_insert(target);
    }

    for (category, changes) in other.changes {
      self.changes.entry(category.clone()).or_default();

      let mut change_list = self.changes[&category].clone();
      change_list.append(&mut changes.clone());
      self.changes.insert(category, change_list);
    }
  }

  /// Move all known references out of this instance.
  #[must_use]
  pub fn move_references(&mut self) -> RonlogReferences {
    let result = self.references.clone();
    self.references.clear();
    result
  }

  /// Create a new instance.
  #[must_use]
  pub fn new(
    references: &RonlogReferences,
    changes: &HashMap<String, Vec<String>>,
  ) -> Self {
    Self {
      references: references.clone(),
      changes: changes.clone(),
    }
  }
}

impl crate::ToMd for Fragment {
  fn to_md(&self, header_level: u8) -> Result<String> {
    if (1..=3).contains(&header_level) {
      let mut result = String::new();

      for (link_name, target) in &self.references {
        result.append_as_line(format!("[{link_name}]:  {target}"));
      }

      if !self.references.is_empty() {
        result.push('\n');
      }

      for (category, changes) in &self.changes {
        result.append_as_line(format!(
          "{} {category}\n",
          "#".repeat(header_level.into())
        ));

        for change in changes {
          result.append_as_line(format!("- {change}\n"));
        }
      }

      Ok(result)
    } else {
      Err(ExitCode::DataErr)
    }
  }
}

impl crate::ToRst for Fragment {
  fn to_rst(&self, header_level: u8) -> Result<String> {
    if (1..=3).contains(&header_level) {
      let mut result = String::new();

      for (link_name, target) in &self.references {
        result.append_as_line(format!(".. _{link_name}:  {target}"));
      }

      if !self.references.is_empty() {
        result.push('\n');
      }

      for (category, changes) in &self.changes {
        result.append_as_line(format!(
          "{category}\n{}\n",
          match header_level {
            1 => "=",
            2 => "-",
            3 => ".",
            _ => unreachable!(),
          }
          .repeat(category.len())
        ));

        for change in changes {
          result.append_as_line(format!("- {change}\n"));
        }
      }

      Ok(result)
    } else {
      Err(ExitCode::DataErr)
    }
  }
}

impl Default for Fragment {
  fn default() -> Self {
    Self::new(&RonlogReferences::new(), &HashMap::new())
  }
}

/******************************************************************************/