yerba 0.5.0-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.
data/rust/src/json.rs CHANGED
@@ -1,11 +1,11 @@
1
1
  use crate::selector::{Selector, SelectorSegment};
2
2
 
3
- pub fn yaml_to_json(value: &serde_yaml::Value) -> serde_json::Value {
3
+ pub fn yaml_to_json(value: &yaml_serde::Value) -> serde_json::Value {
4
4
  match value {
5
- serde_yaml::Value::Null => serde_json::Value::Null,
6
- serde_yaml::Value::Bool(boolean) => serde_json::Value::Bool(*boolean),
5
+ yaml_serde::Value::Null => serde_json::Value::Null,
6
+ yaml_serde::Value::Bool(boolean) => serde_json::Value::Bool(*boolean),
7
7
 
8
- serde_yaml::Value::Number(number) => {
8
+ yaml_serde::Value::Number(number) => {
9
9
  if let Some(integer) = number.as_i64() {
10
10
  serde_json::Value::Number(integer.into())
11
11
  } else if let Some(float) = number.as_f64() {
@@ -15,16 +15,16 @@ pub fn yaml_to_json(value: &serde_yaml::Value) -> serde_json::Value {
15
15
  }
16
16
  }
17
17
 
18
- serde_yaml::Value::String(string) => serde_json::Value::String(string.clone()),
18
+ yaml_serde::Value::String(string) => serde_json::Value::String(string.clone()),
19
19
 
20
- serde_yaml::Value::Sequence(sequence) => serde_json::Value::Array(sequence.iter().map(yaml_to_json).collect()),
20
+ yaml_serde::Value::Sequence(sequence) => serde_json::Value::Array(sequence.iter().map(yaml_to_json).collect()),
21
21
 
22
- serde_yaml::Value::Mapping(mapping) => {
22
+ yaml_serde::Value::Mapping(mapping) => {
23
23
  let mut map = serde_json::Map::new();
24
24
 
25
25
  for (key, yaml_value) in mapping {
26
26
  let json_key = match key {
27
- serde_yaml::Value::String(string) => string.clone(),
27
+ yaml_serde::Value::String(string) => string.clone(),
28
28
  _ => format!("{:?}", key),
29
29
  };
30
30
 
@@ -34,19 +34,19 @@ pub fn yaml_to_json(value: &serde_yaml::Value) -> serde_json::Value {
34
34
  serde_json::Value::Object(map)
35
35
  }
36
36
 
37
- serde_yaml::Value::Tagged(tagged) => yaml_to_json(&tagged.value),
37
+ yaml_serde::Value::Tagged(tagged) => yaml_to_json(&tagged.value),
38
38
  }
39
39
  }
40
40
 
41
- pub fn resolve_select_field(value: &serde_yaml::Value, field: &str) -> serde_json::Value {
41
+ pub fn resolve_select_field(value: &yaml_serde::Value, field: &str) -> serde_json::Value {
42
42
  let parsed = Selector::parse(field);
43
43
  let segments = parsed.segments();
44
44
 
45
45
  if segments.len() == 1 {
46
46
  if let SelectorSegment::Key(key) = &segments[0] {
47
- if let serde_yaml::Value::Mapping(map) = value {
47
+ if let yaml_serde::Value::Mapping(map) = value {
48
48
  for (map_key, yaml_value) in map {
49
- if let serde_yaml::Value::String(key_string) = map_key {
49
+ if let yaml_serde::Value::String(key_string) = map_key {
50
50
  if key_string == key {
51
51
  return yaml_to_json(yaml_value);
52
52
  }
@@ -66,13 +66,13 @@ pub fn resolve_select_field(value: &serde_yaml::Value, field: &str) -> serde_jso
66
66
  for current in &current_values {
67
67
  match segment {
68
68
  SelectorSegment::AllItems => {
69
- if let serde_yaml::Value::Sequence(sequence) = current {
69
+ if let yaml_serde::Value::Sequence(sequence) = current {
70
70
  next_values.extend(sequence.iter().cloned());
71
71
  }
72
72
  }
73
73
 
74
74
  SelectorSegment::Index(index) => {
75
- if let serde_yaml::Value::Sequence(sequence) = current {
75
+ if let yaml_serde::Value::Sequence(sequence) = current {
76
76
  if let Some(item) = sequence.get(*index) {
77
77
  next_values.push(item.clone());
78
78
  }
@@ -80,9 +80,9 @@ pub fn resolve_select_field(value: &serde_yaml::Value, field: &str) -> serde_jso
80
80
  }
81
81
 
82
82
  SelectorSegment::Key(key) => {
83
- if let serde_yaml::Value::Mapping(map) = current {
83
+ if let yaml_serde::Value::Mapping(map) = current {
84
84
  for (map_key, yaml_value) in map {
85
- if let serde_yaml::Value::String(key_string) = map_key {
85
+ if let yaml_serde::Value::String(key_string) = map_key {
86
86
  if key_string == key {
87
87
  next_values.push(yaml_value.clone());
88
88
  }
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};
@@ -88,10 +89,10 @@ pub fn glob_find(pattern: &str, selector: &str, condition: Option<&str>, select:
88
89
  }
89
90
  }
90
91
  None => {
91
- if let serde_yaml::Value::Mapping(map) = value {
92
+ if let yaml_serde::Value::Mapping(map) = value {
92
93
  for (key, yaml_value) in map {
93
94
  let json_key = match key {
94
- serde_yaml::Value::String(string) => string.clone(),
95
+ yaml_serde::Value::String(string) => string.clone(),
95
96
  _ => format!("{:?}", key),
96
97
  };
97
98
 
data/rust/src/selector.rs CHANGED
@@ -171,6 +171,22 @@ impl Selector {
171
171
 
172
172
  result
173
173
  }
174
+
175
+ pub fn parent_path(&self) -> String {
176
+ let segments = self.segments();
177
+
178
+ if segments.len() <= 1 {
179
+ return String::new();
180
+ }
181
+
182
+ let parent_segments = &segments[..segments.len() - 1];
183
+ let parent = match self {
184
+ Selector::Relative(_) => Selector::Relative(parent_segments.to_vec()),
185
+ Selector::Absolute(_) => Selector::Absolute(parent_segments.to_vec()),
186
+ };
187
+
188
+ parent.to_selector_string()
189
+ }
174
190
  }
175
191
 
176
192
  fn parse_segments(input: &str) -> Vec<SelectorSegment> {
@@ -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),
@@ -108,70 +114,80 @@ impl<'de> Deserialize<'de> for PipelineStep {
108
114
  where
109
115
  D: serde::Deserializer<'de>,
110
116
  {
111
- let mapping = serde_yaml::Mapping::deserialize(deserializer)?;
117
+ let mapping = yaml_serde::Mapping::deserialize(deserializer)?;
112
118
 
113
- if let Some(value) = mapping.get(serde_yaml::Value::String("sort_keys".to_string())) {
114
- let config: SortKeysConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
119
+ if let Some(value) = mapping.get(yaml_serde::Value::String("sort_keys".to_string())) {
120
+ let config: SortKeysConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
115
121
  return Ok(PipelineStep::SortKeys(config));
116
122
  }
117
123
 
118
- if let Some(value) = mapping.get(serde_yaml::Value::String("quote_style".to_string())) {
119
- let config: QuoteStyleConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
124
+ if let Some(value) = mapping.get(yaml_serde::Value::String("quote_style".to_string())) {
125
+ let config: QuoteStyleConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
120
126
  return Ok(PipelineStep::QuoteStyle(config));
121
127
  }
122
128
 
123
- if let Some(value) = mapping.get(serde_yaml::Value::String("set".to_string())) {
124
- let config: SetConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
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
+
139
+ if let Some(value) = mapping.get(yaml_serde::Value::String("set".to_string())) {
140
+ let config: SetConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
125
141
  return Ok(PipelineStep::Set(config));
126
142
  }
127
143
 
128
- if let Some(value) = mapping.get(serde_yaml::Value::String("insert".to_string())) {
129
- let config: InsertConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
144
+ if let Some(value) = mapping.get(yaml_serde::Value::String("insert".to_string())) {
145
+ let config: InsertConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
130
146
  return Ok(PipelineStep::Insert(config));
131
147
  }
132
148
 
133
- if let Some(value) = mapping.get(serde_yaml::Value::String("delete".to_string())) {
134
- let config: DeleteConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
149
+ if let Some(value) = mapping.get(yaml_serde::Value::String("delete".to_string())) {
150
+ let config: DeleteConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
135
151
  return Ok(PipelineStep::Delete(config));
136
152
  }
137
153
 
138
- if let Some(value) = mapping.get(serde_yaml::Value::String("rename".to_string())) {
139
- let config: RenameConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
154
+ if let Some(value) = mapping.get(yaml_serde::Value::String("rename".to_string())) {
155
+ let config: RenameConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
140
156
  return Ok(PipelineStep::Rename(config));
141
157
  }
142
158
 
143
- if let Some(value) = mapping.get(serde_yaml::Value::String("remove".to_string())) {
144
- let config: RemoveConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
159
+ if let Some(value) = mapping.get(yaml_serde::Value::String("remove".to_string())) {
160
+ let config: RemoveConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
145
161
  return Ok(PipelineStep::Remove(config));
146
162
  }
147
163
 
148
- if let Some(value) = mapping.get(serde_yaml::Value::String("blank_lines".to_string())) {
149
- let config: BlankLinesConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
164
+ if let Some(value) = mapping.get(yaml_serde::Value::String("blank_lines".to_string())) {
165
+ let config: BlankLinesConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
150
166
  return Ok(PipelineStep::BlankLines(config));
151
167
  }
152
168
 
153
- if let Some(value) = mapping.get(serde_yaml::Value::String("directives".to_string())) {
154
- let config: DirectivesConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
169
+ if let Some(value) = mapping.get(yaml_serde::Value::String("directives".to_string())) {
170
+ let config: DirectivesConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
155
171
  return Ok(PipelineStep::Directives(config));
156
172
  }
157
173
 
158
- if let Some(value) = mapping.get(serde_yaml::Value::String("sort".to_string())) {
159
- let config: SortConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
174
+ if let Some(value) = mapping.get(yaml_serde::Value::String("sort".to_string())) {
175
+ let config: SortConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
160
176
  return Ok(PipelineStep::Sort(config));
161
177
  }
162
178
 
163
- if let Some(value) = mapping.get(serde_yaml::Value::String("unique".to_string())) {
164
- let config: UniqueConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
179
+ if let Some(value) = mapping.get(yaml_serde::Value::String("unique".to_string())) {
180
+ let config: UniqueConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
165
181
  return Ok(PipelineStep::Unique(config));
166
182
  }
167
183
 
168
- if let Some(value) = mapping.get(serde_yaml::Value::String("schema".to_string())) {
169
- let config: SchemaConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
184
+ if let Some(value) = mapping.get(yaml_serde::Value::String("schema".to_string())) {
185
+ let config: SchemaConfig = yaml_serde::from_value(value.clone()).map_err(serde::de::Error::custom)?;
170
186
  return Ok(PipelineStep::Schema(config));
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,
@@ -229,7 +259,7 @@ pub struct RuleResult {
229
259
  impl Yerbafile {
230
260
  pub fn load(path: impl AsRef<Path>) -> Result<Self, YerbaError> {
231
261
  let content = fs::read_to_string(path.as_ref())?;
232
- let mut yerbafile: Yerbafile = serde_yaml::from_str(&content).map_err(|error| YerbaError::ParseError(format!("{}", error)))?;
262
+ let mut yerbafile: Yerbafile = yaml_serde::from_str(&content).map_err(|error| YerbaError::ParseError(format!("{}", error)))?;
233
263
  yerbafile.directory = path.as_ref().parent().map(|p| p.to_path_buf());
234
264
  Ok(yerbafile)
235
265
  }
@@ -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,59 +342,25 @@ 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 mut has_validation_error = false;
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();
315
352
 
316
- for step in &rule.pipeline {
317
- if let PipelineStep::SortKeys(config) = step {
318
- let full_path = resolve_step_path(rule.path.as_deref(), config.path.as_deref());
319
- let key_order: Vec<&str> = config.order.iter().map(|key| key.as_str()).collect();
320
-
321
- let validation_results: Vec<RuleResult> = file_strings
322
- .par_iter()
323
- .filter_map(|file| {
324
- let document = match Document::parse_file(file) {
325
- Ok(document) => document,
326
- Err(error) => {
327
- return Some(RuleResult {
328
- file: file.clone(),
329
- changed: false,
330
- error: Some(error),
331
- });
332
- }
333
- };
334
-
335
- if let Err(error) = document.validate_sort_keys(&full_path, &key_order) {
336
- Some(RuleResult {
337
- file: file.clone(),
338
- changed: false,
339
- error: Some(error),
340
- })
341
- } else {
342
- None
343
- }
344
- })
345
- .collect();
346
-
347
- if !validation_results.is_empty() {
348
- has_validation_error = true;
349
- results.extend(validation_results);
350
- }
351
- }
352
- }
353
-
354
- if has_validation_error {
355
- continue;
353
+ for result in &file_results {
354
+ globally_processed.insert(result.file.clone());
356
355
  }
357
356
 
358
- let file_results: Vec<RuleResult> = file_strings.par_iter().map(|file| self.apply_pipeline_to_file(rule, file, write)).collect();
359
-
360
357
  results.extend(file_results);
361
358
  }
362
359
 
363
360
  results
364
361
  }
365
362
 
366
- 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 {
367
364
  let mut document = match Document::parse_file(file) {
368
365
  Ok(document) => document,
369
366
  Err(error) => {
@@ -376,6 +373,17 @@ impl Yerbafile {
376
373
  };
377
374
 
378
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
+
379
387
  let base_path = rule.path.as_deref();
380
388
 
381
389
  for step in &rule.pipeline {
@@ -410,6 +418,7 @@ impl Yerbafile {
410
418
 
411
419
  pub fn apply_file(&self, file: &str, write: bool) -> Vec<RuleResult> {
412
420
  let mut results = Vec::new();
421
+ let mut ran_global = false;
413
422
 
414
423
  for rule in &self.rules {
415
424
  if let Ok(pattern) = glob::Pattern::new(&rule.files) {
@@ -420,15 +429,34 @@ impl Yerbafile {
420
429
  continue;
421
430
  }
422
431
 
423
- 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;
424
435
  }
425
436
 
426
437
  results
427
438
  }
428
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
+
429
453
  pub fn apply_to_document(&self, document: &mut Document, file_path: &str) -> Result<bool, YerbaError> {
430
454
  let original = document.to_string();
431
455
 
456
+ if self.should_run_global_pipeline(file_path) {
457
+ execute_pipeline(document, &self.pipeline, None, file_path, self)?;
458
+ }
459
+
432
460
  for rule in &self.rules {
433
461
  if !file_path.is_empty() {
434
462
  if let Ok(pattern) = glob::Pattern::new(&rule.files) {
@@ -451,6 +479,55 @@ impl Yerbafile {
451
479
  }
452
480
  }
453
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
+
454
531
  fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<&str>, _file: &str, yerbafile: &Yerbafile) -> Result<(), YerbaError> {
455
532
  match step {
456
533
  PipelineStep::QuoteStyle(config) => {
@@ -470,10 +547,25 @@ fn execute_step(document: &mut Document, step: &PipelineStep, base_path: Option<
470
547
  Ok(())
471
548
  }
472
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
+
473
564
  PipelineStep::SortKeys(config) => {
474
565
  let full_path = resolve_step_path(base_path, config.path.as_deref());
475
566
  let key_order: Vec<&str> = config.order.iter().map(|key| key.as_str()).collect();
476
567
 
568
+ document.validate_sort_keys(&full_path, &key_order)?;
477
569
  document.sort_keys(&full_path, &key_order)
478
570
  }
479
571
 
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.5.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-05-12 00:00:00.000000000 Z
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.