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