1#![warn(clippy::pedantic)]
2
3use clap::Parser;
4
5use rst_parser::parse;
6use rst_renderer::{
7 SchemaSettings, render_html, render_json, render_json_schema_document, render_xml,
8};
9
10use std::io::{self, Read};
11
12#[derive(Debug, Clone, clap::ValueEnum)]
13enum Format {
14 Json,
15 Xml,
16 Html,
17}
18
19#[derive(Debug, Default, Clone, clap::ValueEnum)]
20enum SchemaVersion {
21 #[default]
23 #[value(name = "draft7")]
24 Draft07,
25 #[value(name = "2019-09")]
26 Draft2019_09,
27 #[value(name = "2020-12")]
28 Draft2020_12,
29 #[value(name = "openapi3")]
30 OpenApi3,
31}
32
33#[derive(Debug, Parser)]
34struct Cli {
35 #[arg(short = 'f', long, default_value = "html")]
37 format: Format,
38 file: Option<String>,
40 #[command(flatten)]
41 verbosity: clap_verbosity_flag::Verbosity,
42 #[arg(long, num_args = ..=1, require_equals = true, default_missing_value = "draft7")]
44 schema: Option<SchemaVersion>,
45}
46
47fn main() -> Result<(), anyhow::Error> {
48 let args = Cli::parse();
49
50 let level_filter = args
51 .verbosity
52 .log_level()
53 .map_or(log::LevelFilter::Off, |l| l.to_level_filter());
54 env_logger::Builder::new()
55 .filter(Some("rst"), level_filter)
56 .filter(None, log::Level::Warn.to_level_filter())
57 .try_init()?;
58
59 let stdout = std::io::stdout();
60
61 if let Some(schema) = &args.schema {
62 let settings = match schema {
63 SchemaVersion::Draft07 => SchemaSettings::draft07(),
64 SchemaVersion::Draft2019_09 => SchemaSettings::draft2019_09(),
65 SchemaVersion::Draft2020_12 => SchemaSettings::draft2020_12(),
66 SchemaVersion::OpenApi3 => SchemaSettings::openapi3(),
67 };
68 render_json_schema_document(stdout, settings, level_filter.to_level().is_some());
69 return Ok(());
70 }
71
72 let content = preprocess_content(args.file.as_deref())?;
73 let document = parse(&content)?;
74 match args.format {
75 Format::Json => render_json(&document, stdout)?,
76 Format::Xml => render_xml(&document, stdout)?,
77 Format::Html => render_html(&document, stdout, true)?,
78 }
79 Ok(())
80}
81
82fn preprocess_content(file: Option<&str>) -> Result<String, clap::Error> {
83 let mut content = if let Some(file) = file {
84 std::fs::read_to_string(file)?
85 } else {
86 let mut stdin = String::new();
87 io::stdin().read_to_string(&mut stdin)?;
88 stdin
89 };
90 content = content.replace('\t', " ".repeat(8).as_ref());
91 if !content.ends_with('\n') {
92 content.push('\n');
93 }
94 Ok(content)
95}