diff --git a/README.md b/README.md index c3fc63a..d5525bc 100644 --- a/README.md +++ b/README.md @@ -272,19 +272,19 @@ cook server --port 8080 cook server --open ``` -### `cook build` +### `cook build web` Generate a self-contained static website from your recipes. Hostable anywhere or browsable via `file://`. ```bash # Build into ./_site from the current directory -cook build +cook build web # Build a specific collection into a custom output -cook build dist --base-path ~/my-recipes +cook build web dist --base-path ~/my-recipes # Build for hosting under a subpath -cook build --base-url /recipes/ +cook build web --base-url /recipes/ ``` ### `cook search` diff --git a/docs/build.md b/docs/build.md index 903d4cc..24d8d12 100644 --- a/docs/build.md +++ b/docs/build.md @@ -1,11 +1,15 @@ # Build Command +The `cook build` command groups artifact-generation subcommands. Today it offers `web` for static-site generation; future targets (e.g. cookbooks) will live alongside it. + +## `cook build web` + Generate a self-contained static website from your recipe collection. The output mirrors `cook server`'s browsing experience but ships as plain HTML, CSS, and JS — no Rust process needed at runtime, so it can be hosted on GitHub Pages, Netlify, S3, or opened directly via `file://`. ## Usage ``` -cook build [OPTIONS] [OUTPUT_DIR] +cook build web [OPTIONS] [OUTPUT_DIR] ``` ## Arguments @@ -25,13 +29,13 @@ cook build [OPTIONS] [OUTPUT_DIR] ```bash # Build into ./_site from the current directory -cook build +cook build web # Build a specific recipe collection into a custom output directory -cook build dist --base-path ~/my-recipes +cook build web dist --base-path ~/my-recipes # Build for hosting under /recipes/ on your domain -cook build --base-url /recipes/ +cook build web --base-url /recipes/ ``` ## What gets generated @@ -66,7 +70,7 @@ Because internal links default to page-relative paths, no configuration is neede ```bash # GitHub Pages: push _site/ to gh-pages -cook build && git -C _site init && git -C _site add . && \ +cook build web && git -C _site init && git -C _site add . && \ git -C _site commit -m "site" && \ git -C _site push -f git@github.com:user/repo gh-pages @@ -85,5 +89,5 @@ Use `--base-url` only if your host serves the site under a fixed subpath and you - The generated site has no server dependency — it works fully offline via `file://`. - Search runs entirely in the browser by loading `static/search-index.json`. -- Re-run `cook build` after editing recipes; the command is idempotent. +- Re-run `cook build web` after editing recipes; the command is idempotent. - For a live editing experience, use `cook server` instead. diff --git a/src/args.rs b/src/args.rs index d34e429..991571d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -87,18 +87,18 @@ pub enum Command { )] Server(server::ServerArgs), - /// Generate a self-contained static website from your recipe collection + /// Build artifacts from your recipe collection /// - /// Renders your recipes as static HTML files browsable on any static-file - /// host or directly from disk via file://. Excludes dynamic features - /// (shopping list, pantry, editing). + /// Container for build subcommands. Currently supports `web` (static + /// website generation). More targets (e.g. cookbooks) may be added in + /// future releases. /// /// Examples: - /// cook build # Build to ./_site - /// cook build out # Build to ./out - /// cook build --base-path ~/recipes # Use specific source directory - /// cook build --base-url /recipes/ # Absolute URL prefix for subpath hosting - #[command(long_about = "Generate a static HTML website from your recipe collection")] + /// cook build web # Build a static website to ./_site + /// cook build web out # Build to ./out + /// cook build web --base-path ~/recipes # Use specific source directory + /// cook build web --base-url /recipes/ # Absolute URL prefix for subpath hosting + #[command(long_about = "Build artifacts (static website, etc.) from your recipe collection")] Build(build::BuildArgs), /// Generate a combined shopping list from multiple recipes diff --git a/src/build/mod.rs b/src/build/mod.rs index e593e8d..11819c0 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -8,11 +8,33 @@ use crate::util::resolve_to_absolute_path; use crate::Context; use anyhow::{bail, Context as _, Result}; use camino::Utf8PathBuf; -use clap::Args; +use clap::{Args, Subcommand}; use unic_langid::LanguageIdentifier; #[derive(Debug, Args)] pub struct BuildArgs { + #[command(subcommand)] + pub command: BuildCommand, +} + +#[derive(Debug, Subcommand)] +pub enum BuildCommand { + /// Generate a self-contained static website from your recipes + /// + /// Renders your recipes as static HTML files browsable on any static-file + /// host or directly from disk via file://. Excludes dynamic features + /// (shopping list, pantry, editing). + /// + /// Examples: + /// cook build web # Build to ./_site + /// cook build web out # Build to ./out + /// cook build web --base-path ~/recipes # Use specific source directory + /// cook build web --base-url /recipes/ # Absolute URL prefix for subpath hosting + Web(WebBuildArgs), +} + +#[derive(Debug, Args)] +pub struct WebBuildArgs { /// Output directory for the generated static site /// /// Defaults to ./_site if not specified. The directory is created if @@ -51,11 +73,19 @@ fn parse_lang_arg(s: &str) -> Result { impl BuildArgs { pub fn get_base_path(&self) -> Option { - self.base_path.clone() + match &self.command { + BuildCommand::Web(args) => args.base_path.clone(), + } } } pub fn run(ctx: &Context, args: BuildArgs) -> Result<()> { + match args.command { + BuildCommand::Web(web_args) => run_web(ctx, web_args), + } +} + +fn run_web(ctx: &Context, args: WebBuildArgs) -> Result<()> { let source = resolve_to_absolute_path(ctx.base_path())?; if !source.is_dir() { bail!("Source base path is not a directory: {source}"); @@ -82,7 +112,7 @@ pub fn run(ctx: &Context, args: BuildArgs) -> Result<()> { let mut tree = cooklang_find::build_tree(&source) .map_err(|e| anyhow::anyhow!("Failed to build recipe tree: {e}"))?; // If the user pointed the output directory inside the source directory - // (the common case: `cook build` with default `_site` next to recipes), + // (the common case: `cook build web` with default `_site` next to recipes), // strip the output subtree so we don't re-process the previous run's // generated files. Without this, every run would nest `_site/recipe/...` // and `_site/api/static/...` one level deeper until the OS rejects the diff --git a/src/server/builders.rs b/src/server/builders.rs index e9e0661..a6a6b8b 100644 --- a/src/server/builders.rs +++ b/src/server/builders.rs @@ -3,7 +3,7 @@ //! struct ready to render (or be turned into an `axum::Response` by a handler). //! //! The builders intentionally avoid any axum / tokio-async types so they can be -//! reused from a non-async context (e.g. `cook build`). +//! reused from a non-async context (e.g. `cook build web`). use crate::server::templates::*; use anyhow::Result; diff --git a/tests/build.rs b/tests/build.rs index 3105ac9..dc45d9d 100644 --- a/tests/build.rs +++ b/tests/build.rs @@ -9,7 +9,7 @@ fn seed_dir() -> PathBuf { #[test] fn build_command_help_works() { let mut cmd = Command::cargo_bin("cook").unwrap(); - cmd.args(["build", "--help"]).assert().success(); + cmd.args(["build", "web", "--help"]).assert().success(); } #[test] @@ -21,6 +21,7 @@ fn build_creates_output_dir() { let mut cmd = Command::cargo_bin("cook").unwrap(); cmd.args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -41,6 +42,7 @@ fn build_writes_index_and_static_assets() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -75,6 +77,7 @@ fn build_lang_arg_changes_ui_locale() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -103,6 +106,7 @@ fn build_lang_arg_rejects_unsupported() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -123,6 +127,7 @@ fn build_writes_recipe_pages() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -194,6 +199,7 @@ fn build_renders_recipes_with_title_metadata() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -223,6 +229,7 @@ fn build_writes_search_index() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -266,6 +273,7 @@ fn build_copies_images_when_present() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -291,6 +299,7 @@ fn build_writes_search_js() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -339,6 +348,7 @@ fn build_writes_menu_pages_without_dotmenu_suffix() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -368,6 +378,7 @@ fn static_output_omits_dynamic_ui() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -415,6 +426,7 @@ fn build_internal_links_resolve_to_existing_files() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", seed.to_str().unwrap(), @@ -449,7 +461,7 @@ fn build_internal_links_resolve_to_existing_files() { #[test] fn build_twice_with_output_inside_source_does_not_recurse() { // Regression: when the output dir lives inside the source dir - // (`cook build` from the recipe root with default `_site`), every run + // (`cook build web` from the recipe root with default `_site`), every run // used to discover the previous run's generated files and copy them one // level deeper, eventually hitting ENAMETOOLONG. let tmp = TempDir::new().unwrap(); @@ -477,6 +489,7 @@ fn build_twice_with_output_inside_source_does_not_recurse() { .unwrap() .args([ "build", + "web", out.to_str().unwrap(), "--base-path", source.to_str().unwrap(), diff --git a/tests/snapshots/snapshot_test__help_output.snap b/tests/snapshots/snapshot_test__help_output.snap index f345797..0a7be13 100644 --- a/tests/snapshots/snapshot_test__help_output.snap +++ b/tests/snapshots/snapshot_test__help_output.snap @@ -10,7 +10,7 @@ Usage: cook [OPTIONS] Commands: recipe Parse, validate and display recipe files in various formats server Start a local web server to browse and view your recipe collection - build Generate a self-contained static website from your recipe collection + build Build artifacts from your recipe collection shopping-list Generate a combined shopping list from multiple recipes [aliases: sl] seed Initialize a directory with example Cooklang recipes search Search through your recipe collection for matching text diff --git a/tests/snapshots/snapshot_test__help_output_no_update.snap b/tests/snapshots/snapshot_test__help_output_no_update.snap index fdb15b6..515b001 100644 --- a/tests/snapshots/snapshot_test__help_output_no_update.snap +++ b/tests/snapshots/snapshot_test__help_output_no_update.snap @@ -10,7 +10,7 @@ Usage: cook [OPTIONS] Commands: recipe Parse, validate and display recipe files in various formats server Start a local web server to browse and view your recipe collection - build Generate a self-contained static website from your recipe collection + build Build artifacts from your recipe collection shopping-list Generate a combined shopping list from multiple recipes [aliases: sl] seed Initialize a directory with example Cooklang recipes search Search through your recipe collection for matching text