use super::{Draft, FileIo};
use crate::xdg::Env;
use std::path::PathBuf;
pub const MODELS_YAML: &str = "models.yaml";
#[derive(Debug, Clone)]
pub struct LitanyGlobal {
root: PathBuf,
}
impl LitanyGlobal {
pub fn resolve(env: &Env) -> Self {
Self {
root: env.litany_config_root(),
}
}
pub fn models(&self) -> PathBuf {
self.root.join(MODELS_YAML)
}
pub fn workflows_dir(&self) -> PathBuf {
self.root.join("workflows")
}
pub fn workflows(&self, io: &dyn FileIo) -> std::io::Result<Vec<PathBuf>> {
let mut files: Vec<PathBuf> = io
.list_dir(&self.workflows_dir())?
.into_iter()
.filter(|p| p.extension().and_then(|e| e.to_str()) == Some("yaml"))
.collect();
files.sort();
Ok(files)
}
pub fn new_workflow(&self, name: &str) -> Result<PathBuf, WorkflowNameError> {
validate_workflow_name(name)?;
Ok(self.workflows_dir().join(format!("{name}.yaml")))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum WorkflowNameError {
#[error("name required")]
Empty,
#[error("name must be a single file, no path")]
Separator,
#[error("name must not start with a dot")]
DotLeading,
}
fn validate_workflow_name(name: &str) -> Result<(), WorkflowNameError> {
if name.is_empty() {
return Err(WorkflowNameError::Empty);
}
if name.starts_with('.') {
return Err(WorkflowNameError::DotLeading);
}
if name.contains('/') || name.contains('\\') {
return Err(WorkflowNameError::Separator);
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct Editor {
draft: Draft,
}
impl Editor {
pub fn load(path: PathBuf, io: &dyn FileIo) -> std::io::Result<Self> {
Ok(Self {
draft: Draft::load(path, io)?,
})
}
pub fn seeded(path: PathBuf, seed: &[u8]) -> Self {
Self {
draft: Draft::seeded(path, seed),
}
}
#[cfg(test)]
pub(crate) fn path(&self) -> &std::path::Path {
self.draft.path()
}
#[cfg(test)]
pub(crate) fn draft(&self) -> &str {
self.draft.text()
}
pub fn set_draft(&mut self, text: String) {
self.draft.set(text);
}
pub fn is_new(&self) -> bool {
self.draft.is_new()
}
pub fn reload(&mut self, io: &dyn FileIo) -> std::io::Result<()> {
self.draft.reload(io)
}
pub fn refresh(&mut self, io: &dyn FileIo) -> std::io::Result<bool> {
self.draft.refresh(io)
}
pub fn apply(&mut self, io: &dyn FileIo) -> Saved {
match self.apply_inner(io) {
Ok(saved) => saved,
Err(e) => Saved::Io {
error: self.draft.io_fault(&e),
},
}
}
fn apply_inner(&mut self, io: &dyn FileIo) -> std::io::Result<Saved> {
let staged = self.draft.stage(io)?;
if self.draft.commit(staged, io)? {
Ok(Saved::Ok)
} else {
Ok(Saved::Conflict)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Saved {
Ok,
Conflict,
Io { error: String },
}
#[cfg(test)]
mod tests;