use super::validate::{
ensure_directory, normalize_path, parse_port, validate_directory, validate_theme_dir,
};
use crate::content::{create_page, create_post};
use crate::error::{KrikError, KrikResult, ServerError, ServerErrorKind};
use crate::generator::SiteGenerator;
use crate::init::init_site;
use crate::lint::{generate_html_report, lint_content, lint_content_with_links};
use crate::logging;
use crate::server::DevServer;
use crate::site::SiteConfig;
use clap::ArgMatches;
use std::path::{Path, PathBuf};
use tracing::{debug, error, info, warn};
fn resolve_theme_path(cli_theme: Option<&str>, source_dir: &Path) -> KrikResult<Option<PathBuf>> {
if let Some(theme_path) = cli_theme {
info!("Using command line theme: {}", theme_path);
return validate_theme_dir(
Some(theme_path),
"Validating --theme directory from command line",
);
}
if let Ok(site_config) = SiteConfig::load_from_path(source_dir) {
if let Some(theme_path) = &site_config.theme {
info!("Using theme from site.toml: {}", theme_path);
return validate_theme_dir(
Some(theme_path.as_str()),
"Validating theme directory from site.toml",
);
}
}
info!("Using default theme: themes/default");
Ok(Some(PathBuf::from("themes/default")))
}
pub async fn handle_server(server_matches: &ArgMatches) -> KrikResult<()> {
let _span = logging::get_logger("server");
let _enter = _span.enter();
let input_dir = validate_directory(
server_matches
.get_one::<String>("input")
.map(|s| s.as_str())
.unwrap_or("content"),
"Validating --input directory for server",
)?;
let output_dir = ensure_directory(
server_matches
.get_one::<String>("output")
.map(|s| s.as_str())
.unwrap_or("_site"),
"Ensuring --output directory for server",
)?;
let theme_dir = resolve_theme_path(
server_matches
.get_one::<String>("theme")
.map(|s| s.as_str()),
&input_dir,
)?;
let port = parse_port(
server_matches
.get_one::<String>("port")
.map(|s| s.as_str())
.unwrap_or("3000"),
"Parsing --port value for server",
)?;
let no_live_reload = server_matches.get_flag("no-live-reload");
info!("Starting development server on port {}", port);
debug!("Input directory: {}", input_dir.display());
debug!("Output directory: {}", output_dir.display());
debug!(
"Theme directory: {:?}",
theme_dir.as_ref().map(|p| p.display())
);
debug!("Live reload: {}", !no_live_reload);
let server = DevServer::new(input_dir, output_dir, theme_dir, port, !no_live_reload);
server
.start()
.await
.map_err(|e| match e.downcast::<std::io::Error>() {
Ok(io_err) => KrikError::Server(Box::new(ServerError {
kind: ServerErrorKind::BindError {
port,
source: *io_err,
},
context: format!("Starting development server on port {port}"),
})),
Err(other_err) => KrikError::Server(Box::new(ServerError {
kind: ServerErrorKind::WebSocketError(other_err.to_string()),
context: "Starting development server".to_string(),
})),
})?;
Ok(())
}
pub fn handle_init(init_matches: &ArgMatches) -> KrikResult<()> {
let _span = logging::get_logger("init");
let _enter = _span.enter();
let directory = normalize_path(
init_matches
.get_one::<String>("directory")
.map(|s| s.as_str())
.unwrap_or("."),
false,
"Normalizing target directory for init",
)?;
let force = init_matches.get_flag("force");
info!("Initializing new Krik site in: {}", directory.display());
debug!("Force overwrite: {}", force);
init_site(&directory, force)
}
pub fn handle_post(post_matches: &ArgMatches) -> KrikResult<()> {
let _span = logging::get_logger("post");
let _enter = _span.enter();
let title = post_matches
.get_one::<String>("title")
.map(|s| s.as_str())
.unwrap_or("New post");
let filename = post_matches.get_one::<String>("filename");
let content_dir = ensure_directory(
post_matches
.get_one::<String>("content-dir")
.map(|s| s.as_str())
.unwrap_or("content"),
"Ensuring content directory for post",
)?;
info!("Creating new post: {}", title);
debug!("Content directory: {}", content_dir.display());
debug!("Custom filename: {:?}", filename);
create_post(&content_dir, title, filename)
}
pub fn handle_page(page_matches: &ArgMatches) -> KrikResult<()> {
let _span = logging::get_logger("page");
let _enter = _span.enter();
let title = page_matches
.get_one::<String>("title")
.map(|s| s.as_str())
.unwrap_or("New page");
let filename = page_matches.get_one::<String>("filename");
let content_dir = ensure_directory(
page_matches
.get_one::<String>("content-dir")
.map(|s| s.as_str())
.unwrap_or("content"),
"Ensuring content directory for page",
)?;
info!("Creating new page: {}", title);
debug!("Content directory: {}", content_dir.display());
debug!("Custom filename: {:?}", filename);
create_page(&content_dir, title, filename)
}
pub async fn handle_lint(lint_matches: &ArgMatches) -> KrikResult<()> {
let _span = logging::get_logger("lint");
let _enter = _span.enter();
let input_dir = validate_directory(
lint_matches
.get_one::<String>("input")
.map(|s| s.as_str())
.unwrap_or("content"),
"Validating --input directory for lint",
)?;
let strict = lint_matches.get_flag("strict");
let check_links = lint_matches.get_flag("check-links");
let create_report = lint_matches.get_flag("create-report");
let _verbose = lint_matches.get_flag("verbose");
info!("🔎 Linting content in: {}", input_dir.display());
debug!("Strict mode: {}", strict);
debug!("Check links: {}", check_links);
debug!("Create report: {}", create_report);
debug!("Starting content validation...");
debug!("Verbose logging enabled");
let report = if check_links {
info!("🔗 Checking links for validity...");
lint_content_with_links(&input_dir).await?
} else {
lint_content(&input_dir)?
};
info!("Scanned {} file(s)", report.files_scanned);
debug!("Validation completed successfully");
if create_report {
match generate_html_report(&report, check_links) {
Ok(filename) => {
info!("📄 HTML report generated: {}", filename);
}
Err(e) => {
warn!("Failed to generate HTML report: {}", e);
}
}
}
if !report.warnings.is_empty() {
warn!("Found {} warning(s):", report.warnings.len());
for w in &report.warnings {
warn!(" - {}", w);
}
}
if !report.broken_links.is_empty() {
error!("Found {} broken link(s):", report.broken_links.len());
for broken_link in &report.broken_links {
error!(
" - {}:{} - {} ({})",
broken_link.file_path.display(),
broken_link.line_number,
broken_link.url,
broken_link.error
);
}
}
let has_failures = !report.errors.is_empty()
|| !report.broken_links.is_empty()
|| (strict && !report.warnings.is_empty());
if has_failures {
if !report.errors.is_empty() {
error!("Found {} error(s):", report.errors.len());
for e in &report.errors {
error!(" - {}", e);
}
}
if strict && !report.warnings.is_empty() {
error!(
"Strict mode: treating {} warning(s) as error(s)",
report.warnings.len()
);
}
return Err(KrikError::Content(Box::new(crate::error::ContentError {
kind: crate::error::ContentErrorKind::ValidationFailed({
let mut msgs = report.errors.clone();
if strict {
msgs.extend(report.warnings.clone());
}
for broken_link in &report.broken_links {
msgs.push(format!(
"{}:{} - Broken link: {} ({})",
broken_link.file_path.display(),
broken_link.line_number,
broken_link.url,
broken_link.error
));
}
msgs
}),
path: None,
context: "Content lint failed".to_string(),
})));
}
if check_links {
info!("✅ No lint errors or broken links found");
} else {
info!("✅ No lint errors found");
}
Ok(())
}
pub fn handle_generate(matches: &ArgMatches) -> KrikResult<()> {
let _span = logging::get_logger("generate");
let _enter = _span.enter();
let input_dir = validate_directory(
matches
.get_one::<String>("input")
.map(|s| s.as_str())
.unwrap_or("content"),
"Validating --input directory for generate",
)?;
let output_dir = ensure_directory(
matches
.get_one::<String>("output")
.map(|s| s.as_str())
.unwrap_or("_site"),
"Ensuring --output directory for generate",
)?;
let theme_dir = resolve_theme_path(
matches.get_one::<String>("theme").map(|s| s.as_str()),
&input_dir,
)?;
info!("Scanning files in: {}", input_dir.display());
info!("Output directory: {}", output_dir.display());
debug!(
"Theme directory: {:?}",
theme_dir.as_ref().map(|p| p.display())
);
let generator = SiteGenerator::new(&input_dir, &output_dir, theme_dir.as_ref())
.map_err(|e| match &e {
KrikError::Theme(theme_err) => {
error!("Theme Error: {theme_err}");
error!("Suggestion: Check that the theme directory exists and contains required templates");
e
}
_ => e,
})?;
generator.generate_site().map_err(|e| {
match &e {
KrikError::Generation(gen_err) => match &gen_err.kind {
crate::error::GenerationErrorKind::NoContent => {
error!("No Content Error: {e}");
error!("Suggestion: Check that the content directory contains markdown files");
}
_ => {
error!("Generation Error: {e}");
}
},
KrikError::Io(_) => {
error!("IO Error: {e}");
error!("Suggestion: Check file permissions and disk space");
}
KrikError::Markdown(_) => {
error!("Markdown Error: {e}");
error!("Suggestion: Fix the markdown or front matter syntax error");
}
_ => {
error!("Error: {e}");
}
}
e
})?;
info!("Site generated successfully!");
Ok(())
}