Skip to main content

mdbook_preprocessor_utils/
lib.rs

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