// The `init` command (upstream `typst-cli/src/init.rs`). Templates are
// served from the local package directories only (downloading packages is
// not supported by this port).
///|
/// Execute an initialization command.
fn init_project(command : InitCommand) -> Unit raise HintedFailure {
let packages = system_packages(command.package_args)
// Parse the package specification. If the user didn't specify the
// version, we try to figure it out automatically by searching the disk.
let spec = match @syntax.PackageSpec::from_str(command.template) {
Ok(spec) => spec
Err(err) => {
// Try to parse without version, but prefer the error message of the
// normal package spec parsing if it fails.
guard @syntax.VersionlessPackageSpec::from_str(command.template)
is Ok(spec) else {
raise bail(err)
}
let version = packages.latest_version(spec) catch {
@kit.StrError(msg) => raise bail(msg)
}
spec.at(version)
}
}
// Find the package.
let root = packages.obtain(spec) catch { err => raise bail(err.to_string()) }
let package_path = root.path()
// Parse the manifest.
let manifest = parse_manifest(package_path)
if manifest.validate(spec) is Err(err) {
raise bail(err)
}
// Ensure that it is indeed a template.
guard manifest.template is Some(template) else {
raise bail("package \{spec} is not a template")
}
// Determine the directory at which we will create the project.
let project_dir = command.dir.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)
}
///|
/// Parses the manifest of the package located at `package_path`.
fn parse_manifest(
package_path : String,
) -> @syntax.PackageManifest raise HintedFailure {
let toml_path = @platform.join(package_path, "typst.toml")
let data = @platform.read_file(toml_path) catch {
err =>
raise bail(
"failed to read package manifest (\{@kit.file_error_from_io(err, toml_path)})",
)
}
let string = @utf8.decode(data) catch {
_ =>
raise bail(
"failed to read package manifest (\{@library.FileError::InvalidUtf8})",
)
}
match @syntax.PackageManifest::from_toml(string) {
Ok(manifest) => manifest
Err(err) => raise bail("package manifest is malformed (\{err})")
}
}
///|
/// Creates the project directory with the template's contents.
fn scaffold_project(
project_dir : String,
package_path : String,
template : @syntax.TemplateInfo,
) -> Unit raise HintedFailure {
if @platform.exists(project_dir) {
raise bail("project directory already exists (at \{project_dir})")
}
let template_dir = @platform.join(package_path, template.path)
if !@platform.exists(template_dir) {
raise bail("template directory does not exist (at \{template_dir})")
}
copy_dir_contents(template_dir, project_dir) catch {
err => raise bail("failed to create project directory (\{err})")
}
}
///|
/// Copies the contents of a directory into a new directory (like
/// `fs_extra::dir::copy` with `content_only`).
fn copy_dir_contents(
from : String,
to : String,
) -> Unit raise @platform.IoError {
@platform.create_dir_all(to)
for name in @platform.read_dir(from) {
let src = @platform.join(from, name)
let dst = @platform.join(to, name)
match @platform.stat_kind(src) {
Dir => copy_dir_contents(src, dst)
_ => @platform.write_file(dst, @platform.read_file(src))
}
}
}
///|
/// Prints a summary after successful initialization.
fn print_summary(
spec : @syntax.PackageSpec,
project_dir : String,
template : @syntax.TemplateInfo,
) -> Unit {
let gray = @kit.ColorSpec::new().with_fg(White).with_dimmed()
let out = terminal_out()
out.writeln("Successfully created new project from \{spec} 🎉")
out.writeln("To start writing, run:")
out.set_color(gray)
out.write("> ")
out.reset()
out.writeln("cd \{shell_escape(project_dir)}")
out.set_color(gray)
out.write("> ")
out.reset()
out.writeln("typst watch \{shell_escape(template.entrypoint)}")
out.writeln("")
flush_terminal(out)
}