use anyhow::Result;
use log::LevelFilter;
use std::{env, process, sync::Once};
#[cfg(feature = "cli")]
use clap::Parser;
#[cfg(feature = "cli")]
use frontmatter_gen::cli::Cli;
#[cfg(feature = "ssg")]
use frontmatter_gen::ssg::SsgCommand;
static INIT: Once = Once::new();
#[tokio::main]
async fn main() -> Result<()> {
setup_logging();
log::info!("Starting Frontmatter Generator");
log::info!(
"Initializing with features `{}`",
get_enabled_features()
);
let result = execute_command().await;
if let Err(ref e) = result {
log::error!("Application error: {:#}", e);
eprintln!("Error: {}", e);
process::exit(1);
}
log::info!("Process completed successfully");
Ok(())
}
fn setup_logging() {
INIT.call_once(|| {
let env = env::var("RUST_LOG").unwrap_or_else(|_| "debug".to_string());
let level = match env.to_lowercase().as_str() {
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"info" => LevelFilter::Info,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
"off" => LevelFilter::Off,
_ => {
eprintln!(
"Invalid RUST_LOG value '{}', defaulting to 'debug'",
env
);
LevelFilter::Debug
}
};
if log::set_logger(&LOGGER).is_ok() {
log::set_max_level(level);
} else {
eprintln!("Logger already initialized.");
}
});
}
async fn execute_command() -> Result<()> {
#[cfg(all(feature = "ssg", not(feature = "cli")))]
{
log::info!("Executing in SSG mode");
let ssg_command = SsgCommand::parse();
return ssg_command.execute().await;
}
#[cfg(all(feature = "cli", not(feature = "ssg")))]
{
log::info!("Executing in CLI mode");
let cli_command = Cli::parse();
return cli_command.process().await;
}
#[cfg(all(feature = "cli", feature = "ssg"))]
{
log::info!("Executing with both CLI and SSG features enabled");
let args: Vec<String> = env::args().collect();
if args.len() > 1 && (args[1] == "build" || args[1] == "serve")
{
let ssg_command = SsgCommand::parse();
ssg_command.execute().await
} else {
let cli_command = Cli::parse();
cli_command.process().await
}
}
#[cfg(not(any(feature = "cli", feature = "ssg")))]
{
log::error!("No features enabled");
eprintln!("Error: No features enabled. Enable 'cli' or 'ssg' in Cargo.toml.");
process::exit(1);
}
}
fn get_enabled_features() -> String {
let mut features = Vec::new();
#[cfg(feature = "cli")]
features.push("cli");
#[cfg(feature = "ssg")]
features.push("ssg");
if features.is_empty() {
"none".to_string()
} else {
features.join(", ")
}
}
#[derive(Clone, Copy)]
struct Logger;
static LOGGER: Logger = Logger;
impl log::Log for Logger {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
let level_color = match record.level() {
log::Level::Error => "\x1b[31m", log::Level::Warn => "\x1b[33m", log::Level::Info => "\x1b[32m", log::Level::Debug => "\x1b[36m", log::Level::Trace => "\x1b[90m", };
eprintln!(
"{}[{}]\x1b[0m {}",
level_color,
record.level(),
record.args()
);
}
}
fn flush(&self) {}
}
#[cfg(test)]
mod tests {
use super::*;
use log::Log;
use std::sync::Once;
#[test]
fn test_logging_setup() {
env::remove_var("RUST_LOG");
setup_logging();
env::set_var("RUST_LOG", "debug");
setup_logging();
assert_eq!(env::var("RUST_LOG").unwrap(), "debug");
}
#[test]
fn test_enabled_features() {
let features = get_enabled_features();
assert!(
!features.is_empty(),
"Should list enabled features or 'none'"
);
}
#[tokio::test]
#[ignore = "This test is only for interactive testing"]
async fn test_command_execution() {
let original_args: Vec<String> = env::args().collect();
env::set_var("CARGO_PKG_VERSION", "0.1.0");
let result = execute_command().await;
assert!(result.is_err());
env::set_var("CARGO_PKG_VERSION", "0.1.0");
let _args = ["program".to_string(), "help".to_string()];
env::set_var("CARGO_PKG_NAME", "frontmatter-gen");
let result = execute_command().await;
assert!(result.is_err());
for (i, arg) in original_args.iter().enumerate() {
if i == 0 {
env::set_var("CARGO_PKG_NAME", arg);
}
}
}
#[test]
fn test_help_output() {
env::set_var("CARGO_PKG_NAME", "frontmatter-gen");
env::set_var("CARGO_PKG_VERSION", "0.1.0");
#[cfg(feature = "cli")]
{
let cli =
Cli::try_parse_from(["frontmatter-gen", "--help"]);
assert!(cli.is_err());
let err = cli.unwrap_err();
let output = err.to_string();
assert!(output.contains("Usage:"));
assert!(output.contains("Commands:"));
assert!(output.contains("extract"));
assert!(output.contains("validate"));
}
}
#[test]
fn test_version_output() {
env::set_var("CARGO_PKG_NAME", "frontmatter-gen");
env::set_var("CARGO_PKG_VERSION", "0.0.5");
#[cfg(feature = "cli")]
{
let cli =
Cli::try_parse_from(["frontmatter-gen", "--version"]);
assert!(
cli.is_err(),
"Expected an error for version output"
);
let err = cli.unwrap_err();
let output = err.to_string();
assert!(
output.contains("0.0.5"),
"Version output does not contain '0.0.5'. Actual output: {}",
output
);
}
}
static INIT: Once = Once::new();
fn init_logging() {
INIT.call_once(|| {
setup_logging();
});
}
#[test]
fn test_logger_enabled() {
init_logging();
let logger = Logger;
let metadata =
log::Metadata::builder().level(log::Level::Info).build();
assert!(logger.enabled(&metadata));
}
#[test]
fn test_logger_log_levels() {
init_logging();
let logger = Logger;
let levels = vec![
log::Level::Error,
log::Level::Warn,
log::Level::Info,
log::Level::Debug,
log::Level::Trace,
];
for level in levels {
let record = log::Record::builder()
.args(format_args!("Test log message"))
.level(level)
.target("test")
.build();
logger.log(&record);
}
}
#[test]
fn test_setup_logging_failure() {
setup_logging();
setup_logging();
}
#[cfg(not(any(feature = "cli", feature = "ssg")))]
#[tokio::test]
async fn test_execute_command_no_features() {
let result = execute_command().await;
assert!(result.is_err());
}
#[cfg(not(any(feature = "cli", feature = "ssg")))]
#[test]
fn test_get_enabled_features_none() {
let features = get_enabled_features();
assert_eq!(features, "none");
}
#[cfg(all(feature = "cli", not(feature = "ssg")))]
mod cli_tests {
use super::*;
use clap::Parser;
use frontmatter_gen::cli::Cli;
#[tokio::test]
async fn test_execute_command_cli() {
init_logging();
use std::io::Write;
use tempfile::NamedTempFile;
let mut temp_file = NamedTempFile::new()
.expect("Failed to create temp file");
writeln!(temp_file, "Invalid frontmatter content")
.expect("Failed to write to temp file");
let file_path = temp_file.path().to_str().unwrap();
let args = vec![
"frontmatter-gen",
"validate",
file_path,
"--required",
"title,date",
];
let cli_command = Cli::try_parse_from(&args)
.expect("Failed to parse arguments");
let result = cli_command.process().await;
assert!(
result.is_err(),
"Expected validation to fail due to invalid content"
);
}
#[test]
fn test_get_enabled_features_cli() {
let features = get_enabled_features();
assert_eq!(features, "cli");
}
}
#[cfg(all(feature = "ssg", not(feature = "cli")))]
mod ssg_tests {
use super::*;
use clap::Parser;
use frontmatter_gen::ssg::SsgCommand;
#[tokio::test]
async fn test_execute_command_ssg() {
init_logging();
let args = vec![
"frontmatter-gen",
"build",
"--content-dir",
"content",
"--output-dir",
"public",
"--template-dir",
"templates",
];
let ssg_command = SsgCommand::try_parse_from(&args)
.expect("Failed to parse arguments");
let result = ssg_command.execute().await;
assert!(result.is_err());
}
#[test]
fn test_get_enabled_features_ssg() {
let features = get_enabled_features();
assert_eq!(features, "ssg");
}
}
#[cfg(all(feature = "cli", feature = "ssg"))]
mod cli_ssg_tests {
use super::*;
use clap::Parser;
use frontmatter_gen::{cli::Cli, ssg::SsgCommand};
use std::io::Write;
use tempfile::NamedTempFile;
#[tokio::test]
async fn test_execute_command_both_features() {
init_logging();
let args_ssg = vec![
"frontmatter-gen",
"build",
"--content-dir",
"content",
"--output-dir",
"public",
"--template-dir",
"templates",
];
let ssg_command = SsgCommand::try_parse_from(&args_ssg)
.expect("Failed to parse SSG arguments");
let result_ssg = ssg_command.execute().await;
assert!(result_ssg.is_err());
let mut temp_file = NamedTempFile::new()
.expect("Failed to create temp file");
writeln!(temp_file, "Invalid content")
.expect("Failed to write to temp file");
let file_path = temp_file.path().to_str().unwrap();
let args_cli = vec![
"frontmatter-gen",
"validate",
file_path,
"--required",
"title,date",
];
let cli_command = Cli::try_parse_from(&args_cli)
.expect("Failed to parse CLI arguments");
let result_cli = cli_command.process().await;
assert!(
result_cli.is_err(),
"Expected an error due to invalid content"
);
}
#[test]
fn test_get_enabled_features_both() {
let features = get_enabled_features();
assert_eq!(features, "cli, ssg");
}
#[test]
fn test_logging_with_invalid_rust_log_value() {
env::set_var("RUST_LOG", "invalid_level");
setup_logging();
assert_eq!(log::max_level(), LevelFilter::Debug);
}
#[test]
fn test_logging_with_empty_rust_log_value() {
env::set_var("RUST_LOG", "");
setup_logging();
assert_eq!(log::max_level(), LevelFilter::Debug);
}
#[test]
fn test_logger_flush() {
let logger = Logger;
logger.flush();
}
#[test]
#[cfg(all(feature = "ssg", feature = "cli"))]
fn test_get_enabled_features_order() {
let features = get_enabled_features();
assert!(
features == "cli, ssg" || features == "ssg, cli",
"Features should include both 'cli' and 'ssg'"
);
}
}
}