yerba 0.5.1 → 0.6.0

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.
@@ -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(),
@@ -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.par_iter().map(|file| self.apply_pipeline_to_file(rule, file, write)).collect();
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
- results.push(self.apply_pipeline_to_file(rule, file, write));
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yerba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Roth