yerba 0.7.5 → 0.8.0
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 +4 -4
- data/README.md +67 -2
- data/ext/yerba/include/yerba.h +8 -0
- data/ext/yerba/yerba.c +70 -8
- data/lib/yerba/map.rb +15 -10
- data/lib/yerba/node.rb +2 -2
- data/lib/yerba/sequence.rb +13 -1
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/commands/get.rs +12 -2
- data/rust/src/commands/rename.rs +3 -3
- data/rust/src/commands/set.rs +11 -1
- data/rust/src/document/condition.rs +89 -32
- data/rust/src/document/delete.rs +6 -1
- data/rust/src/document/get.rs +94 -26
- data/rust/src/document/insert.rs +22 -0
- data/rust/src/document/mod.rs +67 -18
- data/rust/src/document/set.rs +81 -0
- data/rust/src/document/style.rs +12 -0
- data/rust/src/error.rs +14 -0
- data/rust/src/ffi.rs +71 -9
- data/rust/src/json.rs +10 -1
- data/rust/src/lib.rs +14 -6
- data/rust/src/main.rs +1 -0
- data/rust/src/selector.rs +19 -3
- data/rust/src/syntax.rs +46 -25
- data/rust/src/yerbafile.rs +111 -7
- metadata +1 -1
data/rust/src/ffi.rs
CHANGED
|
@@ -330,6 +330,24 @@ pub unsafe extern "C" fn yerba_document_resolve_selectors(document: *const Docum
|
|
|
330
330
|
CString::new(json_string).unwrap_or_default().into_raw()
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
#[no_mangle]
|
|
334
|
+
pub unsafe extern "C" fn yerba_selector_has_wildcard(path: *const c_char) -> bool {
|
|
335
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
336
|
+
|
|
337
|
+
crate::selector::Selector::parse(selector_string).has_wildcard()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[no_mangle]
|
|
341
|
+
pub unsafe extern "C" fn yerba_document_keys(document: *const Document, path: *const c_char) -> *mut c_char {
|
|
342
|
+
let document = &*document;
|
|
343
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
344
|
+
|
|
345
|
+
let keys = document.keys_at(selector_string);
|
|
346
|
+
let json_string = serde_json::to_string(&keys).unwrap_or_else(|_| "[]".to_string());
|
|
347
|
+
|
|
348
|
+
CString::new(json_string).unwrap_or_default().into_raw()
|
|
349
|
+
}
|
|
350
|
+
|
|
333
351
|
#[no_mangle]
|
|
334
352
|
pub unsafe extern "C" fn yerba_document_get_quote_style(document: *const Document, path: *const c_char) -> *mut c_char {
|
|
335
353
|
let document = &*document;
|
|
@@ -409,12 +427,33 @@ pub unsafe extern "C" fn yerba_document_set_sequence_indent(document: *mut Docum
|
|
|
409
427
|
}
|
|
410
428
|
}
|
|
411
429
|
|
|
430
|
+
#[no_mangle]
|
|
431
|
+
pub unsafe extern "C" fn yerba_validate_condition(condition: *const c_char, item_context: bool) -> *mut c_char {
|
|
432
|
+
if condition.is_null() {
|
|
433
|
+
return ptr::null_mut();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
let cond = CStr::from_ptr(condition).to_str().unwrap_or("");
|
|
437
|
+
|
|
438
|
+
let result = if item_context {
|
|
439
|
+
crate::validate_item_condition(cond)
|
|
440
|
+
} else {
|
|
441
|
+
crate::validate_condition(cond)
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
match result {
|
|
445
|
+
Ok(()) => ptr::null_mut(),
|
|
446
|
+
Err(error) => CString::new(error.to_string()).unwrap_or_default().into_raw(),
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
412
450
|
#[no_mangle]
|
|
413
451
|
pub unsafe extern "C" fn yerba_document_evaluate_condition(document: *const Document, parent_path: *const c_char, condition: *const c_char) -> bool {
|
|
414
452
|
let document = &*document;
|
|
415
453
|
let parent = CStr::from_ptr(parent_path).to_str().unwrap_or("");
|
|
416
454
|
let cond = CStr::from_ptr(condition).to_str().unwrap_or("");
|
|
417
|
-
|
|
455
|
+
|
|
456
|
+
document.evaluate_condition(parent, cond).unwrap_or(false)
|
|
418
457
|
}
|
|
419
458
|
|
|
420
459
|
#[no_mangle]
|
|
@@ -440,7 +479,7 @@ pub unsafe extern "C" fn yerba_document_find(document: *const Document, path: *c
|
|
|
440
479
|
|
|
441
480
|
let select_string = if select.is_null() { None } else { CStr::from_ptr(select).to_str().ok() };
|
|
442
481
|
|
|
443
|
-
let results = document.find_items(selector_string, condition_string, select_string);
|
|
482
|
+
let results = document.find_items(selector_string, condition_string, select_string).unwrap_or_default();
|
|
444
483
|
let json = serde_json::to_string_pretty(&results).unwrap_or_else(|_| "[]".to_string());
|
|
445
484
|
|
|
446
485
|
CString::new(json).unwrap_or_default().into_raw()
|
|
@@ -830,12 +869,7 @@ pub unsafe extern "C" fn yerba_get_result_free(result: YerbaGetResult) {
|
|
|
830
869
|
}
|
|
831
870
|
}
|
|
832
871
|
|
|
833
|
-
|
|
834
|
-
pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *const c_char) -> YerbaTypedList {
|
|
835
|
-
let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
|
|
836
|
-
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
837
|
-
let nodes = crate::glob_get(pattern, selector_string);
|
|
838
|
-
|
|
872
|
+
fn located_nodes_to_list(nodes: &[crate::LocatedNode]) -> YerbaTypedList {
|
|
839
873
|
let results: Vec<serde_json::Value> = nodes
|
|
840
874
|
.iter()
|
|
841
875
|
.map(|node| {
|
|
@@ -865,6 +899,18 @@ pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *cons
|
|
|
865
899
|
value["type"] = serde_json::json!(vt as u8);
|
|
866
900
|
}
|
|
867
901
|
|
|
902
|
+
if let Some(key_name) = &node.key_name {
|
|
903
|
+
value["key_name"] = serde_json::json!(key_name);
|
|
904
|
+
value["key_location"] = serde_json::json!({
|
|
905
|
+
"start_line": node.key_location.start_line,
|
|
906
|
+
"start_column": node.key_location.start_column,
|
|
907
|
+
"end_line": node.key_location.end_line,
|
|
908
|
+
"end_column": node.key_location.end_column,
|
|
909
|
+
"start_offset": node.key_location.start_offset,
|
|
910
|
+
"end_offset": node.key_location.end_offset,
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
868
914
|
value
|
|
869
915
|
})
|
|
870
916
|
.collect();
|
|
@@ -878,6 +924,22 @@ pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *cons
|
|
|
878
924
|
}
|
|
879
925
|
}
|
|
880
926
|
|
|
927
|
+
#[no_mangle]
|
|
928
|
+
pub unsafe extern "C" fn yerba_glob_get(glob_pattern: *const c_char, path: *const c_char) -> YerbaTypedList {
|
|
929
|
+
let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
|
|
930
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
931
|
+
|
|
932
|
+
located_nodes_to_list(&crate::glob_get(pattern, selector_string))
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
#[no_mangle]
|
|
936
|
+
pub unsafe extern "C" fn yerba_document_get_all(document: *const Document, path: *const c_char) -> YerbaTypedList {
|
|
937
|
+
let document = &*document;
|
|
938
|
+
let selector_string = CStr::from_ptr(path).to_str().unwrap_or("");
|
|
939
|
+
|
|
940
|
+
located_nodes_to_list(&document.get_all_located(selector_string))
|
|
941
|
+
}
|
|
942
|
+
|
|
881
943
|
#[no_mangle]
|
|
882
944
|
pub unsafe extern "C" fn yerba_glob_find(glob_pattern: *const c_char, path: *const c_char, condition: *const c_char, select: *const c_char) -> YerbaTypedList {
|
|
883
945
|
let pattern = CStr::from_ptr(glob_pattern).to_str().unwrap_or("");
|
|
@@ -885,7 +947,7 @@ pub unsafe extern "C" fn yerba_glob_find(glob_pattern: *const c_char, path: *con
|
|
|
885
947
|
let condition_string = if condition.is_null() { None } else { CStr::from_ptr(condition).to_str().ok() };
|
|
886
948
|
let select_string = if select.is_null() { None } else { CStr::from_ptr(select).to_str().ok() };
|
|
887
949
|
|
|
888
|
-
let all_results = crate::glob_find(pattern, selector_string, condition_string, select_string);
|
|
950
|
+
let all_results = crate::glob_find(pattern, selector_string, condition_string, select_string).unwrap_or_default();
|
|
889
951
|
let length = all_results.len();
|
|
890
952
|
let json = serde_json::to_string_pretty(&all_results).unwrap_or_else(|_| "[]".to_string());
|
|
891
953
|
|
data/rust/src/json.rs
CHANGED
|
@@ -71,6 +71,12 @@ pub fn resolve_select_field(value: &yaml_serde::Value, field: &str) -> serde_jso
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
SelectorSegment::AllKeys => {
|
|
75
|
+
if let yaml_serde::Value::Mapping(mapping) = current {
|
|
76
|
+
next_values.extend(mapping.iter().map(|(_, entry)| entry.clone()));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
74
80
|
SelectorSegment::Index(index) => {
|
|
75
81
|
if let yaml_serde::Value::Sequence(sequence) = current {
|
|
76
82
|
if let Some(item) = sequence.get(*index) {
|
|
@@ -96,7 +102,10 @@ pub fn resolve_select_field(value: &yaml_serde::Value, field: &str) -> serde_jso
|
|
|
96
102
|
current_values = next_values;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
let used_all_items = parsed
|
|
105
|
+
let used_all_items = parsed
|
|
106
|
+
.segments()
|
|
107
|
+
.iter()
|
|
108
|
+
.any(|s| matches!(s, SelectorSegment::AllItems | SelectorSegment::AllKeys));
|
|
100
109
|
|
|
101
110
|
if current_values.is_empty() {
|
|
102
111
|
if used_all_items {
|
data/rust/src/lib.rs
CHANGED
|
@@ -11,7 +11,9 @@ mod yaml_writer;
|
|
|
11
11
|
pub mod yerbafile;
|
|
12
12
|
|
|
13
13
|
pub use document::style::StyleEnforcement;
|
|
14
|
-
pub use document::{
|
|
14
|
+
pub use document::{
|
|
15
|
+
collect_selectors, validate_condition, validate_item_condition, Document, DuplicateInfo, InsertPosition, LocatedNode, Location, NodeInfo, NodeType, SortField,
|
|
16
|
+
};
|
|
15
17
|
pub use error::YerbaError;
|
|
16
18
|
pub use quote_style::{KeyStyle, QuoteStyle};
|
|
17
19
|
pub use selector::Selector;
|
|
@@ -53,24 +55,28 @@ pub fn glob_get(pattern: &str, selector: &str) -> Vec<document::LocatedNode> {
|
|
|
53
55
|
.collect()
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
pub fn glob_find(pattern: &str, selector: &str, condition: Option<&str>, select: Option<&str>) -> Vec<serde_json::Value> {
|
|
58
|
+
pub fn glob_find(pattern: &str, selector: &str, condition: Option<&str>, select: Option<&str>) -> Result<Vec<serde_json::Value>, YerbaError> {
|
|
57
59
|
use rayon::prelude::*;
|
|
58
60
|
|
|
61
|
+
if let Some(cond) = condition {
|
|
62
|
+
crate::document::validate_condition(cond)?;
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
let files = match glob::glob(pattern) {
|
|
60
66
|
Ok(paths) => paths.filter_map(|p| p.ok()).collect::<Vec<_>>(),
|
|
61
|
-
Err(_) => return vec![],
|
|
67
|
+
Err(_) => return Ok(vec![]),
|
|
62
68
|
};
|
|
63
69
|
|
|
64
70
|
let select_fields: Option<Vec<&str>> = select.map(|s| s.split(',').collect());
|
|
65
71
|
|
|
66
|
-
files
|
|
72
|
+
let results: Vec<serde_json::Value> = files
|
|
67
73
|
.par_iter()
|
|
68
74
|
.flat_map(|file| {
|
|
69
75
|
let mut file_results = Vec::new();
|
|
70
76
|
|
|
71
77
|
if let Ok(document) = Document::parse_file(file) {
|
|
72
78
|
let values = match condition {
|
|
73
|
-
Some(cond) => document.filter(selector, cond),
|
|
79
|
+
Some(cond) => document.filter(selector, cond).unwrap_or_default(),
|
|
74
80
|
None => document.get_values(selector),
|
|
75
81
|
};
|
|
76
82
|
|
|
@@ -108,5 +114,7 @@ pub fn glob_find(pattern: &str, selector: &str, condition: Option<&str>, select:
|
|
|
108
114
|
|
|
109
115
|
file_results
|
|
110
116
|
})
|
|
111
|
-
.collect()
|
|
117
|
+
.collect();
|
|
118
|
+
|
|
119
|
+
Ok(results)
|
|
112
120
|
}
|
data/rust/src/main.rs
CHANGED
|
@@ -20,6 +20,7 @@ static HELP: LazyLock<String> = LazyLock::new(|| {
|
|
|
20
20
|
key.nested Nested key path "database.settings.pool"
|
|
21
21
|
[] All items in array "[].title"
|
|
22
22
|
[N] Item at index "[0].title"
|
|
23
|
+
* All values in a map "database.*"
|
|
23
24
|
[].key[].nested Nested array access "[].speakers[].name"
|
|
24
25
|
|
|
25
26
|
Conditions:
|
data/rust/src/selector.rs
CHANGED
|
@@ -21,6 +21,7 @@ pub enum Selector {
|
|
|
21
21
|
pub enum SelectorSegment {
|
|
22
22
|
Key(String),
|
|
23
23
|
AllItems,
|
|
24
|
+
AllKeys,
|
|
24
25
|
Index(usize),
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -69,7 +70,10 @@ impl Selector {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
pub fn has_wildcard(&self) -> bool {
|
|
72
|
-
self
|
|
73
|
+
self
|
|
74
|
+
.segments()
|
|
75
|
+
.iter()
|
|
76
|
+
.any(|s| matches!(s, SelectorSegment::AllItems | SelectorSegment::AllKeys))
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
/// Split into the container selector (up to and including the last []) and the remaining field selector.
|
|
@@ -163,6 +167,14 @@ impl Selector {
|
|
|
163
167
|
result.push_str("[]");
|
|
164
168
|
}
|
|
165
169
|
|
|
170
|
+
SelectorSegment::AllKeys => {
|
|
171
|
+
if index > 0 && !result.ends_with('.') {
|
|
172
|
+
result.push('.');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
result.push('*');
|
|
176
|
+
}
|
|
177
|
+
|
|
166
178
|
SelectorSegment::Index(i) => {
|
|
167
179
|
result.push_str(&format!("[{}]", i));
|
|
168
180
|
}
|
|
@@ -227,7 +239,9 @@ fn parse_segments(input: &str) -> Vec<SelectorSegment> {
|
|
|
227
239
|
Some(index) => {
|
|
228
240
|
let key = &rest[..index];
|
|
229
241
|
|
|
230
|
-
if
|
|
242
|
+
if key == "*" {
|
|
243
|
+
segments.push(SelectorSegment::AllKeys);
|
|
244
|
+
} else if !key.is_empty() {
|
|
231
245
|
segments.push(SelectorSegment::Key(key.to_string()));
|
|
232
246
|
}
|
|
233
247
|
|
|
@@ -238,7 +252,9 @@ fn parse_segments(input: &str) -> Vec<SelectorSegment> {
|
|
|
238
252
|
}
|
|
239
253
|
}
|
|
240
254
|
None => {
|
|
241
|
-
if
|
|
255
|
+
if rest == "*" {
|
|
256
|
+
segments.push(SelectorSegment::AllKeys);
|
|
257
|
+
} else if !rest.is_empty() {
|
|
242
258
|
segments.push(SelectorSegment::Key(rest.to_string()));
|
|
243
259
|
}
|
|
244
260
|
|
data/rust/src/syntax.rs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use rowan::ast::AstNode;
|
|
2
2
|
use rowan::{TextRange, TextSize};
|
|
3
3
|
|
|
4
|
-
use yaml_parser::ast::{BlockMap, BlockMapEntry, BlockSeq};
|
|
4
|
+
use yaml_parser::ast::{BlockMap, BlockMapEntry, BlockSeq, FlowMap, FlowMapEntry, FlowSeq};
|
|
5
5
|
use yaml_parser::{SyntaxKind, SyntaxNode, SyntaxToken};
|
|
6
6
|
|
|
7
7
|
#[derive(Debug, Clone, PartialEq)]
|
|
@@ -82,6 +82,46 @@ pub fn find_block_sequence(node: &SyntaxNode) -> Option<BlockSeq> {
|
|
|
82
82
|
node.descendants().find_map(BlockSeq::cast)
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
fn first_collection_node(node: &SyntaxNode) -> Option<SyntaxNode> {
|
|
86
|
+
node.descendants().find(|descendant| {
|
|
87
|
+
matches!(
|
|
88
|
+
descendant.kind(),
|
|
89
|
+
SyntaxKind::BLOCK_MAP | SyntaxKind::BLOCK_SEQ | SyntaxKind::FLOW_MAP | SyntaxKind::FLOW_SEQ
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
pub fn find_flow_sequence(node: &SyntaxNode) -> Option<FlowSeq> {
|
|
95
|
+
first_collection_node(node).and_then(FlowSeq::cast)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pub fn find_flow_map(node: &SyntaxNode) -> Option<FlowMap> {
|
|
99
|
+
first_collection_node(node).and_then(FlowMap::cast)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pub fn flow_sequence_entries(sequence: &FlowSeq) -> Vec<SyntaxNode> {
|
|
103
|
+
sequence
|
|
104
|
+
.entries()
|
|
105
|
+
.map(|entries| entries.entries().map(|entry| entry.syntax().clone()).collect())
|
|
106
|
+
.unwrap_or_default()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub fn flow_map_entries(map: &FlowMap) -> Vec<FlowMapEntry> {
|
|
110
|
+
map.entries().map(|entries| entries.entries().collect()).unwrap_or_default()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pub fn find_flow_entry_by_key(map: &FlowMap, key: &str) -> Option<FlowMapEntry> {
|
|
114
|
+
flow_map_entries(map)
|
|
115
|
+
.into_iter()
|
|
116
|
+
.find(|entry| entry.key().and_then(|found| extract_scalar_text(found.syntax())).as_deref() == Some(key))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
pub fn in_flow_collection(node: &SyntaxNode) -> bool {
|
|
120
|
+
node
|
|
121
|
+
.ancestors()
|
|
122
|
+
.any(|ancestor| matches!(ancestor.kind(), SyntaxKind::FLOW_MAP | SyntaxKind::FLOW_SEQ))
|
|
123
|
+
}
|
|
124
|
+
|
|
85
125
|
pub enum FirstCollection {
|
|
86
126
|
Map(BlockMap),
|
|
87
127
|
Sequence(BlockSeq),
|
|
@@ -288,7 +328,7 @@ pub fn is_yaml_non_string(value: &str) -> bool {
|
|
|
288
328
|
}
|
|
289
329
|
|
|
290
330
|
pub fn is_yaml_truthy(value: &str) -> bool {
|
|
291
|
-
matches!(value, "true" | "True" | "TRUE" | "yes" | "Yes" | "YES" | "on" | "On" | "ON"
|
|
331
|
+
matches!(value, "true" | "True" | "TRUE" | "yes" | "Yes" | "YES" | "on" | "On" | "ON")
|
|
292
332
|
}
|
|
293
333
|
|
|
294
334
|
pub fn detect_yaml_type_from_plain(value: &str) -> YerbaValueType {
|
|
@@ -297,31 +337,12 @@ pub fn detect_yaml_type_from_plain(value: &str) -> YerbaValueType {
|
|
|
297
337
|
return YerbaValueType::Null;
|
|
298
338
|
}
|
|
299
339
|
|
|
300
|
-
// Boolean
|
|
340
|
+
// Boolean, resolved the way libyaml/Psych resolve it: the YAML 1.1 bool set
|
|
341
|
+
// without the single-letter y/Y/n/N forms (which libyaml treats as strings),
|
|
342
|
+
// and broader than YAML 1.2 core schema (which only accepts true/false).
|
|
301
343
|
if matches!(
|
|
302
344
|
value,
|
|
303
|
-
"true"
|
|
304
|
-
| "True"
|
|
305
|
-
| "TRUE"
|
|
306
|
-
| "false"
|
|
307
|
-
| "False"
|
|
308
|
-
| "FALSE"
|
|
309
|
-
| "yes"
|
|
310
|
-
| "Yes"
|
|
311
|
-
| "YES"
|
|
312
|
-
| "no"
|
|
313
|
-
| "No"
|
|
314
|
-
| "NO"
|
|
315
|
-
| "on"
|
|
316
|
-
| "On"
|
|
317
|
-
| "ON"
|
|
318
|
-
| "off"
|
|
319
|
-
| "Off"
|
|
320
|
-
| "OFF"
|
|
321
|
-
| "y"
|
|
322
|
-
| "Y"
|
|
323
|
-
| "n"
|
|
324
|
-
| "N"
|
|
345
|
+
"true" | "True" | "TRUE" | "false" | "False" | "FALSE" | "yes" | "Yes" | "YES" | "no" | "No" | "NO" | "on" | "On" | "ON" | "off" | "Off" | "OFF"
|
|
325
346
|
) {
|
|
326
347
|
return YerbaValueType::Boolean;
|
|
327
348
|
}
|
data/rust/src/yerbafile.rs
CHANGED
|
@@ -41,6 +41,7 @@ pub enum PipelineStep {
|
|
|
41
41
|
Directives(DirectivesConfig),
|
|
42
42
|
Unique(UniqueConfig),
|
|
43
43
|
Schema(SchemaConfig),
|
|
44
|
+
FinalNewline(FinalNewlineConfig),
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
#[derive(Debug, Clone, Deserialize)]
|
|
@@ -95,6 +96,16 @@ pub struct SchemaConfig {
|
|
|
95
96
|
pub items: bool,
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
100
|
+
pub struct FinalNewlineConfig {
|
|
101
|
+
#[serde(default = "default_one")]
|
|
102
|
+
pub count: usize,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn default_one() -> usize {
|
|
106
|
+
1
|
|
107
|
+
}
|
|
108
|
+
|
|
98
109
|
#[derive(Debug, Clone, Deserialize)]
|
|
99
110
|
pub struct RenameConfig {
|
|
100
111
|
pub from: String,
|
|
@@ -188,8 +199,13 @@ impl<'de> Deserialize<'de> for PipelineStep {
|
|
|
188
199
|
return Ok(PipelineStep::Schema(config));
|
|
189
200
|
}
|
|
190
201
|
|
|
202
|
+
if let Some(value) = mapping.get(yaml_serde::Value::String("final_newline".to_string())) {
|
|
203
|
+
let config: FinalNewlineConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
|
|
204
|
+
return Ok(PipelineStep::FinalNewline(config));
|
|
205
|
+
}
|
|
206
|
+
|
|
191
207
|
Err(serde::de::Error::custom(
|
|
192
|
-
"unknown pipeline step: expected sort_keys, quote_style, collection_style, sequence_indent, set, insert, delete, rename, remove, blank_lines, sort, directives, unique, or
|
|
208
|
+
"unknown pipeline step: expected sort_keys, quote_style, collection_style, sequence_indent, set, insert, delete, rename, remove, blank_lines, sort, directives, unique, schema, or final_newline",
|
|
193
209
|
))
|
|
194
210
|
}
|
|
195
211
|
}
|
|
@@ -263,9 +279,33 @@ impl Yerbafile {
|
|
|
263
279
|
let content = fs::read_to_string(path.as_ref())?;
|
|
264
280
|
let mut yerbafile: Yerbafile = yaml_serde::from_str(&content).map_err(|error| YerbaError::ParseError(format!("{}", error)))?;
|
|
265
281
|
yerbafile.directory = path.as_ref().parent().map(|p| p.to_path_buf());
|
|
282
|
+
yerbafile.validate_conditions()?;
|
|
266
283
|
Ok(yerbafile)
|
|
267
284
|
}
|
|
268
285
|
|
|
286
|
+
fn validate_conditions(&self) -> Result<(), YerbaError> {
|
|
287
|
+
let pipelines = std::iter::once(&self.pipeline).chain(self.rules.iter().map(|rule| &rule.pipeline));
|
|
288
|
+
|
|
289
|
+
for pipeline in pipelines {
|
|
290
|
+
for step in pipeline {
|
|
291
|
+
let condition = match step {
|
|
292
|
+
PipelineStep::Set(config) => config.condition.as_deref(),
|
|
293
|
+
PipelineStep::Insert(config) => config.condition.as_deref(),
|
|
294
|
+
PipelineStep::Delete(config) => config.condition.as_deref(),
|
|
295
|
+
PipelineStep::Rename(config) => config.condition.as_deref(),
|
|
296
|
+
PipelineStep::Remove(config) => config.condition.as_deref(),
|
|
297
|
+
_ => None,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
if let Some(condition) = condition {
|
|
301
|
+
crate::validate_condition(condition)?;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
Ok(())
|
|
307
|
+
}
|
|
308
|
+
|
|
269
309
|
pub fn resolve_path(&self, relative: &str) -> PathBuf {
|
|
270
310
|
match &self.directory {
|
|
271
311
|
Some(directory) => directory.join(relative),
|
|
@@ -359,6 +399,22 @@ impl Yerbafile {
|
|
|
359
399
|
results.extend(file_results);
|
|
360
400
|
}
|
|
361
401
|
|
|
402
|
+
if !self.pipeline.is_empty() {
|
|
403
|
+
if let Some(ref global_glob) = self.files {
|
|
404
|
+
if let Ok(paths) = glob::glob(global_glob) {
|
|
405
|
+
let unprocessed: Vec<String> = paths
|
|
406
|
+
.filter_map(|entry| entry.ok())
|
|
407
|
+
.map(|path| path.to_string_lossy().to_string())
|
|
408
|
+
.filter(|file| !globally_processed.contains(file.as_str()))
|
|
409
|
+
.collect();
|
|
410
|
+
|
|
411
|
+
let global_results: Vec<RuleResult> = unprocessed.par_iter().map(|file| self.apply_global_pipeline_to_file(file, write)).collect();
|
|
412
|
+
|
|
413
|
+
results.extend(global_results);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
362
418
|
results
|
|
363
419
|
}
|
|
364
420
|
|
|
@@ -436,9 +492,55 @@ impl Yerbafile {
|
|
|
436
492
|
ran_global = true;
|
|
437
493
|
}
|
|
438
494
|
|
|
495
|
+
if !ran_global && self.should_run_global_pipeline(file) {
|
|
496
|
+
results.push(self.apply_global_pipeline_to_file(file, write));
|
|
497
|
+
}
|
|
498
|
+
|
|
439
499
|
results
|
|
440
500
|
}
|
|
441
501
|
|
|
502
|
+
fn apply_global_pipeline_to_file(&self, file: &str, write: bool) -> RuleResult {
|
|
503
|
+
let mut document = match Document::parse_file(file) {
|
|
504
|
+
Ok(document) => document,
|
|
505
|
+
Err(error) => {
|
|
506
|
+
return RuleResult {
|
|
507
|
+
file: file.to_string(),
|
|
508
|
+
changed: false,
|
|
509
|
+
error: Some(error),
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
let original = document.to_string();
|
|
515
|
+
|
|
516
|
+
if let Err(error) = execute_pipeline(&mut document, &self.pipeline, None, file, self) {
|
|
517
|
+
return RuleResult {
|
|
518
|
+
file: file.to_string(),
|
|
519
|
+
changed: false,
|
|
520
|
+
error: Some(error),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
let new_content = document.to_string();
|
|
525
|
+
let changed = new_content != original;
|
|
526
|
+
|
|
527
|
+
if changed && write {
|
|
528
|
+
if let Err(error) = fs::write(file, &new_content) {
|
|
529
|
+
return RuleResult {
|
|
530
|
+
file: file.to_string(),
|
|
531
|
+
changed,
|
|
532
|
+
error: Some(YerbaError::IoError(error)),
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
RuleResult {
|
|
538
|
+
file: file.to_string(),
|
|
539
|
+
changed,
|
|
540
|
+
error: None,
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
442
544
|
fn relativize_path(&self, file_path: &str) -> Option<String> {
|
|
443
545
|
let path = Path::new(file_path);
|
|
444
546
|
|
|
@@ -597,7 +699,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
597
699
|
if let Some(condition) = &config.condition {
|
|
598
700
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
599
701
|
|
|
600
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
702
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
601
703
|
return Ok(());
|
|
602
704
|
}
|
|
603
705
|
}
|
|
@@ -611,7 +713,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
611
713
|
if let Some(condition) = &config.condition {
|
|
612
714
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
613
715
|
|
|
614
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
716
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
615
717
|
return Ok(());
|
|
616
718
|
}
|
|
617
719
|
}
|
|
@@ -629,7 +731,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
629
731
|
if let Some(condition) = &config.condition {
|
|
630
732
|
let parent_path = selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
631
733
|
|
|
632
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
734
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
633
735
|
continue;
|
|
634
736
|
}
|
|
635
737
|
}
|
|
@@ -642,7 +744,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
642
744
|
if let Some(condition) = &config.condition {
|
|
643
745
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
644
746
|
|
|
645
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
747
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
646
748
|
return Ok(());
|
|
647
749
|
}
|
|
648
750
|
}
|
|
@@ -657,7 +759,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
657
759
|
if let Some(condition) = &config.condition {
|
|
658
760
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
659
761
|
|
|
660
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
762
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
661
763
|
return Ok(());
|
|
662
764
|
}
|
|
663
765
|
}
|
|
@@ -671,7 +773,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
671
773
|
if let Some(condition) = &config.condition {
|
|
672
774
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
673
775
|
|
|
674
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
776
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
675
777
|
return Ok(());
|
|
676
778
|
}
|
|
677
779
|
}
|
|
@@ -744,6 +846,8 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
744
846
|
|
|
745
847
|
Ok(())
|
|
746
848
|
}
|
|
849
|
+
|
|
850
|
+
PipelineStep::FinalNewline(config) => document.enforce_final_newline(config.count),
|
|
747
851
|
}
|
|
748
852
|
}
|
|
749
853
|
|