use anyhow::{Context, Result};
use clap::{Args, Subcommand};
use laterite_core::Db;
use laterite_web::theme::archive;
use std::path::PathBuf;
#[derive(Subcommand)]
pub enum ThemeCommand {
Export(ExportArgs),
Import(ImportArgs),
List,
Build(BuildArgs),
}
#[derive(Args)]
pub struct BuildArgs {
pub code: String,
#[arg(long, short, default_value = "dist")]
pub out: PathBuf,
#[arg(long)]
pub base_url: Option<String>,
}
#[derive(Args)]
pub struct ExportArgs {
pub code: String,
#[arg(long, short)]
pub out: Option<PathBuf>,
}
#[derive(Args)]
pub struct ImportArgs {
pub code: String,
pub file: PathBuf,
#[arg(long)]
pub replace: bool,
}
pub async fn run(command: ThemeCommand, db: Db) -> Result<()> {
match command {
ThemeCommand::Export(args) => {
let bytes = archive::export(&db, &args.code)
.await
.with_context(|| format!("could not export the theme {:?}", args.code))?;
let out = args
.out
.unwrap_or_else(|| PathBuf::from(format!("{}.zip", args.code)));
std::fs::write(&out, &bytes)
.with_context(|| format!("could not write {}", out.display()))?;
println!("Wrote {} ({} bytes)", out.display(), bytes.len());
}
ThemeCommand::Import(args) => {
let bytes = std::fs::read(&args.file)
.with_context(|| format!("could not read {}", args.file.display()))?;
let report = archive::import(&db, &args.code, &bytes, args.replace, None)
.await
.with_context(|| format!("could not import into the theme {:?}", args.code))?;
println!(
"{} {}: {} template(s) written, {} skipped{}",
if report.created { "Created" } else { "Updated" },
args.code,
report.imported,
report.skipped,
if report.ignored.is_empty() {
String::new()
} else {
format!(", ignored {}", report.ignored.join(", "))
}
);
if report.skipped > 0 && !args.replace {
println!("Pass --replace to overwrite the templates the theme already has.");
}
}
ThemeCommand::Build(args) => {
let base_url = args.base_url.unwrap_or_else(base_url_from_project);
let report = laterite_web::theme::build::build(&db, &args.code, &args.out, &base_url)
.await
.with_context(|| format!("could not build the theme {:?}", args.code))?;
println!(
"Wrote {} page(s) and {} asset(s) to {} for {base_url}",
report.pages.len(),
report.assets.len(),
args.out.display()
);
for url in &report.pages {
println!(" {url}");
}
if !report.skipped.is_empty() {
println!(
"Skipped {} page(s) with parameters or hidden: {}",
report.skipped.len(),
report.skipped.join(", ")
);
}
}
ThemeCommand::List => {
let active = laterite_web::theme::active_theme(&db)
.await?
.map(|t| t.code);
for theme in laterite_web::theme::store::list_themes(&db).await? {
let mark = if active.as_deref() == Some(theme.code.as_str()) {
" (active)"
} else {
""
};
let parent = theme.parent.map(|p| format!(" < {p}")).unwrap_or_default();
println!("{}{}: {}{}", theme.code, parent, theme.config.name, mark);
}
}
}
Ok(())
}
fn base_url_from_project() -> String {
#[derive(serde::Deserialize, Default)]
struct App {
#[serde(default)]
url: Option<String>,
}
#[derive(serde::Deserialize, Default)]
struct Server {
#[serde(default)]
listen: Option<String>,
}
#[derive(serde::Deserialize, Default)]
struct Only {
#[serde(default)]
app: App,
#[serde(default)]
server: Server,
}
let only: Only = crate::project::Project::locate()
.and_then(|p| p.load::<Only>())
.unwrap_or_default();
laterite_core::config::base_url(
only.app.url.as_deref(),
only.server.listen.as_deref().unwrap_or("127.0.0.1:8080"),
)
}