panrelease 0.19.0

Utility to release software
Documentation
use std::marker::PhantomData;
use std::path::PathBuf;

use anyhow::anyhow;
use regex::Regex;
use semver::Version;

use crate::package::PanPackage;
use crate::parser::FormatCodec;
use crate::parser::xml::xmlstring::XmlString;
use crate::system::FileSystem;

pub struct MavenPackage<F> {
    path: PathBuf,
    doc: XmlString,
    filesystem: PhantomData<F>
}

impl <F: FileSystem> MavenPackage<F> {
    pub fn new(path: PathBuf) -> anyhow::Result<Self> {
        let package_str = F::read_string(&path.join("pom.xml"))?;
        Ok(Self {
            path,
            doc: XmlString::new(&package_str),
            filesystem: PhantomData,
        })
    }
}

impl <F: FileSystem> PanPackage for MavenPackage<F> {
    fn extract_version(&self) -> anyhow::Result<Version> {
        let version_str = self.doc.extract("project/version")?
            .ok_or_else(|| anyhow!("Could not find version in pom.xml"))?;

        let placeholder_regex = Regex::new(r#"^\$\{([A-Za-z0-9_.-]+)\}$"#)?;
        let maybe_placeholder = placeholder_regex.captures(version_str)
            .and_then(|cap| cap.get(1).map(|m| m.as_str().to_string()));

        if let Some(placeholder) = maybe_placeholder {
            let version_prop_str = self.doc.extract(&format!("project/properties/{placeholder}"))?
                .ok_or_else(|| anyhow!("Could not find version property in pom.xml"))?;

            Ok(Version::parse(version_prop_str)?)
        } else {
            Ok(Version::parse(version_str)?)
        }
    }

    fn set_version(&mut self, version: &Version) -> anyhow::Result<()> {
        let version_str = self.doc.extract("project/version")?
            .ok_or_else(|| anyhow!("Could not find version in pom.xml"))?;

        let placeholder_regex = Regex::new(r#"^\$\{([A-Za-z0-9_.-]+)\}$"#)?;
        let maybe_placeholder = placeholder_regex.captures(version_str)
            .and_then(|cap| cap.get(1).map(|m| m.as_str().to_string()));

        if let Some(placeholder) = maybe_placeholder {
            self.doc.replace(&format!("project/properties/{placeholder}"), &version.to_string())?;
        } else {
            self.doc.replace("project/version", &version.to_string())?;
        }
        Ok(())
    }

    fn persist(&self) -> anyhow::Result<()> {
        F::write_string(&self.path.join("pom.xml"), &self.doc.to_string())?;
        Ok(())
    }

    fn hook_after_rel(&self) -> anyhow::Result<()> {
        Ok(())
    }
}