use std::path::{Path, PathBuf};
use clap::{Parser, Subcommand};
use crate::build;
use crate::config::{Config, DEFAULT_CONFIG_FILE};
use crate::dev::{self, DevOptions};
use crate::scaffold::{self, InitOptions, title_from_slug};
#[derive(Debug, Parser)]
#[command(
name = "orbit",
bin_name = "orbit",
version,
about = "Orbit — Markdown static sites with React-like components",
long_about = "Orbit (orbit-md) compiles Markdown pages and JSX-style components into static HTML.\n\nInstall: cargo install orbit-md\nInit: orbit init my-site\nBuild: orbit build"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Init {
path: PathBuf,
#[arg(short, long)]
title: Option<String>,
},
Build {
#[arg(short, long, default_value = DEFAULT_CONFIG_FILE)]
config: PathBuf,
},
Dev {
#[arg(short, long, default_value = DEFAULT_CONFIG_FILE)]
config: PathBuf,
#[arg(short, long, default_value_t = 3000)]
port: u16,
#[arg(long, default_value = "127.0.0.1")]
host: String,
#[arg(long)]
open: bool,
},
New {
#[command(subcommand)]
target: NewTarget,
},
}
#[derive(Debug, Subcommand)]
pub enum NewTarget {
Page {
path: PathBuf,
#[arg(short, long)]
title: Option<String>,
},
}
pub fn execute(cli: Cli) -> anyhow::Result<()> {
match cli.command {
Command::Init { path, title } => {
let mut options = InitOptions::from_path(path.clone());
if let Some(custom_title) = title {
options.title = custom_title;
}
scaffold::init_project(&options).map_err(anyhow::Error::from)?;
print_init_success(&path);
}
Command::Build { config } => {
let site_config =
Config::load(&config)
.map_err(anyhow::Error::from)
.map_err(|err| {
anyhow::anyhow!("loading config from {}: {err}", config.display())
})?;
let report = build(&site_config).map_err(anyhow::Error::from)?;
println!(
"Built {} pages in {} ms → {}",
report.pages_written,
report.elapsed_ms,
site_config.output_dir.display()
);
}
Command::Dev {
config,
port,
host,
open,
} => {
dev::run(&DevOptions {
config_path: config,
host,
port,
open,
})
.map_err(anyhow::Error::from)?;
}
Command::New { target } => match target {
NewTarget::Page { path, title } => {
let config = Config::load(DEFAULT_CONFIG_FILE).map_err(anyhow::Error::from)?;
let page_title = title.unwrap_or_else(|| default_page_title(&path));
let created = scaffold::new_page(&config.source_dir, &path, &page_title)
.map_err(anyhow::Error::from)?;
println!("Created {}", created.display());
}
},
}
Ok(())
}
fn print_init_success(path: &Path) {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("your-site");
println!("Created Orbit project at {}", path.display());
println!();
println!(" cd {name}");
println!(" orbit dev");
println!();
println!("Edit Markdown in content/ and components in components/.");
}
fn default_page_title(path: &Path) -> String {
path.file_stem()
.and_then(|s| s.to_str())
.map(title_from_slug)
.filter(|t| !t.is_empty())
.unwrap_or_else(|| "Untitled".to_owned())
}