stream_line_bridge/
stream_line_bridge.rs1use std::env;
2use std::error::Error;
3use std::fs;
4use std::io::{self, Write};
5
6use highlight_spans::{Grammar, SpanHighlighter};
7use render_ansi::{ColorMode, StreamLineRenderer};
8use theme_engine::load_theme;
9
10#[derive(Debug, Clone)]
11struct Options {
12 source_path: String,
13 theme_name: String,
14 grammar_name: String,
15 color_mode: ColorMode,
16 previous_source_path: Option<String>,
17}
18
19fn parse_grammar(input: &str) -> Result<Grammar, String> {
21 Grammar::from_name(input).ok_or_else(|| {
22 format!(
23 "unknown grammar '{}'; use one of: {}",
24 input,
25 Grammar::supported_names().join(", ")
26 )
27 })
28}
29
30fn parse_color_mode(input: &str) -> Result<ColorMode, String> {
32 ColorMode::from_name(input).ok_or_else(|| {
33 format!(
34 "unknown color mode '{}'; use one of: {}",
35 input,
36 ColorMode::supported_names().join(", ")
37 )
38 })
39}
40
41fn parse_args(args: &[String]) -> Result<Options, String> {
45 if args.len() < 2 {
46 return Err("missing source file".to_string());
47 }
48
49 let source_path = args[1].clone();
50 let theme_name = args
51 .get(2)
52 .cloned()
53 .unwrap_or_else(|| "tokyonight-dark".to_string());
54 let grammar_name = args
55 .get(3)
56 .cloned()
57 .unwrap_or_else(|| "objectscript".to_string());
58
59 let mut color_mode = ColorMode::TrueColor;
60 let mut previous_source_path: Option<String> = None;
61 let mut i = 4usize;
62 while i < args.len() {
63 match args[i].as_str() {
64 "--prev" => {
65 i += 1;
66 let Some(value) = args.get(i) else {
67 return Err("expected value after --prev".to_string());
68 };
69 previous_source_path = Some(value.clone());
70 }
71 "--color-mode" => {
72 i += 1;
73 let Some(value) = args.get(i) else {
74 return Err("expected value after --color-mode".to_string());
75 };
76 color_mode = parse_color_mode(value)?;
77 }
78 flag => return Err(format!("unknown option '{flag}'")),
79 }
80 i += 1;
81 }
82
83 Ok(Options {
84 source_path,
85 theme_name,
86 grammar_name,
87 color_mode,
88 previous_source_path,
89 })
90}
91
92fn print_usage() {
94 eprintln!("Usage:");
95 eprintln!(
96 " cargo run -p render-ansi --example stream_line_bridge -- <source-file> [theme] [grammar] [--color-mode truecolor|ansi256|ansi16] [--prev <old-source-file>]"
97 );
98 eprintln!();
99 eprintln!("Notes:");
100 eprintln!(" - Inputs must be single-line text files (no newline).");
101 eprintln!();
102 eprintln!("Examples:");
103 eprintln!(" cargo run -p render-ansi --example stream_line_bridge -- new.sql");
104 eprintln!(
105 " cargo run -p render-ansi --example stream_line_bridge -- new.sql tokyonight-dark sql --prev old.sql"
106 );
107 eprintln!(
108 " cargo run -p render-ansi --example stream_line_bridge -- new.sql tokyonight-dark sql --color-mode ansi16"
109 );
110}
111
112fn main() -> Result<(), Box<dyn Error>> {
121 let args: Vec<String> = env::args().collect();
122 let options = match parse_args(&args) {
123 Ok(options) => options,
124 Err(err) => {
125 eprintln!("invalid arguments: {err}");
126 eprintln!();
127 print_usage();
128 return Ok(());
129 }
130 };
131
132 let grammar =
133 parse_grammar(&options.grammar_name).map_err(|msg| format!("invalid grammar: {msg}"))?;
134 let source = fs::read(&options.source_path)?;
135 let theme = load_theme(&options.theme_name)?;
136 let mut highlighter = SpanHighlighter::new()?;
137 let mut renderer = StreamLineRenderer::new();
138 renderer.set_color_mode(options.color_mode);
139
140 if let Some(previous_source_path) = &options.previous_source_path {
141 let previous_source = fs::read(previous_source_path)?;
142 let _ = renderer.highlight_line_to_patch(
143 &mut highlighter,
144 &previous_source,
145 grammar,
146 &theme,
147 )?;
148 }
149
150 let patch = renderer.highlight_line_to_patch(&mut highlighter, &source, grammar, &theme)?;
151 print!("{patch}");
152 io::stdout().flush()?;
153
154 Ok(())
155}