yerba 0.5.1-x86_64-linux-gnu → 0.6.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 +4 -4
- data/README.md +1 -25
- data/exe/x86_64-linux-gnu/yerba +0 -0
- data/ext/yerba/include/yerba.h +12 -0
- data/ext/yerba/yerba.c +74 -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/formatting.rb +61 -0
- data/lib/yerba/map.rb +50 -6
- data/lib/yerba/sequence.rb +20 -0
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.lock +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/document/delete.rs +14 -1
- data/rust/src/document/get.rs +47 -22
- data/rust/src/document/insert.rs +201 -6
- data/rust/src/document/mod.rs +54 -4
- data/rust/src/document/style.rs +609 -2
- data/rust/src/ffi.rs +46 -0
- data/rust/src/lib.rs +1 -0
- data/rust/src/yaml_writer.rs +98 -0
- data/rust/src/yerbafile.rs +139 -4
- metadata +2 -2
data/rust/src/yaml_writer.rs
CHANGED
|
@@ -37,6 +37,23 @@ pub fn json_to_yaml_text(value: &Value, quote_style: &QuoteStyle, indent: usize)
|
|
|
37
37
|
.join("\n")
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
Value::Array(array) => {
|
|
41
|
+
let prefix = " ".repeat(indent);
|
|
42
|
+
|
|
43
|
+
array
|
|
44
|
+
.iter()
|
|
45
|
+
.map(|item| match item {
|
|
46
|
+
Value::Object(_) => {
|
|
47
|
+
let inner = json_to_yaml_text(item, quote_style, indent + 2);
|
|
48
|
+
format!("{}- {}", prefix, inner.trim_start())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_ => format!("{}- {}", prefix, format_yaml_scalar(item, quote_style)),
|
|
52
|
+
})
|
|
53
|
+
.collect::<Vec<_>>()
|
|
54
|
+
.join("\n")
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
_ => {
|
|
41
58
|
let prefix = " ".repeat(indent);
|
|
42
59
|
|
|
@@ -45,6 +62,87 @@ pub fn json_to_yaml_text(value: &Value, quote_style: &QuoteStyle, indent: usize)
|
|
|
45
62
|
}
|
|
46
63
|
}
|
|
47
64
|
|
|
65
|
+
pub fn yaml_value_to_flow_text(value: &yaml_serde::Value) -> String {
|
|
66
|
+
match value {
|
|
67
|
+
yaml_serde::Value::Null => "null".to_string(),
|
|
68
|
+
yaml_serde::Value::Bool(boolean) => boolean.to_string(),
|
|
69
|
+
yaml_serde::Value::Number(number) => number.to_string(),
|
|
70
|
+
|
|
71
|
+
yaml_serde::Value::String(string) => {
|
|
72
|
+
if crate::syntax::is_yaml_non_string(string) {
|
|
73
|
+
format!("\"{}\"", string.replace('"', "\\\""))
|
|
74
|
+
} else {
|
|
75
|
+
string.clone()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
yaml_serde::Value::Sequence(sequence) => {
|
|
80
|
+
let items: Vec<String> = sequence.iter().map(yaml_value_to_flow_text).collect();
|
|
81
|
+
|
|
82
|
+
format!("[{}]", items.join(", "))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
yaml_serde::Value::Mapping(mapping) => {
|
|
86
|
+
let pairs: Vec<String> = mapping
|
|
87
|
+
.iter()
|
|
88
|
+
.map(|(key, value)| {
|
|
89
|
+
let key_string = match key {
|
|
90
|
+
yaml_serde::Value::String(string) => string.clone(),
|
|
91
|
+
_ => format!("{:?}", key),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
format!("{}: {}", key_string, yaml_value_to_flow_text(value))
|
|
95
|
+
})
|
|
96
|
+
.collect();
|
|
97
|
+
|
|
98
|
+
format!("{{{}}}", pairs.join(", "))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
yaml_serde::Value::Tagged(tagged) => yaml_value_to_flow_text(&tagged.value),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
pub fn yaml_value_to_block_text(value: &yaml_serde::Value, indent: usize) -> String {
|
|
106
|
+
let prefix = " ".repeat(indent);
|
|
107
|
+
|
|
108
|
+
match value {
|
|
109
|
+
yaml_serde::Value::Sequence(sequence) => sequence
|
|
110
|
+
.iter()
|
|
111
|
+
.map(|item| match item {
|
|
112
|
+
yaml_serde::Value::Mapping(_) => {
|
|
113
|
+
let inner = yaml_value_to_block_text(item, indent + 2);
|
|
114
|
+
format!("{}- {}", prefix, inner.trim_start())
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_ => format!("{}- {}", prefix, yaml_value_to_flow_text(item)),
|
|
118
|
+
})
|
|
119
|
+
.collect::<Vec<_>>()
|
|
120
|
+
.join("\n"),
|
|
121
|
+
|
|
122
|
+
yaml_serde::Value::Mapping(mapping) => mapping
|
|
123
|
+
.iter()
|
|
124
|
+
.map(|(key, value)| {
|
|
125
|
+
let key_string = match key {
|
|
126
|
+
yaml_serde::Value::String(string) => string.clone(),
|
|
127
|
+
_ => format!("{:?}", key),
|
|
128
|
+
};
|
|
129
|
+
match value {
|
|
130
|
+
yaml_serde::Value::Sequence(_) | yaml_serde::Value::Mapping(_) => {
|
|
131
|
+
let inner = yaml_value_to_block_text(value, indent + 2);
|
|
132
|
+
|
|
133
|
+
format!("{}{}:\n{}", prefix, key_string, inner)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_ => format!("{}{}: {}", prefix, key_string, yaml_value_to_flow_text(value)),
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
.collect::<Vec<_>>()
|
|
140
|
+
.join("\n"),
|
|
141
|
+
|
|
142
|
+
_ => format!("{}{}", prefix, yaml_value_to_flow_text(value)),
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
48
146
|
fn format_yaml_scalar(value: &Value, quote_style: &QuoteStyle) -> String {
|
|
49
147
|
match value {
|
|
50
148
|
Value::Null => "null".to_string(),
|
data/rust/src/yerbafile.rs
CHANGED
|
@@ -7,6 +7,10 @@ use crate::{Document, QuoteStyle, YerbaError};
|
|
|
7
7
|
|
|
8
8
|
#[derive(Debug, Deserialize)]
|
|
9
9
|
pub struct Yerbafile {
|
|
10
|
+
#[serde(default)]
|
|
11
|
+
pub files: Option<String>,
|
|
12
|
+
#[serde(default)]
|
|
13
|
+
pub pipeline: Vec<PipelineStep>,
|
|
10
14
|
#[serde(default)]
|
|
11
15
|
pub rules: Vec<Rule>,
|
|
12
16
|
#[serde(skip)]
|
|
@@ -25,6 +29,8 @@ pub struct Rule {
|
|
|
25
29
|
pub enum PipelineStep {
|
|
26
30
|
SortKeys(SortKeysConfig),
|
|
27
31
|
QuoteStyle(QuoteStyleConfig),
|
|
32
|
+
CollectionStyle(CollectionStyleConfig),
|
|
33
|
+
SequenceIndent(SequenceIndentConfig),
|
|
28
34
|
Set(SetConfig),
|
|
29
35
|
Insert(InsertConfig),
|
|
30
36
|
Delete(DeleteConfig),
|
|
@@ -120,6 +126,16 @@ impl<'de> Deserialize<'de> for PipelineStep {
|
|
|
120
126
|
return Ok(PipelineStep::QuoteStyle(config));
|
|
121
127
|
}
|
|
122
128
|
|
|
129
|
+
if let Some(value) = mapping.get(yaml_serde::Value::String("collection_style".to_string())) {
|
|
130
|
+
let config: CollectionStyleConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
|
|
131
|
+
return Ok(PipelineStep::CollectionStyle(config));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if let Some(value) = mapping.get(yaml_serde::Value::String("sequence_indent".to_string())) {
|
|
135
|
+
let config: SequenceIndentConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
|
|
136
|
+
return Ok(PipelineStep::SequenceIndent(config));
|
|
137
|
+
}
|
|
138
|
+
|
|
123
139
|
if let Some(value) = mapping.get(yaml_serde::Value::String("set".to_string())) {
|
|
124
140
|
let config: SetConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
|
|
125
141
|
return Ok(PipelineStep::Set(config));
|
|
@@ -171,7 +187,7 @@ impl<'de> Deserialize<'de> for PipelineStep {
|
|
|
171
187
|
}
|
|
172
188
|
|
|
173
189
|
Err(serde::de::Error::custom(
|
|
174
|
-
"unknown pipeline step: expected sort_keys, quote_style, set, insert, delete, rename, remove, blank_lines, sort, directives, unique, or schema",
|
|
190
|
+
"unknown pipeline step: expected sort_keys, quote_style, collection_style, sequence_indent, set, insert, delete, rename, remove, blank_lines, sort, directives, unique, or schema",
|
|
175
191
|
))
|
|
176
192
|
}
|
|
177
193
|
}
|
|
@@ -192,6 +208,20 @@ pub struct QuoteStyleConfig {
|
|
|
192
208
|
pub path: Option<String>,
|
|
193
209
|
}
|
|
194
210
|
|
|
211
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
212
|
+
pub struct CollectionStyleConfig {
|
|
213
|
+
pub style: String,
|
|
214
|
+
#[serde(default)]
|
|
215
|
+
pub path: Option<String>,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
219
|
+
pub struct SequenceIndentConfig {
|
|
220
|
+
pub style: String,
|
|
221
|
+
#[serde(default)]
|
|
222
|
+
pub path: Option<String>,
|
|
223
|
+
}
|
|
224
|
+
|
|
195
225
|
#[derive(Debug, Clone, Deserialize)]
|
|
196
226
|
pub struct SetConfig {
|
|
197
227
|
pub path: String,
|
|
@@ -293,6 +323,7 @@ impl Yerbafile {
|
|
|
293
323
|
|
|
294
324
|
pub fn apply(&self, write: bool) -> Vec<RuleResult> {
|
|
295
325
|
let mut results = Vec::new();
|
|
326
|
+
let mut globally_processed: std::collections::HashSet<String> = std::collections::HashSet::new();
|
|
296
327
|
|
|
297
328
|
for rule in &self.rules {
|
|
298
329
|
let files = match glob::glob(&rule.files) {
|
|
@@ -311,7 +342,17 @@ impl Yerbafile {
|
|
|
311
342
|
|
|
312
343
|
let file_strings: Vec<String> = files.iter().map(|path| path.to_string_lossy().to_string()).collect();
|
|
313
344
|
|
|
314
|
-
let file_results: Vec<RuleResult> = file_strings
|
|
345
|
+
let file_results: Vec<RuleResult> = file_strings
|
|
346
|
+
.par_iter()
|
|
347
|
+
.map(|file| {
|
|
348
|
+
let run_global = !globally_processed.contains(file.as_str()) && self.should_run_global_pipeline(file);
|
|
349
|
+
self.apply_pipeline_to_file(rule, file, write, run_global)
|
|
350
|
+
})
|
|
351
|
+
.collect();
|
|
352
|
+
|
|
353
|
+
for result in &file_results {
|
|
354
|
+
globally_processed.insert(result.file.clone());
|
|
355
|
+
}
|
|
315
356
|
|
|
316
357
|
results.extend(file_results);
|
|
317
358
|
}
|
|
@@ -319,7 +360,7 @@ impl Yerbafile {
|
|
|
319
360
|
results
|
|
320
361
|
}
|
|
321
362
|
|
|
322
|
-
fn apply_pipeline_to_file(&self, rule: &Rule, file: &str, write: bool) -> RuleResult {
|
|
363
|
+
fn apply_pipeline_to_file(&self, rule: &Rule, file: &str, write: bool, run_global: bool) -> RuleResult {
|
|
323
364
|
let mut document = match Document::parse_file(file) {
|
|
324
365
|
Ok(document) => document,
|
|
325
366
|
Err(error) => {
|
|
@@ -332,6 +373,17 @@ impl Yerbafile {
|
|
|
332
373
|
};
|
|
333
374
|
|
|
334
375
|
let original = document.to_string();
|
|
376
|
+
|
|
377
|
+
if run_global {
|
|
378
|
+
if let Err(error) = execute_pipeline(&mut document, &self.pipeline, None, file, self) {
|
|
379
|
+
return RuleResult {
|
|
380
|
+
file: file.to_string(),
|
|
381
|
+
changed: false,
|
|
382
|
+
error: Some(error),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
335
387
|
let base_path = rule.path.as_deref();
|
|
336
388
|
|
|
337
389
|
for step in &rule.pipeline {
|
|
@@ -366,6 +418,7 @@ impl Yerbafile {
|
|
|
366
418
|
|
|
367
419
|
pub fn apply_file(&self, file: &str, write: bool) -> Vec<RuleResult> {
|
|
368
420
|
let mut results = Vec::new();
|
|
421
|
+
let mut ran_global = false;
|
|
369
422
|
|
|
370
423
|
for rule in &self.rules {
|
|
371
424
|
if let Ok(pattern) = glob::Pattern::new(&rule.files) {
|
|
@@ -376,15 +429,34 @@ impl Yerbafile {
|
|
|
376
429
|
continue;
|
|
377
430
|
}
|
|
378
431
|
|
|
379
|
-
|
|
432
|
+
let run_global = !ran_global && self.should_run_global_pipeline(file);
|
|
433
|
+
results.push(self.apply_pipeline_to_file(rule, file, write, run_global));
|
|
434
|
+
ran_global = true;
|
|
380
435
|
}
|
|
381
436
|
|
|
382
437
|
results
|
|
383
438
|
}
|
|
384
439
|
|
|
440
|
+
fn should_run_global_pipeline(&self, file_path: &str) -> bool {
|
|
441
|
+
if self.pipeline.is_empty() {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
match &self.files {
|
|
446
|
+
Some(glob_pattern) => glob::Pattern::new(glob_pattern)
|
|
447
|
+
.map(|pattern| pattern.matches(file_path) || pattern.matches_path(Path::new(file_path)))
|
|
448
|
+
.unwrap_or(false),
|
|
449
|
+
None => true,
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
385
453
|
pub fn apply_to_document(&self, document: &mut Document, file_path: &str) -> Result<bool, YerbaError> {
|
|
386
454
|
let original = document.to_string();
|
|
387
455
|
|
|
456
|
+
if self.should_run_global_pipeline(file_path) {
|
|
457
|
+
execute_pipeline(document, &self.pipeline, None, file_path, self)?;
|
|
458
|
+
}
|
|
459
|
+
|
|
388
460
|
for rule in &self.rules {
|
|
389
461
|
if !file_path.is_empty() {
|
|
390
462
|
if let Ok(pattern) = glob::Pattern::new(&rule.files) {
|
|
@@ -407,6 +479,55 @@ impl Yerbafile {
|
|
|
407
479
|
}
|
|
408
480
|
}
|
|
409
481
|
|
|
482
|
+
fn execute_pipeline(document: &mut Document, steps: &[PipelineStep], base_path: Option<&str>, file: &str, yerbafile: &Yerbafile) -> Result<(), YerbaError> {
|
|
483
|
+
use crate::document::style::StyleEnforcement;
|
|
484
|
+
|
|
485
|
+
let mut enforcement = StyleEnforcement {
|
|
486
|
+
collection_style: None,
|
|
487
|
+
sequence_indent: None,
|
|
488
|
+
key_style: None,
|
|
489
|
+
value_style: None,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
let mut has_enforcement = false;
|
|
493
|
+
let mut remaining_steps: Vec<&PipelineStep> = Vec::new();
|
|
494
|
+
|
|
495
|
+
for step in steps {
|
|
496
|
+
match step {
|
|
497
|
+
PipelineStep::CollectionStyle(config) if config.path.is_none() && base_path.is_none() => {
|
|
498
|
+
enforcement.collection_style = Some(config.style.clone());
|
|
499
|
+
has_enforcement = true;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
PipelineStep::SequenceIndent(config) if config.path.is_none() && base_path.is_none() => {
|
|
503
|
+
enforcement.sequence_indent = Some(config.style.clone());
|
|
504
|
+
has_enforcement = true;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
PipelineStep::QuoteStyle(config) if config.path.is_none() && base_path.is_none() => {
|
|
508
|
+
let key_style = config.key_style.parse::<crate::KeyStyle>().ok();
|
|
509
|
+
let value_style = config.value_style.parse::<QuoteStyle>().ok();
|
|
510
|
+
|
|
511
|
+
enforcement.key_style = key_style;
|
|
512
|
+
enforcement.value_style = value_style;
|
|
513
|
+
has_enforcement = true;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
_ => remaining_steps.push(step),
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if has_enforcement {
|
|
521
|
+
document.enforce_styles(&enforcement)?;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
for step in remaining_steps {
|
|
525
|
+
execute_step(document, step, base_path, file, yerbafile)?;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
Ok(())
|
|
529
|
+
}
|
|
530
|
+
|
|
410
531
|
fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<&str>, _file: &str, yerbafile: &Yerbafile) -> Result<(), YerbaError> {
|
|
411
532
|
match step {
|
|
412
533
|
PipelineStep::QuoteStyle(config) => {
|
|
@@ -426,6 +547,20 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
426
547
|
Ok(())
|
|
427
548
|
}
|
|
428
549
|
|
|
550
|
+
PipelineStep::CollectionStyle(config) => {
|
|
551
|
+
let dot_path = resolve_step_path(base_path, config.path.as_deref());
|
|
552
|
+
let path = if dot_path.is_empty() { None } else { Some(dot_path.as_str()) };
|
|
553
|
+
|
|
554
|
+
document.enforce_collection_style(&config.style, path)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
PipelineStep::SequenceIndent(config) => {
|
|
558
|
+
let dot_path = resolve_step_path(base_path, config.path.as_deref());
|
|
559
|
+
let path = if dot_path.is_empty() { None } else { Some(dot_path.as_str()) };
|
|
560
|
+
|
|
561
|
+
document.enforce_sequence_indent(&config.style, path)
|
|
562
|
+
}
|
|
563
|
+
|
|
429
564
|
PipelineStep::SortKeys(config) => {
|
|
430
565
|
let full_path = resolve_step_path(base_path, config.path.as_deref());
|
|
431
566
|
let key_order: Vec<&str> = config.order.iter().map(|key| key.as_str()).collect();
|
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.6.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-
|
|
11
|
+
date: 2026-06-17 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.
|