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