yerba 0.2.0-x86_64-linux-gnu
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +528 -0
- data/exe/yerba +6 -0
- data/ext/yerba/extconf.rb +111 -0
- data/ext/yerba/yerba.c +752 -0
- data/lib/yerba/3.2/yerba.so +0 -0
- data/lib/yerba/3.3/yerba.so +0 -0
- data/lib/yerba/3.4/yerba.so +0 -0
- data/lib/yerba/4.0/yerba.so +0 -0
- data/lib/yerba/collection.rb +31 -0
- data/lib/yerba/document.rb +59 -0
- data/lib/yerba/formatting.rb +18 -0
- data/lib/yerba/location.rb +5 -0
- data/lib/yerba/map.rb +166 -0
- data/lib/yerba/scalar.rb +85 -0
- data/lib/yerba/sequence.rb +182 -0
- data/lib/yerba/version.rb +5 -0
- data/lib/yerba.rb +131 -0
- data/rust/Cargo.lock +805 -0
- data/rust/Cargo.toml +36 -0
- data/rust/build.rs +11 -0
- data/rust/cbindgen.toml +27 -0
- data/rust/rustfmt.toml +2 -0
- data/rust/src/commands/apply.rs +5 -0
- data/rust/src/commands/blank_lines.rs +58 -0
- data/rust/src/commands/check.rs +5 -0
- data/rust/src/commands/delete.rs +35 -0
- data/rust/src/commands/get.rs +194 -0
- data/rust/src/commands/init.rs +89 -0
- data/rust/src/commands/insert.rs +106 -0
- data/rust/src/commands/mate.rs +55 -0
- data/rust/src/commands/mod.rs +349 -0
- data/rust/src/commands/move_item.rs +54 -0
- data/rust/src/commands/move_key.rs +87 -0
- data/rust/src/commands/quote_style.rs +62 -0
- data/rust/src/commands/remove.rs +35 -0
- data/rust/src/commands/rename.rs +37 -0
- data/rust/src/commands/set.rs +59 -0
- data/rust/src/commands/sort.rs +52 -0
- data/rust/src/commands/sort_keys.rs +62 -0
- data/rust/src/commands/version.rs +8 -0
- data/rust/src/document.rs +2237 -0
- data/rust/src/error.rs +45 -0
- data/rust/src/ffi.rs +991 -0
- data/rust/src/json.rs +128 -0
- data/rust/src/lib.rs +29 -0
- data/rust/src/main.rs +72 -0
- data/rust/src/quote_style.rs +42 -0
- data/rust/src/selector.rs +241 -0
- data/rust/src/syntax.rs +262 -0
- data/rust/src/yaml_writer.rs +89 -0
- data/rust/src/yerbafile.rs +475 -0
- data/sig/yerba.rbs +3 -0
- data/yerba.gemspec +52 -0
- metadata +108 -0
data/rust/src/syntax.rs
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
use rowan::ast::AstNode;
|
|
2
|
+
use rowan::{TextRange, TextSize};
|
|
3
|
+
|
|
4
|
+
use yaml_parser::ast::{BlockMap, BlockMapEntry};
|
|
5
|
+
use yaml_parser::{SyntaxKind, SyntaxNode, SyntaxToken};
|
|
6
|
+
|
|
7
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
8
|
+
pub struct ScalarValue {
|
|
9
|
+
pub text: String,
|
|
10
|
+
pub kind: SyntaxKind,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#[repr(C)]
|
|
14
|
+
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
15
|
+
pub enum YerbaValueType {
|
|
16
|
+
Null = 0,
|
|
17
|
+
Boolean = 1,
|
|
18
|
+
Integer = 2,
|
|
19
|
+
Float = 3,
|
|
20
|
+
String = 4,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub fn detect_yaml_type(scalar: &ScalarValue) -> YerbaValueType {
|
|
24
|
+
if scalar.kind != SyntaxKind::PLAIN_SCALAR {
|
|
25
|
+
return YerbaValueType::String;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
detect_yaml_type_from_plain(&scalar.text)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub fn extract_scalar(node: &SyntaxNode) -> Option<ScalarValue> {
|
|
32
|
+
let token = find_scalar_token(node)?;
|
|
33
|
+
|
|
34
|
+
let text = match token.kind() {
|
|
35
|
+
SyntaxKind::PLAIN_SCALAR => token.text().to_string(),
|
|
36
|
+
|
|
37
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
38
|
+
let raw = token.text();
|
|
39
|
+
unescape_double_quoted(&raw[1..raw.len() - 1])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
43
|
+
let raw = token.text();
|
|
44
|
+
unescape_single_quoted(&raw[1..raw.len() - 1])
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_ => return None,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
Some(ScalarValue {
|
|
51
|
+
text,
|
|
52
|
+
kind: token.kind(),
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub fn is_map_key(token: &SyntaxToken) -> bool {
|
|
57
|
+
token
|
|
58
|
+
.parent_ancestors()
|
|
59
|
+
.any(|ancestor| ancestor.kind() == SyntaxKind::BLOCK_MAP_KEY)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub fn find_entry_by_key(map: &BlockMap, key: &str) -> Option<BlockMapEntry> {
|
|
63
|
+
map.entries().find(|entry| {
|
|
64
|
+
entry
|
|
65
|
+
.key()
|
|
66
|
+
.and_then(|key_node| extract_scalar_text(key_node.syntax()))
|
|
67
|
+
.map(|key_text| key_text == key)
|
|
68
|
+
.unwrap_or(false)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn find_scalar_token(node: &SyntaxNode) -> Option<SyntaxToken> {
|
|
73
|
+
node
|
|
74
|
+
.descendants_with_tokens()
|
|
75
|
+
.filter_map(|element| element.into_token())
|
|
76
|
+
.find(|token| {
|
|
77
|
+
matches!(
|
|
78
|
+
token.kind(),
|
|
79
|
+
SyntaxKind::PLAIN_SCALAR | SyntaxKind::DOUBLE_QUOTED_SCALAR | SyntaxKind::SINGLE_QUOTED_SCALAR
|
|
80
|
+
)
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pub fn format_scalar_value(value: &str, kind: SyntaxKind) -> String {
|
|
85
|
+
match kind {
|
|
86
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
87
|
+
let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
|
|
88
|
+
format!("\"{}\"", escaped)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
92
|
+
let escaped = value.replace('\'', "''");
|
|
93
|
+
format!("'{}'", escaped)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_ => value.to_string(),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pub fn extract_scalar_text(node: &SyntaxNode) -> Option<String> {
|
|
101
|
+
let token = find_scalar_token(node)?;
|
|
102
|
+
|
|
103
|
+
match token.kind() {
|
|
104
|
+
SyntaxKind::PLAIN_SCALAR => Some(token.text().to_string()),
|
|
105
|
+
|
|
106
|
+
SyntaxKind::DOUBLE_QUOTED_SCALAR => {
|
|
107
|
+
let text = token.text();
|
|
108
|
+
let inner = &text[1..text.len() - 1];
|
|
109
|
+
|
|
110
|
+
Some(unescape_double_quoted(inner))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
SyntaxKind::SINGLE_QUOTED_SCALAR => {
|
|
114
|
+
let text = token.text();
|
|
115
|
+
let inner = &text[1..text.len() - 1];
|
|
116
|
+
|
|
117
|
+
Some(unescape_single_quoted(inner))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_ => None,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pub fn unescape_double_quoted(text: &str) -> String {
|
|
125
|
+
text.replace("\\\"", "\"").replace("\\\\", "\\")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
pub fn unescape_single_quoted(text: &str) -> String {
|
|
129
|
+
text.replace("''", "'")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
pub fn preceding_whitespace_indent(node: &SyntaxNode) -> String {
|
|
133
|
+
if let Some(token) = preceding_whitespace_token(node) {
|
|
134
|
+
let text = token.text();
|
|
135
|
+
|
|
136
|
+
if let Some(newline) = text.rfind('\n') {
|
|
137
|
+
return text[newline + 1..].to_string();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let start_offset: usize = node.text_range().start().into();
|
|
142
|
+
let root = node.ancestors().last().unwrap_or_else(|| node.clone());
|
|
143
|
+
let source = root.text().to_string();
|
|
144
|
+
|
|
145
|
+
if start_offset > 0 {
|
|
146
|
+
let before = &source[..start_offset];
|
|
147
|
+
|
|
148
|
+
if let Some(newline_position) = before.rfind('\n') {
|
|
149
|
+
return before[newline_position + 1..].to_string();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
String::new()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
pub fn preceding_whitespace_token(node: &SyntaxNode) -> Option<SyntaxToken> {
|
|
157
|
+
node
|
|
158
|
+
.prev_sibling_or_token()
|
|
159
|
+
.and_then(|sibling| sibling.into_token())
|
|
160
|
+
.filter(|token| token.kind() == SyntaxKind::WHITESPACE)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
pub fn following_whitespace_token(node: &SyntaxNode) -> Option<SyntaxToken> {
|
|
164
|
+
node
|
|
165
|
+
.next_sibling_or_token()
|
|
166
|
+
.and_then(|sibling| sibling.into_token())
|
|
167
|
+
.filter(|token| token.kind() == SyntaxKind::WHITESPACE)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
pub fn removal_range(node: &SyntaxNode) -> TextRange {
|
|
171
|
+
let node_range = node.text_range();
|
|
172
|
+
|
|
173
|
+
if let Some(whitespace_token) = preceding_whitespace_token(node) {
|
|
174
|
+
let whitespace_text = whitespace_token.text();
|
|
175
|
+
let whitespace_start = whitespace_token.text_range().start();
|
|
176
|
+
|
|
177
|
+
let remove_from = whitespace_text
|
|
178
|
+
.rfind('\n')
|
|
179
|
+
.map(|offset| whitespace_start + TextSize::from(offset as u32))
|
|
180
|
+
.unwrap_or(whitespace_start);
|
|
181
|
+
|
|
182
|
+
return TextRange::new(remove_from, node_range.end());
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if let Some(whitespace_token) = following_whitespace_token(node) {
|
|
186
|
+
return TextRange::new(node_range.start(), whitespace_token.text_range().end());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
node_range
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
pub fn is_yaml_non_string(value: &str) -> bool {
|
|
193
|
+
detect_yaml_type_from_plain(value) != YerbaValueType::String
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
pub fn is_yaml_truthy(value: &str) -> bool {
|
|
197
|
+
matches!(
|
|
198
|
+
value,
|
|
199
|
+
"true" | "True" | "TRUE" | "yes" | "Yes" | "YES" | "on" | "On" | "ON" | "y" | "Y"
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
pub fn detect_yaml_type_from_plain(value: &str) -> YerbaValueType {
|
|
204
|
+
// Null (YAML 1.1 + 1.2)
|
|
205
|
+
if matches!(value, "null" | "Null" | "NULL" | "~" | "") {
|
|
206
|
+
return YerbaValueType::Null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Boolean (YAML 1.2 + 1.1)
|
|
210
|
+
if matches!(
|
|
211
|
+
value,
|
|
212
|
+
"true"
|
|
213
|
+
| "True"
|
|
214
|
+
| "TRUE"
|
|
215
|
+
| "false"
|
|
216
|
+
| "False"
|
|
217
|
+
| "FALSE"
|
|
218
|
+
| "yes"
|
|
219
|
+
| "Yes"
|
|
220
|
+
| "YES"
|
|
221
|
+
| "no"
|
|
222
|
+
| "No"
|
|
223
|
+
| "NO"
|
|
224
|
+
| "on"
|
|
225
|
+
| "On"
|
|
226
|
+
| "ON"
|
|
227
|
+
| "off"
|
|
228
|
+
| "Off"
|
|
229
|
+
| "OFF"
|
|
230
|
+
| "y"
|
|
231
|
+
| "Y"
|
|
232
|
+
| "n"
|
|
233
|
+
| "N"
|
|
234
|
+
) {
|
|
235
|
+
return YerbaValueType::Boolean;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Integer
|
|
239
|
+
if value.parse::<i64>().is_ok() {
|
|
240
|
+
return YerbaValueType::Integer;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Octal (0o...) and hex (0x...)
|
|
244
|
+
if value.starts_with("0x") || value.starts_with("0X") || value.starts_with("0o") || value.starts_with("0O") {
|
|
245
|
+
return YerbaValueType::Integer;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Special floats (YAML 1.1 + 1.2)
|
|
249
|
+
if matches!(
|
|
250
|
+
value,
|
|
251
|
+
".inf" | ".Inf" | ".INF" | "-.inf" | "-.Inf" | "-.INF" | "+.inf" | "+.Inf" | "+.INF" | ".nan" | ".NaN" | ".NAN"
|
|
252
|
+
) {
|
|
253
|
+
return YerbaValueType::Float;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Float
|
|
257
|
+
if value.parse::<f64>().is_ok() {
|
|
258
|
+
return YerbaValueType::Float;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
YerbaValueType::String
|
|
262
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
use serde_json::Value;
|
|
2
|
+
|
|
3
|
+
use crate::QuoteStyle;
|
|
4
|
+
|
|
5
|
+
pub fn json_to_yaml_text(value: &Value, quote_style: &QuoteStyle, indent: usize) -> String {
|
|
6
|
+
match value {
|
|
7
|
+
Value::Object(map) => {
|
|
8
|
+
let prefix = " ".repeat(indent);
|
|
9
|
+
|
|
10
|
+
map
|
|
11
|
+
.iter()
|
|
12
|
+
.map(|(k, v)| match v {
|
|
13
|
+
Value::Array(arr) => {
|
|
14
|
+
let items: Vec<String> = arr
|
|
15
|
+
.iter()
|
|
16
|
+
.map(|item| match item {
|
|
17
|
+
Value::Object(_) => {
|
|
18
|
+
let inner = json_to_yaml_text(item, quote_style, indent + 4);
|
|
19
|
+
format!("{} - {}", prefix, inner.trim_start())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_ => format!("{} - {}", prefix, format_yaml_scalar(item, quote_style)),
|
|
23
|
+
})
|
|
24
|
+
.collect();
|
|
25
|
+
|
|
26
|
+
format!("{}{}:\n{}", prefix, k, items.join("\n"))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Value::Object(_) => {
|
|
30
|
+
let inner = json_to_yaml_text(v, quote_style, indent + 2);
|
|
31
|
+
format!("{}{}:\n{}", prefix, k, inner)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_ => format!("{}{}: {}", prefix, k, format_yaml_scalar(v, quote_style)),
|
|
35
|
+
})
|
|
36
|
+
.collect::<Vec<_>>()
|
|
37
|
+
.join("\n")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_ => {
|
|
41
|
+
let prefix = " ".repeat(indent);
|
|
42
|
+
|
|
43
|
+
format!("{}{}", prefix, format_yaml_scalar(value, quote_style))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn format_yaml_scalar(value: &Value, quote_style: &QuoteStyle) -> String {
|
|
49
|
+
match value {
|
|
50
|
+
Value::Null => "null".to_string(),
|
|
51
|
+
Value::Bool(boolean) => boolean.to_string(),
|
|
52
|
+
Value::Number(number) => number.to_string(),
|
|
53
|
+
Value::String(string) => match quote_style {
|
|
54
|
+
QuoteStyle::Double => {
|
|
55
|
+
let escaped = string.replace('\\', "\\\\").replace('"', "\\\"");
|
|
56
|
+
|
|
57
|
+
format!("\"{}\"", escaped)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
QuoteStyle::Single => {
|
|
61
|
+
let escaped = string.replace('\'', "''");
|
|
62
|
+
|
|
63
|
+
format!("'{}'", escaped)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
QuoteStyle::Plain => {
|
|
67
|
+
if crate::syntax::is_yaml_non_string(string) {
|
|
68
|
+
format!("\"{}\"", string.replace('"', "\\\""))
|
|
69
|
+
} else {
|
|
70
|
+
string.clone()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_ => {
|
|
75
|
+
let escaped = string.replace('\\', "\\\\").replace('"', "\\\"");
|
|
76
|
+
|
|
77
|
+
format!("\"{}\"", escaped)
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
Value::Array(array) => {
|
|
82
|
+
let items: Vec<String> = array.iter().map(|item| format_yaml_scalar(item, quote_style)).collect();
|
|
83
|
+
|
|
84
|
+
format!("[{}]", items.join(", "))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Value::Object(_) => format!("{:?}", value),
|
|
88
|
+
}
|
|
89
|
+
}
|