use std::path::Path;
struct TemplateFile {
path: &'static str,
content: &'static str,
executable: bool,
}
const DEFAULT_FILES: &[TemplateFile] = &[
TemplateFile {
path: "config.toml",
content: include_str!("../templates/default/config.toml"),
executable: false,
},
TemplateFile {
path: "content/_index.md",
content: include_str!("../templates/default/content/_index.md"),
executable: false,
},
TemplateFile {
path: "content/posts/_index.md",
content: include_str!("../templates/default/content/posts/_index.md"),
executable: false,
},
TemplateFile {
path: "content/posts/hello.md",
content: include_str!("../templates/default/content/posts/hello.md"),
executable: false,
},
TemplateFile {
path: "templates/base.html",
content: include_str!("../templates/default/templates/base.html"),
executable: false,
},
TemplateFile {
path: "templates/index.html",
content: include_str!("../templates/default/templates/index.html"),
executable: false,
},
TemplateFile {
path: "templates/section.html",
content: include_str!("../templates/default/templates/section.html"),
executable: false,
},
TemplateFile {
path: "templates/page.html",
content: include_str!("../templates/default/templates/page.html"),
executable: false,
},
];
const BLOG_FILES: &[TemplateFile] = &[
TemplateFile {
path: "config.toml",
content: include_str!("../templates/blog/config.toml"),
executable: false,
},
TemplateFile {
path: "content/_index.md",
content: include_str!("../templates/blog/content/_index.md"),
executable: false,
},
TemplateFile {
path: "content/posts/_index.md",
content: include_str!("../templates/blog/content/posts/_index.md"),
executable: false,
},
TemplateFile {
path: "content/posts/hello-world.md",
content: include_str!("../templates/blog/content/posts/hello-world.md"),
executable: false,
},
TemplateFile {
path: "content/posts/getting-started.md",
content: include_str!("../templates/blog/content/posts/getting-started.md"),
executable: false,
},
];
const DOCS_FILES: &[TemplateFile] = &[
TemplateFile {
path: "config.toml",
content: include_str!("../templates/docs/config.toml"),
executable: false,
},
TemplateFile {
path: "content/_index.md",
content: include_str!("../templates/docs/content/_index.md"),
executable: false,
},
TemplateFile {
path: "content/guide/_index.md",
content: include_str!("../templates/docs/content/guide/_index.md"),
executable: false,
},
TemplateFile {
path: "content/guide/introduction.md",
content: include_str!("../templates/docs/content/guide/introduction.md"),
executable: false,
},
TemplateFile {
path: "content/guide/installation.md",
content: include_str!("../templates/docs/content/guide/installation.md"),
executable: false,
},
TemplateFile {
path: "content/guide/configuration.md",
content: include_str!("../templates/docs/content/guide/configuration.md"),
executable: false,
},
];
const BUSINESS_FILES: &[TemplateFile] = &[
TemplateFile {
path: "config.toml",
content: include_str!("../templates/business/config.toml"),
executable: false,
},
TemplateFile {
path: "content/_index.md",
content: include_str!("../templates/business/content/_index.md"),
executable: false,
},
];
pub struct TemplateInfo {
pub name: &'static str,
pub description: &'static str,
}
pub const TEMPLATES: &[TemplateInfo] = &[
TemplateInfo {
name: "default",
description: "Clean starter site",
},
TemplateInfo {
name: "blog",
description: "Blog with example posts",
},
TemplateInfo {
name: "docs",
description: "Documentation site",
},
TemplateInfo {
name: "business",
description: "Business / landing page",
},
];
pub const TEMPLATE_NAMES: &[&str] = &["default", "blog", "docs", "business"];
pub fn write_template(target: &Path, template: &str) -> anyhow::Result<()> {
let files = match template {
"default" => DEFAULT_FILES,
"blog" => BLOG_FILES,
"docs" => DOCS_FILES,
"business" => BUSINESS_FILES,
_ => anyhow::bail!(
"unknown template \"{template}\". Available templates: {}",
TEMPLATE_NAMES.join(", ")
),
};
for file in files {
let dest = target.join(file.path);
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&dest, file.content)?;
#[cfg(unix)]
if file.executable {
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&dest, std::fs::Permissions::from_mode(0o755))?;
}
}
std::fs::create_dir_all(target.join("static"))?;
Ok(())
}
pub fn customize_config(
target: &Path,
title: &str,
base_url: &str,
theme: Option<&str>,
author: Option<&str>,
) -> anyhow::Result<()> {
let config_path = target.join("config.toml");
let content = std::fs::read_to_string(&config_path)?;
let mut lines: Vec<String> = Vec::new();
let mut has_theme = false;
for line in content.lines() {
if line.starts_with("base_url") {
lines.push(format!("base_url = \"{base_url}\""));
} else if line.starts_with("title") && !line.starts_with("title =")
|| line.starts_with("title =")
{
lines.push(format!("title = \"{}\"", title.replace('\"', "\\\"")));
} else if line.starts_with("theme") {
has_theme = true;
if let Some(t) = theme {
lines.push(format!("theme = \"{t}\""));
} else {
lines.push(line.to_string());
}
} else {
lines.push(line.to_string());
}
}
if let Some(t) = theme {
if !has_theme {
if let Some(pos) = lines.iter().position(|l| l.starts_with("base_url")) {
lines.insert(pos + 1, format!("theme = \"{t}\""));
}
}
}
if let Some(author_name) = author {
if let Some(pos) = lines.iter().position(|l| l.starts_with("author =")) {
lines[pos] = format!("author = \"{}\"", author_name.replace('\"', "\\\""));
}
}
if let Some(pos) = lines
.iter()
.position(|l| l.trim_start().starts_with("copyright_html"))
{
let safe_title = title.replace('\'', "'");
let author_part = match author {
Some(a) => a.replace('\'', "'"),
None => "Author".to_string(),
};
lines[pos] = format!(
"copyright_html = '<a href=\"/\">{safe_title}</a> by {author_part} via <a href=\"https://zorto.dev\" target=\"_blank\" rel=\"noopener\">Zorto</a>'"
);
}
let mut output = lines.join("\n");
if !output.ends_with('\n') {
output.push('\n');
}
std::fs::write(&config_path, output)?;
Ok(())
}