ricecoder_cli/
branding.rs

1// Branding and visual identity
2// Loads and displays RiceCoder branding
3
4use crate::error::CliResult;
5use crate::output::OutputStyle;
6use std::path::Path;
7
8/// Branding manager
9pub struct BrandingManager;
10
11impl BrandingManager {
12    /// Load ASCII logo from file
13    pub fn load_ascii_logo() -> CliResult<String> {
14        // Check for branding file in multiple locations
15        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        // Fallback to default ASCII art
31        Ok(Self::default_ascii_logo())
32    }
33
34    /// Get default ASCII logo
35    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    /// Display branding on startup
50    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    /// Display branding on version command
62    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    /// Detect terminal capabilities
74    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    /// Check if terminal supports Unicode
85    pub fn supports_unicode() -> bool {
86        // Check environment variables
87        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        // Default to true on most modern terminals
94        true
95    }
96
97    /// Check if terminal supports images
98    pub fn supports_images() -> bool {
99        // Check for Kitty, iTerm2, or other image-capable terminals
100        if let Ok(term) = std::env::var("TERM") {
101            return term.contains("kitty") || term.contains("iterm");
102        }
103        false
104    }
105
106    /// Get terminal width
107    pub fn get_terminal_width() -> u16 {
108        term_size::dimensions().map(|(w, _)| w as u16).unwrap_or(80)
109    }
110
111    /// Get terminal height
112    pub fn get_terminal_height() -> u16 {
113        term_size::dimensions().map(|(_, h)| h as u16).unwrap_or(24)
114    }
115}
116
117/// Terminal capabilities
118#[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        // Just verify it returns a boolean
149        assert!(supports || !supports);
150    }
151}