Skip to main content

mdbook_structured/
cli.rs

1use std::ffi::OsString;
2use std::io::{Read, Write};
3use std::path::{Path, PathBuf};
4use std::process::ExitCode;
5
6use crate::protocol::write_diagnostic;
7
8enum Command {
9    Run(Phase),
10    Supports { phase: Phase, renderer: String },
11    Install { directory: Option<PathBuf> },
12}
13
14pub(crate) enum Phase {
15    Render,
16    RewriteLinks,
17}
18
19pub fn run_with_io(
20    args: impl IntoIterator<Item = OsString>,
21    current_dir: &Path,
22    stdin: &mut dyn Read,
23    stdout: &mut dyn Write,
24    stderr: &mut dyn Write,
25) -> ExitCode {
26    match parse_command(args) {
27        Ok(Command::Supports { phase, renderer }) if renderer == "html" => {
28            let _ = phase;
29            ExitCode::SUCCESS
30        }
31        Ok(Command::Supports { renderer, .. }) => write_diagnostic(
32            stderr,
33            "UnsupportedRenderer",
34            &format!("mdbook-structured supports only the html renderer, not {renderer}"),
35        ),
36        Ok(Command::Run(phase)) => crate::protocol::run_phase(phase, stdin, stdout, stderr),
37        Ok(Command::Install { directory }) => {
38            crate::protocol::run_install(current_dir, directory.as_deref(), stdout, stderr)
39        }
40        Err(message) => write_diagnostic(stderr, "Protocol", &message),
41    }
42}
43
44fn parse_command(args: impl IntoIterator<Item = OsString>) -> Result<Command, String> {
45    let mut arguments = args.into_iter();
46    let _program = arguments.next();
47    let arguments: Vec<_> = arguments.collect();
48
49    match arguments.as_slice() {
50        [command] if command == "install" => Ok(Command::Install { directory: None }),
51        [command, directory] if command == "install" => Ok(Command::Install {
52            directory: Some(PathBuf::from(directory)),
53        }),
54        [phase] => Ok(Command::Run(parse_phase(phase)?)),
55        [phase, supports, renderer] if supports == "supports" => Ok(Command::Supports {
56            phase: parse_phase(phase)?,
57            renderer: renderer
58                .to_str()
59                .ok_or_else(|| "renderer name is not valid Unicode".to_owned())?
60                .to_owned(),
61        }),
62        _ => Err(
63            "expected render or rewrite-links with optional supports <renderer>, or install [DIR]"
64                .to_owned(),
65        ),
66    }
67}
68
69fn parse_phase(value: &OsString) -> Result<Phase, String> {
70    match value.to_str() {
71        Some("render") => Ok(Phase::Render),
72        Some("rewrite-links") => Ok(Phase::RewriteLinks),
73        Some(other) => Err(format!("unknown preprocessor phase {other}")),
74        None => Err("preprocessor phase is not valid Unicode".to_owned()),
75    }
76}