use std::convert::TryFrom;
use std::fs;
use std::path::Path;
use color_eyre::eyre::{eyre, WrapErr};
use color_eyre::Result;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::Deserialize;
use crate::config::{tracker::Service, KeyOrSearch};
use crate::REGEX_ERROR;
static BZ_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^BZ#(\d+)$").expect(REGEX_ERROR));
static JIRA_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^JIRA:([A-Z0-9-]+)$").expect(REGEX_ERROR));
static BZ_TRAC_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^BZ_TRAC#(\d+)$").expect(REGEX_ERROR));
static BZ_QUERY_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^BZ_QUERY:(.*)$").expect(REGEX_ERROR));
static JIRA_QUERY_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^JIRA_QUERY:(.*)$").expect(REGEX_ERROR));
static PES_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^PES_QUERY:(\d+)\.(\d+)$").expect(REGEX_ERROR));
#[derive(Debug, Deserialize)]
struct CornConfig {
ids: Vec<CornEntry>,
}
#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct CornEntry {
id: String,
overrides: Option<Overrides>,
#[serde(default)]
references: Vec<String>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Overrides {
subsystem: Option<String>,
component: Option<String>,
doc_type: Option<String>,
}
impl Overrides {
fn into_new_format(self) -> String {
let ssts = self.subsystem.map(|sst| format!("subsystems: [{sst}]"));
let components = self
.component
.map(|component| format!("components: [{component}]"));
let doc_type = self
.doc_type
.map(|doc_type| format!("doc_type: {doc_type}"));
let list = [ssts, components, doc_type]
.into_iter()
.flatten()
.collect::<Vec<String>>()
.join(", ");
format!("overrides: {{{list}}}")
}
}
pub fn convert(legacy: &Path, new: &Path) -> Result<()> {
log::info!(
"Reading the legacy configuration file:\n\t{}",
legacy.display()
);
let text = fs::read_to_string(legacy).wrap_err("Cannot read the legacy configuration file.")?;
let new_config = convert_format(&text)?;
log::info!("Saving the new configuration file:\n\t{}", new.display());
fs::write(new, new_config).wrap_err("Cannot write to the new configuration file.")?;
Ok(())
}
fn convert_format(legacy_format: &str) -> Result<String> {
let legacy_config: CornConfig = serde_yaml::from_str(legacy_format)
.wrap_err("Cannot parse the legacy configuration file.")?;
log::debug!("The legacy configuration:\n{legacy_config:#?}");
let new_entries: Vec<String> = legacy_config
.ids
.into_iter()
.map(String::try_from)
.collect::<Result<_>>()
.wrap_err("Cannot parse an entry in the legacy configuration file.")?;
let new_config = new_entries
.into_iter()
.map(|entry| format!("- {entry}"))
.collect::<Vec<_>>()
.join("\n")
+ "\n";
log::debug!("The new configuration:\n{new_config:#?}");
Ok(new_config)
}
impl TryFrom<CornEntry> for String {
type Error = color_eyre::eyre::Error;
fn try_from(item: CornEntry) -> Result<Self> {
let (service, key_or_search) = parse_stamp(&item.id)?;
let prefix = match key_or_search {
KeyOrSearch::Key(key) => format!("[{}, key: {key}", service.short_name()),
KeyOrSearch::Search(search) => {
format!("[{}, search: \"{search}\"", service.short_name())
}
};
let overrides = item.overrides.map(Overrides::into_new_format);
let references: Vec<String> = item
.references
.into_iter()
.map(|reference| {
let legacy_entry = CornEntry {
id: reference,
..Default::default()
};
String::try_from(legacy_entry)
})
.collect::<Result<_>>()?;
let references = if references.is_empty() {
None
} else {
Some(format!("references: [{}]", references.join(", ")))
};
let options = if overrides.is_some() || references.is_some() {
let list = [overrides, references]
.into_iter()
.flatten()
.collect::<Vec<String>>()
.join(", ");
Some(format!("{{ {list} }}"))
} else {
None
};
let new_entry = [Some(prefix), options]
.into_iter()
.flatten()
.collect::<Vec<String>>()
.join(", ");
Ok(new_entry + "]")
}
}
fn parse_stamp(stamp: &str) -> Result<(Service, KeyOrSearch)> {
if let Some(captures) = BZ_REGEX.captures(stamp) {
let service = Service::Bugzilla;
let key = KeyOrSearch::Key(captures[1].to_string());
Ok((service, key))
} else if let Some(captures) = JIRA_REGEX.captures(stamp) {
let service = Service::Jira;
let key = KeyOrSearch::Key(captures[1].to_string());
Ok((service, key))
} else if let Some(captures) = BZ_QUERY_REGEX.captures(stamp) {
let service = Service::Bugzilla;
let search = KeyOrSearch::Search(captures[1].to_string());
Ok((service, search))
} else if let Some(captures) = JIRA_QUERY_REGEX.captures(stamp) {
let service = Service::Jira;
let search = KeyOrSearch::Search(captures[1].to_string());
Ok((service, search))
} else if BZ_TRAC_REGEX.is_match(stamp) {
Err(eyre!("The Bugzilla tracker option is not implemented yet."))
} else if PES_REGEX.is_match(stamp) {
Err(eyre!("The PES option is not implemented yet."))
} else {
Err(eyre!("Failed to parse the ticket ID: `{}`", stamp))
}
}