yerba 0.1.0-aarch64-linux

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.
@@ -0,0 +1,563 @@
1
+ use rayon::prelude::*;
2
+ use serde::Deserialize;
3
+ use std::collections::HashMap;
4
+ use std::fs;
5
+ use std::path::{Path, PathBuf};
6
+
7
+ use crate::{Document, QuoteStyle, YerbaError};
8
+
9
+ #[derive(Debug, Deserialize)]
10
+ pub struct Yerbafile {
11
+ #[serde(default)]
12
+ pub rules: Vec<Rule>,
13
+ }
14
+
15
+ #[derive(Debug, Clone, Deserialize)]
16
+ pub struct Rule {
17
+ pub files: String,
18
+ #[serde(default)]
19
+ pub path: Option<String>,
20
+ pub pipeline: Vec<PipelineStep>,
21
+ }
22
+
23
+ #[derive(Debug, Clone)]
24
+ pub enum PipelineStep {
25
+ Get(GetConfig),
26
+ SortKeys(SortKeysConfig),
27
+ QuoteStyle(QuoteStyleConfig),
28
+ Set(SetConfig),
29
+ Insert(InsertConfig),
30
+ Delete(DeleteConfig),
31
+ Rename(RenameConfig),
32
+ Remove(RemoveConfig),
33
+ BlankLines(BlankLinesConfig),
34
+ }
35
+
36
+ #[derive(Debug, Clone, Deserialize)]
37
+ pub struct BlankLinesConfig {
38
+ #[serde(default)]
39
+ pub path: Option<String>,
40
+ pub count: usize,
41
+ }
42
+
43
+ #[derive(Debug, Clone, Deserialize)]
44
+ pub struct GetConfig {
45
+ pub path: String,
46
+ #[serde(rename = "as")]
47
+ pub as_name: String,
48
+ #[serde(default)]
49
+ pub file: Option<String>,
50
+ }
51
+
52
+ #[derive(Debug, Clone)]
53
+ pub enum Variable {
54
+ Single(String),
55
+ List(Vec<String>),
56
+ }
57
+
58
+ #[derive(Debug, Clone, Deserialize)]
59
+ pub struct RenameConfig {
60
+ pub from: String,
61
+ pub to: String,
62
+ #[serde(default)]
63
+ pub condition: Option<String>,
64
+ }
65
+
66
+ #[derive(Debug, Clone, Deserialize)]
67
+ pub struct RemoveConfig {
68
+ pub path: String,
69
+ pub value: String,
70
+ #[serde(default)]
71
+ pub condition: Option<String>,
72
+ }
73
+
74
+ impl<'de> Deserialize<'de> for PipelineStep {
75
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
76
+ where
77
+ D: serde::Deserializer<'de>,
78
+ {
79
+ let mapping = serde_yaml::Mapping::deserialize(deserializer)?;
80
+
81
+ if let Some(value) = mapping.get(serde_yaml::Value::String("get".to_string())) {
82
+ let config: GetConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
83
+ return Ok(PipelineStep::Get(config));
84
+ }
85
+
86
+ if let Some(value) = mapping.get(serde_yaml::Value::String("sort_keys".to_string())) {
87
+ let config: SortKeysConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
88
+ return Ok(PipelineStep::SortKeys(config));
89
+ }
90
+
91
+ if let Some(value) = mapping.get(serde_yaml::Value::String("quote_style".to_string())) {
92
+ let config: QuoteStyleConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
93
+ return Ok(PipelineStep::QuoteStyle(config));
94
+ }
95
+
96
+ if let Some(value) = mapping.get(serde_yaml::Value::String("set".to_string())) {
97
+ let config: SetConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
98
+ return Ok(PipelineStep::Set(config));
99
+ }
100
+
101
+ if let Some(value) = mapping.get(serde_yaml::Value::String("insert".to_string())) {
102
+ let config: InsertConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
103
+ return Ok(PipelineStep::Insert(config));
104
+ }
105
+
106
+ if let Some(value) = mapping.get(serde_yaml::Value::String("delete".to_string())) {
107
+ let config: DeleteConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
108
+ return Ok(PipelineStep::Delete(config));
109
+ }
110
+
111
+ if let Some(value) = mapping.get(serde_yaml::Value::String("rename".to_string())) {
112
+ let config: RenameConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
113
+ return Ok(PipelineStep::Rename(config));
114
+ }
115
+
116
+ if let Some(value) = mapping.get(serde_yaml::Value::String("remove".to_string())) {
117
+ let config: RemoveConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
118
+ return Ok(PipelineStep::Remove(config));
119
+ }
120
+
121
+ if let Some(value) = mapping.get(serde_yaml::Value::String("blank_lines".to_string())) {
122
+ let config: BlankLinesConfig = serde_yaml::from_value(value.clone()).map_err(serde::de::Error::custom)?;
123
+ return Ok(PipelineStep::BlankLines(config));
124
+ }
125
+
126
+ Err(serde::de::Error::custom(
127
+ "unknown pipeline step: expected get, sort_keys, quote_style, set, insert, delete, rename, remove, or blank_lines",
128
+ ))
129
+ }
130
+ }
131
+
132
+ #[derive(Debug, Clone, Deserialize)]
133
+ pub struct SortKeysConfig {
134
+ #[serde(default)]
135
+ pub path: Option<String>,
136
+ pub order: Vec<String>,
137
+ }
138
+
139
+ #[derive(Debug, Clone, Deserialize)]
140
+ pub struct QuoteStyleConfig {
141
+ #[serde(default = "default_key_style")]
142
+ pub key_style: String,
143
+ pub value_style: String,
144
+ #[serde(default)]
145
+ pub path: Option<String>,
146
+ }
147
+
148
+ #[derive(Debug, Clone, Deserialize)]
149
+ pub struct SetConfig {
150
+ pub path: String,
151
+ pub value: String,
152
+ #[serde(default)]
153
+ pub condition: Option<String>,
154
+ }
155
+
156
+ #[derive(Debug, Clone, Deserialize)]
157
+ pub struct InsertConfig {
158
+ pub path: String,
159
+ pub value: String,
160
+ #[serde(default)]
161
+ pub condition: Option<String>,
162
+ }
163
+
164
+ #[derive(Debug, Clone, Deserialize)]
165
+ pub struct DeleteConfig {
166
+ pub path: String,
167
+ #[serde(default)]
168
+ pub condition: Option<String>,
169
+ }
170
+
171
+ fn default_key_style() -> String {
172
+ "plain".to_string()
173
+ }
174
+
175
+ #[derive(Debug)]
176
+ pub struct RuleResult {
177
+ pub file: String,
178
+ pub changed: bool,
179
+ pub error: Option<String>,
180
+ }
181
+
182
+ impl Yerbafile {
183
+ pub fn load(path: impl AsRef<Path>) -> Result<Self, YerbaError> {
184
+ let content = fs::read_to_string(path.as_ref())?;
185
+ let yerbafile: Yerbafile =
186
+ serde_yaml::from_str(&content).map_err(|error| YerbaError::ParseError(format!("{}", error)))?;
187
+ Ok(yerbafile)
188
+ }
189
+
190
+ pub fn find() -> Option<PathBuf> {
191
+ let candidates = ["Yerbafile", "Yerbafile.yml", "Yerbafile.yaml", ".yerbafile"];
192
+
193
+ let mut directory = std::env::current_dir().ok()?;
194
+
195
+ loop {
196
+ for candidate in &candidates {
197
+ let path = directory.join(candidate);
198
+
199
+ if path.exists() {
200
+ return Some(path);
201
+ }
202
+ }
203
+
204
+ if !directory.pop() {
205
+ return None;
206
+ }
207
+ }
208
+ }
209
+
210
+ pub fn sort_order_for(&self, file_path: &str, dot_path: &str) -> Option<Vec<String>> {
211
+ for rule in &self.rules {
212
+ let sort_keys_config = rule.pipeline.iter().find_map(|step| match step {
213
+ PipelineStep::SortKeys(config) => Some(config),
214
+ _ => None,
215
+ });
216
+
217
+ if let Some(config) = sort_keys_config {
218
+ let full_path = resolve_step_path(rule.path.as_deref(), config.path.as_deref());
219
+ let normalized = full_path.trim_end_matches("[]").trim_end_matches('.');
220
+
221
+ if normalized != dot_path && !normalized.is_empty() && !dot_path.is_empty() {
222
+ continue;
223
+ }
224
+
225
+ if let Ok(pattern) = glob::Pattern::new(&rule.files) {
226
+ if pattern.matches(file_path) || pattern.matches_path(Path::new(file_path)) {
227
+ return Some(config.order.clone());
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ None
234
+ }
235
+
236
+ pub fn apply(&self, write: bool) -> Vec<RuleResult> {
237
+ let mut results = Vec::new();
238
+
239
+ for rule in &self.rules {
240
+ let files = match glob::glob(&rule.files) {
241
+ Ok(paths) => paths.filter_map(|entry| entry.ok()).collect::<Vec<_>>(),
242
+
243
+ Err(error) => {
244
+ results.push(RuleResult {
245
+ file: rule.files.clone(),
246
+ changed: false,
247
+ error: Some(format!("invalid glob: {}", error)),
248
+ });
249
+
250
+ continue;
251
+ }
252
+ };
253
+
254
+ let file_strings: Vec<String> = files.iter().map(|path| path.to_string_lossy().to_string()).collect();
255
+
256
+ let mut has_validation_error = false;
257
+
258
+ for step in &rule.pipeline {
259
+ if let PipelineStep::SortKeys(config) = step {
260
+ let full_path = resolve_step_path(rule.path.as_deref(), config.path.as_deref());
261
+ let key_order: Vec<&str> = config.order.iter().map(|key| key.as_str()).collect();
262
+
263
+ let validation_results: Vec<RuleResult> = file_strings
264
+ .par_iter()
265
+ .filter_map(|file| {
266
+ let document = match Document::parse_file(file) {
267
+ Ok(document) => document,
268
+ Err(error) => {
269
+ return Some(RuleResult {
270
+ file: file.clone(),
271
+ changed: false,
272
+ error: Some(format!("{}", error)),
273
+ });
274
+ }
275
+ };
276
+
277
+ if let Err(error) = document.validate_sort_keys(&full_path, &key_order) {
278
+ Some(RuleResult {
279
+ file: file.clone(),
280
+ changed: false,
281
+ error: Some(format!("{}", error)),
282
+ })
283
+ } else {
284
+ None
285
+ }
286
+ })
287
+ .collect();
288
+
289
+ if !validation_results.is_empty() {
290
+ has_validation_error = true;
291
+ results.extend(validation_results);
292
+ }
293
+ }
294
+ }
295
+
296
+ if has_validation_error {
297
+ continue;
298
+ }
299
+
300
+ let file_results: Vec<RuleResult> = file_strings
301
+ .par_iter()
302
+ .map(|file| self.apply_pipeline_to_file(rule, file, write))
303
+ .collect();
304
+
305
+ results.extend(file_results);
306
+ }
307
+
308
+ results
309
+ }
310
+
311
+ fn apply_pipeline_to_file(&self, rule: &Rule, file: &str, write: bool) -> RuleResult {
312
+ let mut document = match Document::parse_file(file) {
313
+ Ok(document) => document,
314
+ Err(error) => {
315
+ return RuleResult {
316
+ file: file.to_string(),
317
+ changed: false,
318
+ error: Some(format!("{}", error)),
319
+ }
320
+ }
321
+ };
322
+
323
+ let original = document.to_string();
324
+ let base_path = rule.path.as_deref();
325
+ let mut variables: HashMap<String, Variable> = HashMap::new();
326
+
327
+ for step in &rule.pipeline {
328
+ if let Err(error) = execute_step(&mut document, step, base_path, &mut variables) {
329
+ return RuleResult {
330
+ file: file.to_string(),
331
+ changed: false,
332
+ error: Some(format!("{}", error)),
333
+ };
334
+ }
335
+ }
336
+
337
+ let new_content = document.to_string();
338
+ let changed = new_content != original;
339
+
340
+ if changed && write {
341
+ if let Err(error) = fs::write(file, &new_content) {
342
+ return RuleResult {
343
+ file: file.to_string(),
344
+ changed,
345
+ error: Some(format!("{}", error)),
346
+ };
347
+ }
348
+ }
349
+
350
+ RuleResult {
351
+ file: file.to_string(),
352
+ changed,
353
+ error: None,
354
+ }
355
+ }
356
+ }
357
+
358
+ fn execute_step(
359
+ document: &mut Document,
360
+ step: &PipelineStep,
361
+ base_path: Option<&str>,
362
+ variables: &mut HashMap<String, Variable>,
363
+ ) -> Result<(), YerbaError> {
364
+ match step {
365
+ PipelineStep::Get(config) => {
366
+ let full_path = resolve_step_path(base_path, Some(&config.path));
367
+
368
+ if let Some(file_pattern) = &config.file {
369
+ let mut all_values = Vec::new();
370
+
371
+ let files =
372
+ glob::glob(file_pattern).map_err(|error| YerbaError::ParseError(format!("invalid glob: {}", error)))?;
373
+
374
+ for entry in files.flatten() {
375
+ let external_document = Document::parse_file(&entry)?;
376
+ all_values.extend(external_document.get_all(&config.path));
377
+ }
378
+
379
+ if all_values.len() == 1 && !config.path.contains('[') {
380
+ variables.insert(config.as_name.clone(), Variable::Single(all_values.remove(0)));
381
+ } else {
382
+ variables.insert(config.as_name.clone(), Variable::List(all_values));
383
+ }
384
+ } else if config.path.contains('[') {
385
+ let values = document.get_all(&full_path);
386
+
387
+ variables.insert(config.as_name.clone(), Variable::List(values));
388
+ } else {
389
+ let value = document
390
+ .get(&full_path)
391
+ .ok_or_else(|| YerbaError::PathNotFound(full_path.clone()))?;
392
+ variables.insert(config.as_name.clone(), Variable::Single(value));
393
+ }
394
+
395
+ Ok(())
396
+ }
397
+
398
+ PipelineStep::QuoteStyle(config) => {
399
+ let dot_path = config.path.as_deref();
400
+
401
+ let key_style = config.key_style.parse::<QuoteStyle>().map_err(YerbaError::ParseError)?;
402
+
403
+ let value_style = config
404
+ .value_style
405
+ .parse::<QuoteStyle>()
406
+ .map_err(YerbaError::ParseError)?;
407
+
408
+ document.enforce_key_style(&key_style, dot_path)?;
409
+ document.enforce_quotes_at(&value_style, dot_path)?;
410
+
411
+ Ok(())
412
+ }
413
+
414
+ PipelineStep::SortKeys(config) => {
415
+ let full_path = resolve_step_path(base_path, config.path.as_deref());
416
+ let key_order: Vec<&str> = config.order.iter().map(|key| key.as_str()).collect();
417
+
418
+ document.sort_keys(&full_path, &key_order)
419
+ }
420
+
421
+ PipelineStep::Set(config) => {
422
+ let full_path = resolve_step_path(base_path, Some(&config.path));
423
+ let resolved_value = resolve_template(&config.value, document, base_path, variables)?;
424
+
425
+ if let Some(condition) = &config.condition {
426
+ let resolved_condition = resolve_template(condition, document, base_path, variables)?;
427
+ let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
428
+
429
+ if !document.evaluate_condition(parent_path, &resolved_condition) {
430
+ return Ok(());
431
+ }
432
+ }
433
+
434
+ document.set(&full_path, &resolved_value)
435
+ }
436
+
437
+ PipelineStep::Insert(config) => {
438
+ let full_path = resolve_step_path(base_path, Some(&config.path));
439
+ let resolved_value = resolve_template(&config.value, document, base_path, variables)?;
440
+
441
+ if let Some(condition) = &config.condition {
442
+ let resolved_condition = resolve_template(condition, document, base_path, variables)?;
443
+ let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
444
+
445
+ if !document.evaluate_condition(parent_path, &resolved_condition) {
446
+ return Ok(());
447
+ }
448
+ }
449
+
450
+ document.insert_into(&full_path, &resolved_value, crate::InsertPosition::Last)
451
+ }
452
+
453
+ PipelineStep::Delete(config) => {
454
+ let full_path = resolve_step_path(base_path, Some(&config.path));
455
+
456
+ if let Some(condition) = &config.condition {
457
+ let resolved_condition = resolve_template(condition, document, base_path, variables)?;
458
+ let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
459
+
460
+ if !document.evaluate_condition(parent_path, &resolved_condition) {
461
+ return Ok(());
462
+ }
463
+ }
464
+
465
+ document.delete(&full_path)
466
+ }
467
+
468
+ PipelineStep::Rename(config) => {
469
+ let full_path = resolve_step_path(base_path, Some(&config.from));
470
+
471
+ if let Some(condition) = &config.condition {
472
+ let resolved_condition = resolve_template(condition, document, base_path, variables)?;
473
+ let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
474
+
475
+ if !document.evaluate_condition(parent_path, &resolved_condition) {
476
+ return Ok(());
477
+ }
478
+ }
479
+
480
+ document.rename(&full_path, &config.to)
481
+ }
482
+
483
+ PipelineStep::Remove(config) => {
484
+ let full_path = resolve_step_path(base_path, Some(&config.path));
485
+ let resolved_value = resolve_template(&config.value, document, base_path, variables)?;
486
+
487
+ if let Some(condition) = &config.condition {
488
+ let resolved_condition = resolve_template(condition, document, base_path, variables)?;
489
+ let parent_path = full_path.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
490
+
491
+ if !document.evaluate_condition(parent_path, &resolved_condition) {
492
+ return Ok(());
493
+ }
494
+ }
495
+
496
+ document.remove(&full_path, &resolved_value)
497
+ }
498
+
499
+ PipelineStep::BlankLines(config) => {
500
+ let full_path = resolve_step_path(base_path, config.path.as_deref());
501
+
502
+ document.enforce_blank_lines(&full_path, config.count)
503
+ }
504
+ }
505
+ }
506
+
507
+ pub fn resolve_template(
508
+ template: &str,
509
+ document: &Document,
510
+ base_path: Option<&str>,
511
+ variables: &HashMap<String, Variable>,
512
+ ) -> Result<String, YerbaError> {
513
+ if !template.contains("${") {
514
+ return Ok(template.to_string());
515
+ }
516
+
517
+ let mut result = String::new();
518
+ let mut rest = template;
519
+
520
+ while let Some(start) = rest.find("${") {
521
+ result.push_str(&rest[..start]);
522
+
523
+ let after_dollar = &rest[start + 2..];
524
+
525
+ let end = after_dollar
526
+ .find('}')
527
+ .ok_or_else(|| YerbaError::ParseError("unclosed ${ in template".to_string()))?;
528
+
529
+ let reference = &after_dollar[..end];
530
+
531
+ let resolved = if let Some(variable) = variables.get(reference) {
532
+ match variable {
533
+ Variable::Single(value) => value.clone(),
534
+ Variable::List(values) => values.join(", "),
535
+ }
536
+ } else {
537
+ let full_path = resolve_step_path(base_path, Some(reference));
538
+
539
+ document
540
+ .get(&full_path)
541
+ .ok_or_else(|| YerbaError::ReferenceNotFound(reference.to_string()))?
542
+ };
543
+
544
+ result.push_str(&resolved);
545
+ rest = &after_dollar[end + 1..];
546
+ }
547
+
548
+ result.push_str(rest);
549
+
550
+ Ok(result)
551
+ }
552
+
553
+ fn resolve_step_path(base_path: Option<&str>, step_path: Option<&str>) -> String {
554
+ let base = base_path.unwrap_or("");
555
+ let step = step_path.unwrap_or("");
556
+
557
+ match (base.is_empty(), step.is_empty()) {
558
+ (true, true) => String::new(),
559
+ (true, false) => step.to_string(),
560
+ (false, true) => base.to_string(),
561
+ (false, false) => format!("{}.{}", base, step),
562
+ }
563
+ }
data/sig/yerba.rbs ADDED
@@ -0,0 +1,3 @@
1
+ module Yerba
2
+ VERSION: String
3
+ end
data/yerba.gemspec ADDED
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require_relative "lib/yerba/version"
5
+ rescue LoadError
6
+ puts "WARNING: Could not load Yerba::VERSION"
7
+ end
8
+
9
+ Gem::Specification.new do |spec|
10
+ spec.name = "yerba"
11
+ spec.version = defined?(Yerba::VERSION) ? Yerba::VERSION : "0.0.0"
12
+ spec.authors = ["Marco Roth"]
13
+ spec.email = ["marco.roth@intergga.ch"]
14
+
15
+ spec.summary = "YAML Editing and Refactoring with Better Accuracy"
16
+ spec.description = "A CLI tool for editing YAML while preserving structure, comments, and format."
17
+ spec.homepage = "https://github.com/marcoroth/yerba"
18
+ spec.license = "MIT"
19
+
20
+ spec.required_ruby_version = ">= 3.2.0"
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.files = Dir[
24
+ "yerba.gemspec",
25
+ "LICENSE.txt",
26
+ "README.md",
27
+ "lib/**/*.rb",
28
+ "sig/**/*.rbs",
29
+ "exe/yerba",
30
+ "ext/yerba/extconf.rb",
31
+ "rust/Cargo.toml",
32
+ "rust/Cargo.lock",
33
+ "rust/src/**/*.rs",
34
+ "rust/rustfmt.toml"
35
+ ]
36
+
37
+ spec.bindir = "exe"
38
+ spec.executables = ["yerba"]
39
+ spec.extensions = ["ext/yerba/extconf.rb"]
40
+
41
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
42
+ spec.metadata["rubygems_mfa_required"] = "true"
43
+
44
+ spec.metadata["homepage_uri"] = "https://github.com/marcoroth/yerba"
45
+ spec.metadata["changelog_uri"] = "https://github.com/marcoroth/yerba/releases"
46
+ spec.metadata["source_code_uri"] = "https://github.com/marcoroth/yerba"
47
+ spec.metadata["bug_tracker_uri"] = "https://github.com/marcoroth/yerba/issues"
48
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yerba
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: aarch64-linux
6
+ authors:
7
+ - Marco Roth
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A CLI tool for editing YAML while preserving structure, comments, and
13
+ format.
14
+ email:
15
+ - marco.roth@intergga.ch
16
+ executables:
17
+ - yerba
18
+ extensions:
19
+ - ext/yerba/extconf.rb
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE.txt
23
+ - README.md
24
+ - exe/aarch64-linux/yerba
25
+ - exe/yerba
26
+ - ext/yerba/extconf.rb
27
+ - lib/yerba.rb
28
+ - lib/yerba/version.rb
29
+ - rust/Cargo.lock
30
+ - rust/Cargo.toml
31
+ - rust/rustfmt.toml
32
+ - rust/src/document.rs
33
+ - rust/src/error.rs
34
+ - rust/src/json.rs
35
+ - rust/src/lib.rs
36
+ - rust/src/main.rs
37
+ - rust/src/quote_style.rs
38
+ - rust/src/syntax.rs
39
+ - rust/src/yerbafile.rs
40
+ - sig/yerba.rbs
41
+ - yerba.gemspec
42
+ homepage: https://github.com/marcoroth/yerba
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ allowed_push_host: https://rubygems.org
47
+ rubygems_mfa_required: 'true'
48
+ homepage_uri: https://github.com/marcoroth/yerba
49
+ changelog_uri: https://github.com/marcoroth/yerba/releases
50
+ source_code_uri: https://github.com/marcoroth/yerba
51
+ bug_tracker_uri: https://github.com/marcoroth/yerba/issues
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 3.2.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 4.0.6
67
+ specification_version: 4
68
+ summary: YAML Editing and Refactoring with Better Accuracy
69
+ test_files: []