panrelease 0.19.0

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

use anyhow::anyhow;
use semver::Version;

use crate::package::PanPackage;
use crate::parser::FormatCodec;
use crate::parser::json::JsonString;
use crate::parser::tomlcodec::TomlString;
use crate::parser::xml::xmlstring::XmlString;
use crate::project::config::GenericFormat;
use crate::system::FileSystem;

fn translate_path(user_path: &str, format: &GenericFormat) -> String {
    match format {
        GenericFormat::Xml => user_path.replace('.', "/"),
        GenericFormat::Json | GenericFormat::Toml => user_path.to_string(),
    }
}

enum Codec {
    Json(JsonString),
    Xml(XmlString),
    Toml(TomlString),
}

pub struct GenericPackage<F> {
    file_path: PathBuf,
    codec: Codec,
    version_path: String,
    filesystem: PhantomData<F>,
}

impl<F: FileSystem> GenericPackage<F> {
    pub fn new(
        module_path: PathBuf,
        file: &str,
        format: &GenericFormat,
        version_field: &str,
    ) -> anyhow::Result<Self> {
        let file_path = module_path.join(file);
        let content = F::read_string(&file_path)?;
        let version_path = translate_path(version_field, format);

        let codec = match format {
            GenericFormat::Json => Codec::Json(JsonString::new(&content)),
            GenericFormat::Xml => Codec::Xml(XmlString::new(&content)),
            GenericFormat::Toml => Codec::Toml(TomlString::new(&content)?),
        };

        Ok(Self {
            file_path,
            codec,
            version_path,
            filesystem: PhantomData,
        })
    }
}

impl<F: FileSystem> PanPackage for GenericPackage<F> {
    fn extract_version(&self) -> anyhow::Result<Version> {
        let version_str = match &self.codec {
            Codec::Json(c) => c.extract(&self.version_path)?,
            Codec::Xml(c) => c.extract(&self.version_path)?,
            Codec::Toml(c) => c.extract(&self.version_path)?,
        };
        let version_str = version_str.ok_or_else(|| {
            anyhow!(
                "Could not find version at path '{}' in {:?}",
                self.version_path,
                self.file_path
            )
        })?;
        Ok(Version::parse(version_str)?)
    }

    fn set_version(&mut self, version: &Version) -> anyhow::Result<()> {
        match &mut self.codec {
            Codec::Json(c) => c.replace(&self.version_path, &version.to_string())?,
            Codec::Xml(c) => c.replace(&self.version_path, &version.to_string())?,
            Codec::Toml(c) => c.replace(&self.version_path, &version.to_string())?,
        }
        Ok(())
    }

    fn persist(&self) -> anyhow::Result<()> {
        let content = match &self.codec {
            Codec::Json(c) => c.to_string(),
            Codec::Xml(c) => c.to_string(),
            Codec::Toml(c) => c.to_string(),
        };
        F::write_string(&self.file_path, &content)?;
        Ok(())
    }

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