use crate::error::{Error, ResultExt};
use itertools::Itertools;
use log::info;
use quick_xml::de::from_str;
use serde::{Deserialize, Serialize};
use std::{
fs::read_to_string,
path::{Path, PathBuf},
};
use tower_lsp::lsp_types::Url;
use walkdir::WalkDir;
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Catalog {
#[serde(default)]
uri: Vec<CatalogUri>,
#[serde(default)]
group: Vec<CatalogGroup>,
#[serde(skip)]
locaton: String,
}
impl Catalog {
#[cfg(test)]
pub fn new(location: &str) -> Catalog {
Catalog {
uri: vec![],
group: vec![],
locaton: location.into(),
}
}
#[cfg(test)]
pub fn with_uri(self, name: &str, uri: &str) -> Catalog {
let mut c = self.clone();
c.uri.push(CatalogUri {
_id: "Constructed in a builder".into(),
name: name.into(),
uri: uri.into(),
});
c
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct CatalogUri {
#[serde(rename = "@id")]
_id: String,
#[serde(rename = "@name")]
pub name: String,
#[serde(rename = "@uri")]
pub uri: String,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct CatalogGroup {
pub uri: Vec<CatalogUri>,
}
impl Catalog {
pub fn load_catalogs_recursive(uri: &Url) -> Vec<Catalog> {
uri.to_file_path()
.map_err(|()| Error::InvalidUrl(uri.clone()))
.map(|path| {
WalkDir::new(path)
.into_iter()
.filter_map(std::result::Result::ok)
.filter(|entry| entry.file_type().is_file())
.filter(|entry| entry.file_name().to_str() == "catalog-v001.xml".into())
.filter_map(|entry| read_to_string(entry.path()).ok().map(|s| (s, entry)))
.filter_map(|(xml, entry)| {
from_str(xml.as_str()).ok().map(|c: Catalog| (c, entry))
})
.map(|(mut catalog, entry)| {
catalog.locaton = entry
.path()
.to_str()
.expect("path should be valid utf-8")
.to_string();
info!("Found catalog {catalog:?}");
catalog
})
.collect_vec()
})
.inspect_log()
.unwrap_or_default()
}
pub fn all_catalog_uris(&self) -> impl Iterator<Item = &CatalogUri> {
self.uri
.iter()
.chain(self.group.iter().flat_map(|g| &g.uri))
}
pub fn contains(&self, path: &PathBuf) -> bool {
for catalog_uri in self.all_catalog_uris() {
let catalog_item_path = self.parent_folder().join(&catalog_uri.uri);
if &catalog_item_path == path {
return true;
}
}
false
}
pub fn parent_folder(&self) -> &Path {
Path::new(self.locaton.as_str())
.parent()
.expect("catalog file should have a parent folder, because it is a file")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_deserialize_should_work() {
let xml = r#"
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<catalog prefer="public" xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-import-edits.owl" uri="oeo-import-edits.owl"/>
<uri id="Imports Wizard Entry" name="http://purl.obolibrary.org/obo/bfo/2.0/bfo.owl" uri="https://raw.githubusercontent.com/BFO-ontology/BFO/v2.0/bfo.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-model.omn" uri="oeo-model.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-physical.omn" uri="oeo-physical.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-sector.omn" uri="oeo-sector.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-social.omn" uri="oeo-social.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-shared.omn" uri="oeo-shared.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/iao-extracted.owl" uri="../imports/iao-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/ro-extracted.owl" uri="../imports/ro-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/uo-extracted.owl" uri="../imports/uo-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/stato-extracted.owl" uri="../imports/stato-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/cco-extracted.owl" uri="../imports/cco-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/meno-extracted.owl" uri="../imports/meno-extracted.owl"/>
<group id="Folder Repository, directory=, recursive=true, Auto-Update=true, version=2" prefer="public" xml:base="">
<uri id="Automatically generated entry, Timestamp=1740265509439" name="http://openenergy-platform.org/ontology/oeo/oeo-import-edits.owl" uri="oeo-import-edits.owl"/>
<uri id="Automatically generated entry, Timestamp=1740265509439" name="http://openenergy-platform.org/ontology/oeo/oeo-physical-axioms/" uri="oeo-physical-axioms.owl"/>
</group>
</catalog>
"#;
let catalog: Catalog = from_str(xml).unwrap();
assert_eq!(catalog.all_catalog_uris().count(), 15);
assert_eq!(
catalog.all_catalog_uris().next().unwrap().uri,
"oeo-import-edits.owl".to_string()
);
assert_eq!(
catalog.all_catalog_uris().last().unwrap().uri,
"oeo-physical-axioms.owl".to_string()
);
}
#[test]
fn catalog_deserialize_without_group_should_work() {
let xml = r#"
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<catalog prefer="public" xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-import-edits.owl" uri="oeo-import-edits.owl"/>
<uri id="Imports Wizard Entry" name="http://purl.obolibrary.org/obo/bfo/2.0/bfo.owl" uri="https://raw.githubusercontent.com/BFO-ontology/BFO/v2.0/bfo.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-model.omn" uri="oeo-model.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-physical.omn" uri="oeo-physical.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-sector.omn" uri="oeo-sector.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-social.omn" uri="oeo-social.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/oeo-shared.omn" uri="oeo-shared.omn"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/iao-extracted.owl" uri="../imports/iao-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/ro-extracted.owl" uri="../imports/ro-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/uo-extracted.owl" uri="../imports/uo-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/stato-extracted.owl" uri="../imports/stato-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/cco-extracted.owl" uri="../imports/cco-extracted.owl"/>
<uri id="Imports Wizard Entry" name="http://openenergy-platform.org/ontology/oeo/dev/imports/meno-extracted.owl" uri="../imports/meno-extracted.owl"/>
</catalog>
"#;
let catalog: Catalog = from_str(xml).unwrap();
assert_eq!(catalog.all_catalog_uris().count(), 13);
}
#[test]
fn catalog_serialize_with_empty_catalog_should_work() {
let value = Catalog::new("testing");
let xml = quick_xml::se::to_string(&value).unwrap();
assert_eq!(xml, "<Catalog/>");
}
}