mdbook_preprocessor_utils/
lib.rs

1use std::{
2  env,
3  io::{self, Write},
4  process,
5};
6
7use chrono::Local;
8use clap::{arg, Command, CommandFactory};
9use env_logger::Builder;
10use log::LevelFilter;
11use mdbook::{
12  errors::Error,
13  preprocess::{CmdPreprocessor, Preprocessor},
14};
15use semver::{Version, VersionReq};
16
17mod copy_assets;
18mod html;
19mod processor;
20#[cfg(feature = "testing")]
21pub mod testing;
22
23pub use copy_assets::copy_assets;
24pub use html::HtmlElementBuilder;
25pub use mdbook;
26pub use processor::{Asset, SimplePreprocessor};
27pub use rayon;
28
29// This is copied verbatim from mdbook so the style is consistent.
30// https://github.com/rust-lang/mdBook/blob/94e0a44e152d8d7c62620e83e0632160977b1dd5/src/main.rs#L97-L121
31fn init_logger() {
32  let mut builder = Builder::new();
33
34  builder.format(|formatter, record| {
35    writeln!(
36      formatter,
37      "{} [{}] ({}): {}",
38      Local::now().format("%Y-%m-%d %H:%M:%S"),
39      record.level(),
40      record.target(),
41      record.args()
42    )
43  });
44
45  if let Ok(var) = env::var("RUST_LOG") {
46    builder.parse_filters(&var);
47  } else {
48    // if no RUST_LOG provided, default to logging at the Info level
49    builder.filter(None, LevelFilter::Info);
50    // Filter extraneous html5ever not-implemented messages
51    builder.filter(Some("html5ever"), LevelFilter::Error);
52  }
53
54  builder.init();
55}
56
57pub fn main<P: SimplePreprocessor>() {
58  init_logger();
59
60  let args = P::Args::command()
61    .subcommand(Command::new("supports").arg(arg!(<renderer> "Checks if renderer is supported")))
62    .get_matches();
63
64  let preprocessor = processor::SimplePreprocessorDriver::<P>::new();
65
66  match args.subcommand() {
67    Some(("supports", m)) => {
68      let renderer = m.get_one::<String>("renderer").unwrap();
69      handle_supports(&preprocessor, renderer);
70    }
71    _ => {
72      if let Err(e) = handle_preprocessing(&preprocessor) {
73        eprintln!("{}", e);
74        process::exit(1);
75      }
76    }
77  }
78}
79
80fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> {
81  let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
82
83  let book_version = Version::parse(&ctx.mdbook_version)?;
84  let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
85
86  if !version_req.matches(&book_version) {
87    eprintln!(
88      "Warning: The {} plugin was built against version {} of mdbook, \
89             but we're being called from version {}",
90      pre.name(),
91      mdbook::MDBOOK_VERSION,
92      ctx.mdbook_version
93    );
94  }
95
96  let processed_book = pre.run(&ctx, book)?;
97  serde_json::to_writer(io::stdout(), &processed_book)?;
98
99  Ok(())
100}
101
102fn handle_supports(pre: &dyn Preprocessor, renderer: &str) -> ! {
103  let supported = pre.supports_renderer(renderer);
104
105  // Signal whether the renderer is supported by exiting with 1 or 0.
106  if supported {
107    process::exit(0);
108  } else {
109    process::exit(1);
110  }
111}