yerba 0.7.6-x86_64-linux-gnu → 0.8.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.
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.segments().iter().any(|s| matches!(s, SelectorSegment::AllItems));
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::{collect_selectors, Document, DuplicateInfo, InsertPosition, LocatedNode, Location, NodeInfo, NodeType, SortField};
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.segments().iter().any(|s| matches!(s, SelectorSegment::AllItems))
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 !key.is_empty() {
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 !rest.is_empty() {
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" | "y" | "Y")
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 (YAML 1.2 + 1.1)
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
  }
@@ -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.7.6
4
+ version: 0.8.0
5
5
  platform: x86_64-linux-gnu
6
6
  authors:
7
7
  - Marco Roth
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-08 00:00:00.000000000 Z
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.