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