use crate::template::PackMetadata;
use anyhow::{Context, bail};
use serde_json::Value;
use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
pub struct ExternalPack {
pub metadata: PackMetadata,
pub primitives: BTreeMap<String, String>, pub themes: BTreeMap<String, Value>,
pub presets: BTreeMap<String, PresetFile>,
}
pub struct PresetFile {
pub source: String,
pub schema: Value,
}
pub fn load_external_pack(path: &Path) -> anyhow::Result<ExternalPack> {
let pack_dir = if path.is_dir() {
path.to_path_buf()
} else if path.extension().and_then(|s| s.to_str()) == Some("gz") {
extract_targz(path)?
} else {
bail!("unsupported pack format: {}", path.display())
};
let metadata = load_pack_metadata(&pack_dir)?;
let primitives = load_primitives(&pack_dir)?;
let themes = load_themes(&pack_dir)?;
let presets = load_presets(&pack_dir)?;
Ok(ExternalPack {
metadata,
primitives,
themes,
presets,
})
}
fn load_pack_metadata(pack_dir: &Path) -> anyhow::Result<PackMetadata> {
let json_path = pack_dir.join("pack.json");
let content = fs::read_to_string(&json_path)
.with_context(|| format!("reading {}", json_path.display()))?;
serde_json::from_str(&content).context("parsing pack.json")
}
fn load_primitives(pack_dir: &Path) -> anyhow::Result<BTreeMap<String, String>> {
let dir = pack_dir.join("primitives");
if !dir.exists() {
return Ok(BTreeMap::new());
}
let mut map = BTreeMap::new();
for entry in fs::read_dir(&dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("hbs") {
let name = path
.file_stem()
.and_then(|s| s.to_str())
.context("primitive filename invalid")?
.to_string();
let source =
fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?;
map.insert(name, source);
}
}
Ok(map)
}
fn load_themes(pack_dir: &Path) -> anyhow::Result<BTreeMap<String, Value>> {
let dir = pack_dir.join("themes");
if !dir.exists() {
return Ok(BTreeMap::new());
}
let mut map = BTreeMap::new();
for entry in fs::read_dir(&dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("json") {
let name = path
.file_stem()
.and_then(|s| s.to_str())
.context("theme filename invalid")?
.to_string();
let content =
fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?;
let value: Value = serde_json::from_str(&content)
.with_context(|| format!("parsing {}", path.display()))?;
map.insert(name, value);
}
}
Ok(map)
}
fn load_presets(pack_dir: &Path) -> anyhow::Result<BTreeMap<String, PresetFile>> {
let dir = pack_dir.join("presets");
if !dir.exists() {
return Ok(BTreeMap::new());
}
let mut map = BTreeMap::new();
load_presets_recursive(&dir, &mut map)?;
Ok(map)
}
fn load_presets_recursive(
dir: &Path,
map: &mut BTreeMap<String, PresetFile>,
) -> anyhow::Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
load_presets_recursive(&path, map)?;
continue;
}
if path.extension().and_then(|s| s.to_str()) == Some("hbs") {
let name = path
.file_stem()
.and_then(|s| s.to_str())
.context("preset filename invalid")?
.to_string();
let source =
fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?;
let schema_path = path.with_extension("schema.json");
let schema: Value = if schema_path.exists() {
let content = fs::read_to_string(&schema_path)
.with_context(|| format!("reading {}", schema_path.display()))?;
serde_json::from_str(&content)?
} else {
Value::Null
};
map.insert(name, PresetFile { source, schema });
}
}
Ok(())
}
fn extract_targz(archive: &Path) -> anyhow::Result<PathBuf> {
use flate2::read::GzDecoder;
use tar::Archive;
let tmp = std::env::temp_dir().join(format!("flow-builder-pack-{}", std::process::id()));
fs::create_dir_all(&tmp)?;
let file = fs::File::open(archive)?;
let gz = GzDecoder::new(file);
let mut tar = Archive::new(gz);
tar.unpack(&tmp)?;
Ok(tmp)
}