ricecoder_cli/
branding.rs1use crate::error::CliResult;
5use crate::output::OutputStyle;
6use std::path::Path;
7
8pub struct BrandingManager;
10
11impl BrandingManager {
12 pub fn load_ascii_logo() -> CliResult<String> {
14 let possible_paths = vec![
16 ".branding/Ascii1.txt",
17 "projects/ricecoder/.branding/Ascii1.txt",
18 "/projects/ricecoder/.branding/Ascii1.txt",
19 ];
20
21 for path in possible_paths {
22 if Path::new(path).exists() {
23 match std::fs::read_to_string(path) {
24 Ok(content) => return Ok(content),
25 Err(_) => continue,
26 }
27 }
28 }
29
30 Ok(Self::default_ascii_logo())
32 }
33
34 pub fn default_ascii_logo() -> String {
36 r#"
37 ╔═══════════════════════════════════╗
38 ║ ║
39 ║ 🍚 RiceCoder 🍚 ║
40 ║ ║
41 ║ Terminal-first, Spec-driven ║
42 ║ Coding Assistant ║
43 ║ ║
44 ╚═══════════════════════════════════╝
45"#
46 .to_string()
47 }
48
49 pub fn display_startup_banner() -> CliResult<()> {
51 let style = OutputStyle::default();
52 let logo = Self::load_ascii_logo()?;
53
54 println!("{}", logo);
55 println!("{}", style.info("Type 'rice --help' for usage information"));
56 println!();
57
58 Ok(())
59 }
60
61 pub fn display_version_banner(version: &str) -> CliResult<()> {
63 let style = OutputStyle::default();
64 let logo = Self::load_ascii_logo()?;
65
66 println!("{}", logo);
67 println!("{}", style.header(&format!("Version: {}", version)));
68 println!();
69
70 Ok(())
71 }
72
73 pub fn detect_terminal_capabilities() -> TerminalCapabilities {
75 TerminalCapabilities {
76 supports_colors: atty::is(atty::Stream::Stdout),
77 supports_unicode: Self::supports_unicode(),
78 supports_images: Self::supports_images(),
79 width: Self::get_terminal_width(),
80 height: Self::get_terminal_height(),
81 }
82 }
83
84 pub fn supports_unicode() -> bool {
86 if let Ok(lang) = std::env::var("LANG") {
88 return lang.contains("UTF-8") || lang.contains("utf8");
89 }
90 if let Ok(lc_all) = std::env::var("LC_ALL") {
91 return lc_all.contains("UTF-8") || lc_all.contains("utf8");
92 }
93 true
95 }
96
97 pub fn supports_images() -> bool {
99 if let Ok(term) = std::env::var("TERM") {
101 return term.contains("kitty") || term.contains("iterm");
102 }
103 false
104 }
105
106 pub fn get_terminal_width() -> u16 {
108 term_size::dimensions().map(|(w, _)| w as u16).unwrap_or(80)
109 }
110
111 pub fn get_terminal_height() -> u16 {
113 term_size::dimensions().map(|(_, h)| h as u16).unwrap_or(24)
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct TerminalCapabilities {
120 pub supports_colors: bool,
121 pub supports_unicode: bool,
122 pub supports_images: bool,
123 pub width: u16,
124 pub height: u16,
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_default_ascii_logo() {
133 let logo = BrandingManager::default_ascii_logo();
134 assert!(logo.contains("RiceCoder"));
135 assert!(!logo.is_empty());
136 }
137
138 #[test]
139 fn test_terminal_capabilities() {
140 let caps = BrandingManager::detect_terminal_capabilities();
141 assert!(caps.width > 0);
142 assert!(caps.height > 0);
143 }
144
145 #[test]
146 fn test_supports_unicode() {
147 let supports = BrandingManager::supports_unicode();
148 assert!(supports || !supports);
150 }
151}