yerba 0.3.0-arm-linux-gnu → 0.4.1-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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +93 -8
  3. data/exe/arm-linux-gnu/yerba +0 -0
  4. data/ext/yerba/extconf.rb +22 -3
  5. data/ext/yerba/include/yerba.h +32 -9
  6. data/ext/yerba/yerba.c +133 -25
  7. data/lib/yerba/3.2/yerba.so +0 -0
  8. data/lib/yerba/3.3/yerba.so +0 -0
  9. data/lib/yerba/3.4/yerba.so +0 -0
  10. data/lib/yerba/4.0/yerba.so +0 -0
  11. data/lib/yerba/collection.rb +35 -0
  12. data/lib/yerba/document.rb +45 -3
  13. data/lib/yerba/query_result.rb +50 -0
  14. data/lib/yerba/sequence.rb +187 -1
  15. data/lib/yerba/version.rb +1 -1
  16. data/lib/yerba/yerbafile.rb +45 -0
  17. data/lib/yerba.rb +2 -0
  18. data/rust/Cargo.lock +3 -1
  19. data/rust/Cargo.toml +3 -2
  20. data/rust/cbindgen.toml +1 -0
  21. data/rust/rustfmt.toml +1 -1
  22. data/rust/src/commands/apply.rs +11 -2
  23. data/rust/src/commands/blank_lines.rs +1 -4
  24. data/rust/src/commands/check.rs +11 -2
  25. data/rust/src/commands/directives.rs +61 -0
  26. data/rust/src/commands/get.rs +5 -22
  27. data/rust/src/commands/mod.rs +16 -18
  28. data/rust/src/commands/quote_style.rs +12 -6
  29. data/rust/src/commands/selectors.rs +3 -19
  30. data/rust/src/commands/sort.rs +22 -157
  31. data/rust/src/didyoumean.rs +2 -4
  32. data/rust/src/document/condition.rs +139 -0
  33. data/rust/src/document/delete.rs +91 -0
  34. data/rust/src/document/get.rs +262 -0
  35. data/rust/src/document/insert.rs +384 -0
  36. data/rust/src/document/mod.rs +784 -0
  37. data/rust/src/document/set.rs +100 -0
  38. data/rust/src/document/sort.rs +639 -0
  39. data/rust/src/document/style.rs +473 -0
  40. data/rust/src/error.rs +24 -6
  41. data/rust/src/ffi.rs +272 -518
  42. data/rust/src/json.rs +1 -7
  43. data/rust/src/lib.rs +88 -2
  44. data/rust/src/main.rs +2 -0
  45. data/rust/src/quote_style.rs +83 -7
  46. data/rust/src/selector.rs +2 -7
  47. data/rust/src/syntax.rs +41 -21
  48. data/rust/src/yerbafile.rs +86 -19
  49. metadata +13 -3
  50. data/rust/src/document.rs +0 -2304
@@ -2,6 +2,7 @@ pub mod apply;
2
2
  pub mod blank_lines;
3
3
  pub mod check;
4
4
  pub mod delete;
5
+ pub mod directives;
5
6
  pub mod get;
6
7
  pub mod init;
7
8
  pub mod insert;
@@ -126,10 +127,7 @@ pub(crate) fn colorize_help(input: &str) -> String {
126
127
  }
127
128
 
128
129
  if columns.len() == 3 {
129
- output.push_str(&format!(
130
- " \x1b[36m{:<20}{RESET} {:<21} {DIM}{}{RESET}\n",
131
- columns[0], columns[1], columns[2]
132
- ));
130
+ output.push_str(&format!(" \x1b[36m{:<20}{RESET} {:<21} {DIM}{}{RESET}\n", columns[0], columns[1], columns[2]));
133
131
  } else if columns.len() == 2 {
134
132
  output.push_str(&format!(" \x1b[36m{:<20}{RESET} {}\n", columns[0], columns[1]));
135
133
  } else {
@@ -154,13 +152,12 @@ pub enum Command {
154
152
  Sort(sort::Args),
155
153
  QuoteStyle(quote_style::Args),
156
154
  BlankLines(blank_lines::Args),
155
+ Directives(directives::Args),
157
156
  Selectors(selectors::Args),
158
157
  #[command(about = "Create a new Yerbafile in the current directory")]
159
158
  Init,
160
- #[command(about = "Apply all rules from the Yerbafile and write changes")]
161
- Apply,
162
- #[command(about = "Check if all files match Yerbafile rules (exits 1 if not)")]
163
- Check,
159
+ Apply(apply::Args),
160
+ Check(check::Args),
164
161
  #[command(about = "Print the yerba version")]
165
162
  Version,
166
163
  #[command(about = "\u{1f9c9}")]
@@ -182,17 +179,18 @@ impl Command {
182
179
  Command::Sort(args) => args.run(),
183
180
  Command::QuoteStyle(args) => args.run(),
184
181
  Command::BlankLines(args) => args.run(),
182
+ Command::Directives(args) => args.run(),
185
183
  Command::Selectors(args) => args.run(),
186
184
  Command::Init => init::run(),
187
- Command::Apply => apply::run(),
188
- Command::Check => check::run(),
185
+ Command::Apply(args) => args.run(),
186
+ Command::Check(args) => args.run(),
189
187
  Command::Version => version::run(),
190
188
  Command::Mate => mate::run(),
191
189
  }
192
190
  }
193
191
  }
194
192
 
195
- pub(crate) fn run_yerbafile(write: bool) {
193
+ pub(crate) fn run_yerbafile(write: bool, files: Vec<String>) {
196
194
  use color::*;
197
195
 
198
196
  let yerbafile_path = yerba::Yerbafile::find().unwrap_or_else(|| {
@@ -207,7 +205,12 @@ pub(crate) fn run_yerbafile(write: bool) {
207
205
 
208
206
  eprintln!("🧉 {BOLD}Using{RESET} {}", yerbafile_path.display());
209
207
 
210
- let results = yerbafile.apply(write);
208
+ let results = if files.is_empty() {
209
+ yerbafile.apply(write)
210
+ } else {
211
+ files.iter().flat_map(|file| yerbafile.apply_file(file, write)).collect()
212
+ };
213
+
211
214
  let mut has_changes = false;
212
215
  let mut has_errors = false;
213
216
 
@@ -334,12 +337,7 @@ pub(crate) fn run_op(file: &str, document: &yerba::Document, result: Result<(),
334
337
  run_op_with_hint(file, document, result, None);
335
338
  }
336
339
 
337
- pub(crate) fn run_op_with_hint(
338
- file: &str,
339
- document: &yerba::Document,
340
- result: Result<(), yerba::YerbaError>,
341
- hint: Option<&str>,
342
- ) {
340
+ pub(crate) fn run_op_with_hint(file: &str, document: &yerba::Document, result: Result<(), yerba::YerbaError>, hint: Option<&str>) {
343
341
  use color::*;
344
342
 
345
343
  if let Err(error) = result {
@@ -11,7 +11,7 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
11
11
  yerba quote-style config.yml --keys plain
12
12
  yerba quote-style config.yml --keys plain --values double
13
13
  yerba quote-style config.yml "[].speakers" --values plain
14
- yerba quote-style "data/**/*.yml" --keys plain --values double
14
+ yerba quote-style "data/**/*.yml" --keys plain --values double
15
15
  "#})
16
16
  });
17
17
 
@@ -25,12 +25,12 @@ pub struct Args {
25
25
  file: String,
26
26
  /// Selector to scope the operation (optional — omit for whole file)
27
27
  selector: Option<String>,
28
- /// Quote style for values (plain, single, double, literal, folded)
28
+ /// Quote style for keys
29
29
  #[arg(long)]
30
- values: Option<yerba::QuoteStyle>,
31
- /// Quote style for keys (plain, single, double)
30
+ keys: Option<yerba::KeyStyle>,
31
+ /// Quote style for values
32
32
  #[arg(long)]
33
- keys: Option<yerba::QuoteStyle>,
33
+ values: Option<yerba::QuoteStyle>,
34
34
  #[arg(long)]
35
35
  dry_run: bool,
36
36
  }
@@ -53,7 +53,13 @@ impl Args {
53
53
  }
54
54
 
55
55
  if let Some(style) = &self.values {
56
- let _ = document.enforce_quotes_at(style, selector);
56
+ if let Ok(warnings) = document.enforce_quotes_at(style, selector) {
57
+ for warning in warnings {
58
+ use super::color::*;
59
+
60
+ eprintln!("{YELLOW}Warning:{RESET} {} — {}", resolved_file, warning);
61
+ }
62
+ }
57
63
  }
58
64
 
59
65
  output(&resolved_file, &document, self.dry_run);
@@ -81,11 +81,7 @@ impl Args {
81
81
 
82
82
  for resolved_file in resolve_files(&self.file) {
83
83
  let document = parse_file(&resolved_file);
84
- let prefix = if selector.is_empty() {
85
- String::new()
86
- } else {
87
- selector.to_string()
88
- };
84
+ let prefix = if selector.is_empty() { String::new() } else { selector.to_string() };
89
85
 
90
86
  let values = if selector.is_empty() {
91
87
  document.get_value("").into_iter().collect::<Vec<_>>()
@@ -112,14 +108,7 @@ impl Args {
112
108
  if let Some(label) = info.count_label() {
113
109
  let padding = max_selector_len - selector.len() + 2;
114
110
 
115
- println!(
116
- "{}{}{}{}{}",
117
- selector,
118
- " ".repeat(padding),
119
- color::DIM,
120
- label,
121
- color::RESET
122
- );
111
+ println!("{}{}{}{}{}", selector, " ".repeat(padding), color::DIM, label, color::RESET);
123
112
  } else {
124
113
  println!("{}", selector);
125
114
  }
@@ -127,12 +116,7 @@ impl Args {
127
116
  }
128
117
  }
129
118
 
130
- fn collect_selectors(
131
- value: &serde_yaml::Value,
132
- prefix: &str,
133
- selectors: &mut BTreeMap<String, SelectorInfo>,
134
- counter: &mut usize,
135
- ) {
119
+ fn collect_selectors(value: &serde_yaml::Value, prefix: &str, selectors: &mut BTreeMap<String, SelectorInfo>, counter: &mut usize) {
136
120
  match value {
137
121
  serde_yaml::Value::Mapping(map) => {
138
122
  for (key, child) in map {
@@ -89,13 +89,7 @@ impl Args {
89
89
 
90
90
  let by = &self.by[0];
91
91
  let selector = self.selector.as_deref().unwrap_or("");
92
-
93
- let items_selector = if selector.is_empty() {
94
- "[]".to_string()
95
- } else {
96
- format!("{}[]", selector)
97
- };
98
-
92
+ let items_selector = if selector.is_empty() { "[]".to_string() } else { format!("{}[]", selector) };
99
93
  let document = parse_file(&self.file);
100
94
  let (labels, context_values, selector_display) = self.resolve_labels(&document, by, selector, &items_selector);
101
95
 
@@ -111,6 +105,7 @@ impl Args {
111
105
 
112
106
  for (index, label) in labels.iter().enumerate() {
113
107
  let context = context_values.get(index).map(|c| c.as_slice()).unwrap_or(&[]);
108
+
114
109
  eprintln!(" {}", self.format_label_line(index, label, context));
115
110
  }
116
111
 
@@ -119,26 +114,14 @@ impl Args {
119
114
  eprintln!("{context_hint}");
120
115
  eprintln!();
121
116
  eprintln!(" {BOLD}To sort alphabetically:{RESET}");
122
- eprintln!(
123
- " yerba sort \"{}\"{selector_display} --by \"{by}\" --order asc",
124
- self.file
125
- );
126
- eprintln!(
127
- " yerba sort \"{}\"{selector_display} --by \"{by}\" --order desc",
128
- self.file
129
- );
117
+ eprintln!(" yerba sort \"{}\"{selector_display} --by \"{by}\" --order asc", self.file);
118
+ eprintln!(" yerba sort \"{}\"{selector_display} --by \"{by}\" --order desc", self.file);
130
119
  eprintln!();
131
120
  eprintln!(" {BOLD}To reorder explicitly:{RESET}");
132
- eprintln!(
133
- " yerba sort \"{}\"{selector_display} --by \"{by}\" --order \"{csv}\"",
134
- self.file
135
- );
121
+ eprintln!(" yerba sort \"{}\"{selector_display} --by \"{by}\" --order \"{csv}\"", self.file);
136
122
  eprintln!();
137
123
  eprintln!(" {BOLD}To move individual items:{RESET}");
138
- eprintln!(
139
- " yerba move \"{}\"{selector_display} <item> --before/--after <target>",
140
- self.file
141
- );
124
+ eprintln!(" yerba move \"{}\"{selector_display} <item> --before/--after <target>", self.file);
142
125
 
143
126
  process::exit(1);
144
127
  }
@@ -174,11 +157,7 @@ impl Args {
174
157
  let mut document = parse_file(&resolved_file);
175
158
 
176
159
  if sort_fields.is_empty() {
177
- let first_item_selector = if selector.is_empty() {
178
- "[0]".to_string()
179
- } else {
180
- format!("{}[0]", selector)
181
- };
160
+ let first_item_selector = if selector.is_empty() { "[0]".to_string() } else { format!("{}[0]", selector) };
182
161
 
183
162
  match document.get_value(&first_item_selector) {
184
163
  Some(first) if first.is_mapping() => {
@@ -190,11 +169,7 @@ impl Args {
190
169
  let fields: Vec<&String> = selectors
191
170
  .iter()
192
171
  .filter(|s| {
193
- let check = if prefix.is_empty() {
194
- format!("{}[].", selector)
195
- } else {
196
- prefix.to_string()
197
- };
172
+ let check = if prefix.is_empty() { format!("{}[].", selector) } else { prefix.to_string() };
198
173
  s.starts_with(&check) && !s[check.len()..].contains('.') && !s[check.len()..].contains('[')
199
174
  })
200
175
  .collect();
@@ -212,10 +187,7 @@ impl Args {
212
187
  }
213
188
 
214
189
  None if selector.is_empty() => {
215
- eprintln!(
216
- "{RED}Error:{RESET} no sequence found at root level in {}",
217
- resolved_file
218
- );
190
+ eprintln!("{RED}Error:{RESET} no sequence found at root level in {}", resolved_file);
219
191
  eprintln!();
220
192
  eprintln!(" {DIM}Specify a selector for the sequence to sort:{RESET}");
221
193
  eprintln!(" yerba sort \"{}\" \"<selector>\"", self.file);
@@ -255,11 +227,7 @@ impl Args {
255
227
  let order = &self.order[0];
256
228
  let selector = self.selector.as_deref().unwrap_or("");
257
229
 
258
- let items_selector = if selector.is_empty() {
259
- "[]".to_string()
260
- } else {
261
- format!("{}[]", selector)
262
- };
230
+ let items_selector = if selector.is_empty() { "[]".to_string() } else { format!("{}[]", selector) };
263
231
 
264
232
  let mut document = parse_file(&self.file);
265
233
  let mut seen = std::collections::HashSet::new();
@@ -286,11 +254,7 @@ impl Args {
286
254
  let values_with_commas: Vec<&String> = labels.iter().filter(|l| l.contains(',')).collect();
287
255
 
288
256
  if !values_with_commas.is_empty() {
289
- let selector_display = if selector.is_empty() {
290
- String::new()
291
- } else {
292
- format!(" \"{selector}\"")
293
- };
257
+ let selector_display = if selector.is_empty() { String::new() } else { format!(" \"{selector}\"") };
294
258
 
295
259
  eprintln!("{RED}Error:{RESET} some values for {by} contain commas, which conflicts with --order parsing");
296
260
  eprintln!();
@@ -302,108 +266,24 @@ impl Args {
302
266
 
303
267
  eprintln!();
304
268
  eprintln!(" {BOLD}Use yerba move to reorder individual items instead:{RESET}");
305
- eprintln!(
306
- " yerba move \"{}\"{selector_display} <item> --before/--after <target>",
307
- self.file
308
- );
269
+ eprintln!(" yerba move \"{}\"{selector_display} <item> --before/--after <target>", self.file);
309
270
 
310
271
  process::exit(1);
311
272
  }
312
273
 
313
274
  let desired_order: Vec<&str> = order.split(',').map(|s| s.trim()).collect();
314
-
315
- let mut used = vec![false; labels.len()];
316
- let mut moves: Vec<usize> = Vec::new();
317
-
318
- for desired in &desired_order {
319
- let found = labels
320
- .iter()
321
- .enumerate()
322
- .find(|(index, label)| label.as_str() == *desired && !used[*index]);
323
-
324
- if let Some((index, _)) = found {
325
- moves.push(index);
326
- used[index] = true;
327
- } else {
328
- eprintln!("{RED}Error:{RESET} no item found with {by} == \"{desired}\"");
329
- eprintln!();
330
- eprintln!(" {BOLD}Available values:{RESET}");
331
-
332
- for (index, label) in labels.iter().enumerate() {
333
- eprintln!(" {DIM}{index}:{RESET} {label}");
334
- }
335
-
336
- process::exit(1);
337
- }
338
- }
339
-
340
- let missing: Vec<&String> = labels
341
- .iter()
342
- .enumerate()
343
- .filter(|(index, _)| !used[*index])
344
- .map(|(_, label)| label)
345
- .collect();
346
-
347
- if !missing.is_empty() {
348
- eprintln!(
349
- "{RED}Error:{RESET} --order must specify all {} items, but {} are missing",
350
- labels.len(),
351
- missing.len()
352
- );
353
- eprintln!();
354
- eprintln!(" {BOLD}Missing values:{RESET}");
355
-
356
- for label in &missing {
357
- eprintln!(" {label}");
358
- }
359
-
360
- eprintln!();
361
- eprintln!(" {BOLD}All values (by {by}):{RESET}");
362
-
363
- for label in &labels {
364
- eprintln!(" {label}");
365
- }
366
-
367
- eprintln!();
368
- eprintln!(" {BOLD}To move individual items, use:{RESET}");
369
- eprintln!(" yerba move <file> <selector> <item> --before/--after <target>");
370
-
371
- process::exit(1);
372
- }
373
-
374
275
  let container = if selector.is_empty() { "" } else { selector };
375
276
 
376
- for target in 0..moves.len() {
377
- let source = moves[target];
378
-
379
- if source != target {
380
- let result = document.move_item(container, source, target);
381
-
382
- if let Err(error) = result {
383
- eprintln!("{RED}Error:{RESET} {}", error);
384
- process::exit(1);
385
- }
386
-
387
- for item in moves.iter_mut().skip(target + 1) {
388
- if *item >= target && *item < source {
389
- *item += 1;
390
- } else if *item == source {
391
- *item = target;
392
- }
393
- }
277
+ match document.reorder_items(container, by, &desired_order) {
278
+ Ok(()) => output(&self.file, &document, self.dry_run),
279
+ Err(error) => {
280
+ eprintln!("{RED}Error:{RESET} {}", error);
281
+ process::exit(1);
394
282
  }
395
283
  }
396
-
397
- output(&self.file, &document, self.dry_run);
398
284
  }
399
285
 
400
- fn resolve_labels(
401
- &self,
402
- document: &yerba::Document,
403
- by: &str,
404
- selector: &str,
405
- items_selector: &str,
406
- ) -> (Vec<String>, Vec<Vec<String>>, String) {
286
+ fn resolve_labels(&self, document: &yerba::Document, by: &str, selector: &str, items_selector: &str) -> (Vec<String>, Vec<Vec<String>>, String) {
407
287
  use super::color::*;
408
288
 
409
289
  let items = document.get_values(items_selector);
@@ -413,10 +293,7 @@ impl Args {
413
293
  eprintln!("{RED}Error:{RESET} no sequence found at root level");
414
294
  eprintln!();
415
295
  eprintln!(" {DIM}If the file is a map, specify which sequence to sort:{RESET}");
416
- eprintln!(
417
- " yerba sort \"{}\" \"<selector>\" --by \"{by}\" --order asc",
418
- self.file
419
- );
296
+ eprintln!(" yerba sort \"{}\" \"<selector>\" --by \"{by}\" --order asc", self.file);
420
297
  eprintln!();
421
298
 
422
299
  super::show_similar_selectors(&self.file, document, "[]");
@@ -458,10 +335,7 @@ impl Args {
458
335
  } else {
459
336
  let field = by.strip_prefix('.').unwrap_or(by);
460
337
 
461
- yerba::json::resolve_select_field(item, field)
462
- .as_str()
463
- .unwrap_or("")
464
- .to_string()
338
+ yerba::json::resolve_select_field(item, field).as_str().unwrap_or("").to_string()
465
339
  }
466
340
  })
467
341
  .collect();
@@ -487,11 +361,7 @@ impl Args {
487
361
  })
488
362
  .collect();
489
363
 
490
- let selector_display = if selector.is_empty() {
491
- String::new()
492
- } else {
493
- format!(" \"{selector}\"")
494
- };
364
+ let selector_display = if selector.is_empty() { String::new() } else { format!(" \"{selector}\"") };
495
365
 
496
366
  (labels, context_values, selector_display)
497
367
  }
@@ -502,12 +372,7 @@ impl Args {
502
372
  if context.is_empty() {
503
373
  format!("{DIM}[{index}]{RESET} {label}")
504
374
  } else {
505
- let context = context
506
- .iter()
507
- .filter(|c| !c.is_empty())
508
- .cloned()
509
- .collect::<Vec<_>>()
510
- .join(", ");
375
+ let context = context.iter().filter(|c| !c.is_empty()).cloned().collect::<Vec<_>>().join(", ");
511
376
 
512
377
  if context.is_empty() {
513
378
  format!("{DIM}[{index}]{RESET} {label}")
@@ -10,8 +10,7 @@ pub fn didyoumean(input: &str, list: &[String]) -> Option<String> {
10
10
  .map(|item| (item.clone(), levenshtein(&input_lower, &item.to_lowercase())))
11
11
  .collect();
12
12
 
13
- scored
14
- .sort_by(|(a_item, a_distance), (b_item, b_distance)| a_distance.cmp(b_distance).then_with(|| a_item.cmp(b_item)));
13
+ scored.sort_by(|(a_item, a_distance), (b_item, b_distance)| a_distance.cmp(b_distance).then_with(|| a_item.cmp(b_item)));
15
14
 
16
15
  scored.into_iter().next().map(|(item, _)| item)
17
16
  }
@@ -29,8 +28,7 @@ pub fn didyoumean_ranked(input: &str, list: &[String], threshold: usize) -> Vec<
29
28
  .filter(|(_, distance)| *distance <= threshold)
30
29
  .collect();
31
30
 
32
- scored
33
- .sort_by(|(a_item, a_distance), (b_item, b_distance)| a_distance.cmp(b_distance).then_with(|| a_item.cmp(b_item)));
31
+ scored.sort_by(|(a_item, a_distance), (b_item, b_distance)| a_distance.cmp(b_distance).then_with(|| a_item.cmp(b_item)));
34
32
 
35
33
  scored.into_iter().map(|(item, _)| item).collect()
36
34
  }
@@ -0,0 +1,139 @@
1
+ use super::*;
2
+
3
+ impl Document {
4
+ pub fn filter(&self, dot_path: &str, condition: &str) -> Vec<serde_yaml::Value> {
5
+ self
6
+ .navigate_all(dot_path)
7
+ .iter()
8
+ .filter(|node| self.evaluate_condition_on_node(node, condition))
9
+ .map(node_to_yaml_value)
10
+ .collect()
11
+ }
12
+
13
+ pub(super) fn evaluate_condition_on_node(&self, node: &SyntaxNode, condition: &str) -> bool {
14
+ let condition = condition.trim();
15
+
16
+ let (left, operator, right) = match parse_condition(condition) {
17
+ Some(parts) => parts,
18
+ None => return false,
19
+ };
20
+
21
+ let path = crate::selector::Selector::parse(&left);
22
+
23
+ if !path.is_relative() {
24
+ return false;
25
+ }
26
+
27
+ let target_nodes = navigate_from_node(node, &path.to_selector_string());
28
+ let values: Vec<String> = target_nodes.iter().filter_map(extract_scalar_text).collect();
29
+
30
+ match operator {
31
+ "==" => values.iter().any(|value| value == &right),
32
+ "!=" => values.iter().all(|value| value != &right),
33
+ "contains" => {
34
+ if values.iter().any(|value| value == &right || value.contains(&right)) {
35
+ return true;
36
+ }
37
+
38
+ for node in &target_nodes {
39
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
40
+ for entry in sequence.entries() {
41
+ if let Some(text) = entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())) {
42
+ if text == right {
43
+ return true;
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ false
51
+ }
52
+ "not_contains" => {
53
+ for node in &target_nodes {
54
+ if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
55
+ for entry in sequence.entries() {
56
+ if let Some(text) = entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())) {
57
+ if text == right {
58
+ return false;
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ !values.iter().any(|value| value == &right || value.contains(&right))
66
+ }
67
+ _ => false,
68
+ }
69
+ }
70
+
71
+ pub fn evaluate_condition(&self, parent_path: &str, condition: &str) -> bool {
72
+ let condition = condition.trim();
73
+
74
+ let (left, operator, right) = match parse_condition(condition) {
75
+ Some(parts) => parts,
76
+ None => return false,
77
+ };
78
+
79
+ let path = crate::selector::Selector::parse(&left);
80
+
81
+ let full_path = if path.is_relative() {
82
+ let path_string = path.to_selector_string();
83
+
84
+ if parent_path.is_empty() {
85
+ path_string
86
+ } else {
87
+ format!("{}.{}", parent_path, path_string)
88
+ }
89
+ } else {
90
+ path.to_selector_string()
91
+ };
92
+
93
+ let has_brackets = crate::selector::Selector::parse(&full_path).has_brackets();
94
+
95
+ match operator {
96
+ "==" => {
97
+ if has_brackets {
98
+ self.get_all(&full_path).iter().any(|value| value == &right)
99
+ } else {
100
+ self.get(&full_path).unwrap_or_default() == right
101
+ }
102
+ }
103
+ "!=" => {
104
+ if has_brackets {
105
+ self.get_all(&full_path).iter().all(|value| value != &right)
106
+ } else {
107
+ self.get(&full_path).unwrap_or_default() != right
108
+ }
109
+ }
110
+ "contains" => {
111
+ if has_brackets {
112
+ self.get_all(&full_path).iter().any(|value| value == &right || value.contains(&right))
113
+ } else {
114
+ let items = self.get_sequence_values(&full_path);
115
+
116
+ if !items.is_empty() {
117
+ items.iter().any(|item| item == &right)
118
+ } else {
119
+ self.get(&full_path).map(|value| value.contains(&right)).unwrap_or(false)
120
+ }
121
+ }
122
+ }
123
+ "not_contains" => {
124
+ if has_brackets {
125
+ self.get_all(&full_path).iter().all(|value| value != &right && !value.contains(&right))
126
+ } else {
127
+ let items = self.get_sequence_values(&full_path);
128
+
129
+ if !items.is_empty() {
130
+ !items.iter().any(|item| item == &right)
131
+ } else {
132
+ self.get(&full_path).map(|value| !value.contains(&right)).unwrap_or(true)
133
+ }
134
+ }
135
+ }
136
+ _ => false,
137
+ }
138
+ }
139
+ }