yerba 0.7.6-arm-linux-gnu → 0.8.0-arm-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 +4 -4
- data/README.md +66 -2
- data/exe/arm-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +8 -0
- data/ext/yerba/yerba.c +70 -8
- 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/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.lock +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/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 +30 -6
- metadata +2 -2
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
|
@@ -279,9 +279,33 @@ impl Yerbafile {
|
|
|
279
279
|
let content = fs::read_to_string(path.as_ref())?;
|
|
280
280
|
let mut yerbafile: Yerbafile = yaml_serde::from_str(&content).map_err(|error| YerbaError::ParseError(format!("{}", error)))?;
|
|
281
281
|
yerbafile.directory = path.as_ref().parent().map(|p| p.to_path_buf());
|
|
282
|
+
yerbafile.validate_conditions()?;
|
|
282
283
|
Ok(yerbafile)
|
|
283
284
|
}
|
|
284
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
|
+
|
|
285
309
|
pub fn resolve_path(&self, relative: &str) -> PathBuf {
|
|
286
310
|
match &self.directory {
|
|
287
311
|
Some(directory) => directory.join(relative),
|
|
@@ -675,7 +699,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
675
699
|
if let Some(condition) = &config.condition {
|
|
676
700
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
677
701
|
|
|
678
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
702
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
679
703
|
return Ok(());
|
|
680
704
|
}
|
|
681
705
|
}
|
|
@@ -689,7 +713,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
689
713
|
if let Some(condition) = &config.condition {
|
|
690
714
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
691
715
|
|
|
692
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
716
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
693
717
|
return Ok(());
|
|
694
718
|
}
|
|
695
719
|
}
|
|
@@ -707,7 +731,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
707
731
|
if let Some(condition) = &config.condition {
|
|
708
732
|
let parent_path = selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
709
733
|
|
|
710
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
734
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
711
735
|
continue;
|
|
712
736
|
}
|
|
713
737
|
}
|
|
@@ -720,7 +744,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
720
744
|
if let Some(condition) = &config.condition {
|
|
721
745
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
722
746
|
|
|
723
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
747
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
724
748
|
return Ok(());
|
|
725
749
|
}
|
|
726
750
|
}
|
|
@@ -735,7 +759,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
735
759
|
if let Some(condition) = &config.condition {
|
|
736
760
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
737
761
|
|
|
738
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
762
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
739
763
|
return Ok(());
|
|
740
764
|
}
|
|
741
765
|
}
|
|
@@ -749,7 +773,7 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
749
773
|
if let Some(condition) = &config.condition {
|
|
750
774
|
let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
|
|
751
775
|
|
|
752
|
-
if !document.evaluate_condition(parent_path, condition) {
|
|
776
|
+
if !document.evaluate_condition(parent_path, condition)? {
|
|
753
777
|
return Ok(());
|
|
754
778
|
}
|
|
755
779
|
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yerba
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: arm-linux-gnu
|
|
6
6
|
authors:
|
|
7
7
|
- Marco Roth
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A CLI tool for editing YAML while preserving structure, comments, and
|
|
14
14
|
format.
|