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 preserve_terminal_background: bool,
17 previous_source_path: Option<String>,
18}
19
20fn parse_grammar(input: &str) -> Result<Grammar, String> {
22 Grammar::from_name(input).ok_or_else(|| {
23 format!(
24 "unknown grammar '{}'; use one of: {}",
25 input,
26 Grammar::supported_names().join(", ")
27 )
28 })
29}
30
31fn parse_color_mode(input: &str) -> Result<ColorMode, String> {
33 ColorMode::from_name(input).ok_or_else(|| {
34 format!(
35 "unknown color mode '{}'; use one of: {}",
36 input,
37 ColorMode::supported_names().join(", ")
38 )
39 })
40}
41
42fn parse_args(args: &[String]) -> Result<Options, String> {
46 if args.len() < 2 {
47 return Err("missing source file".to_string());
48 }
49
50 let source_path = args[1].clone();
51 let theme_name = args
52 .get(2)
53 .cloned()
54 .unwrap_or_else(|| "tokyonight-dark".to_string());
55 let grammar_name = args
56 .get(3)
57 .cloned()
58 .unwrap_or_else(|| "objectscript".to_string());
59
60 let mut color_mode = ColorMode::TrueColor;
61 let mut preserve_terminal_background = true;
62 let mut previous_source_path: Option<String> = None;
63 let mut i = 4usize;
64 while i < args.len() {
65 match args[i].as_str() {
66 "--prev" => {
67 i += 1;
68 let Some(value) = args.get(i) else {
69 return Err("expected value after --prev".to_string());
70 };
71 previous_source_path = Some(value.clone());
72 }
73 "--color-mode" => {
74 i += 1;
75 let Some(value) = args.get(i) else {
76 return Err("expected value after --color-mode".to_string());
77 };
78 color_mode = parse_color_mode(value)?;
79 }
80 "--theme-bg" => {
81 preserve_terminal_background = false;
82 }
83 "--terminal-bg" => {
84 preserve_terminal_background = true;
85 }
86 flag => return Err(format!("unknown option '{flag}'")),
87 }
88 i += 1;
89 }
90
91 Ok(Options {
92 source_path,
93 theme_name,
94 grammar_name,
95 color_mode,
96 preserve_terminal_background,
97 previous_source_path,
98 })
99}
100
101fn print_usage() {
103 eprintln!("Usage:");
104 eprintln!(
105 " cargo run -p render-ansi --example stream_line_bridge -- <source-file> [theme] [grammar] [--color-mode truecolor|ansi256|ansi16] [--theme-bg] [--prev <old-source-file>]"
106 );
107 eprintln!();
108 eprintln!("Notes:");
109 eprintln!(" - Inputs must be single-line text files (no newline).");
110 eprintln!();
111 eprintln!("Examples:");
112 eprintln!(" cargo run -p render-ansi --example stream_line_bridge -- new.sql");
113 eprintln!(
114 " cargo run -p render-ansi --example stream_line_bridge -- new.sql tokyonight-dark sql --prev old.sql"
115 );
116 eprintln!(
117 " cargo run -p render-ansi --example stream_line_bridge -- new.sql tokyonight-dark sql --color-mode ansi16"
118 );
119 eprintln!(
120 " cargo run -p render-ansi --example stream_line_bridge -- new.sql tokyonight-dark sql --theme-bg"
121 );
122}
123
124fn main() -> Result<(), Box<dyn Error>> {
133 let args: Vec<String> = env::args().collect();
134 let options = match parse_args(&args) {
135 Ok(options) => options,
136 Err(err) => {
137 eprintln!("invalid arguments: {err}");
138 eprintln!();
139 print_usage();
140 return Ok(());
141 }
142 };
143
144 let grammar =
145 parse_grammar(&options.grammar_name).map_err(|msg| format!("invalid grammar: {msg}"))?;
146 let source = fs::read(&options.source_path)?;
147 let theme = load_theme(&options.theme_name)?;
148 let mut highlighter = SpanHighlighter::new()?;
149 let mut renderer = StreamLineRenderer::new();
150 renderer.set_color_mode(options.color_mode);
151 renderer.set_preserve_terminal_background(options.preserve_terminal_background);
152
153 if let Some(previous_source_path) = &options.previous_source_path {
154 let previous_source = fs::read(previous_source_path)?;
155 let _ = renderer.highlight_line_to_patch(
156 &mut highlighter,
157 &previous_source,
158 grammar,
159 &theme,
160 )?;
161 }
162
163 let patch = renderer.highlight_line_to_patch(&mut highlighter, &source, grammar, &theme)?;
164 print!("{patch}");
165 io::stdout().flush()?;
166
167 Ok(())
168}