Skip to main content

tideway_cli/
lib.rs

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