commit_wizard/infra/ui/
mod.rs1use std::cell::RefCell;
2use std::io::{self, Write};
3mod logger;
4mod prompt;
5
6use crate::engine::{Error, ErrorCode};
7use scriba::{Config, Format, Logger, Meta, Output, envelope, output::render};
8pub type UiResult<T> = Result<T, Error>;
9
10thread_local! {
12 static UI_CACHE: RefCell<Option<Ui>> = const { RefCell::new(None) };
13}
14
15#[derive(Debug, Clone)]
16pub struct Ui {
17 config: Config,
18 envelope: envelope::EnvelopeConfig,
19 prompt_theme: scriba::prompt::PromptTheme,
20}
21
22impl Ui {
23 pub fn new() -> Self {
24 Self::with_config(Config::default())
25 }
26
27 pub fn with_config(config: Config) -> Self {
28 Self {
29 config,
30 envelope: envelope::EnvelopeConfig::default(),
31 prompt_theme: scriba::prompt::PromptTheme::default(),
32 }
33 }
34
35 pub fn cached_with_config(config: Config, envelope_mode: envelope::EnvelopeMode) -> Self {
39 UI_CACHE.with(|cache| {
40 let mut cached = cache.borrow_mut();
41 match cached.as_ref() {
42 Some(ui) if ui.config == config && ui.envelope.mode == envelope_mode => ui.clone(),
43 _ => {
44 let ui = Self::with_config(config).with_envelope_mode(envelope_mode);
45 *cached = Some(ui.clone());
46 ui
47 }
48 }
49 })
50 }
51
52 pub fn config(&self) -> &Config {
54 &self.config
55 }
56
57 pub fn use_color(&self) -> bool {
58 if self.config.color == scriba::ColorMode::Always
59 || (self.config.color == scriba::ColorMode::Auto && self.config.interactive)
60 {
61 return true;
62 }
63 false
64 }
65
66 pub fn envelope(&self) -> &envelope::EnvelopeConfig {
68 &self.envelope
69 }
70
71 pub fn with_envelope(mut self, config: envelope::EnvelopeConfig) -> Self {
72 self.envelope = config;
73 self
74 }
75
76 pub fn with_envelope_mode(mut self, mode: envelope::EnvelopeMode) -> Self {
77 self.envelope.mode = mode;
78 self
79 }
80
81 pub fn with_envelope_layout(mut self, layout: envelope::EnvelopeLayout) -> Self {
82 self.envelope.layout = layout;
83 self
84 }
85
86 pub fn with_envelope_fields(mut self, fields: envelope::EnvelopeFields) -> Self {
87 self.envelope.fields = fields;
88 self
89 }
90
91 pub fn with_prompt_theme(mut self, theme: scriba::prompt::PromptTheme) -> Self {
92 self.prompt_theme = theme;
93 self
94 }
95
96 pub fn prompt_theme(&self) -> &scriba::prompt::PromptTheme {
97 &self.prompt_theme
98 }
99
100 pub fn with_format(mut self, format: Format) -> Self {
101 self.config.format = format;
102 self
103 }
104
105 pub fn interactive(mut self, value: bool) -> Self {
106 self.config.interactive = value;
107 self
108 }
109
110 pub fn auto_yes(mut self, value: bool) -> Self {
111 self.config.auto_yes = value;
112 self
113 }
114
115 pub fn logger(&self) -> Logger<'_> {
116 Logger::new(&self.config)
117 }
118
119 pub fn new_output_content(&self) -> Output {
120 Output::new()
121 }
122
123 pub fn new_output_meta(&self) -> Meta {
124 Meta::default()
125 }
126
127 pub fn render(&self, output: &Output) -> UiResult<String> {
128 render::render_output(self.config.format, output).map_err(map_scriba_error_to_cw_error)
129 }
130
131 pub fn print(&self, output: &Output) -> UiResult<()> {
132 self.print_with_meta(output, None, true)
133 }
134
135 pub fn print_with_meta(&self, output: &Output, meta: Option<&Meta>, ok: bool) -> UiResult<()> {
136 let text = if self.envelope.mode.is_json() {
137 let content = render::render_output_value(self.config.format, output)
138 .map_err(map_scriba_error_to_cw_error)?;
139 let wrapped = envelope::wrap(
140 &self.envelope,
141 self.config.format.as_str(),
142 content,
143 meta,
144 ok,
145 );
146 serde_json::to_string_pretty(&wrapped)?
147 } else {
148 self.render(output)?
149 };
150 let mut stdout = io::stdout();
151 stdout.write_all(text.as_bytes())?;
152 stdout.flush()?;
153 Ok(())
154 }
155
156 pub fn git_diff(&self, diff: &str) -> String {
157 scriba::output::render_colored_diff(diff, self.use_color())
158 }
159}
160
161impl Default for Ui {
162 fn default() -> Self {
163 Self::new()
164 }
165}
166
167fn map_scriba_error_to_cw_error(err: scriba::Error) -> Error {
168 ErrorCode::UiPromptFailed
169 .error()
170 .with_context("error", err.to_string())
171}