use crate::cli::json::doc_to_json;
use crate::engine::config::{Config, StoreBackend};
use crate::engine::document::{split_frontmatter, DocMeta, DocType};
use crate::engine::fs_ops;
use crate::engine::gh::GhCli;
use crate::engine::git_ref::GitCli;
use crate::engine::git_ref_store::GitRefStore;
use crate::engine::issue_cache::IssueCache;
use crate::engine::issue_map::IssueMap;
use crate::engine::reservation;
use crate::engine::store::{Filter, Store};
use crate::engine::store_dispatch::{DocumentStore, GithubIssuesStore};
use anyhow::{anyhow, bail, Result};
use std::fs;
use std::path::{Path, PathBuf};
pub fn run(
root: &Path,
config: &Config,
store: &Store,
doc_type: &str,
title: &str,
author: &str,
on_progress: impl Fn(reservation::ReservationProgress),
) -> Result<PathBuf> {
run_with_body(
root,
config,
store,
doc_type,
title,
author,
None,
on_progress,
)
}
#[allow(clippy::too_many_arguments)]
pub fn run_with_body(
root: &Path,
config: &Config,
store: &Store,
doc_type: &str,
title: &str,
author: &str,
body: Option<&str>,
on_progress: impl Fn(reservation::ReservationProgress),
) -> Result<PathBuf> {
let type_def = config.type_by_name(doc_type).ok_or_else(|| {
anyhow!(
"unknown doc type: '{}'. valid types: {}",
doc_type,
config
.documents
.types
.iter()
.map(|t| t.name.as_str())
.collect::<Vec<_>>()
.join(", ")
)
})?;
if type_def.singleton {
let existing: Vec<_> = store.list(&Filter {
doc_type: Some(DocType::new(doc_type)),
..Default::default()
});
if let Some(doc) = existing.first() {
bail!("{} already exists at {}", doc_type, doc.path.display());
}
}
if type_def.store == StoreBackend::GithubIssues {
let gh_config = config.documents.github.as_ref().ok_or_else(|| {
anyhow!(
"type '{}' uses github-issues store but no [github] config found",
doc_type
)
})?;
let repo = gh_config.repo.as_ref().ok_or_else(|| {
anyhow!(
"type '{}' uses github-issues store but no github.repo configured",
doc_type
)
})?;
let mut store = GithubIssuesStore {
client: GhCli::new(),
root: root.to_path_buf(),
repo: repo.clone(),
config: config.clone(),
issue_map: IssueMap::load(root)?,
issue_cache: IssueCache::new(root),
};
let created = store.create(type_def, title, author, body.unwrap_or(""))?;
return Ok(root.join(&created.path));
}
if type_def.store == StoreBackend::GitRef {
let reserved_number = if let Some(coord) = &config.coordination {
let cache_dir = root.join(".lazyspec/cache").join(&type_def.name);
std::fs::create_dir_all(&cache_dir)?;
Some(reservation::reserve_next(
root,
&coord.remote,
&type_def.prefix,
coord.max_push_retries,
&cache_dir,
&on_progress,
)?)
} else {
None
};
let mut store = GitRefStore {
git: GitCli,
root: root.to_path_buf(),
config: config.clone(),
reserved_number,
};
let created = store.create(type_def, title, author, body.unwrap_or(""))?;
return Ok(root.join(&created.path));
}
let path = fs_ops::create_document(
root,
config,
doc_type,
&type_def.dir,
&type_def.prefix,
title,
author,
&type_def.numbering,
type_def.subdirectory,
on_progress,
)?;
if let Some(body_text) = body {
let content = fs::read_to_string(&path)?;
let (yaml, _) = split_frontmatter(&content)?;
let new_content = format!("---\n{}\n---\n\n{}\n", yaml.trim(), body_text);
fs::write(&path, new_content)?;
}
Ok(path)
}
pub fn run_json(
root: &Path,
config: &Config,
store: &Store,
doc_type: &str,
title: &str,
author: &str,
on_progress: impl Fn(reservation::ReservationProgress),
) -> Result<String> {
run_json_with_body(
root,
config,
store,
doc_type,
title,
author,
None,
on_progress,
)
}
#[allow(clippy::too_many_arguments)]
pub fn run_json_with_body(
root: &Path,
config: &Config,
store: &Store,
doc_type: &str,
title: &str,
author: &str,
body: Option<&str>,
on_progress: impl Fn(reservation::ReservationProgress),
) -> Result<String> {
let path = run_with_body(
root,
config,
store,
doc_type,
title,
author,
body,
on_progress,
)?;
let relative = path.strip_prefix(root).unwrap_or(&path).to_path_buf();
let content = fs::read_to_string(&path)?;
let mut meta = DocMeta::parse(&content)?;
meta.path = relative;
let json = doc_to_json(&meta);
Ok(serde_json::to_string_pretty(&json)?)
}