rst/
main.rs

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    // tooling is hopelessly outdated, draft 7 is kind of the best bet
22    #[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    /// Output format
36    #[arg(short = 'f', long, default_value = "html")]
37    format: Format,
38    /// Input file
39    file: Option<String>,
40    #[command(flatten)]
41    verbosity: clap_verbosity_flag::Verbosity,
42    /// Print schema
43    #[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}