1#![allow(dead_code)]
23
24use colored::Colorize;
25use std::io::{self, Write};
26
27pub fn is_tty() -> bool {
39 atty::is(atty::Stream::Stdout)
40}
41
42pub struct OutputStyle {
62 pub color: bool,
64 pub format: String,
66 pub compact: bool,
68 pub quiet: bool,
70}
71
72impl Default for OutputStyle {
73 fn default() -> Self {
74 Self {
75 color: is_tty(),
76 format: "text".to_string(),
77 compact: false,
78 quiet: false,
79 }
80 }
81}
82
83impl OutputStyle {
84 pub fn new(color: bool, format: &str) -> Self {
86 let effective_color = color && is_tty() && format != "json" && format != "compact";
88 let compact = format == "compact";
89 Self {
90 color: effective_color,
91 format: format.to_string(),
92 compact,
93 quiet: false,
94 }
95 }
96
97 pub fn with_quiet(color: bool, format: &str, quiet: bool) -> Self {
99 let mut style = Self::new(color, format);
100 style.quiet = quiet;
101 style
102 }
103
104 pub fn is_compact(&self) -> bool {
106 self.compact || self.format == "compact"
107 }
108
109 pub fn is_quiet(&self) -> bool {
111 self.quiet
112 }
113}
114
115pub fn success(msg: &str) {
117 if is_tty() {
118 println!("{} {}", "✓".green().bold(), msg.green());
119 } else {
120 println!("{}", msg);
121 }
122}
123
124pub fn error(msg: &str) {
126 if is_tty() {
127 eprintln!("{} {}", "✗".red().bold(), msg.red());
128 } else {
129 eprintln!("error: {}", msg);
130 }
131}
132
133pub fn warning(msg: &str) {
135 if is_tty() {
136 eprintln!("{} {}", "!".yellow().bold(), msg.yellow());
137 } else {
138 eprintln!("warning: {}", msg);
139 }
140}
141
142pub fn info(msg: &str) {
144 if is_tty() {
145 println!("{} {}", "ℹ".blue().bold(), msg);
146 } else {
147 println!("{}", msg);
148 }
149}
150
151pub fn print_cid(label: &str, cid: &str) {
153 if is_tty() {
154 println!("{}: {}", label, cid.cyan().bold());
155 } else {
156 println!("{}: {}", label, cid);
157 }
158}
159
160pub fn print_kv(key: &str, value: &str) {
162 if is_tty() {
163 println!(" {}: {}", key.dimmed(), value);
164 } else {
165 println!(" {}: {}", key, value);
166 }
167}
168
169pub fn print_header(title: &str) {
171 if is_tty() {
172 println!("{}", title.bold().underline());
173 } else {
174 println!("{}", title);
175 println!("{}", "=".repeat(title.len()));
176 }
177}
178
179pub fn print_section(title: &str) {
181 if is_tty() {
182 println!("\n{}", title.bold());
183 } else {
184 println!("\n{}", title);
185 }
186}
187
188pub fn format_bytes(bytes: u64) -> String {
190 const KB: u64 = 1024;
191 const MB: u64 = KB * 1024;
192 const GB: u64 = MB * 1024;
193 const TB: u64 = GB * 1024;
194
195 if bytes >= TB {
196 format!("{:.2} TB", bytes as f64 / TB as f64)
197 } else if bytes >= GB {
198 format!("{:.2} GB", bytes as f64 / GB as f64)
199 } else if bytes >= MB {
200 format!("{:.2} MB", bytes as f64 / MB as f64)
201 } else if bytes >= KB {
202 format!("{:.2} KB", bytes as f64 / KB as f64)
203 } else {
204 format!("{} B", bytes)
205 }
206}
207
208pub fn format_bytes_detailed(bytes: u64) -> String {
210 if bytes >= 1024 {
211 format!("{} ({} bytes)", format_bytes(bytes), bytes)
212 } else {
213 format!("{} bytes", bytes)
214 }
215}
216
217pub fn print_list_item(item: &str) {
219 if is_tty() {
220 println!(" {} {}", "•".dimmed(), item);
221 } else {
222 println!(" - {}", item);
223 }
224}
225
226pub fn print_numbered_item(num: usize, item: &str) {
228 if is_tty() {
229 println!(" {}. {}", num.to_string().dimmed(), item);
230 } else {
231 println!(" {}. {}", num, item);
232 }
233}
234
235pub struct TablePrinter {
237 headers: Vec<String>,
238 rows: Vec<Vec<String>>,
239 column_widths: Vec<usize>,
240}
241
242impl TablePrinter {
243 pub fn new(headers: Vec<&str>) -> Self {
245 let headers: Vec<String> = headers.iter().map(|s| s.to_string()).collect();
246 let column_widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
247 Self {
248 headers,
249 rows: Vec::new(),
250 column_widths,
251 }
252 }
253
254 pub fn add_row(&mut self, row: Vec<&str>) {
256 let row: Vec<String> = row.iter().map(|s| s.to_string()).collect();
257 for (i, cell) in row.iter().enumerate() {
258 if i < self.column_widths.len() {
259 self.column_widths[i] = self.column_widths[i].max(cell.len());
260 }
261 }
262 self.rows.push(row);
263 }
264
265 pub fn print(&self) {
267 let color = is_tty();
268
269 let header_line: String = self
271 .headers
272 .iter()
273 .enumerate()
274 .map(|(i, h)| format!("{:width$}", h, width = self.column_widths[i]))
275 .collect::<Vec<_>>()
276 .join(" ");
277
278 if color {
279 println!("{}", header_line.bold());
280 } else {
281 println!("{}", header_line);
282 }
283
284 let separator: String = self
286 .column_widths
287 .iter()
288 .map(|&w| "-".repeat(w))
289 .collect::<Vec<_>>()
290 .join(" ");
291
292 if color {
293 println!("{}", separator.dimmed());
294 } else {
295 println!("{}", separator);
296 }
297
298 for row in &self.rows {
300 let row_line: String = row
301 .iter()
302 .enumerate()
303 .map(|(i, cell)| {
304 let width = self.column_widths.get(i).copied().unwrap_or(cell.len());
305 format!("{:width$}", cell, width = width)
306 })
307 .collect::<Vec<_>>()
308 .join(" ");
309 println!("{}", row_line);
310 }
311 }
312}
313
314pub fn write_raw(data: &[u8]) -> io::Result<()> {
316 let stdout = io::stdout();
317 let mut handle = stdout.lock();
318 handle.write_all(data)?;
319 handle.flush()
320}
321
322pub fn compact_print(key: &str, value: &str) {
324 println!("{}:{}", key, value);
325}
326
327pub fn compact_cid(cid: &str) {
329 println!("{}", cid);
330}
331
332pub fn compact_list(items: &[String]) {
334 for item in items {
335 println!("{}", item);
336 }
337}
338
339pub fn compact_kv_pairs(pairs: &[(&str, &str)]) {
341 for (key, value) in pairs {
342 println!("{}:{}", key, value);
343 }
344}
345
346pub fn troubleshooting_hint(error_type: &str) {
350 let hint = match error_type {
351 "daemon_not_running" => {
352 "The IPFRS daemon is not running.\n\
353 To start the daemon, run: ipfrs daemon start\n\
354 Or run in foreground: ipfrs daemon"
355 }
356 "daemon_already_running" => {
357 "The IPFRS daemon is already running.\n\
358 To stop it, run: ipfrs daemon stop\n\
359 To check status: ipfrs daemon status"
360 }
361 "repo_not_initialized" => {
362 "IPFRS repository not initialized.\n\
363 To initialize a repository, run: ipfrs init\n\
364 Or specify a custom directory: ipfrs init -d /path/to/repo"
365 }
366 "connection_failed" => {
367 "Failed to connect to peer.\n\
368 Troubleshooting steps:\n\
369 1. Check if the peer is online\n\
370 2. Verify the multiaddr format is correct\n\
371 3. Check your network connection\n\
372 4. Ensure firewall allows IPFRS connections"
373 }
374 "cid_not_found" => {
375 "Content not found.\n\
376 This could mean:\n\
377 1. The CID is incorrect or malformed\n\
378 2. The content is not available on the network\n\
379 3. You need to connect to more peers\n\
380 Try: ipfrs swarm peers (to check connections)"
381 }
382 "permission_denied" => {
383 "Permission denied.\n\
384 Troubleshooting steps:\n\
385 1. Check file/directory permissions\n\
386 2. Ensure you have write access to the data directory\n\
387 3. Try running with appropriate permissions"
388 }
389 "config_error" => {
390 "Configuration error.\n\
391 Troubleshooting steps:\n\
392 1. Check config file syntax (TOML format)\n\
393 2. Verify config file location: ~/.config/ipfrs/config.toml\n\
394 3. Reset to defaults: rm ~/.config/ipfrs/config.toml && ipfrs init"
395 }
396 "network_timeout" => {
397 "Network operation timed out.\n\
398 Troubleshooting steps:\n\
399 1. Check your internet connection\n\
400 2. Try connecting to bootstrap peers\n\
401 3. Increase timeout in config file\n\
402 4. Check if peers are reachable: ipfrs ping <peer-id>"
403 }
404 _ => "For more help, visit: https://github.com/tensorlogic/ipfrs/issues",
405 };
406
407 if is_tty() {
408 println!("\n{} {}", "Hint:".yellow().bold(), hint);
409 } else {
410 println!("\nHint: {}", hint);
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn test_format_bytes() {
420 assert_eq!(format_bytes(0), "0 B");
421 assert_eq!(format_bytes(512), "512 B");
422 assert_eq!(format_bytes(1024), "1.00 KB");
423 assert_eq!(format_bytes(1536), "1.50 KB");
424 assert_eq!(format_bytes(1048576), "1.00 MB");
425 assert_eq!(format_bytes(1073741824), "1.00 GB");
426 }
427
428 #[test]
429 fn test_format_bytes_detailed() {
430 assert_eq!(format_bytes_detailed(512), "512 bytes");
431 assert_eq!(format_bytes_detailed(1024), "1.00 KB (1024 bytes)");
432 }
433
434 #[test]
435 fn test_table_printer() {
436 let mut table = TablePrinter::new(vec!["Name", "Size", "CID"]);
437 table.add_row(vec!["file.txt", "1024", "Qm..."]);
438 table.add_row(vec!["data.bin", "2048", "Qm..."]);
439 table.print();
441 }
442
443 #[test]
444 fn test_output_style_quiet_mode() {
445 let style = OutputStyle::with_quiet(true, "text", true);
446 assert!(style.is_quiet());
447 assert!(!style.is_compact());
448 }
449
450 #[test]
451 fn test_output_style_quiet_mode_disabled() {
452 let style = OutputStyle::with_quiet(true, "text", false);
453 assert!(!style.is_quiet());
454 }
455
456 #[test]
457 fn test_output_style_quiet_with_json() {
458 let style = OutputStyle::with_quiet(true, "json", true);
459 assert!(style.is_quiet());
460 assert_eq!(style.format, "json");
461 }
462
463 #[test]
464 fn test_output_style_quiet_default() {
465 let style = OutputStyle::default();
466 assert!(!style.is_quiet());
467 }
468}