1use clap::ArgEnum;
4use once_cell::sync::Lazy;
5use regex::Regex;
6use std::fmt;
7use std::path::Path;
8
9#[non_exhaustive]
15#[derive(ArgEnum, Debug, PartialEq, Clone, Copy)]
16pub enum Encoding {
17 Json,
19 #[clap(alias = "yml")]
21 Yaml,
22 Toml,
24 Json5,
26 Csv,
28 #[clap(alias = "qs")]
30 QueryString,
31 Xml,
33 #[clap(alias = "txt")]
35 Text,
36 Gron,
38 Hcl,
40}
41
42static FIRST_LINES: Lazy<Vec<(Encoding, Regex)>> = Lazy::new(|| {
51 vec![
52 (
54 Encoding::Xml,
55 Regex::new(
56 r#"^(?x:
57 <\?xml\s
58 | \s*<(?:[\w-]+):Envelope\s+
59 | \s*(?i:<!DOCTYPE\s+)
60 )"#,
61 )
62 .unwrap(),
63 ),
64 (
70 Encoding::Hcl,
71 Regex::new(
72 r#"^(?xi:
73 [a-z_][a-z0-9_-]*\s+
74 (?:(?:[a-z_][a-z0-9_-]*|"[^"]*")\s+)*\{
75 )"#,
76 )
77 .unwrap(),
78 ),
79 (Encoding::Yaml, Regex::new(r"^(?:%YAML.*|---\s*)$").unwrap()),
81 (
83 Encoding::Toml,
84 Regex::new(
85 r#"^(?xi:
86 # array of tables
87 \[\[\s*[a-z0-9_-]+(?:\s*\.\s*(?:[a-z0-9_-]+|"[^"]*"))*\s*\]\]\s*
88 # table
89 | \[\s*[a-z0-9_-]+(?:\s*\.\s*(?:[a-z0-9_-]+|"[^"]*"))*\s*\]\s*
90 )$"#,
91 )
92 .unwrap(),
93 ),
94 (
96 Encoding::Json,
97 Regex::new(r#"^(?:\{\s*(?:"|$)|\[\s*$)"#).unwrap(),
98 ),
99 ]
100});
101
102impl Encoding {
103 pub fn from_path<P>(path: P) -> Option<Encoding>
108 where
109 P: AsRef<Path>,
110 {
111 let ext = path.as_ref().extension()?.to_str()?;
112
113 match ext {
114 "json" => Some(Encoding::Json),
115 "yaml" | "yml" => Some(Encoding::Yaml),
116 "toml" => Some(Encoding::Toml),
117 "json5" => Some(Encoding::Json5),
118 "csv" => Some(Encoding::Csv),
119 "xml" => Some(Encoding::Xml),
120 "txt" | "text" => Some(Encoding::Text),
121 "hcl" | "tf" => Some(Encoding::Hcl),
122 _ => None,
123 }
124 }
125
126 pub fn from_first_line(line: &str) -> Option<Encoding> {
130 if line.is_empty() {
131 return None;
133 }
134
135 for (encoding, regex) in FIRST_LINES.iter() {
136 if regex.is_match(line) {
137 return Some(*encoding);
138 }
139 }
140
141 None
142 }
143
144 pub fn as_str(&self) -> &'static str {
146 match self {
147 Encoding::Json => "json",
148 Encoding::Yaml => "yaml",
149 Encoding::Toml => "toml",
150 Encoding::Json5 => "json5",
151 Encoding::Csv => "csv",
152 Encoding::QueryString => "query-string",
153 Encoding::Xml => "xml",
154 Encoding::Text => "text",
155 Encoding::Gron => "gron",
156 Encoding::Hcl => "hcl",
157 }
158 }
159}
160
161impl fmt::Display for Encoding {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 fmt::Display::fmt(self.as_str(), f)
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use pretty_assertions::assert_eq;
171
172 #[test]
173 fn test_encoding_from_path() {
174 assert_eq!(Encoding::from_path("foo.yaml"), Some(Encoding::Yaml));
175 assert_eq!(Encoding::from_path("foo.yml"), Some(Encoding::Yaml));
176 assert_eq!(Encoding::from_path("foo.json"), Some(Encoding::Json));
177 assert_eq!(Encoding::from_path("foo.json5"), Some(Encoding::Json5));
178 assert_eq!(Encoding::from_path("foo.toml"), Some(Encoding::Toml));
179 assert_eq!(Encoding::from_path("foo.bak"), None);
180 assert_eq!(Encoding::from_path("foo"), None);
181 }
182
183 #[test]
184 fn test_encoding_from_first_line() {
185 assert_eq!(Encoding::from_first_line(""), None);
187 assert_eq!(Encoding::from_first_line(r#"["foo"]"#), None);
188
189 assert_eq!(
191 Encoding::from_first_line(r#"resource "aws_s3_bucket" "my-bucket" {"#),
192 Some(Encoding::Hcl)
193 );
194 assert_eq!(Encoding::from_first_line("{ "), Some(Encoding::Json));
195 assert_eq!(Encoding::from_first_line("[ "), Some(Encoding::Json));
196 assert_eq!(
197 Encoding::from_first_line(r#"{"foo": 1 }"#),
198 Some(Encoding::Json)
199 );
200 assert_eq!(
201 Encoding::from_first_line(r#"[foo .bar."baz".qux]"#),
202 Some(Encoding::Toml)
203 );
204 assert_eq!(
205 Encoding::from_first_line(r#"[[foo .bar."baz".qux]] "#),
206 Some(Encoding::Toml)
207 );
208 assert_eq!(Encoding::from_first_line("%YAML 1.2"), Some(Encoding::Yaml));
209 assert_eq!(
210 Encoding::from_first_line("<!doctype html>"),
211 Some(Encoding::Xml)
212 );
213 assert_eq!(
214 Encoding::from_first_line(r#"<?xml version="1.0" ?>"#),
215 Some(Encoding::Xml)
216 );
217 assert_eq!(
218 Encoding::from_first_line(
219 r#"<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope/" soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">"#
220 ),
221 Some(Encoding::Xml)
222 );
223 }
224}