use crate::init_logging;
use anyhow::Result;
use clap::{CommandFactory, Parser, Subcommand};
use std::path::PathBuf;
const DEFAULT_PORT: u16 = 3000;
#[derive(Parser)]
#[command(name = "nano-web")]
#[command(about = "Static file server built with Rust")]
#[command(
long_about = "Static file server built with Rust\nRepository: https://github.com/radiosilence/nano-web"
)]
#[command(version)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Commands>,
#[arg(long = "dir", default_value = "public")]
#[arg(help = "Directory to serve")]
pub dir: PathBuf,
#[arg(short = 'p', long = "port", default_value_t = DEFAULT_PORT)]
#[arg(help = "Port to listen on")]
pub port: u16,
#[arg(short = 'd', long = "dev")]
#[arg(help = "Check/reload files if modified")]
pub dev: bool,
#[arg(long = "spa")]
#[arg(help = "Enable SPA mode (serve index.html for all routes)")]
pub spa: bool,
#[arg(long = "config-prefix", default_value = "VITE_")]
#[arg(help = "Environment variable prefix for config injection")]
pub config_prefix: String,
#[arg(long = "log-level", default_value = "info")]
#[arg(help = "Log level (debug, info, warn, error)")]
pub log_level: String,
#[arg(long = "log-format", default_value = "console")]
#[arg(help = "Log format (json, console)")]
pub log_format: String,
#[arg(long = "log-requests")]
#[arg(help = "Log HTTP requests")]
pub log_requests: bool,
}
#[derive(Subcommand)]
pub enum Commands {
#[command(about = "Start the web server")]
Serve {
#[arg(help = "Directory to serve")]
directory: Option<PathBuf>,
#[arg(short = 'p', long = "port")]
#[arg(help = "Port to listen on")]
port: Option<u16>,
#[arg(short = 'd', long = "dev")]
#[arg(help = "Check/reload files if modified")]
dev: bool,
#[arg(long = "spa")]
#[arg(help = "Enable SPA mode (serve index.html for all routes)")]
spa: bool,
#[arg(long = "config-prefix")]
#[arg(help = "Environment variable prefix for config injection")]
config_prefix: Option<String>,
#[arg(long = "log-level")]
#[arg(help = "Log level (debug, info, warn, error)")]
log_level: Option<String>,
#[arg(long = "log-format")]
#[arg(help = "Log format (json, console)")]
log_format: Option<String>,
#[arg(long = "log-requests")]
#[arg(help = "Log HTTP requests")]
log_requests: bool,
},
#[command(about = "Show version information")]
Version,
#[command(about = "Generate completion script")]
Completion {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
}
impl Cli {
pub async fn run(self) -> Result<()> {
if self.command.is_none() || !matches!(self.command, Some(Commands::Serve { .. })) {
init_logging(&self.log_level, &self.log_format);
}
match self.command {
Some(Commands::Serve {
ref directory,
port,
dev,
spa,
config_prefix,
log_level,
log_format,
log_requests,
}) => {
let public_dir = self.dir.clone();
let serve_dir = directory.clone().unwrap_or(public_dir);
let final_log_level = log_level.unwrap_or(self.log_level);
let final_log_format = log_format.unwrap_or(self.log_format);
init_logging(&final_log_level, &final_log_format);
crate::server::start_server(crate::server::ServeConfig {
public_dir: serve_dir,
port: port.unwrap_or(self.port),
dev: dev || self.dev,
spa_mode: spa || self.spa,
config_prefix: config_prefix.unwrap_or(self.config_prefix),
log_requests: log_requests || self.log_requests,
})
.await
}
Some(Commands::Version) => {
println!("{}", full_version());
println!("Static file server built with Rust");
println!("Repository: https://github.com/radiosilence/nano-web");
Ok(())
}
Some(Commands::Completion { shell }) => {
generate_completion(shell);
Ok(())
}
None => {
let mut cmd = Self::command();
cmd.print_help()?;
Ok(())
}
}
}
}
fn full_version() -> String {
format!("nano-web v{}", env!("CARGO_PKG_VERSION"))
}
fn generate_completion(shell: clap_complete::Shell) {
use clap_complete::generate;
use std::io;
let mut cmd = Cli::command();
generate(shell, &mut cmd, "nano-web", &mut io::stdout());
}