use std::io::Write;
use std::path::Path;
use codespan_reporting::term::termcolor::{Color, ColorSpec, WriteColor};
use ecow::eco_format;
use fs_extra::dir::CopyOptions;
use typst::diag::{bail, FileError, StrResult};
use typst::syntax::package::{
PackageManifest, PackageSpec, TemplateInfo, VersionlessPackageSpec,
};
use crate::args::InitCommand;
use crate::download::PrintDownload;
use crate::package;
pub fn init(command: &InitCommand) -> StrResult<()> {
let package_storage = package::storage(&command.package);
let spec: PackageSpec = command.template.parse().or_else(|err| {
let spec: VersionlessPackageSpec = command.template.parse().map_err(|_| err)?;
let version = package_storage.determine_latest_version(&spec)?;
StrResult::Ok(spec.at(version))
})?;
let package_path =
package_storage.prepare_package(&spec, &mut PrintDownload(&spec))?;
let manifest = parse_manifest(&package_path)?;
manifest.validate(&spec)?;
let Some(template) = &manifest.template else {
bail!("package {spec} is not a template");
};
let project_dir = Path::new(command.dir.as_deref().unwrap_or(&manifest.package.name));
scaffold_project(project_dir, &package_path, template)?;
print_summary(spec, project_dir, template).unwrap();
Ok(())
}
fn parse_manifest(package_path: &Path) -> StrResult<PackageManifest> {
let toml_path = package_path.join("typst.toml");
let string = std::fs::read_to_string(&toml_path).map_err(|err| {
eco_format!(
"failed to read package manifest ({})",
FileError::from_io(err, &toml_path)
)
})?;
toml::from_str(&string)
.map_err(|err| eco_format!("package manifest is malformed ({})", err.message()))
}
fn scaffold_project(
project_dir: &Path,
package_path: &Path,
template: &TemplateInfo,
) -> StrResult<()> {
if project_dir.exists() {
bail!("project directory already exists (at {})", project_dir.display());
}
let template_dir = package_path.join(template.path.as_str());
if !template_dir.exists() {
bail!("template directory does not exist (at {})", template_dir.display());
}
fs_extra::dir::copy(
&template_dir,
project_dir,
&CopyOptions::new().content_only(true),
)
.map_err(|err| eco_format!("failed to create project directory ({err})"))?;
Ok(())
}
fn print_summary(
spec: PackageSpec,
project_dir: &Path,
template: &TemplateInfo,
) -> std::io::Result<()> {
let mut gray = ColorSpec::new();
gray.set_fg(Some(Color::White));
gray.set_dimmed(true);
let mut out = crate::terminal::out();
writeln!(out, "Successfully created new project from {spec} 🎉")?;
writeln!(out, "To start writing, run:")?;
out.set_color(&gray)?;
write!(out, "> ")?;
out.reset()?;
writeln!(
out,
"cd {}",
shell_escape::escape(project_dir.display().to_string().into()),
)?;
out.set_color(&gray)?;
write!(out, "> ")?;
out.reset()?;
writeln!(
out,
"typst watch {}",
shell_escape::escape(template.entrypoint.to_string().into()),
)?;
writeln!(out)?;
Ok(())
}