Skip to content

Commit 21ccaff

Browse files
committed
add initial json5 support
1 parent 1aa06b7 commit 21ccaff

File tree

5 files changed

+117
-27
lines changed

5 files changed

+117
-27
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ json-schema-validator-core = "1.0.0"
1515
lazy_static = "1.5.0"
1616
regex = "1.12.2"
1717
serde_json = "1.0.145"
18+
serde_json5 = "0.2.1"
1819
tinyrick = { version = "0.0.17", optional = true }
1920
tinyrick_extras = { version = "0.0.11", optional = true }
2021
walkdir = "2.5.0"

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
kirill scans projects large and small for JSON correctness:
66

7-
* Basic JSON syntactical validity
8-
* Optionally, enforce [JSON Schema](https://json-schema.org/)
7+
* Check for basic JSON(5) syntactical validity
8+
* Identify JSON(5) files recursively in large project directories
9+
* Verify compliance with a [JSON Schema](https://json-schema.org/)
910

1011
# EXAMPLES
1112

@@ -21,6 +22,12 @@ error: settings.json: expected value at line 1 column 1
2122

2223
$ kirill --schema species.json zoo
2324
error: zoo/bad-bear.json: Missing required property 'species'
25+
26+
$ kirill books.json5
27+
error: books.json5: expected value at line 9 column 9
28+
29+
$ kirill --json5 books.json5
30+
$
2431
```
2532

2633
See `kirill -h` for more options.

examples/books.json5

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"books": [
3+
"The Hobbit",
4+
"The Fellowship of the Ring",
5+
"The Two Towers",
6+
"The Return of the King",
7+
"The Silmarillion",
8+
"Unfinished Tales of Númenor and Middle-earth",
9+
// Nonfiction
10+
"The History of Middle-earth",
11+
"The Children of Húrin",
12+
"Beren and Lúthien",
13+
"The Fall of Gondolin",
14+
"The Fall of Númenor",
15+
]
16+
}

src/kirill.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ use std::process;
1212
/// CLI entrypoint
1313
fn main() {
1414
let brief: String = format!(
15-
"Usage: {} [OPTIONS] <path> [<path> [<path> ...]]",
15+
"usage: {} [OPTIONS] <path> [<path> [<path> ...]]",
1616
env!("CARGO_PKG_NAME")
1717
);
1818

1919
let mut opts: getopts::Options = getopts::Options::new();
20+
opts.optflag("5", "json5", "parse according to JSON5");
2021
opts.optflag("l", "list", "list JSON files");
2122
opts.optopt(
2223
"s",
@@ -39,8 +40,15 @@ fn main() {
3940
die!(0; format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")));
4041
}
4142

43+
let parse_json5: bool = optmatches.opt_present("5");
4244
let list_documents: bool = optmatches.opt_present("l");
4345
let validate_schema: bool = optmatches.opt_present("s");
46+
47+
if parse_json5 && validate_schema {
48+
eprintln!("error: JSON5 incompatible with JSON Schema");
49+
die!(usage);
50+
}
51+
4452
let schema_option: Option<String> = optmatches.opt_str("s");
4553

4654
if validate_schema && schema_option.is_none() {
@@ -54,7 +62,8 @@ fn main() {
5462
}
5563

5664
let roots: Vec<&path::Path> = rest_args.iter().map(path::Path::new).collect();
57-
let json_documents_result = kirill::find_json_documents_sorted(roots);
65+
66+
let json_documents_result = kirill::find_json_documents_sorted(roots, parse_json5);
5867

5968
if let Err(e) = json_documents_result {
6069
die!(e);
@@ -91,7 +100,7 @@ fn main() {
91100
let mut found_invalid: bool = false;
92101

93102
for json_document in json_documents {
94-
if let Some(e) = kirill::validate_json_file_basic(&json_document) {
103+
if let Some(e) = kirill::validate_json_file_basic(&json_document, parse_json5) {
95104
found_invalid = true;
96105
eprintln!("error: {}: {}", json_document, e);
97106
}

src/lib.rs

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern crate json_schema_validator_core;
55
extern crate lazy_static;
66
extern crate regex;
77
extern crate serde_json;
8+
extern crate serde_json5;
89
extern crate walkdir;
910

1011
use std::fmt;
@@ -13,24 +14,58 @@ use std::path;
1314
use std::process;
1415

1516
lazy_static::lazy_static! {
16-
/// DEFAULT_JSON_FILE_PATTERNS collects patterns for identifying JSON files.
17-
pub static ref DEFAULT_JSON_FILE_PATTERNS: regex::Regex = regex::Regex::new(
18-
&[
19-
// eslint
20-
r".*\.eslintrc",
17+
/// JSON_FILE_EXTENSIONS collects common JSON file extensions.
18+
pub static ref JSON_FILE_EXTENSIONS: Vec<String> = vec![
19+
// eslint
20+
"eslintrc".to_string(),
2121

22-
// jsfmt
23-
r".*\.jsfmtrc",
22+
// jsfmt
23+
"jsfmtrc".to_string(),
2424

25-
// jslint
26-
r".*\.jslintrc",
25+
// jslint
26+
"jslintrc".to_string(),
2727

28-
// jshint
29-
r".*\.jshintrc",
28+
// jshint
29+
"jshintrc".to_string(),
3030

31-
// JSON
32-
r".*\.json",
33-
].join("|")
31+
// JSON
32+
"json".to_string(),
33+
];
34+
35+
/// JSON5_FILE_EXTENSIONS collects common JSON5 file extensions.
36+
pub static ref JSON5_FILE_EXTENSIONS: Vec<String> = JSON_FILE_EXTENSIONS
37+
.iter()
38+
.chain(
39+
&[
40+
// JSON5
41+
"json5".to_string(),
42+
]
43+
).cloned()
44+
.collect();
45+
46+
/// DEFAULT_JSON_FILE_PATTERNS collects patterns for identifying JSON files.
47+
pub static ref DEFAULT_JSON_FILE_PATTERNS: regex::Regex = regex::Regex::new(
48+
&format!(
49+
"({})$",
50+
JSON_FILE_EXTENSIONS
51+
.iter()
52+
.map(|e| format!(r".*\.{}", e))
53+
.collect::<Vec<String>>()
54+
.join("|")
55+
)
56+
)
57+
.unwrap();
58+
59+
/// DEFAULT_JSON5_FILE_PATTERNS collects patterns for identifying JSON files.
60+
pub static ref DEFAULT_JSON5_FILE_PATTERNS: regex::Regex = regex::Regex::new(
61+
&format!(
62+
"({})$",
63+
JSON5_FILE_EXTENSIONS
64+
.iter()
65+
.map(|e| format!(r".*\.{}", e))
66+
.collect::<Vec<String>>()
67+
.join("|")
68+
)
3469
)
3570
.unwrap();
3671

@@ -68,6 +103,7 @@ pub enum KirillError {
68103
UnsupportedPathError(String),
69104
PathRenderError(String),
70105
JSONParseError(serde_json::Error),
106+
JSON5ParseError(serde_json5::Error),
71107
JSONSchemaError(String),
72108
}
73109

@@ -79,6 +115,7 @@ impl fmt::Display for KirillError {
79115
KirillError::UnsupportedPathError(e) => write!(f, "{}", e),
80116
KirillError::PathRenderError(e) => write!(f, "{}", e),
81117
KirillError::JSONParseError(e) => write!(f, "{}", e),
118+
KirillError::JSON5ParseError(e) => write!(f, "{}", e),
82119
KirillError::JSONSchemaError(e) => write!(f, "{}", e),
83120
}
84121
}
@@ -94,7 +131,10 @@ impl die::PrintExit for KirillError {
94131
/// find_json_documents recursively searches
95132
/// the given directories and/or file root paths
96133
/// for JSON documents.
97-
pub fn find_json_documents(roots: Vec<&path::Path>) -> Result<Vec<String>, KirillError> {
134+
pub fn find_json_documents(
135+
roots: Vec<&path::Path>,
136+
parse_json5: bool,
137+
) -> Result<Vec<String>, KirillError> {
98138
let mut pth_bufs = Vec::<path::PathBuf>::new();
99139

100140
for root in roots {
@@ -146,7 +186,13 @@ pub fn find_json_documents(roots: Vec<&path::Path>) -> Result<Vec<String>, Kiril
146186
continue;
147187
}
148188

149-
if DEFAULT_JSON_FILE_PATTERNS.is_match(pth_abs_str) {
189+
let pattern = if parse_json5 {
190+
&*DEFAULT_JSON5_FILE_PATTERNS
191+
} else {
192+
&*DEFAULT_JSON_FILE_PATTERNS
193+
};
194+
195+
if pattern.is_match(pth_abs_str) {
150196
let pth_clean_buf = clean_path::clean(pth);
151197
let pth_clean = pth_clean_buf.as_path();
152198
let pth_clean_str = pth_clean
@@ -163,8 +209,11 @@ pub fn find_json_documents(roots: Vec<&path::Path>) -> Result<Vec<String>, Kiril
163209
}
164210

165211
/// find_json_documents_sorted lexicographically sorts any JSON document results.
166-
pub fn find_json_documents_sorted(roots: Vec<&path::Path>) -> Result<Vec<String>, KirillError> {
167-
let mut json_documents = find_json_documents(roots)?;
212+
pub fn find_json_documents_sorted(
213+
roots: Vec<&path::Path>,
214+
parse_json5: bool,
215+
) -> Result<Vec<String>, KirillError> {
216+
let mut json_documents = find_json_documents(roots, parse_json5)?;
168217
json_documents.sort();
169218
Ok(json_documents)
170219
}
@@ -173,7 +222,7 @@ pub fn find_json_documents_sorted(roots: Vec<&path::Path>) -> Result<Vec<String>
173222
///
174223
/// Returns Some(error) on error.
175224
/// Otherwise, returns None.
176-
pub fn validate_json_file_basic(s: &str) -> Option<KirillError> {
225+
pub fn validate_json_file_basic(s: &str, parse_json5: bool) -> Option<KirillError> {
177226
let pth = path::Path::new(s);
178227

179228
match fs::read_to_string(pth) {
@@ -182,9 +231,17 @@ pub fn validate_json_file_basic(s: &str) -> Option<KirillError> {
182231
pth.display()
183232
))),
184233

185-
Ok(contents) => serde_json::from_str::<serde_json::Value>(&contents)
186-
.map_err(KirillError::JSONParseError)
187-
.err(),
234+
Ok(contents) => {
235+
if parse_json5 {
236+
serde_json5::from_str::<serde_json::Value>(&contents)
237+
.map_err(KirillError::JSON5ParseError)
238+
.err()
239+
} else {
240+
serde_json::from_str::<serde_json::Value>(&contents)
241+
.map_err(KirillError::JSONParseError)
242+
.err()
243+
}
244+
}
188245
}
189246
}
190247

0 commit comments

Comments
 (0)