use crate::{AppendAsLine, PatternWriter, ReadFile};
use std::path::PathBuf;
use sysexits::Result;
#[derive(clap::Parser, Clone)]
#[command(visible_aliases = ["cffref", "cff-ref", "cff-reference"])]
pub struct Cffreference {
#[arg(long = "input", short)]
input_file: Option<PathBuf>,
#[arg(long = "output", short)]
output_file: Option<PathBuf>,
}
impl Cffreference {
pub fn main(&self) -> Result<()> {
self.wrap().main()
}
#[must_use]
pub const fn new(
input_file: Option<PathBuf>,
output_file: Option<PathBuf>,
) -> Self {
Self {
input_file,
output_file,
}
}
fn wrap(&self) -> Logic {
Logic {
cff_data: String::new(),
cff_reference: String::new(),
cli: self.clone(),
preferred_citation_reached: false,
properties: Properties::default(),
references_reached: false,
}
}
}
struct Logic {
cff_data: String,
cff_reference: String,
cli: Cffreference,
preferred_citation_reached: bool,
properties: Properties,
references_reached: bool,
}
impl Logic {
fn extract(&mut self) -> String {
if self.properties.has_preferred_citation() {
for line in self.cff_data.lines() {
if self.preferred_citation_reached && line.starts_with(' ') {
self.cff_reference
.push_str(&(" ".to_string() + line + "\n"));
} else if self.preferred_citation_reached {
self.preferred_citation_reached = false;
}
if line.starts_with("preferred-citation:") {
self.preferred_citation_reached = true;
}
}
let mut lines = self.cff_reference.lines();
lines
.next()
.map_or_else(String::new, |l| format!(" - {}\n", l.trim()))
+ &lines.map(|l| format!("{l}\n")).collect::<String>()
} else {
let mut lines = self.cff_data.lines();
(if self.properties.has_type() {
lines
.next()
.map_or_else(String::new, |l| format!(" - {}\n", l.trim()))
} else {
" - type: software\n".to_string()
}) + &lines.map(|l| format!(" {l}\n")).collect::<String>()
}
}
fn main(&mut self) -> Result<()> {
self.read()?;
self.cli
.output_file
.clone()
.append(Box::new(self.extract()))
}
fn read(&mut self) -> Result<()> {
for line in self.cli.input_file.read()?.lines() {
if self.references_reached
&& !matches!(line.chars().next(), Some(' ' | '-'))
{
self.references_reached = false;
}
if !line.is_empty()
&& !line.starts_with('#')
&& !line.starts_with("---")
&& !line.starts_with("...")
&& !line.starts_with("cff-version:")
&& !line.starts_with("message:")
&& !line.starts_with("references:")
&& !self.references_reached
{
if line.starts_with("preferred-citation:") {
self.properties.find_preferred_citation();
} else if line.starts_with("type:") {
self.properties.find_type();
}
self.cff_data.append_as_line(line);
} else if line.starts_with("references:") {
self.references_reached = true;
}
}
Ok(())
}
}
#[derive(Default)]
enum Properties {
BothPreferredCitationAndType,
#[default]
NeitherPreferredCitationNorType,
PreferredCitation,
Type,
}
impl Properties {
fn find_preferred_citation(&mut self) {
if matches!(self, Self::NeitherPreferredCitationNorType) {
*self = Self::PreferredCitation;
} else if matches!(self, Self::Type) {
*self = Self::BothPreferredCitationAndType;
}
}
fn find_type(&mut self) {
if matches!(self, Self::NeitherPreferredCitationNorType) {
*self = Self::Type;
} else if matches!(self, Self::PreferredCitation) {
*self = Self::BothPreferredCitationAndType;
}
}
const fn has_preferred_citation(&self) -> bool {
matches!(
self,
Self::PreferredCitation | Self::BothPreferredCitationAndType
)
}
const fn has_type(&self) -> bool {
matches!(self, Self::Type | Self::BothPreferredCitationAndType)
}
}