use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
mod app;
use app::{scan_markdown_files, serve_markdown, DiagramOpts};
#[derive(Parser)]
#[command(name = "mdrvserve")]
#[command(about = "A simple HTTP server for markdown preview")]
#[command(version)]
struct Args {
path: PathBuf,
#[arg(short = 'H', long, default_value = "127.0.0.1")]
hostname: String,
#[arg(short, long, default_value = "3000")]
port: u16,
#[arg(short = 'd', long)]
debug: bool,
#[arg(long)]
trace: bool,
#[arg(short, long)]
open: bool,
#[arg(short, long)]
recursive: bool,
#[arg(long = "with-mermaid")]
with_mermaid: bool,
#[arg(long = "with-d2")]
with_d2: bool,
#[arg(long = "with-latex")]
with_latex: bool,
#[arg(long = "with-typst")]
with_typst: bool,
#[arg(long = "with-gfm", visible_alias = "gfm")]
with_gfm: bool,
#[arg(long = "include-html")]
include_html: bool,
#[arg(long = "include-typst")]
include_typst: bool,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
init_logging(args.debug, args.trace);
tracing::debug!(
path = ?args.path,
host = %args.hostname,
port = args.port,
recursive = args.recursive,
open = args.open,
"mdrvserve starting"
);
let absolute_path = args.path.canonicalize().unwrap_or(args.path);
let (base_dir, tracked_files, is_directory_mode) = if absolute_path.is_file() {
let base_dir = absolute_path
.parent()
.unwrap_or_else(|| std::path::Path::new("."))
.to_path_buf();
let tracked_files = vec![absolute_path];
(base_dir, tracked_files, false)
} else if absolute_path.is_dir() {
let tracked_files = scan_markdown_files(&absolute_path, args.recursive, args.include_html, args.include_typst)?;
if tracked_files.is_empty() {
anyhow::bail!("No markdown files found in directory");
}
(absolute_path, tracked_files, true)
} else {
anyhow::bail!("Path must be a file or directory");
};
serve_markdown(
base_dir,
tracked_files,
is_directory_mode,
args.recursive,
args.hostname,
args.port,
args.open,
DiagramOpts {
mermaid: args.with_mermaid,
d2: args.with_d2,
latex: args.with_latex,
typst: args.with_typst,
gfm: args.with_gfm,
},
args.include_html,
args.include_typst,
)
.await?;
Ok(())
}
fn init_logging(debug: bool, trace: bool) {
use tracing_subscriber::EnvFilter;
let default_level = if trace {
"trace"
} else if debug {
"debug"
} else {
"info"
};
let filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(default_level));
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_target(false)
.compact()
.init();
}