use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use crate::types::Intent;
pub const FILENAME: &str = "skillpack.toml";
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct Config {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skill: Option<SkillConfig>,
#[serde(default)]
pub defaults: Defaults,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct SkillConfig {
pub name: String,
pub one_line_description: String,
pub when_to_use_phrases: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub invocation_command: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub import_pattern: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Defaults {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
}
impl Default for Defaults {
fn default() -> Self {
Self {
author: None,
license: Some("MIT".to_string()),
}
}
}
impl Config {
pub fn load(root: &Path) -> Result<Option<Config>> {
let path = Self::path(root);
if !path.exists() {
return Ok(None);
}
let raw = fs::read_to_string(&path)
.with_context(|| format!("failed to read {}", path.display()))?;
let cfg: Config =
toml::from_str(&raw).with_context(|| format!("failed to parse {}", path.display()))?;
Ok(Some(cfg))
}
pub fn path(root: &Path) -> PathBuf {
root.join(FILENAME)
}
pub fn save(&self, root: &Path) -> Result<PathBuf> {
let path = Self::path(root);
let serialized =
toml::to_string_pretty(self).context("failed to serialize skillpack.toml")?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("failed to create {}", parent.display()))?;
}
fs::write(&path, serialized)
.with_context(|| format!("failed to write {}", path.display()))?;
Ok(path)
}
pub fn to_intent(&self) -> Option<Intent> {
let s = self.skill.as_ref()?;
Some(Intent {
one_line_description: s.one_line_description.clone(),
when_to_use_phrases: s.when_to_use_phrases.clone(),
invocation_command: s.invocation_command.clone(),
import_pattern: s.import_pattern.clone(),
author: s.author.clone().or_else(|| self.defaults.author.clone()),
license: s.license.clone().or_else(|| self.defaults.license.clone()),
})
}
pub fn from_intent(name: &str, intent: &Intent) -> Self {
let skill = SkillConfig {
name: name.to_string(),
one_line_description: intent.one_line_description.clone(),
when_to_use_phrases: intent.when_to_use_phrases.clone(),
invocation_command: intent.invocation_command.clone(),
import_pattern: intent.import_pattern.clone(),
author: intent.author.clone(),
license: intent.license.clone(),
};
Self {
skill: Some(skill),
defaults: Defaults {
author: intent.author.clone(),
license: intent.license.clone().or(Some("MIT".to_string())),
},
}
}
}