parsql_cli/
utils.rs

1//! Utility functions for parsql CLI
2
3use anyhow::Result;
4use colored::Colorize;
5use std::time::Instant;
6
7/// Parse database URL and determine database type
8pub 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
33/// Format duration in a human-readable way
34pub 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
43/// Print a success message
44pub fn print_success(message: &str) {
45    println!("{} {}", "✓".green().bold(), message);
46}
47
48/// Print an error message
49pub fn print_error(message: &str) {
50    eprintln!("{} {}", "✗".red().bold(), message);
51}
52
53/// Print a warning message
54pub fn print_warning(message: &str) {
55    println!("{} {}", "⚠".yellow().bold(), message);
56}
57
58/// Print an info message
59pub fn print_info(message: &str) {
60    println!("{} {}", "ℹ".blue().bold(), message);
61}
62
63/// Progress indicator for long-running operations
64pub 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
97/// Format a table for display
98pub fn format_table(headers: Vec<&str>, rows: Vec<Vec<String>>) -> String {
99    use std::cmp::max;
100    
101    // Calculate column widths
102    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    // Print headers
115    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    // Print separator
124    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    // Print rows
133    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
148/// Get timestamp for migration files
149pub fn get_timestamp() -> String {
150    chrono::Local::now().format("%Y%m%d%H%M%S").to_string()
151}
152
153/// Colorize a number with a label
154pub fn colorize_number(num: usize, label: &str) -> String {
155    format!("{} {}", num.to_string().bold(), label)
156}