cascade_cli/cli/
output.rs1use console::{style, Color, Emoji, Style};
2use std::fmt::Display;
3
4struct Theme;
7
8impl Theme {
9 fn success_style() -> Style {
12 Style::new().color256(46).bold() }
14
15 const ERROR: Color = Color::Red;
17
18 const WARNING: Color = Color::Yellow;
20
21 fn info_style() -> Style {
24 Style::new().color256(35) }
26
27 fn tip_style() -> Style {
29 Style::new().color256(35) }
31
32 fn dim_style() -> Style {
34 Style::new().dim()
35 }
36}
37
38pub struct Output;
40
41impl Output {
42 pub fn success<T: Display>(message: T) {
44 println!("{} {}", Theme::success_style().apply_to("✓"), message);
45 }
46
47 pub fn error<T: Display>(message: T) {
49 println!("{} {}", style("✗").fg(Theme::ERROR), message);
50 }
51
52 pub fn warning<T: Display>(message: T) {
54 println!("{} {}", style("⚠").fg(Theme::WARNING), message);
55 }
56
57 pub fn info<T: Display>(message: T) {
59 println!("{} {}", Theme::info_style().apply_to("ℹ"), message);
60 }
61
62 pub fn sub_item<T: Display>(message: T) {
64 println!(" {} {}", Theme::dim_style().apply_to("→"), message);
65 }
66
67 pub fn bullet<T: Display>(message: T) {
69 println!(" {} {}", Theme::dim_style().apply_to("•"), message);
70 }
71
72 pub fn section<T: Display>(title: T) {
74 println!("\n{}", style(title).bold().underlined());
75 }
76
77 pub fn tip<T: Display>(message: T) {
79 println!(
80 "{} {}",
81 Theme::tip_style().apply_to("TIP:"),
82 Theme::dim_style().apply_to(message)
83 );
84 }
85
86 pub fn progress<T: Display>(message: T) {
88 println!("{} {}", Theme::info_style().apply_to("→"), message);
89 }
90
91 pub fn divider() {
93 println!("{}", Theme::dim_style().apply_to("─".repeat(50)));
94 }
95
96 pub fn stack_info(
98 name: &str,
99 id: &str,
100 base_branch: &str,
101 working_branch: Option<&str>,
102 is_active: bool,
103 ) {
104 println!(
106 "{} {}",
107 Theme::info_style().apply_to("Stack:"),
108 style(name).bold()
109 );
110 Self::sub_item(format!("Stack ID: {}", Theme::dim_style().apply_to(id)));
111 Self::sub_item(format!(
112 "Base branch: {}",
113 Theme::info_style().apply_to(base_branch)
114 ));
115
116 if let Some(working) = working_branch {
117 Self::sub_item(format!(
118 "Working branch: {}",
119 Theme::info_style().apply_to(working)
120 ));
121 }
122
123 if is_active {
124 Self::sub_item(format!(
125 "Status: {}",
126 Theme::success_style().apply_to("Active")
127 ));
128 }
129 }
130
131 pub fn next_steps(steps: &[&str]) {
133 println!();
134 Self::tip("Next steps:");
135 for step in steps {
136 Self::bullet(step);
137 }
138 }
139
140 pub fn command_example<T: Display>(command: T) {
142 println!(" {}", style(command).fg(Theme::WARNING));
143 }
144
145 pub fn check_start<T: Display>(message: T) {
147 println!("\n{} {}", style("🔍").bright(), style(message).bold());
148 }
149
150 pub fn solution<T: Display>(message: T) {
152 println!(" {}: {}", style("Solution").fg(Theme::WARNING), message);
153 }
154
155 pub fn numbered_item<T: Display>(number: usize, message: T) {
157 println!(" {}. {}", Theme::info_style().apply_to(number), message);
158 }
159
160 pub fn spacing() {
162 println!();
163 }
164
165 pub fn entry_status(is_submitted: bool, is_merged: bool) -> String {
170 if is_merged {
171 format!("{}", Theme::success_style().apply_to("[merged]"))
172 } else if is_submitted {
173 format!("{}", Theme::info_style().apply_to("[submitted]"))
174 } else {
175 format!("{}", style("[pending]").fg(Theme::WARNING))
176 }
177 }
178}
179
180pub struct Emojis;
182
183impl Emojis {
184 pub const SUCCESS: Emoji<'_, '_> = Emoji("✓", "OK");
185 pub const ERROR: Emoji<'_, '_> = Emoji("✗", "ERROR");
186 pub const WARNING: Emoji<'_, '_> = Emoji("⚠", "WARNING");
187 pub const INFO: Emoji<'_, '_> = Emoji("ℹ", "INFO");
188 pub const TIP: Emoji<'_, '_> = Emoji("💡", "TIP");
189 pub const ROCKET: Emoji<'_, '_> = Emoji("🚀", "ROCKET");
190 pub const SEARCH: Emoji<'_, '_> = Emoji("🔍", "SEARCH");
191 pub const UPLOAD: Emoji<'_, '_> = Emoji("📤", "UPLOAD");
192 pub const STACK: Emoji<'_, '_> = Emoji("📊", "STACK");
193}