-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathcompose.rs
More file actions
125 lines (109 loc) · 3.72 KB
/
compose.rs
File metadata and controls
125 lines (109 loc) · 3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*!
Compose command - convert various formats to CBOR
*/
use crate::cdn::{self, CdnValue};
use crate::io::{Input, Output};
use clap::Parser;
/// Input format for compose command
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
enum InputFormat {
/// CBOR Diagnostic Notation (lossless)
Cdn,
/// JSON format (lossy - no tags, limited types)
Json,
}
/// Convert various text formats to CBOR binary
#[derive(Parser, Debug)]
#[command(
about = "Convert text formats to CBOR binary",
long_about = "Parse text in various formats (CDN, JSON) and convert to CBOR binary.\n\n\
CDN is lossless and preserves all CBOR semantics.\n\
JSON is lossy but convenient for simple data structures."
)]
pub struct Command {
/// Input format
#[arg(
long,
default_value = "cdn",
value_name = "FORMAT",
help = "Input format: cdn (lossless), json (lossy)"
)]
format: InputFormat,
/// Output file (default: stdout)
#[arg(short = 'o', long)]
output: Option<Output>,
/// Input file (use '-' for stdin)
input: Input,
}
impl Command {
pub fn exec(self) -> anyhow::Result<()> {
// Read input text
let input_text = self.input.read_to_string()?;
// Parse to CDN AST based on format
let ast = match self.format {
InputFormat::Cdn => {
// Parse CDN to AST
cdn::parse(&input_text).map_err(|errors| {
// Format parse errors nicely
let error_msg = errors
.iter()
.map(|e| format!("Parse error at {:?}: {}", e.span(), e))
.collect::<Vec<_>>()
.join("\n");
anyhow::anyhow!("Failed to parse CDN:\n{}", error_msg)
})?
}
InputFormat::Json => {
// Parse JSON and convert to CDN AST
let json_value: serde_json::Value = serde_json::from_str(&input_text)?;
json_to_cdn(json_value)?
}
};
// Convert AST to CBOR bytes
let cbor_bytes = hardy_cbor::encode::emit(&ast).0;
// Write CBOR to output
let output = self.output.unwrap_or(Output::Stdout);
output.write_all(&cbor_bytes)?;
Ok(())
}
}
/// Convert a JSON value to CDN AST
///
/// Note: This is a lossy conversion since JSON doesn't support all CBOR features
fn json_to_cdn(value: serde_json::Value) -> anyhow::Result<CdnValue> {
use serde_json::Value as J;
Ok(match value {
J::Null => CdnValue::Null,
J::Bool(b) => CdnValue::Bool(b),
J::Number(n) => {
if let Some(i) = n.as_i64() {
if i >= 0 {
CdnValue::Unsigned(i as u64)
} else {
CdnValue::Negative(i)
}
} else if let Some(u) = n.as_u64() {
CdnValue::Unsigned(u)
} else if let Some(f) = n.as_f64() {
CdnValue::Float(f)
} else {
anyhow::bail!("Invalid JSON number: {}", n)
}
}
J::String(s) => CdnValue::TextString(s),
J::Array(arr) => {
let items: Result<Vec<_>, _> = arr.into_iter().map(json_to_cdn).collect();
CdnValue::Array(items?)
}
J::Object(obj) => {
let mut pairs = Vec::new();
for (key, val) in obj {
// JSON object keys are always strings
let cdn_key = CdnValue::TextString(key);
let cdn_val = json_to_cdn(val)?;
pairs.push((cdn_key, cdn_val));
}
CdnValue::Map(pairs)
}
})
}