cyberstorm/src/main.rs

157 lines
4.2 KiB
Rust

use std::env;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use anyhow::{anyhow, Context, Error};
use chrono::Local;
use log::LevelFilter;
use structopt::StructOpt;
use tokio::fs;
use cyberstorm::registry::{DirectoryRegistry, SupportedRegistry};
#[cfg(feature = "mdbook-renderer")]
use cyberstorm::render::mdbook::MDBookEngine;
use cyberstorm::render::Renderer;
#[derive(Debug, StructOpt)]
#[structopt(name = "cyberstorm")]
struct Opt {
/// Registry to use.
registry: RegistryOpt,
#[structopt(subcommand)]
cmd: Cmd,
}
#[derive(Debug, StructOpt)]
enum Cmd {
/// Renders the content into a mdBook.
#[cfg(feature = "mdbook-renderer")]
MdbookRender(MdbookRenderOpt),
/// Renders the content into a mdBook and builds it.
#[cfg(feature = "mdbook-renderer")]
MdbookBuild(MdbookRenderOpt),
}
#[derive(Debug, StructOpt)]
#[cfg(feature = "mdbook-renderer")]
struct MdbookRenderOpt {
/// Path to the output mdBook root or `book.toml`.
#[structopt(parse(from_os_str), default_value = "./book.toml")]
book: PathBuf,
}
#[derive(Debug)]
enum RegistryOpt {
Directory(PathBuf),
}
impl FromStr for RegistryOpt {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::Directory(PathBuf::from(s)))
}
}
async fn run(opt: Opt) -> Result<(), Error> {
let registry = match &opt.registry {
RegistryOpt::Directory(path) => SupportedRegistry::Directory(DirectoryRegistry::new(path)),
};
match &opt.cmd {
#[cfg(feature = "mdbook-renderer")]
Cmd::MdbookRender(render_opt) => {
let (root, config) = load_mdbook_config(&render_opt.book).await?;
render_mdbook(&registry, &root, &config).await
}
#[cfg(feature = "mdbook-renderer")]
Cmd::MdbookBuild(render_opt) => {
let (root, config) = load_mdbook_config(&render_opt.book).await?;
render_mdbook(&registry, &root, &config).await?;
mdbook::MDBook::load_with_config(root, config)?.build()
}
}
}
#[cfg(feature = "mdbook-renderer")]
async fn load_mdbook_config(book: &Path) -> Result<(PathBuf, mdbook::Config), Error> {
let (root, config_file) = book
.canonicalize()
.ok()
.and_then(|book| {
let (root, config) = if book.extension().is_some() {
(book.parent().unwrap().to_owned(), book.to_owned())
} else {
let config_file = book.join("book.toml");
(book, config_file)
};
if config.is_file() {
Some((root, config))
} else {
None
}
})
.ok_or_else(|| {
anyhow!(
"expected mdbook config path (eg. `book.toml`) at `{}`",
book.display()
)
})?;
let config = fs::read_to_string(config_file)
.await
.context("failed to read mdbook configuration")?
.parse()?;
Ok((root, config))
}
#[cfg(feature = "mdbook-renderer")]
async fn render_mdbook(
registry: &SupportedRegistry,
root: &Path,
config: &mdbook::Config,
) -> Result<(), Error> {
let engine = MDBookEngine::new(root, config);
Renderer::new(engine, registry)
.generate()
.await
.context("failed to generate mdbook content")
}
fn init_logger() {
let mut builder = env_logger::Builder::new();
builder.format(|formatter, record| {
writeln!(
formatter,
"{} [{}] ({}): {}",
Local::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
record.target(),
record.args()
)
});
if let Ok(var) = env::var("RUST_LOG") {
builder.parse_filters(&var);
} else {
// if no RUST_LOG provided, default to logging at the Info level
builder.filter(None, LevelFilter::Info);
// Filter extraneous html5ever not-implemented messages
builder.filter(Some("html5ever"), LevelFilter::Error);
}
builder.init();
}
#[tokio::main]
async fn main() {
init_logger();
if let Err(err) = run(Opt::from_args()).await {
log::error!("{:#}", err);
}
}