mod config;
mod fetch_documents;
mod generate_html_pages;
mod inventory;
mod server;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process;
use anyhow::Context;
use anyhow::Result;
use clap::Parser;
use clap::Subcommand;
use clap::crate_version;
use flexi_logger::Cleanup;
use flexi_logger::Criterion;
use flexi_logger::Duplicate;
use flexi_logger::FileSpec;
use flexi_logger::Logger;
use flexi_logger::Naming;
use flexi_logger::WriteMode;
use flexi_logger::colored_default_format;
use flexi_logger::detailed_format;
use log::error;
use log::info;
use schemars::schema_for;
use crate::config::Config;
use crate::config::License;
use crate::config::Site;
use crate::config::Source;
use crate::fetch_documents::Document;
use crate::fetch_documents::fetch_documents;
use crate::generate_html_pages::generate_html_error_pages;
use crate::generate_html_pages::generate_html_index_pages;
use crate::inventory::Inventory;
use crate::inventory::Module;
use crate::inventory::UncheckedInventory;
use crate::server::serve;
#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
GenerateDocs {
#[arg(long, default_value = "./docs", value_name = "PATH")]
output: PathBuf,
},
ValidateConfig {
#[arg(long, value_name = "PATH")]
file: PathBuf,
},
Serve {
#[arg(long, default_value = "0.0.0.0:8080")]
address: String,
#[arg(long, default_value = "config.toml", value_name = "PATH")]
config: PathBuf,
#[arg(long, default_value = ".", value_name = "PATH")]
output: PathBuf,
},
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let _logger = Logger::try_with_str("info")
.unwrap()
.log_to_file(
FileSpec::default()
.directory("logs")
.basename("typst4ei")
.suffix("log"),
)
.rotate(
Criterion::Size(30 * 1024 * 1024), Naming::Timestamps,
Cleanup::KeepLogFiles(15),
)
.duplicate_to_stderr(Duplicate::Info)
.format_for_files(detailed_format)
.format_for_stderr(colored_default_format)
.write_mode(WriteMode::Async)
.print_message()
.start()
.unwrap();
info!("starting Typst4EI v{}", crate_version!());
match run(cli).await {
Ok(_) => process::exit(0),
Err(err) => {
error!("{:?}", err);
process::exit(1);
}
}
}
async fn run(cli: Cli) -> Result<()> {
match cli.command {
Command::ValidateConfig { file } => {
Config::load(&file)?;
info!("config `{}` is valid", file.display());
Ok(())
}
Command::GenerateDocs { output } => {
let schema_config = schema_for!(Config);
let schema_inventory = schema_for!(UncheckedInventory);
fs::create_dir_all(&output)
.with_context(|| format!("unable to create directory `{}`", output.display()))?;
fs::write(
output.join(Path::new("inventory.schema.json")),
serde_json::to_string_pretty(&schema_inventory)?,
)?;
fs::write(
output.join(Path::new("config.schema.json")),
serde_json::to_string_pretty(&schema_config)?,
)?;
Ok(())
}
Command::Serve {
address,
config,
output,
} => {
let config = {
let mut config = Config::load(&config)?;
config.site.output_dir = output;
config
};
fs::create_dir_all(config.site.full_static_dir()).with_context(|| {
format!(
"unable to create directory `{}`",
config.site.full_static_dir().display()
)
})?;
fs::create_dir_all(config.site.full_files_dir()).with_context(|| {
format!(
"unable to create directory `{}`",
config.site.full_files_dir().display()
)
})?;
let css_path = config.site.full_static_dir().join("bootstrap.min.css");
fs::write(&css_path, include_str!("../static/bootstrap.min.css"))
.with_context(|| format!("unable to create file `{}`", css_path.display()))?;
let js_path = config.site.full_static_dir().join("auto-theme.js");
fs::write(&js_path, include_bytes!("../static/auto-theme.js"))
.with_context(|| format!("unable to create file `{}`", js_path.display()))?;
let logo_light_path = config.site.full_static_dir().join("logo-light.png");
fs::write(&logo_light_path, include_bytes!("../static/logo-light.png")).with_context(
|| format!("unable to create file `{}`", logo_light_path.display()),
)?;
let logo_dark_path = config.site.full_static_dir().join("logo-dark.png");
fs::write(&logo_dark_path, include_bytes!("../static/logo-dark.png"))
.with_context(|| format!("unable to create file `{}`", logo_dark_path.display()))?;
let logo_path = config.site.full_static_dir().join("logo.svg");
fs::write(&logo_path, include_bytes!("../static/logo.svg"))
.with_context(|| format!("unable to create file `{}`", logo_path.display()))?;
let favicon_path = config.site.full_static_dir().join("favicon.svg");
fs::write(&favicon_path, include_bytes!("../static/favicon.svg"))
.with_context(|| format!("unable to create file `{}`", favicon_path.display()))?;
serve(config, address).await
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn verify_cli() {
use clap::CommandFactory;
Cli::command().debug_assert();
}
}