rst/
main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
use clap::Parser;

use rst_parser::parse;
use rst_renderer::{render_html, render_json, render_xml};

use std::io::{self, Read};

#[derive(Debug, Clone, clap::ValueEnum)]
#[allow(non_camel_case_types)]
enum Format {
    json,
    xml,
    html,
}

#[derive(Debug, Parser)]
struct Cli {
    /// Output format
    #[arg(short = 'f', long, default_value = "html")]
    format: Format,
    /// Input file
    file: Option<String>,
    #[command(flatten)]
    verbosity: clap_verbosity_flag::Verbosity,
}

fn main() -> Result<(), anyhow::Error> {
    let args = Cli::parse();

    let level_filter = args.verbosity.log_level().unwrap().to_level_filter();
    env_logger::Builder::new()
        .filter(Some("rst"), level_filter)
        .filter(None, log::Level::Warn.to_level_filter())
        .try_init()?;

    let content = preprocess_content(args.file.as_deref())?;
    let document = parse(&content)?;
    let stdout = std::io::stdout();
    match args.format {
        Format::json => render_json(&document, stdout)?,
        Format::xml => render_xml(&document, stdout)?,
        Format::html => render_html(&document, stdout, true)?,
    }
    Ok(())
}

fn preprocess_content(file: Option<&str>) -> Result<String, clap::Error> {
    let mut content = if let Some(file) = file {
        std::fs::read_to_string(file)?
    } else {
        let mut stdin = String::new();
        io::stdin().read_to_string(&mut stdin)?;
        stdin
    };
    content = content.replace('\t', " ".repeat(8).as_ref());
    if !content.ends_with('\n') {
        content.push('\n');
    }
    Ok(content)
}