use std::collections::HashMap;
use std::path::{Path, PathBuf};
use plates::SiteSpec;
use plates::prov::{
Discovery, FileIndex, IdStorage, IndexStore, NoIdentity, Settings, StdFs, Workspace,
WorkspaceConfig, block_on,
};
use crate::config::{self, Source};
pub type Archive = Workspace<StdFs, NoIdentity, FileIndex>;
pub struct Session {
pub root_dir: PathBuf,
pub root_doc: PathBuf,
pub config: WorkspaceConfig,
pub sites: Vec<SiteSpec>,
pub source: Source,
pub warnings: Vec<String>,
registry: Option<PathBuf>,
}
impl Session {
pub fn open() -> Result<Self, String> {
let cwd =
std::env::current_dir().map_err(|e| format!("cannot read this directory: {e}"))?;
Self::open_at(&cwd)
}
pub fn open_at(dir: &Path) -> Result<Self, String> {
let found = match block_on(plates::prov::discover(&StdFs, dir))
.map_err(|e| format!("cannot read {}: {e}", dir.display()))?
{
Discovery::Found(found) => found,
Discovery::Ambiguous { dir, candidates } => {
return Err(format!(
"ambiguous archive root in {}: {} (rename one, or add part_of)",
dir.display(),
candidates.join(", ")
));
}
Discovery::NotFound => {
return Err(
"no prov archive found: no ancestor directory has a document with \
metadata and no part_of\n (run `prov init` to start one)"
.to_string(),
);
}
};
let probe: Workspace<StdFs> = Workspace::builder(StdFs).root(&found.root_dir).build();
let config_doc = block_on(probe.config_path(&found.root_doc)).ok().flatten();
let sites = config::read_sites(
&found.root_dir,
&found.root_doc,
config_doc.as_deref(),
&found.config.exports,
);
Ok(Self {
root_dir: found.root_dir,
root_doc: found.root_doc,
config: found.config,
sites: sites.specs,
source: sites.source,
warnings: sites.warnings,
registry: found.registry,
})
}
pub fn workspace(&self) -> Result<Archive, String> {
Ok(Workspace::builder(StdFs)
.root(&self.root_dir)
.settings(Settings::from(&self.config))
.index(self.index()?)
.build())
}
pub fn id_by_path(&self, ws: &Archive) -> HashMap<PathBuf, String> {
ws.index()
.iter()
.map(|(id, path)| (path.clone(), id.as_str().to_string()))
.collect()
}
fn index(&self) -> Result<FileIndex, String> {
if self.config.id_storage == IdStorage::FrontmatterOnly {
let probe: Workspace<StdFs> = Workspace::builder(StdFs).root(&self.root_dir).build();
let mut index = FileIndex::new(self.config.default_embed_format);
let ids = block_on(probe.scan_ids())
.map_err(|e| format!("cannot scan this archive's ids: {e}"))?;
for (id, path) in ids {
index.register(&id, &path);
}
index.mark_clean();
return Ok(index);
}
let Some(rel) = &self.registry else {
return Ok(FileIndex::new(self.config.default_embed_format));
};
let text = match std::fs::read_to_string(self.root_dir.join(rel)) {
Ok(text) => text,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => String::new(),
Err(e) => return Err(format!("cannot read {}: {e}", rel.display())),
};
FileIndex::parse(rel, &text).map_err(|e| format!("cannot read {}: {e}", rel.display()))
}
pub fn nothing_to_publish(&self) -> String {
match self.source {
Source::None => format!(
"this archive declares no exports, so there is nothing to render\n \
(declare one under `exports:` in {}: a name, a `label`, and a `gate` \
naming the field and value that admit a document)",
self.root_doc.display()
),
_ => "this archive declares no sites to render".to_string(),
}
}
}