1use rpi_tui::{bold as tui_bold, theme as current_theme, Component};
13
14use crate::args::is_truthy_env_flag;
15
16pub const NO_EMOJI_ENV: &str = "RPI_NO_EMOJI";
18
19pub const CRAB: &str = "🦀";
25
26const SEPARATOR: &str = "·";
28
29pub(crate) fn crab_enabled_from(value: Option<&str>) -> bool {
32 !is_truthy_env_flag(value)
33}
34
35fn crab_enabled() -> bool {
36 crab_enabled_from(std::env::var(NO_EMOJI_ENV).ok().as_deref())
37}
38
39fn render_lockup(crab: bool) -> Vec<String> {
42 let c = current_theme().colors;
43 let bar = c.dim.fg("██");
44 let bar_accent = c.accent.fg("██");
45
46 let name = if crab {
49 format!(
50 "{} {} {CRAB}",
51 c.accent.fg(&tui_bold("rpi")),
52 c.muted.fg(SEPARATOR)
53 )
54 } else {
55 c.accent.fg(&tui_bold("rpi"))
56 };
57
58 vec![
59 format!(" {bar_accent}"),
60 format!(" {bar_accent}"),
61 format!(" {bar} {bar_accent}"),
62 format!(" {bar} {bar_accent} {bar}"),
63 format!(" {bar} {bar_accent} {bar} {name}"),
64 ]
65}
66
67pub struct BrandLockup;
69
70impl BrandLockup {
71 pub fn new() -> Self {
72 Self
73 }
74}
75
76impl Default for BrandLockup {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82impl Component for BrandLockup {
83 fn render(&self, _width: usize) -> Vec<String> {
84 render_lockup(crab_enabled())
85 }
86
87 fn invalidate(&self) {
88 }
90
91 fn as_any(&self) -> &dyn std::any::Any {
92 self
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use rpi_tui::ansi::{strip_ansi, visible_width};
100
101 fn plain(crab: bool) -> Vec<String> {
102 render_lockup(crab).iter().map(|r| strip_ansi(r)).collect()
103 }
104
105 #[test]
106 fn mark_rows_match_the_site_brand() {
107 let rows = plain(true);
108
109 assert_eq!(rows[0], " ██");
112 assert_eq!(rows[1], " ██");
113 assert_eq!(rows[2], " ██ ██");
114 assert_eq!(rows[3], " ██ ██ ██");
115 }
116
117 #[test]
118 fn crab_stands_in_for_the_language_name() {
119 let rows = plain(true);
120
121 assert_eq!(rows[4], " ██ ██ ██ rpi · 🦀");
123
124 let without = visible_width(&plain(false)[4]);
127 assert_eq!(
128 visible_width(&rows[4]) - without,
129 visible_width(" · ") + visible_width(CRAB)
130 );
131 }
132
133 #[test]
134 fn emoji_width_is_the_one_thing_the_layout_depends_on() {
135 assert_eq!(visible_width(CRAB), 2, "crab width assumption changed");
137 }
138
139 #[test]
140 fn disabling_drops_the_crab_and_its_separator() {
141 assert!(crab_enabled_from(None));
142 for value in ["1", "true", "yes", "TRUE", "Yes"] {
143 assert!(!crab_enabled_from(Some(value)), "{value} should disable");
144 }
145 for value in ["", "0", "false", "no", "off"] {
146 assert!(crab_enabled_from(Some(value)), "{value} should not disable");
147 }
148
149 let rows = plain(false);
150 assert_eq!(rows.len(), 5);
151 assert!(!rows[4].contains(CRAB));
152 assert!(!rows[4].contains(SEPARATOR), "got {:?}", rows[4]);
154 assert!(rows[4].ends_with("rpi"), "got {:?}", rows[4]);
155 }
156}