From b2d76658fcea485806f9a5aa24c6618caaa51890 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sun, 1 Mar 2026 20:19:32 -0500 Subject: [PATCH] Add default templates, styles, and scripts for documentation site --- docs-gen/defaults/config.toml | 12 ++++ docs-gen/defaults/pages/en/index.md | 10 +++ docs-gen/defaults/pages/ja/index.md | 10 +++ .../defaults}/static/css/main.css | 0 .../defaults}/static/js/main.js | 0 .../defaults}/templates/base.html | 0 .../defaults}/templates/page.html | 0 .../defaults}/templates/portal.html | 0 docs-gen/src/builder.rs | 55 +++++++++++++-- docs-gen/src/defaults.rs | 45 ++++++++++++ docs-gen/src/main.rs | 69 +++++++++++++++++-- 11 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 docs-gen/defaults/config.toml create mode 100644 docs-gen/defaults/pages/en/index.md create mode 100644 docs-gen/defaults/pages/ja/index.md rename {docs-src => docs-gen/defaults}/static/css/main.css (100%) rename {docs-src => docs-gen/defaults}/static/js/main.js (100%) rename {docs-src => docs-gen/defaults}/templates/base.html (100%) rename {docs-src => docs-gen/defaults}/templates/page.html (100%) rename {docs-src => docs-gen/defaults}/templates/portal.html (100%) create mode 100644 docs-gen/src/defaults.rs diff --git a/docs-gen/defaults/config.toml b/docs-gen/defaults/config.toml new file mode 100644 index 0000000..c948b66 --- /dev/null +++ b/docs-gen/defaults/config.toml @@ -0,0 +1,12 @@ +[site] +title = "My Docs" +base_url = "https://example.com" +base_path = "" + +[i18n] +default_lang = "en" +langs = ["en", "ja"] + +[highlight] +theme = "base16-ocean.dark" +theme_light = "InspiredGitHub" diff --git a/docs-gen/defaults/pages/en/index.md b/docs-gen/defaults/pages/en/index.md new file mode 100644 index 0000000..2ccfc54 --- /dev/null +++ b/docs-gen/defaults/pages/en/index.md @@ -0,0 +1,10 @@ +--- +title: Welcome +order: 0 +--- + +# Welcome + +This is the home page of your documentation site. + +Edit this file at `pages/en/index.md` to get started. diff --git a/docs-gen/defaults/pages/ja/index.md b/docs-gen/defaults/pages/ja/index.md new file mode 100644 index 0000000..96312d7 --- /dev/null +++ b/docs-gen/defaults/pages/ja/index.md @@ -0,0 +1,10 @@ +--- +title: ようこそ +order: 0 +--- + +# ようこそ + +ドキュメントサイトのトップページです。 + +`pages/ja/index.md` を編集して始めましょう。 diff --git a/docs-src/static/css/main.css b/docs-gen/defaults/static/css/main.css similarity index 100% rename from docs-src/static/css/main.css rename to docs-gen/defaults/static/css/main.css diff --git a/docs-src/static/js/main.js b/docs-gen/defaults/static/js/main.js similarity index 100% rename from docs-src/static/js/main.js rename to docs-gen/defaults/static/js/main.js diff --git a/docs-src/templates/base.html b/docs-gen/defaults/templates/base.html similarity index 100% rename from docs-src/templates/base.html rename to docs-gen/defaults/templates/base.html diff --git a/docs-src/templates/page.html b/docs-gen/defaults/templates/page.html similarity index 100% rename from docs-src/templates/page.html rename to docs-gen/defaults/templates/page.html diff --git a/docs-src/templates/portal.html b/docs-gen/defaults/templates/portal.html similarity index 100% rename from docs-src/templates/portal.html rename to docs-gen/defaults/templates/portal.html diff --git a/docs-gen/src/builder.rs b/docs-gen/src/builder.rs index 20d6b57..94a6548 100644 --- a/docs-gen/src/builder.rs +++ b/docs-gen/src/builder.rs @@ -1,4 +1,5 @@ use crate::config::SiteConfig; +use crate::defaults; use crate::markdown::{Frontmatter, MarkdownRenderer}; use anyhow::{Context, Result}; use serde::Serialize; @@ -44,9 +45,8 @@ pub fn build(src: &Path, out: &Path) -> Result<()> { let config = SiteConfig::load(src)?; let renderer = MarkdownRenderer::new(config.highlight_theme(), config.highlight_theme_light()); - let templates_dir = src.join("templates"); - let template_glob = format!("{}/**/*.html", templates_dir.display()); - let tera = Tera::new(&template_glob).context("Failed to load templates")?; + // Build Tera: start with embedded defaults, then override with user templates + let tera = build_tera(src)?; // Clean output directory if out.exists() { @@ -54,7 +54,8 @@ pub fn build(src: &Path, out: &Path) -> Result<()> { } fs::create_dir_all(out)?; - // Copy static files + // Copy static files: embedded defaults first, then user overrides on top + copy_default_static(out)?; let static_dir = src.join("static"); if static_dir.exists() { copy_dir_recursive(&static_dir, out)?; @@ -326,6 +327,52 @@ fn generate_root_redirect(out: &Path, config: &SiteConfig) -> Result<()> { Ok(()) } +/// Build Tera with embedded default templates, then override with any files +/// found in `/templates/`. +fn build_tera(src: &Path) -> Result { + let mut tera = Tera::default(); + + // Register embedded defaults + for (name, source) in defaults::default_templates() { + tera.add_raw_template(name, source) + .with_context(|| format!("Failed to add default template '{}'", name))?; + } + + // Override with user-provided templates (if any) + let templates_dir = src.join("templates"); + if templates_dir.exists() { + for entry in WalkDir::new(&templates_dir) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().map_or(false, |ext| ext == "html")) + { + let path = entry.path(); + let rel = path.strip_prefix(&templates_dir)?; + let name = rel.to_string_lossy().replace('\\', "/"); + let source = fs::read_to_string(path) + .with_context(|| format!("Failed to read template {}", path.display()))?; + tera.add_raw_template(&name, &source) + .with_context(|| format!("Failed to register template '{}'", name))?; + } + } + + Ok(tera) +} + +/// Write embedded default static files (css/js) to the output directory. +fn copy_default_static(out: &Path) -> Result<()> { + for (rel_path, content) in defaults::default_static_files() { + let target = out.join(rel_path); + if let Some(parent) = target.parent() { + fs::create_dir_all(parent)?; + } + // Only write if not already present (user file takes precedence via + // the subsequent copy_dir_recursive call, but write defaults first) + fs::write(&target, content)?; + } + Ok(()) +} + fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> { for entry in WalkDir::new(src).into_iter().filter_map(|e| e.ok()) { let path = entry.path(); diff --git a/docs-gen/src/defaults.rs b/docs-gen/src/defaults.rs new file mode 100644 index 0000000..054c345 --- /dev/null +++ b/docs-gen/src/defaults.rs @@ -0,0 +1,45 @@ +// Default embedded theme files. Users can override any of these by placing +// a file with the same name under their /templates/ or /static/. + +pub const TEMPLATE_BASE: &str = include_str!("../defaults/templates/base.html"); +pub const TEMPLATE_PAGE: &str = include_str!("../defaults/templates/page.html"); +pub const TEMPLATE_PORTAL: &str = include_str!("../defaults/templates/portal.html"); + +pub const STATIC_CSS_MAIN: &str = include_str!("../defaults/static/css/main.css"); +pub const STATIC_JS_MAIN: &str = include_str!("../defaults/static/js/main.js"); + +// Init command templates +pub const INIT_CONFIG_TOML: &str = include_str!("../defaults/config.toml"); +pub const INIT_PAGE_EN_INDEX: &str = include_str!("../defaults/pages/en/index.md"); +pub const INIT_PAGE_JA_INDEX: &str = include_str!("../defaults/pages/ja/index.md"); + +/// Returns all default templates as (name, source) pairs for Tera registration. +pub fn default_templates() -> Vec<(&'static str, &'static str)> { + vec![ + ("base.html", TEMPLATE_BASE), + ("page.html", TEMPLATE_PAGE), + ("portal.html", TEMPLATE_PORTAL), + ] +} + +/// Returns all default static files as (relative_path, content) pairs. +pub fn default_static_files() -> Vec<(&'static str, &'static str)> { + vec![ + ("css/main.css", STATIC_CSS_MAIN), + ("js/main.js", STATIC_JS_MAIN), + ] +} + +/// Returns all init scaffold files as (relative_path, content) pairs. +pub fn init_files() -> Vec<(&'static str, &'static str)> { + vec![ + ("config.toml", INIT_CONFIG_TOML), + ("templates/base.html", TEMPLATE_BASE), + ("templates/page.html", TEMPLATE_PAGE), + ("templates/portal.html", TEMPLATE_PORTAL), + ("static/css/main.css", STATIC_CSS_MAIN), + ("static/js/main.js", STATIC_JS_MAIN), + ("pages/en/index.md", INIT_PAGE_EN_INDEX), + ("pages/ja/index.md", INIT_PAGE_JA_INDEX), + ] +} diff --git a/docs-gen/src/main.rs b/docs-gen/src/main.rs index b1ac82f..dad90a9 100644 --- a/docs-gen/src/main.rs +++ b/docs-gen/src/main.rs @@ -1,23 +1,78 @@ mod builder; mod config; +mod defaults; mod markdown; -use clap::Parser; -use std::path::PathBuf; +use anyhow::Result; +use clap::{Parser, Subcommand}; +use std::fs; +use std::path::{Path, PathBuf}; #[derive(Parser)] #[command(version, about = "A simple static site generator")] struct Cli { - /// Source directory containing config.toml + #[command(subcommand)] + command: Option, + + /// Source directory containing config.toml (used when no subcommand given) #[arg(default_value = ".")] src: PathBuf, - /// Output directory + /// Output directory (used when no subcommand given) #[arg(long, default_value = "docs")] out: PathBuf, } -fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); - builder::build(&cli.src, &cli.out) +#[derive(Subcommand)] +enum Command { + /// Build the documentation site + Build { + /// Source directory containing config.toml + #[arg(default_value = ".")] + src: PathBuf, + + /// Output directory + #[arg(long, default_value = "docs")] + out: PathBuf, + }, + /// Initialize a new docs project with default scaffold files + Init { + /// Target directory to initialize (default: current directory) + #[arg(default_value = ".")] + src: PathBuf, + }, } + +fn main() -> Result<()> { + let cli = Cli::parse(); + + match cli.command { + Some(Command::Build { src, out }) => builder::build(&src, &out), + Some(Command::Init { src }) => cmd_init(&src), + None => builder::build(&cli.src, &cli.out), + } +} + +fn cmd_init(target: &Path) -> Result<()> { + let mut skipped = 0usize; + let mut created = 0usize; + + for (rel_path, content) in defaults::init_files() { + let dest = target.join(rel_path); + if dest.exists() { + eprintln!("Skipping (already exists): {}", dest.display()); + skipped += 1; + continue; + } + if let Some(parent) = dest.parent() { + fs::create_dir_all(parent)?; + } + fs::write(&dest, content)?; + println!("Created: {}", dest.display()); + created += 1; + } + + println!("\nInit complete: {} file(s) created, {} skipped.", created, skipped); + Ok(()) +} +