1use anyhow::Result;
4use colored::Colorize;
5use std::time::Instant;
6
7pub fn parse_database_url(url: &str) -> Result<DatabaseType> {
9 if url.starts_with("postgresql://") || url.starts_with("postgres://") {
10 Ok(DatabaseType::PostgreSQL)
11 } else if url.starts_with("sqlite:") || url.ends_with(".db") || url.ends_with(".sqlite") {
12 Ok(DatabaseType::SQLite)
13 } else {
14 anyhow::bail!("Unsupported database URL format. Use postgresql:// or sqlite:")
15 }
16}
17
18#[derive(Debug, Clone, Copy)]
19pub enum DatabaseType {
20 PostgreSQL,
21 SQLite,
22}
23
24impl DatabaseType {
25 pub fn name(&self) -> &'static str {
26 match self {
27 DatabaseType::PostgreSQL => "PostgreSQL",
28 DatabaseType::SQLite => "SQLite",
29 }
30 }
31}
32
33pub fn format_duration(duration: std::time::Duration) -> String {
35 let millis = duration.as_millis();
36 if millis < 1000 {
37 format!("{}ms", millis)
38 } else {
39 format!("{:.2}s", duration.as_secs_f64())
40 }
41}
42
43pub fn print_success(message: &str) {
45 println!("{} {}", "✓".green().bold(), message);
46}
47
48pub fn print_error(message: &str) {
50 eprintln!("{} {}", "✗".red().bold(), message);
51}
52
53pub fn print_warning(message: &str) {
55 println!("{} {}", "⚠".yellow().bold(), message);
56}
57
58pub fn print_info(message: &str) {
60 println!("{} {}", "ℹ".blue().bold(), message);
61}
62
63pub struct Progress {
65 #[allow(dead_code)]
66 message: String,
67 start: Instant,
68}
69
70impl Progress {
71 pub fn new(message: &str) -> Self {
72 print!("{} {}... ", "⟳".cyan().bold(), message);
73 use std::io::{self, Write};
74 io::stdout().flush().unwrap();
75
76 Self {
77 message: message.to_string(),
78 start: Instant::now(),
79 }
80 }
81
82 pub fn finish(self) {
83 let duration = self.start.elapsed();
84 println!("{} ({})", "done".green(), format_duration(duration).dimmed());
85 }
86
87 pub fn finish_with_message(self, message: &str) {
88 let duration = self.start.elapsed();
89 println!("{} {} ({})",
90 "done".green(),
91 message,
92 format_duration(duration).dimmed()
93 );
94 }
95}
96
97pub fn format_table(headers: Vec<&str>, rows: Vec<Vec<String>>) -> String {
99 use std::cmp::max;
100
101 let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
103
104 for row in &rows {
105 for (i, cell) in row.iter().enumerate() {
106 if i < widths.len() {
107 widths[i] = max(widths[i], cell.len());
108 }
109 }
110 }
111
112 let mut output = String::new();
113
114 for (i, header) in headers.iter().enumerate() {
116 if i > 0 {
117 output.push_str(" ");
118 }
119 output.push_str(&format!("{:<width$}", header, width = widths[i]));
120 }
121 output.push('\n');
122
123 for (i, width) in widths.iter().enumerate() {
125 if i > 0 {
126 output.push_str(" ");
127 }
128 output.push_str(&"-".repeat(*width));
129 }
130 output.push('\n');
131
132 for row in rows {
134 for (i, cell) in row.iter().enumerate() {
135 if i > 0 {
136 output.push_str(" ");
137 }
138 if i < widths.len() {
139 output.push_str(&format!("{:<width$}", cell, width = widths[i]));
140 }
141 }
142 output.push('\n');
143 }
144
145 output
146}
147
148pub fn get_timestamp() -> String {
150 chrono::Local::now().format("%Y%m%d%H%M%S").to_string()
151}
152
153pub fn colorize_number(num: usize, label: &str) -> String {
155 format!("{} {}", num.to_string().bold(), label)
156}