mdbook-tabs 1.0.4

mdBook plugin for rendering content in tabs.
Documentation
use std::{
    env, fs,
    io::{self, Read},
};

use anyhow::{Result, anyhow};
use clap::{Args, Parser, Subcommand};
use log::warn;
use mdbook_driver::MDBook;
use mdbook_preprocessor::Preprocessor;
use mdbook_preprocessor::{MDBOOK_VERSION, parse_input};
use mdbook_tabs::TabsPreprocessor;
use semver::{Version, VersionReq};

#[derive(Parser)]
#[command(version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    Install,
    Supports(SupportsArgs),
}

#[derive(Args)]
struct SupportsArgs {
    renderer: String,
}

fn main() -> Result<()> {
    let mut logger = env_logger::builder();
    if env::var("RUST_LOG").is_err() {
        logger.filter_module("mdbook_tabs", log::LevelFilter::Info);
    }
    logger.init();

    let cli = Cli::parse();
    let preprocessor = TabsPreprocessor::new();

    match &cli.command {
        Some(subcommand) => match subcommand {
            Commands::Install => handle_install(),
            Commands::Supports(args) => handle_supports(&preprocessor, args),
        },
        None => handle_preprocessing(&preprocessor, io::stdin()),
    }
}

fn handle_install() -> Result<()> {
    let book = MDBook::load(env::current_dir()?)?;
    let directory = book.root.join("theme");

    if !directory.exists() {
        fs::create_dir(&directory)?;
    }

    let css_content = include_str!("../theme/tabs.css");
    let js_content = include_str!("../theme/tabs.js");

    fs::write(directory.join("tabs.css"), css_content)?;
    fs::write(directory.join("tabs.js"), js_content)?;

    Ok(())
}

fn handle_supports(
    preprocessor: &dyn Preprocessor,
    SupportsArgs { renderer }: &SupportsArgs,
) -> Result<()> {
    match preprocessor.supports_renderer(renderer)? {
        true => Ok(()),
        false => Err(anyhow!("Renderer `{renderer}` is not supported.")),
    }
}

fn handle_preprocessing<R: Read>(preprocessor: &dyn Preprocessor, reader: R) -> Result<()> {
    let (ctx, book) = parse_input(reader)?;

    let book_version = Version::parse(&ctx.mdbook_version)?;
    let version_req = VersionReq::parse(MDBOOK_VERSION)?;

    if !version_req.matches(&book_version) {
        warn!(
            "The {} plugin was built against version {} of mdbook, but we're being called from version {}",
            preprocessor.name(),
            MDBOOK_VERSION,
            ctx.mdbook_version
        );
    }

    let processed_book = preprocessor.run(&ctx, book)?;
    serde_json::to_writer(io::stdout(), &processed_book)?;

    Ok(())
}