1pub 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
44pub 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
53pub fn print_info(message: &str) {
55 if is_json_output() {
56 print_json("info", message);
57 } else {
58 println!("{} {}", "→".blue(), message);
59 }
60}
61
62pub 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
71pub 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}