diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-03-06 12:33:35 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-06 11:33:35 +0000 |
| commit | a558fd232b379e527eac2d5196eb9367b7d28032 (patch) | |
| tree | 1e91330df3b21bb86e067067cc9ff07ac8d513c8 /crates/typst-cli/src/init.rs | |
| parent | 898367f096fef507488438e00caae8c4ea1d0ff4 (diff) | |
Add `typst init` command (#3544)
Diffstat (limited to 'crates/typst-cli/src/init.rs')
| -rw-r--r-- | crates/typst-cli/src/init.rs | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/crates/typst-cli/src/init.rs b/crates/typst-cli/src/init.rs new file mode 100644 index 00000000..01fdb02f --- /dev/null +++ b/crates/typst-cli/src/init.rs @@ -0,0 +1,114 @@ +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; + +/// Execute an initialization command. +pub fn init(command: &InitCommand) -> StrResult<()> { + // Parse the package specification. If the user didn't specify the version, + // we try to figure it out automatically by downloading the package index + // or searching the disk. + let spec: PackageSpec = command.template.parse().or_else(|err| { + // Try to parse without version, but prefer the error message of the + // normal package spec parsing if it fails. + let spec: VersionlessPackageSpec = command.template.parse().map_err(|_| err)?; + let version = crate::package::determine_latest_version(&spec)?; + StrResult::Ok(spec.at(version)) + })?; + + // Find or download the package. + let package_path = crate::package::prepare_package(&spec)?; + + // Parse the manifest. + let manifest = parse_manifest(&package_path)?; + manifest.validate(&spec)?; + + // Ensure that it is indeed a template. + let Some(template) = &manifest.template else { + bail!("package {spec} is not a template"); + }; + + // Determine the directory at which we will create the project. + let project_dir = Path::new(command.dir.as_deref().unwrap_or(&manifest.package.name)); + + // Set up the project. + scaffold_project(project_dir, &package_path, template)?; + + // Print the summary. + print_summary(spec, project_dir, template).unwrap(); + + Ok(()) +} + +/// Parses the manifest of the package located at `package_path`. +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())) +} + +/// Creates the project directory with the template's contents and returns the +/// path at which it was created. +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(()) +} + +/// Prints a summary after successful initialization. +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 {}", project_dir.display())?; + out.set_color(&gray)?; + write!(out, "> ")?; + out.reset()?; + writeln!(out, "typst watch {}", template.entrypoint)?; + writeln!(out)?; + Ok(()) +} |
