yerba 0.5.1 → 0.6.1
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/ext/yerba/include/yerba.h +12 -0
- data/ext/yerba/yerba.c +74 -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.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 +626 -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 +156 -4
- metadata +1 -1
data/rust/src/lib.rs
CHANGED
|
@@ -10,6 +10,7 @@ mod syntax;
|
|
|
10
10
|
mod yaml_writer;
|
|
11
11
|
pub mod yerbafile;
|
|
12
12
|
|
|
13
|
+
pub use document::style::StyleEnforcement;
|
|
13
14
|
pub use document::{collect_selectors, Document, DuplicateInfo, InsertPosition, LocatedNode, Location, NodeInfo, NodeType, SortField};
|
|
14
15
|
pub use error::YerbaError;
|
|
15
16
|
pub use quote_style::{KeyStyle, QuoteStyle};
|
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),
|
|
@@ -60,6 +66,8 @@ pub struct DirectivesConfig {
|
|
|
60
66
|
pub ensure: bool,
|
|
61
67
|
#[serde(default)]
|
|
62
68
|
pub remove: bool,
|
|
69
|
+
#[serde(default)]
|
|
70
|
+
pub max: Option<usize>,
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
#[derive(Debug, Clone, Deserialize)]
|
|
@@ -120,6 +128,16 @@ impl<'de> Deserialize<'de> for PipelineStep {
|
|
|
120
128
|
return Ok(PipelineStep::QuoteStyle(config));
|
|
121
129
|
}
|
|
122
130
|
|
|
131
|
+
if let Some(value) = mapping.get(yaml_serde::Value::String("collection_style".to_string())) {
|
|
132
|
+
let config: CollectionStyleConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
|
|
133
|
+
return Ok(PipelineStep::CollectionStyle(config));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if let Some(value) = mapping.get(yaml_serde::Value::String("sequence_indent".to_string())) {
|
|
137
|
+
let config: SequenceIndentConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
|
|
138
|
+
return Ok(PipelineStep::SequenceIndent(config));
|
|
139
|
+
}
|
|
140
|
+
|
|
123
141
|
if let Some(value) = mapping.get(yaml_serde::Value::String("set".to_string())) {
|
|
124
142
|
let config: SetConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
|
|
125
143
|
return Ok(PipelineStep::Set(config));
|
|
@@ -171,7 +189,7 @@ impl<'de> Deserialize<'de> for PipelineStep {
|
|
|
171
189
|
}
|
|
172
190
|
|
|
173
191
|
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",
|
|
192
|
+
"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
193
|
))
|
|
176
194
|
}
|
|
177
195
|
}
|
|
@@ -192,6 +210,20 @@ pub struct QuoteStyleConfig {
|
|
|
192
210
|
pub path: Option<String>,
|
|
193
211
|
}
|
|
194
212
|
|
|
213
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
214
|
+
pub struct CollectionStyleConfig {
|
|
215
|
+
pub style: String,
|
|
216
|
+
#[serde(default)]
|
|
217
|
+
pub path: Option<String>,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
221
|
+
pub struct SequenceIndentConfig {
|
|
222
|
+
pub style: String,
|
|
223
|
+
#[serde(default)]
|
|
224
|
+
pub path: Option<String>,
|
|
225
|
+
}
|
|
226
|
+
|
|
195
227
|
#[derive(Debug, Clone, Deserialize)]
|
|
196
228
|
pub struct SetConfig {
|
|
197
229
|
pub path: String,
|
|
@@ -293,6 +325,7 @@ impl Yerbafile {
|
|
|
293
325
|
|
|
294
326
|
pub fn apply(&self, write: bool) -> Vec<RuleResult> {
|
|
295
327
|
let mut results = Vec::new();
|
|
328
|
+
let mut globally_processed: std::collections::HashSet<String> = std::collections::HashSet::new();
|
|
296
329
|
|
|
297
330
|
for rule in &self.rules {
|
|
298
331
|
let files = match glob::glob(&rule.files) {
|
|
@@ -311,7 +344,17 @@ impl Yerbafile {
|
|
|
311
344
|
|
|
312
345
|
let file_strings: Vec<String> = files.iter().map(|path| path.to_string_lossy().to_string()).collect();
|
|
313
346
|
|
|
314
|
-
let file_results: Vec<RuleResult> = file_strings
|
|
347
|
+
let file_results: Vec<RuleResult> = file_strings
|
|
348
|
+
.par_iter()
|
|
349
|
+
.map(|file| {
|
|
350
|
+
let run_global = !globally_processed.contains(file.as_str()) && self.should_run_global_pipeline(file);
|
|
351
|
+
self.apply_pipeline_to_file(rule, file, write, run_global)
|
|
352
|
+
})
|
|
353
|
+
.collect();
|
|
354
|
+
|
|
355
|
+
for result in &file_results {
|
|
356
|
+
globally_processed.insert(result.file.clone());
|
|
357
|
+
}
|
|
315
358
|
|
|
316
359
|
results.extend(file_results);
|
|
317
360
|
}
|
|
@@ -319,7 +362,7 @@ impl Yerbafile {
|
|
|
319
362
|
results
|
|
320
363
|
}
|
|
321
364
|
|
|
322
|
-
fn apply_pipeline_to_file(&self, rule: &Rule, file: &str, write: bool) -> RuleResult {
|
|
365
|
+
fn apply_pipeline_to_file(&self, rule: &Rule, file: &str, write: bool, run_global: bool) -> RuleResult {
|
|
323
366
|
let mut document = match Document::parse_file(file) {
|
|
324
367
|
Ok(document) => document,
|
|
325
368
|
Err(error) => {
|
|
@@ -332,6 +375,17 @@ impl Yerbafile {
|
|
|
332
375
|
};
|
|
333
376
|
|
|
334
377
|
let original = document.to_string();
|
|
378
|
+
|
|
379
|
+
if run_global {
|
|
380
|
+
if let Err(error) = execute_pipeline(&mut document, &self.pipeline, None, file, self) {
|
|
381
|
+
return RuleResult {
|
|
382
|
+
file: file.to_string(),
|
|
383
|
+
changed: false,
|
|
384
|
+
error: Some(error),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
335
389
|
let base_path = rule.path.as_deref();
|
|
336
390
|
|
|
337
391
|
for step in &rule.pipeline {
|
|
@@ -366,6 +420,7 @@ impl Yerbafile {
|
|
|
366
420
|
|
|
367
421
|
pub fn apply_file(&self, file: &str, write: bool) -> Vec<RuleResult> {
|
|
368
422
|
let mut results = Vec::new();
|
|
423
|
+
let mut ran_global = false;
|
|
369
424
|
|
|
370
425
|
for rule in &self.rules {
|
|
371
426
|
if let Ok(pattern) = glob::Pattern::new(&rule.files) {
|
|
@@ -376,15 +431,34 @@ impl Yerbafile {
|
|
|
376
431
|
continue;
|
|
377
432
|
}
|
|
378
433
|
|
|
379
|
-
|
|
434
|
+
let run_global = !ran_global && self.should_run_global_pipeline(file);
|
|
435
|
+
results.push(self.apply_pipeline_to_file(rule, file, write, run_global));
|
|
436
|
+
ran_global = true;
|
|
380
437
|
}
|
|
381
438
|
|
|
382
439
|
results
|
|
383
440
|
}
|
|
384
441
|
|
|
442
|
+
fn should_run_global_pipeline(&self, file_path: &str) -> bool {
|
|
443
|
+
if self.pipeline.is_empty() {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
match &self.files {
|
|
448
|
+
Some(glob_pattern) => glob::Pattern::new(glob_pattern)
|
|
449
|
+
.map(|pattern| pattern.matches(file_path) || pattern.matches_path(Path::new(file_path)))
|
|
450
|
+
.unwrap_or(false),
|
|
451
|
+
None => true,
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
385
455
|
pub fn apply_to_document(&self, document: &mut Document, file_path: &str) -> Result<bool, YerbaError> {
|
|
386
456
|
let original = document.to_string();
|
|
387
457
|
|
|
458
|
+
if self.should_run_global_pipeline(file_path) {
|
|
459
|
+
execute_pipeline(document, &self.pipeline, None, file_path, self)?;
|
|
460
|
+
}
|
|
461
|
+
|
|
388
462
|
for rule in &self.rules {
|
|
389
463
|
if !file_path.is_empty() {
|
|
390
464
|
if let Ok(pattern) = glob::Pattern::new(&rule.files) {
|
|
@@ -407,6 +481,55 @@ impl Yerbafile {
|
|
|
407
481
|
}
|
|
408
482
|
}
|
|
409
483
|
|
|
484
|
+
fn execute_pipeline(document: &mut Document, steps: &[PipelineStep], base_path: Option<&str>, file: &str, yerbafile: &Yerbafile) -> Result<(), YerbaError> {
|
|
485
|
+
use crate::document::style::StyleEnforcement;
|
|
486
|
+
|
|
487
|
+
let mut enforcement = StyleEnforcement {
|
|
488
|
+
collection_style: None,
|
|
489
|
+
sequence_indent: None,
|
|
490
|
+
key_style: None,
|
|
491
|
+
value_style: None,
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
let mut has_enforcement = false;
|
|
495
|
+
let mut remaining_steps: Vec<&PipelineStep> = Vec::new();
|
|
496
|
+
|
|
497
|
+
for step in steps {
|
|
498
|
+
match step {
|
|
499
|
+
PipelineStep::CollectionStyle(config) if config.path.is_none() && base_path.is_none() => {
|
|
500
|
+
enforcement.collection_style = Some(config.style.clone());
|
|
501
|
+
has_enforcement = true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
PipelineStep::SequenceIndent(config) if config.path.is_none() && base_path.is_none() => {
|
|
505
|
+
enforcement.sequence_indent = Some(config.style.clone());
|
|
506
|
+
has_enforcement = true;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
PipelineStep::QuoteStyle(config) if config.path.is_none() && base_path.is_none() => {
|
|
510
|
+
let key_style = config.key_style.parse::<crate::KeyStyle>().ok();
|
|
511
|
+
let value_style = config.value_style.parse::<QuoteStyle>().ok();
|
|
512
|
+
|
|
513
|
+
enforcement.key_style = key_style;
|
|
514
|
+
enforcement.value_style = value_style;
|
|
515
|
+
has_enforcement = true;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
_ => remaining_steps.push(step),
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if has_enforcement {
|
|
523
|
+
document.enforce_styles(&enforcement)?;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
for step in remaining_steps {
|
|
527
|
+
execute_step(document, step, base_path, file, yerbafile)?;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
Ok(())
|
|
531
|
+
}
|
|
532
|
+
|
|
410
533
|
fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<&str>, _file: &str, yerbafile: &Yerbafile) -> Result<(), YerbaError> {
|
|
411
534
|
match step {
|
|
412
535
|
PipelineStep::QuoteStyle(config) => {
|
|
@@ -426,6 +549,20 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
426
549
|
Ok(())
|
|
427
550
|
}
|
|
428
551
|
|
|
552
|
+
PipelineStep::CollectionStyle(config) => {
|
|
553
|
+
let dot_path = resolve_step_path(base_path, config.path.as_deref());
|
|
554
|
+
let path = if dot_path.is_empty() { None } else { Some(dot_path.as_str()) };
|
|
555
|
+
|
|
556
|
+
document.enforce_collection_style(&config.style, path)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
PipelineStep::SequenceIndent(config) => {
|
|
560
|
+
let dot_path = resolve_step_path(base_path, config.path.as_deref());
|
|
561
|
+
let path = if dot_path.is_empty() { None } else { Some(dot_path.as_str()) };
|
|
562
|
+
|
|
563
|
+
document.enforce_sequence_indent(&config.style, path)
|
|
564
|
+
}
|
|
565
|
+
|
|
429
566
|
PipelineStep::SortKeys(config) => {
|
|
430
567
|
let full_path = resolve_step_path(base_path, config.path.as_deref());
|
|
431
568
|
let key_order: Vec<&str> = config.order.iter().map(|key| key.as_str()).collect();
|
|
@@ -540,6 +677,21 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
|
|
|
540
677
|
return Err(YerbaError::ParseError("directives: ensure and remove are mutually exclusive".to_string()));
|
|
541
678
|
}
|
|
542
679
|
|
|
680
|
+
if let Some(max) = config.max {
|
|
681
|
+
let locations = document.directive_locations();
|
|
682
|
+
|
|
683
|
+
if locations.len() > max {
|
|
684
|
+
let positions: Vec<String> = locations.iter().map(|(line, col)| format!("{}:{}", line, col)).collect();
|
|
685
|
+
|
|
686
|
+
return Err(YerbaError::ParseError(format!(
|
|
687
|
+
"found {} directive markers (---) at {}, expected at most {}",
|
|
688
|
+
locations.len(),
|
|
689
|
+
positions.join(", "),
|
|
690
|
+
max
|
|
691
|
+
)));
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
543
695
|
if config.ensure {
|
|
544
696
|
document.ensure_directives()
|
|
545
697
|
} else if config.remove {
|