marmite 0.4.2

Marmite is the easiest static site generator.
#![allow(clippy::struct_excessive_bools)]
use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;

/// atproto / standard.site subcommands
#[derive(Subcommand, Debug, Clone)]
pub enum AtprotoCommand {
    /// Authenticate with your atproto PDS using app-password credentials.
    ///
    /// Reads atproto.handle from marmite.yaml and `ATPROTO_APP_PASSWORD` env var.
    /// If no publication exists, prints the config needed in marmite.yaml.
    Auth,

    /// Publish posts to atproto as site.standard.document records.
    Publish {
        /// Force re-publish all posts, ignoring change detection
        #[arg(long, short)]
        force: bool,

        /// Preview what would be published without making any changes
        #[arg(long, short = 'n')]
        dry_run: bool,
    },
}

/// Global CLI Subcommands
#[derive(Subcommand, Debug, Clone)]
pub enum CliSubcommand {
    /// Manage atproto / standard.site integration
    #[command(subcommand)]
    Atproto(AtprotoCommand),
}

/// Command Line Argument Parser for Marmite CLI
#[derive(Parser, Debug, Clone)]
#[command(version, about, long_about = None)]
pub struct Cli {
    /// Input folder containing markdown files
    pub input_folder: Option<PathBuf>,

    /// Output folder to generate the site
    /// [default: `input_folder/site`]
    pub output_folder: Option<PathBuf>,

    /// Verbosity level (0-4)
    /// [default: 0 warn]
    /// options: -v: info,-vv: debug,-vvv: trace,-vvvv: trace all
    #[arg(short, long, action = clap::ArgAction::Count)]
    pub verbose: u8,

    /// Detect changes and rebuild the site automatically
    #[arg(long, short)]
    pub watch: bool,

    /// Serve the site with a built-in HTTP server
    #[arg(long)]
    pub serve: bool,

    /// Address to bind the server
    #[arg(long, default_value = "0.0.0.0:8000", requires = "serve")]
    pub bind: String,

    /// Path to custom configuration file
    #[arg(long, short, default_value = "marmite.yaml")]
    pub config: String,

    /// Print debug messages
    /// Deprecated: Use -vv for debug messages
    #[arg(long, hide = true)]
    pub debug: bool,

    /// Initialize templates in the project
    #[arg(long)]
    pub init_templates: bool,

    /// Initialize a theme with templates and static assets
    #[arg(long)]
    pub start_theme: Option<String>,

    /// Download and set a theme from a remote URL or local folder
    #[arg(long)]
    pub set_theme: Option<String>,

    /// Generate the configuration file
    #[arg(long)]
    pub generate_config: bool,

    /// Init a new site with sample content and default configuration
    /// this will overwrite existing files
    /// usually you don't need to run this because
    /// Marmite can generate a site from any folder with markdown files
    #[arg(long)]
    pub init_site: bool,

    /// Force the rebuild of the site even if no changes detected
    #[arg(long)]
    pub force: bool,

    /// List all available shortcodes
    #[arg(long)]
    pub shortcodes: bool,

    /// Show all site URLs organized by content type
    #[arg(long)]
    pub show_urls: bool,

    /// Print the embedded agent skill document (SKILL.md) to stdout
    #[arg(long)]
    pub skill: bool,

    /// Install the skill into .agents/skills/ in the current directory
    #[arg(long)]
    pub skill_install: bool,

    /// Also install the skill into .claude/skills/ for Claude Code
    #[arg(long)]
    pub skill_install_claude: bool,

    /// Create a new markdown file in the input folder
    #[command(flatten)]
    pub create: Create,

    /// Override configuration values from CLI arguments
    #[command(flatten)]
    pub configuration: Configuration,

    /// Global subcommand
    #[command(subcommand)]
    pub subcommand: Option<CliSubcommand>,
}

/// Create a new markdown file in the input folder
#[derive(Args, Debug, Clone)]
pub struct Create {
    /// Create a new post with the given title and open in the default editor
    #[arg(long)]
    pub new: Option<String>,
    /// Edit the file in the default editor
    #[arg(short, requires = "new")]
    pub edit: bool,
    /// Set the new content as a page
    #[arg(short, requires = "new")]
    pub page: bool,
    /// Set the tags for the new content
    /// tags are comma separated
    #[arg(short, requires = "new")]
    pub tags: Option<String>,
    /// Directory within the content folder to create the file
    #[arg(short = 'd', requires = "new", conflicts_with = "translates")]
    pub directory: Option<String>,
    /// Language code (ISO 639-1) for the new content
    #[arg(long, requires = "new")]
    pub lang: Option<String>,
    /// Slug of the original content this file translates
    #[arg(long, requires_all = ["new", "lang"])]
    pub translates: Option<String>,
    /// Target site within a workspace
    #[arg(long, requires = "new")]
    pub site: Option<String>,
}

/// Gather configuration values from CLI arguments
#[derive(Args, Debug, Clone)]
pub struct Configuration {
    /// Site name [default: "Home" or value from config file]
    #[arg(long)]
    pub name: Option<String>,

    /// Site tagline [default: empty or value from config file]
    #[arg(long)]
    pub tagline: Option<String>,

    /// Site url [default: empty or value from config file]
    #[arg(long)]
    pub url: Option<String>,

    /// If protocol is missing in the URL setting, whether to use HTTPS or not [default: false]
    #[arg(long)]
    pub https: Option<bool>,

    /// Site footer [default: from '_footer.md' or config file]
    #[arg(long)]
    pub footer: Option<String>,

    /// Site main language 2 letter code [default: "en" or value from config file]
    #[arg(long)]
    pub language: Option<String>,

    /// Number of posts per page [default: 10 or value from config file]
    #[arg(long)]
    pub pagination: Option<usize>,

    /// Enable search [default: false or from config file]
    #[arg(long)]
    pub enable_search: Option<bool>,

    /// Show matched text snippets in search results [default: false or from config file]
    #[arg(long)]
    pub search_show_matches: Option<bool>,

    /// Number of match snippets to show per search result [default: 3 or from config file]
    #[arg(long)]
    pub search_match_count: Option<usize>,

    /// Enable backlinks and related content for posts [default: true or from config file]
    #[arg(long, default_value = None)]
    pub enable_related_content: Option<bool>,

    /// Path for content subfolder [default: "content" or value from config file]
    /// this is the folder where markdown files are stored inside `input_folder`
    /// no need to change this if your markdown files are in `input_folder` directly
    #[arg(long)]
    pub content_path: Option<String>,

    /// Path for templates subfolder [default: "templates" or value from config file]
    #[arg(long)]
    pub templates_path: Option<String>,

    /// Path for static subfolder [default: "static" or value from config file]
    #[arg(long)]
    pub static_path: Option<String>,

    /// Path for media subfolder [default: "media" or value from config file]
    /// this path is relative to the folder where your content files are
    #[arg(long)]
    pub media_path: Option<String>,

    /// Default date format [default: "%b %e, %Y" or from config file]
    /// see <https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html>
    #[arg(long)]
    pub default_date_format: Option<String>,

    /// Name of the colorscheme to use [default: "default" or from config file]
    /// see <https://marmite.blog/getting-started.html#colorschemes>
    #[arg(long)]
    pub colorscheme: Option<String>,

    /// Show Table of Contents in posts [default: false or from config file]
    /// this will generate a table of contents for each post
    #[arg(long)]
    pub toc: Option<bool>,

    /// Generate JSON Feed [default: false or from config file]
    #[arg(long)]
    pub json_feed: Option<bool>,

    /// Show next and previous links in posts [default: true or from config file]
    #[arg(long)]
    pub show_next_prev_links: Option<bool>,

    /// Publish markdown source files alongside HTML [default: false or from config file]
    #[arg(long)]
    pub publish_md: Option<bool>,

    /// Source repository URL to link to markdown files [default: None or from config file]
    #[arg(long)]
    pub source_repository: Option<String>,

    /// Image provider for automatic banner image download [default: None or from config file]
    /// Available providers: picsum
    #[arg(long)]
    pub image_provider: Option<String>,

    /// Theme to use for the site [default: from config file or embedded templates]
    #[arg(long)]
    pub theme: Option<String>,

    /// Generate sitemap.xml file [default: true or from config file]
    #[arg(long)]
    pub build_sitemap: Option<bool>,

    /// Generate urls.json file [default: true or from config file]
    #[arg(long)]
    pub publish_urls_json: Option<bool>,

    /// Enable shortcodes processing [default: true or from config file]
    #[arg(long)]
    pub enable_shortcodes: Option<bool>,

    /// Custom shortcode pattern (regex) [default: <!-- \.(\w+)(?:\s+([^-][\s\S]*?))?\s*--> or from config file]
    #[arg(long)]
    pub shortcode_pattern: Option<String>,

    /// Skip image resizing during build [default: false or from config file]
    /// Use this for faster development builds when image optimization is not needed
    #[arg(long)]
    pub skip_image_resize: Option<bool>,

    /// Enable the development toolbar when serving [default: true or from config file]
    #[arg(long)]
    pub enable_toolbar: Option<bool>,

    /// Check internal links during build [default: false or from config file]
    #[arg(long)]
    pub check_internal_links: Option<bool>,

    /// Fail the build on broken internal links [default: false or from config file]
    #[arg(long)]
    pub strict_internal_links: Option<bool>,

    /// Check media file links during build [default: false or from config file]
    #[arg(long)]
    pub check_media_links: Option<bool>,

    /// Render mermaid diagrams to SVG at build time [default: true or from config file]
    #[arg(long)]
    pub native_mermaid_render: Option<bool>,
}