Skip to main content

tideway_cli/
lib.rs

1//! Tideway CLI library exports.
2
3pub mod cli;
4pub mod commands;
5pub mod database;
6pub mod env;
7pub mod templates;
8
9use colored::Colorize;
10use serde_json::json;
11use std::fs;
12use std::path::Path;
13use std::sync::atomic::{AtomicBool, Ordering};
14
15pub const TIDEWAY_VERSION: &str = env!("TIDEWAY_VERSION");
16
17static JSON_OUTPUT: AtomicBool = AtomicBool::new(false);
18static PLAN_MODE: AtomicBool = AtomicBool::new(false);
19
20pub fn set_json_output(enabled: bool) {
21    JSON_OUTPUT.store(enabled, Ordering::Relaxed);
22}
23
24pub fn is_json_output() -> bool {
25    JSON_OUTPUT.load(Ordering::Relaxed)
26}
27
28pub fn set_plan_mode(enabled: bool) {
29    PLAN_MODE.store(enabled, Ordering::Relaxed);
30}
31
32pub fn is_plan_mode() -> bool {
33    PLAN_MODE.load(Ordering::Relaxed)
34}
35
36fn print_json(level: &str, message: &str) {
37    let payload = json!({
38        "level": level,
39        "message": message,
40    });
41    println!("{}", payload);
42}
43
44/// Print a success message
45pub fn print_success(message: &str) {
46    if is_json_output() {
47        print_json("success", message);
48    } else {
49        println!("{} {}", "✓".green().bold(), message);
50    }
51}
52
53/// Print an info message
54pub fn print_info(message: &str) {
55    if is_json_output() {
56        print_json("info", message);
57    } else {
58        println!("{} {}", "→".blue(), message);
59    }
60}
61
62/// Print a warning message
63pub fn print_warning(message: &str) {
64    if is_json_output() {
65        print_json("warning", message);
66    } else {
67        println!("{} {}", "!".yellow().bold(), message);
68    }
69}
70
71/// Print an error message
72pub fn print_error(message: &str) {
73    if is_json_output() {
74        print_json("error", message);
75    } else {
76        println!("{} {}", "✗".red().bold(), message);
77    }
78}
79
80pub fn error_contract(problem: &str, primary_fix: &str, advanced_fix: &str) -> String {
81    format!("Problem: {problem}\nPrimary fix: {primary_fix}\nAdvanced fix: {advanced_fix}")
82}
83
84pub fn parse_error_contract(message: &str) -> Option<(String, String, String)> {
85    let mut problem = None;
86    let mut primary_fix = None;
87    let mut advanced_fix = None;
88
89    for line in message.lines() {
90        if let Some(value) = line.strip_prefix("Problem: ") {
91            problem = Some(value.trim().to_string());
92        } else if let Some(value) = line.strip_prefix("Primary fix: ") {
93            primary_fix = Some(value.trim().to_string());
94        } else if let Some(value) = line.strip_prefix("Advanced fix: ") {
95            advanced_fix = Some(value.trim().to_string());
96        }
97    }
98
99    match (problem, primary_fix, advanced_fix) {
100        (Some(problem), Some(primary_fix), Some(advanced_fix)) => {
101            Some((problem, primary_fix, advanced_fix))
102        }
103        _ => None,
104    }
105}
106
107pub fn print_structured_error(message: &str) {
108    if is_json_output() {
109        if let Some((problem, primary_fix, advanced_fix)) = parse_error_contract(message) {
110            let payload = json!({
111                "level": "error",
112                "message": message,
113                "problem": problem,
114                "primary_fix": primary_fix,
115                "advanced_fix": advanced_fix,
116            });
117            println!("{}", payload);
118        } else {
119            print_json("error", message);
120        }
121    } else {
122        print_error(message);
123    }
124}
125
126pub fn ensure_dir(path: &Path) -> std::io::Result<()> {
127    if is_plan_mode() {
128        print_info(&format!("Plan: create directory {}", path.display()));
129        Ok(())
130    } else {
131        fs::create_dir_all(path)
132    }
133}
134
135pub fn write_file(path: &Path, contents: &str) -> std::io::Result<()> {
136    if is_plan_mode() {
137        print_info(&format!("Plan: write file {}", path.display()));
138        Ok(())
139    } else {
140        fs::write(path, contents)
141    }
142}
143
144pub fn remove_file(path: &Path) -> std::io::Result<()> {
145    if is_plan_mode() {
146        print_info(&format!("Plan: remove file {}", path.display()));
147        Ok(())
148    } else {
149        fs::remove_file(path)
150    }
151}
152
153pub fn remove_dir(path: &Path) -> std::io::Result<()> {
154    if is_plan_mode() {
155        print_info(&format!("Plan: remove directory {}", path.display()));
156        Ok(())
157    } else {
158        fs::remove_dir(path)
159    }
160}