1use colored_json::Styler;
7use std::env;
8use yansi::{Attribute, Color};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Config([u16; 8]);
13
14impl Config {
15 pub fn from_env() -> Self {
19 env::var("JQ_COLORS")
20 .ok()
21 .and_then(Config::from_jq)
22 .unwrap_or_default()
23 }
24
25 fn from_jq<S: AsRef<str>>(s: S) -> Option<Self> {
26 let s = s.as_ref();
27 let segments: Vec<&str> = s.split(':').collect();
28
29 if segments.len() != 8 {
31 return None;
32 }
33
34 let mut colors = [0u16; 8];
35 for (i, segment) in segments.iter().enumerate() {
36 let parts: Vec<&str> = segment.split(';').collect();
37
38 if parts.len() != 2 {
40 return None;
41 }
42
43 let style: u8 = parts[0].parse().ok()?;
45 let color_code: u8 = parts[1].parse().ok()?;
46
47 let color = match color_code {
49 30..=37 => color_code - 30, 39 => 16, 90..=97 => color_code - 90 + 8, _ => return None, };
54
55 colors[i] = ((style as u16) << 8) | (color as u16);
57 }
58
59 Some(Self(colors))
60 }
61
62 pub fn null(&self) -> Style {
64 Style(self.0[0])
65 }
66
67 pub fn r#false(&self) -> Style {
69 Style(self.0[1])
70 }
71
72 pub fn r#true(&self) -> Style {
74 Style(self.0[2])
75 }
76
77 pub fn numbers(&self) -> Style {
79 Style(self.0[3])
80 }
81
82 pub fn strings(&self) -> Style {
84 Style(self.0[4])
85 }
86
87 pub fn arrays(&self) -> Style {
89 Style(self.0[5])
90 }
91
92 pub fn objects(&self) -> Style {
94 Style(self.0[6])
95 }
96
97 pub fn object_keys(&self) -> Style {
99 Style(self.0[7])
100 }
101}
102
103impl Default for Config {
104 fn default() -> Self {
105 Self([8, 16, 16, 16, 2, 272, 272, 260])
108 }
109}
110
111impl From<Config> for Styler {
112 fn from(config: Config) -> Self {
113 Self {
114 object_brackets: config.objects().into(),
115 object_colon: config.objects().into(),
116 array_brackets: config.arrays().into(),
117 key: config.object_keys().into(),
118 string_value: config.strings().into(),
119 integer_value: config.numbers().into(),
120 float_value: config.numbers().into(),
121 bool_value: config.r#false().into(),
122 nil_value: config.null().into(),
123 string_include_quotation: true,
124 }
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct Style(u16);
131
132impl Style {
133 fn attribute(&self) -> Option<Attribute> {
134 let effects: u8 = (self.0 >> 8) as u8;
136 match effects {
137 1 => Some(Attribute::Bold),
138 2 => Some(Attribute::Dim),
139 4 => Some(Attribute::Underline),
140 5 => Some(Attribute::Blink),
141 7 => Some(Attribute::Invert),
142 8 => Some(Attribute::Conceal),
143 _ => None,
144 }
145 }
146
147 fn color(&self) -> Color {
148 let fg = self.0 as u8;
149 match fg {
150 16 => Color::Primary,
151 _ => Color::Fixed(fg),
152 }
153 }
154}
155
156impl From<Style> for colored_json::Style {
157 fn from(value: Style) -> Self {
158 let mut style = colored_json::Style::new().fg(value.color());
159 if let Some(attr) = value.attribute() {
160 style = style.attr(attr);
161 }
162 style
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn from_jq_matches_default() {
172 assert_eq!(
173 Config::from_jq("0;90:0;39:0;39:0;39:0;32:1;39:1;39:1;34"),
174 Some(Config::default()),
175 );
176 }
177}