use super::{
TextRange,
sort::{Graph, SortError, sort_graph},
};
use std::{collections::HashMap, vec};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Spanned<T> {
pub value: T,
pub range: TextRange,
}
impl<T> AsRef<T> for Spanned<T> {
fn as_ref(&self) -> &T {
&self.value
}
}
impl<T> AsMut<T> for Spanned<T> {
fn as_mut(&mut self) -> &mut T {
&mut self.value
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Blueprint {
pub range: Option<TextRange>,
pub version: Option<Spanned<String>>,
pub name: Option<Spanned<String>>,
pub description: Option<Spanned<String>>,
pub author: Option<Spanned<String>>,
pub min_achitek_version: Option<Spanned<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AchitekFile {
blueprint: Blueprint,
prompts: Vec<Spanned<Prompt>>,
}
impl AchitekFile {
pub fn new(blueprint: Blueprint, prompts: Vec<Spanned<Prompt>>) -> Self {
Self { blueprint, prompts }
}
pub fn blueprint(&self) -> &Blueprint {
&self.blueprint
}
pub fn prompts(&self) -> &[Spanned<Prompt>] {
&self.prompts
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Prompt {
pub name: String,
pub prompt_type: Option<PromptType>,
pub help: Option<String>,
pub choices: Vec<Value>,
pub choices_declared: bool,
pub default: Option<Value>,
pub required: Option<bool>,
pub depends_on: Option<Dependency>,
pub validation: Validation,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PromptType {
String,
Paragraph,
Bool,
Select,
MultiSelect,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Value {
String(String),
Bool(bool),
Integer(u64),
Identifier(String),
Array(Vec<Value>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Dependency {
Reference(String),
Comparison {
left: String,
operator: ComparisonOperator,
right: Value,
},
Contains {
receiver: String,
argument: Value,
},
All(Vec<Dependency>),
Any(Vec<Dependency>),
}
impl Dependency {
fn references(&self) -> Vec<&str> {
let mut references = Vec::new();
self.collect_references(&mut references);
references
}
fn collect_references<'a>(&'a self, references: &mut Vec<&'a str>) {
match self {
Self::Reference(name) => references.push(name),
Self::Comparison { left, .. } => references.push(left),
Self::Contains { receiver, .. } => references.push(receiver),
Self::All(dependencies) | Self::Any(dependencies) => {
for dependency in dependencies {
dependency.collect_references(references);
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ComparisonOperator {
Equal,
NotEqual,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Validation {
pub regex: Option<String>,
pub min_length: Option<u64>,
pub max_length: Option<u64>,
pub min_selections: Option<u64>,
pub max_selections: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ValidAchitekFile {
blueprint: ValidBlueprint,
prompts: Vec<ValidPrompt>,
}
impl ValidAchitekFile {
pub fn new(blueprint: ValidBlueprint, prompts: Vec<ValidPrompt>) -> Self {
Self { blueprint, prompts }
}
pub fn blueprint(&self) -> &ValidBlueprint {
&self.blueprint
}
pub fn prompts(&self) -> &[ValidPrompt] {
&self.prompts
}
pub fn prompts_in(&self, order: PromptOrder) -> Result<PromptIter<'_>, SortError<String>> {
match order {
PromptOrder::Source => Ok(PromptIter::Source(self.prompts.iter())),
PromptOrder::Dependency => self.prompts_in_dependency_order(),
}
}
fn prompts_in_dependency_order(&self) -> Result<PromptIter<'_>, SortError<String>> {
let prompt_names = self
.prompts
.iter()
.map(|prompt| prompt.name.clone())
.collect::<Vec<_>>();
let edges = self
.prompts
.iter()
.flat_map(|prompt| {
prompt
.depends_on
.as_ref()
.into_iter()
.flat_map(|dependency| dependency.references())
.map(|reference| (reference.to_owned(), prompt.name.clone()))
})
.collect::<Vec<_>>();
let graph = Graph {
nodes: prompt_names,
edges,
};
let sorted_names = sort_graph(&graph)?;
let prompts_by_name = self
.prompts
.iter()
.map(|prompt| (prompt.name.as_str(), prompt))
.collect::<HashMap<_, _>>();
let prompts = sorted_names
.iter()
.filter_map(|name| prompts_by_name.get(name.as_str()).copied())
.collect::<Vec<_>>();
Ok(PromptIter::Dependency(prompts.into_iter()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PromptOrder {
Source,
Dependency,
}
#[derive(Debug, Clone)]
pub enum PromptIter<'a> {
Source(std::slice::Iter<'a, ValidPrompt>),
Dependency(vec::IntoIter<&'a ValidPrompt>),
}
impl<'a> Iterator for PromptIter<'a> {
type Item = &'a ValidPrompt;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Source(prompts) => prompts.next(),
Self::Dependency(prompts) => prompts.next(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ValidBlueprint {
pub version: String,
pub name: String,
pub description: Option<String>,
pub author: Option<String>,
pub min_achitek_version: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ValidPrompt {
pub name: String,
pub prompt_type: PromptType,
pub help: Option<String>,
pub choices: Vec<Value>,
pub default: Option<Value>,
pub required: bool,
pub depends_on: Option<Dependency>,
pub validation: Validation,
}