Skip to content

Commit a3e3d23

Browse files
committed
feat: add v1 parsing
1 parent ddc32f8 commit a3e3d23

File tree

7 files changed

+189
-44
lines changed

7 files changed

+189
-44
lines changed

Cargo.lock

Lines changed: 60 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ name = "jsonkdl"
88
path = "src/main.rs"
99

1010
[dependencies]
11-
kdl = "6.3.4"
11+
kdl = { version = "6.3.4", features = ["v1"] }
1212
serde_json = "1.0.140"

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ Converts JSON to KDL.
55
## Usage
66

77
```
8-
JSON to KDL Converter
9-
Usage:
10-
jsonkdl <input> <output> [options]
8+
Usage: jsonkdl <input> <output> [options]
9+
Converts JSON to KDL.
10+
By default, KDL spec v2 is used.
11+
1112
Arguments:
12-
<input> Input JSON file
13-
<output> Output KDL file
13+
<input> Path to input JSON file
14+
<output> Path to output KDL file
15+
1416
Options:
15-
-f, --force Overwrite existing files
16-
-v, --verbose Verbose output
17+
-1, --kdl-v1 Convert to KDL v1
18+
-2, --kdl-v2 Convert to KDL v2
19+
-f, --force Overwrite output if it exists
20+
-v, --verbose Print extra information during conversion
1721
-h, --help Show this help message
1822
```

src/cli.rs

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ use std::env;
44
use std::path::Path;
55

66
const HELP_TEXT: &str = "\
7-
jsonkdl - convert json to kdl
8-
Usage:
9-
jsonkdl <input> <output> [options]
7+
Usage: jsonkdl <input> <output> [options]
8+
Converts JSON to KDL.
9+
By default, KDL spec v2 is used.
1010
1111
Arguments:
12-
<input> Path to input json file
13-
<output> Path to output kdl file
12+
<input> Path to input JSON file
13+
<output> Path to output KDL file
1414
15-
Aptions:
15+
Options:
16+
-1, --kdl-v1 Convert to KDL v1
17+
-2, --kdl-v2 Convert to KDL v2
1618
-f, --force Overwrite output if it exists
1719
-v, --verbose Print extra information during conversion
1820
-h, --help Show this help message
@@ -23,6 +25,7 @@ pub enum CliError {
2325
MissingInput,
2426
MissingOutput,
2527
HelpRequested,
28+
MultipleKdlVersion,
2629
InvalidInputPath(String),
2730
FileExists(String),
2831
InputNotFound(String),
@@ -37,6 +40,7 @@ pub struct Args {
3740
pub output: String,
3841
pub force: bool,
3942
pub verbose: bool,
43+
pub kdl_version: i32,
4044
}
4145

4246
impl std::fmt::Display for CliError {
@@ -45,6 +49,7 @@ impl std::fmt::Display for CliError {
4549
CliError::MissingInput => writeln!(f, "missing input path"),
4650
CliError::MissingOutput => writeln!(f, "missing output path"),
4751
CliError::HelpRequested => writeln!(f, "help requested"),
52+
CliError::MultipleKdlVersion => writeln!(f, "specify only one of --kdl-v1 or --kdl-v2"),
4853
CliError::InvalidInputPath(path) => writeln!(f, "not a file: {}", path),
4954
CliError::FileExists(path) => writeln!(f, "file exists: {} (use --force to overwrite)", path),
5055
CliError::InputNotFound(path) => writeln!(f, "no such file: {}", path),
@@ -71,33 +76,62 @@ impl From<ConversionError> for CliError {
7176
impl Args {
7277
fn parse() -> Result<Self> {
7378
let args: Vec<String> = env::args().collect();
79+
let mut positional: Vec<String> = vec![];
80+
let mut force = false;
81+
let mut verbose = false;
82+
let mut kdl_version = 0;
7483

75-
// If no args besides the program name, show help
76-
if args.len() == 1
77-
|| args.iter().any(|arg| arg == "--help" || arg == "-h")
78-
{
84+
if args.len() == 1 {
7985
return Err(CliError::HelpRequested);
8086
}
8187

82-
let force = args.iter().any(|arg| arg == "--force" || arg == "-f");
83-
let verbose = args.iter().any(|arg| arg == "--verbose" || arg == "-v");
88+
for arg in args.iter().skip(1) {
89+
if !arg.starts_with('-') {
90+
positional.push(arg.into());
91+
} else if arg == "-f" || arg == "--force" {
92+
force = true;
93+
} else if arg == "-v" || arg == "--verbose" {
94+
verbose = true;
95+
} else if arg == "-1" || arg == "--kdl-v1" {
96+
if kdl_version != 0 {
97+
return Err(CliError::MultipleKdlVersion);
98+
}
99+
100+
kdl_version = 1;
101+
} else if arg == "-2" || arg == "--kdl-v2" {
102+
if kdl_version != 0 {
103+
return Err(CliError::MultipleKdlVersion);
104+
}
105+
106+
kdl_version = 2;
107+
} else if arg == "-h" || arg == "--help" {
108+
return Err(CliError::HelpRequested);
109+
}
110+
}
111+
112+
if kdl_version == 0 {
113+
kdl_version = 2;
114+
};
84115

85-
let positional: Vec<String> = args
86-
.iter()
87-
.skip(1)
88-
.filter(|arg| !arg.starts_with('-'))
89-
.cloned()
90-
.collect();
116+
let input = positional
117+
.get(0)
118+
.ok_or(CliError::MissingInput)?
119+
.to_string();
91120

92-
let input = positional.get(0).ok_or(CliError::MissingInput)?.to_string();
93-
let output = positional.get(1).ok_or(CliError::MissingOutput)?.to_string();
121+
let output = positional
122+
.get(1)
123+
.ok_or(CliError::MissingOutput)?
124+
.to_string();
94125

95-
Ok(Self {
126+
let result = Self {
96127
input,
97128
output,
98129
force,
99130
verbose,
100-
})
131+
kdl_version,
132+
};
133+
134+
Ok(result)
101135
}
102136
}
103137

@@ -130,6 +164,6 @@ pub fn run() -> Result<()> {
130164
return Err(CliError::FileExists(args.output));
131165
}
132166

133-
convert_file_contents(input_path, output_path, args.verbose)?;
167+
convert_file_contents(input_path, output_path, args.verbose, args.kdl_version)?;
134168
Ok(())
135169
}

src/convert.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,19 @@ impl From<serde_json::Error> for ConversionError {
4343

4444
pub type Result<T> = std::result::Result<T, ConversionError>;
4545

46-
pub fn convert_file_contents(input: &Path, output: &Path, verbose: bool) -> Result<()> {
46+
pub fn convert_file_contents(input: &Path, output: &Path, verbose: bool, kdl_version: i32) -> Result<()> {
4747
let json_content = fs::read_to_string(input)?;
4848
let json_value: Value = serde_json::from_str(&json_content)?;
49-
let kdl_doc = json_to_kdl(json_value)?;
49+
let mut kdl_doc = json_to_kdl(json_value)?;
50+
51+
// For some reason, you MUST autoformat before ensuring version.
52+
kdl_doc.autoformat();
53+
54+
if kdl_version == 2 {
55+
kdl_doc.ensure_v2();
56+
} else {
57+
kdl_doc.ensure_v1();
58+
}
5059

5160
// Create output directory if needed
5261
if let Some(parent) = output.parent() {
@@ -74,8 +83,6 @@ pub fn json_to_kdl(json: Value) -> Result<KdlDocument> {
7483
document.nodes_mut().push(node);
7584
}
7685

77-
document.autoformat();
78-
7986
Ok(document)
8087
}
8188

tests/integration.rs renamed to tests/integration_v1.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use std::process::Command;
55
use kdl::KdlDocument;
66

77
#[test]
8-
fn test_examples() {
8+
fn test_examples_v1() {
99
let examples_dir = Path::new("examples");
10-
let output_dir = Path::new("target/test_outputs");
10+
let output_dir = Path::new("target/test_outputs/v1");
1111

1212
fs::create_dir_all(output_dir).expect("failed to create test output directory");
1313

@@ -23,9 +23,10 @@ fn test_examples() {
2323
let output_file = output_dir.join(format!("{file_name}.kdl"));
2424

2525
let status = Command::new(env!("CARGO_BIN_EXE_jsonkdl"))
26+
.arg("-f")
27+
.arg("-1")
2628
.arg(&path)
2729
.arg(&output_file)
28-
.arg("-f")
2930
.status()
3031
.expect("failed to run binary");
3132

@@ -38,8 +39,6 @@ fn test_examples() {
3839
.read_to_string(&mut kdl_str)
3940
.expect("failed to read output kdl");
4041

41-
let _doc: KdlDocument = kdl_str
42-
.parse()
43-
.expect("output is not valid kdl");
42+
KdlDocument::parse_v1(&kdl_str).expect("output is not valid kdl");
4443
}
4544
}

tests/integration_v2.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use std::fs::{self, File};
2+
use std::io::Read;
3+
use std::path::Path;
4+
use std::process::Command;
5+
use kdl::KdlDocument;
6+
7+
#[test]
8+
fn test_examples_v2() {
9+
let examples_dir = Path::new("examples");
10+
let output_dir = Path::new("target/test_outputs/v2");
11+
12+
fs::create_dir_all(output_dir).expect("failed to create test output directory");
13+
14+
for entry in fs::read_dir(examples_dir).expect("failed to read examples directory") {
15+
let entry = entry.expect("failed to read entry");
16+
let path = entry.path();
17+
18+
if path.extension().and_then(|s| s.to_str()) != Some("json") {
19+
continue;
20+
}
21+
22+
let file_name = path.file_stem().unwrap().to_str().unwrap();
23+
let output_file = output_dir.join(format!("{file_name}.kdl"));
24+
25+
let status = Command::new(env!("CARGO_BIN_EXE_jsonkdl"))
26+
.arg("-f")
27+
.arg("-2")
28+
.arg(&path)
29+
.arg(&output_file)
30+
.status()
31+
.expect("failed to run binary");
32+
33+
assert!(status.success(), "jsonkdl failed on input: {:?}", path);
34+
35+
let mut kdl_str = String::new();
36+
37+
File::open(&output_file)
38+
.expect("failed to open output kdl")
39+
.read_to_string(&mut kdl_str)
40+
.expect("failed to read output kdl");
41+
42+
KdlDocument::parse_v2(&kdl_str).expect("output is not valid kdl");
43+
}
44+
}

0 commit comments

Comments
 (0)