1pub mod json;
8pub mod table;
9pub mod terminal;
10
11use crate::cli::{ColorChoice, GlobalOpts, OutputFormat};
12use console::Term;
13use serde::Serialize;
14
15#[derive(Debug, Clone)]
23pub struct OutputContext {
24 pub format: OutputFormat,
26 pub color_enabled: bool,
28 #[allow(dead_code)]
30 pub is_tty: bool,
31 #[allow(dead_code)]
33 pub term_width: u16,
34 pub verbose: bool,
36 #[allow(dead_code)]
38 pub quiet: bool,
39}
40
41impl OutputContext {
42 pub fn from_global_opts(opts: &GlobalOpts) -> Self {
46 let term = Term::stdout();
47 let is_tty = term.is_term();
48
49 let color_enabled = match opts.color {
50 ColorChoice::Always => true,
51 ColorChoice::Never => false,
52 ColorChoice::Auto => {
53 is_tty
54 && std::env::var("NO_COLOR").is_err()
55 && std::env::var("TERM").map(|t| t != "dumb").unwrap_or(true)
56 }
57 };
58
59 let term_width = if is_tty { term.size().1.max(40) } else { 80 };
60
61 Self {
62 format: opts.format,
63 color_enabled,
64 is_tty,
65 term_width,
66 verbose: opts.verbose,
67 quiet: opts.quiet,
68 }
69 }
70
71 pub fn render<T: Serialize + HumanRenderable>(&self, result: &T) {
75 match self.format {
76 OutputFormat::Json => {
77 json::print_json(result);
78 }
79 OutputFormat::Human => {
80 result.render_human(self);
81 }
82 }
83 }
84
85 #[allow(dead_code)]
87 pub fn print_status(&self, symbol: &str, message: &str) {
88 if self.quiet || self.format == OutputFormat::Json {
89 return;
90 }
91 if self.color_enabled {
92 let styled_symbol = console::style(symbol).green().bold();
93 eprintln!("{styled_symbol} {message}");
94 } else {
95 eprintln!("{symbol} {message}");
96 }
97 }
98
99 pub fn print_warning(&self, message: &str) {
101 if self.format == OutputFormat::Json {
102 return;
103 }
104 if self.color_enabled {
105 let prefix = console::style("warning:").yellow().bold();
106 eprintln!("{prefix} {message}");
107 } else {
108 eprintln!("warning: {message}");
109 }
110 }
111
112 pub fn print_fatal(&self, message: &str) {
114 if self.color_enabled {
115 let prefix = console::style("error:").red().bold();
116 eprintln!("{prefix} {message}");
117 } else {
118 eprintln!("error: {message}");
119 }
120 }
121
122 pub fn print_debug(&self, message: &str) {
124 if !self.verbose || self.format == OutputFormat::Json {
125 return;
126 }
127 if self.color_enabled {
128 let prefix = console::style("debug:").dim();
129 eprintln!("{prefix} {message}");
130 } else {
131 eprintln!("debug: {message}");
132 }
133 }
134
135 #[allow(dead_code)]
137 pub fn progress_bar(&self, total: u64, message: &str) -> Option<indicatif::ProgressBar> {
138 if !self.is_tty || self.quiet || self.format == OutputFormat::Json {
139 return None;
140 }
141 let pb = indicatif::ProgressBar::new(total);
142 pb.set_style(
143 indicatif::ProgressStyle::default_bar()
144 .template(
145 "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}",
146 )
147 .expect("valid progress bar template")
148 .progress_chars("=>-"),
149 );
150 pb.set_message(message.to_string());
151 Some(pb)
152 }
153
154 #[allow(dead_code)]
156 pub fn spinner(&self, message: &str) -> Option<indicatif::ProgressBar> {
157 if !self.is_tty || self.quiet || self.format == OutputFormat::Json {
158 return None;
159 }
160 let sp = indicatif::ProgressBar::new_spinner();
161 sp.set_style(
162 indicatif::ProgressStyle::default_spinner()
163 .template("{spinner:.green} {msg}")
164 .expect("valid spinner template"),
165 );
166 sp.set_message(message.to_string());
167 sp.enable_steady_tick(std::time::Duration::from_millis(80));
168 Some(sp)
169 }
170}
171
172pub trait HumanRenderable {
174 fn render_human(&self, ctx: &OutputContext);
176}
177
178#[derive(Debug, Serialize)]
180pub struct CommandResult<T: Serialize> {
181 pub success: bool,
183 pub command: String,
185 pub data: T,
187 #[serde(skip_serializing_if = "Vec::is_empty")]
189 pub warnings: Vec<String>,
190}
191
192impl<T: Serialize + HumanRenderable> HumanRenderable for CommandResult<T> {
193 fn render_human(&self, ctx: &OutputContext) {
194 self.data.render_human(ctx);
195 for w in &self.warnings {
196 ctx.print_warning(w);
197 }
198 }
199}