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}