yerba 0.2.0-arm-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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +528 -0
  4. data/exe/yerba +6 -0
  5. data/ext/yerba/extconf.rb +111 -0
  6. data/ext/yerba/yerba.c +752 -0
  7. data/lib/yerba/3.2/yerba.so +0 -0
  8. data/lib/yerba/3.3/yerba.so +0 -0
  9. data/lib/yerba/3.4/yerba.so +0 -0
  10. data/lib/yerba/4.0/yerba.so +0 -0
  11. data/lib/yerba/collection.rb +31 -0
  12. data/lib/yerba/document.rb +59 -0
  13. data/lib/yerba/formatting.rb +18 -0
  14. data/lib/yerba/location.rb +5 -0
  15. data/lib/yerba/map.rb +166 -0
  16. data/lib/yerba/scalar.rb +85 -0
  17. data/lib/yerba/sequence.rb +182 -0
  18. data/lib/yerba/version.rb +5 -0
  19. data/lib/yerba.rb +131 -0
  20. data/rust/Cargo.lock +805 -0
  21. data/rust/Cargo.toml +36 -0
  22. data/rust/build.rs +11 -0
  23. data/rust/cbindgen.toml +27 -0
  24. data/rust/rustfmt.toml +2 -0
  25. data/rust/src/commands/apply.rs +5 -0
  26. data/rust/src/commands/blank_lines.rs +58 -0
  27. data/rust/src/commands/check.rs +5 -0
  28. data/rust/src/commands/delete.rs +35 -0
  29. data/rust/src/commands/get.rs +194 -0
  30. data/rust/src/commands/init.rs +89 -0
  31. data/rust/src/commands/insert.rs +106 -0
  32. data/rust/src/commands/mate.rs +55 -0
  33. data/rust/src/commands/mod.rs +349 -0
  34. data/rust/src/commands/move_item.rs +54 -0
  35. data/rust/src/commands/move_key.rs +87 -0
  36. data/rust/src/commands/quote_style.rs +62 -0
  37. data/rust/src/commands/remove.rs +35 -0
  38. data/rust/src/commands/rename.rs +37 -0
  39. data/rust/src/commands/set.rs +59 -0
  40. data/rust/src/commands/sort.rs +52 -0
  41. data/rust/src/commands/sort_keys.rs +62 -0
  42. data/rust/src/commands/version.rs +8 -0
  43. data/rust/src/document.rs +2237 -0
  44. data/rust/src/error.rs +45 -0
  45. data/rust/src/ffi.rs +991 -0
  46. data/rust/src/json.rs +128 -0
  47. data/rust/src/lib.rs +29 -0
  48. data/rust/src/main.rs +72 -0
  49. data/rust/src/quote_style.rs +42 -0
  50. data/rust/src/selector.rs +241 -0
  51. data/rust/src/syntax.rs +262 -0
  52. data/rust/src/yaml_writer.rs +89 -0
  53. data/rust/src/yerbafile.rs +475 -0
  54. data/sig/yerba.rbs +3 -0
  55. data/yerba.gemspec +52 -0
  56. metadata +108 -0
@@ -0,0 +1,349 @@
1
+ pub mod apply;
2
+ pub mod blank_lines;
3
+ pub mod check;
4
+ pub mod delete;
5
+ pub mod get;
6
+ pub mod init;
7
+ pub mod insert;
8
+ pub mod mate;
9
+ pub mod move_item;
10
+ pub mod move_key;
11
+ pub mod quote_style;
12
+ pub mod remove;
13
+ pub mod rename;
14
+ pub mod set;
15
+ pub mod sort;
16
+ pub mod sort_keys;
17
+ pub mod version;
18
+
19
+ use std::fs;
20
+ use std::process;
21
+
22
+ use clap::Subcommand;
23
+
24
+ pub(crate) mod color {
25
+ pub const GREEN: &str = "\x1b[32m";
26
+ pub const RED: &str = "\x1b[31m";
27
+ pub const YELLOW: &str = "\x1b[33m";
28
+ pub const DIM: &str = "\x1b[2m";
29
+ pub const BOLD: &str = "\x1b[1m";
30
+ pub const RESET: &str = "\x1b[0m";
31
+ }
32
+
33
+ // Compile-time ANSI macros for use in concat!() / clap attributes (used in main.rs)
34
+ #[allow(unused_macros)]
35
+ macro_rules! h {
36
+ () => {
37
+ "\x1b[1;32m"
38
+ };
39
+ } // header (bold green)
40
+ #[allow(unused_macros)]
41
+ macro_rules! b {
42
+ () => {
43
+ "\x1b[1m"
44
+ };
45
+ } // bold
46
+ #[allow(unused_macros)]
47
+ macro_rules! c {
48
+ () => {
49
+ "\x1b[36m"
50
+ };
51
+ } // cyan
52
+ #[allow(unused_macros)]
53
+ macro_rules! d {
54
+ () => {
55
+ "\x1b[2m"
56
+ };
57
+ } // dim
58
+ #[allow(unused_macros)]
59
+ macro_rules! r {
60
+ () => {
61
+ "\x1b[0m"
62
+ };
63
+ } // reset
64
+ #[allow(unused_imports)]
65
+ pub(crate) use {b, c, d, h, r};
66
+
67
+ pub(crate) fn colorize_examples(input: &str) -> String {
68
+ colorize_help(&format!("Examples:\n{}", input.trim()))
69
+ }
70
+
71
+ pub(crate) fn colorize_help(input: &str) -> String {
72
+ use color::*;
73
+
74
+ let mut output = String::new();
75
+
76
+ for line in input.lines() {
77
+ let trimmed = line.trim();
78
+
79
+ if trimmed.is_empty() {
80
+ output.push('\n');
81
+ continue;
82
+ }
83
+
84
+ if trimmed.ends_with(':') && !trimmed.contains(' ') {
85
+ output.push_str(&format!("{GREEN}{BOLD}{trimmed}{RESET}\n"));
86
+ continue;
87
+ }
88
+
89
+ if trimmed.starts_with("yerba ") {
90
+ let mut parts = trimmed.splitn(3, ' ');
91
+
92
+ match (parts.next(), parts.next(), parts.next()) {
93
+ (Some(cmd), Some(sub), Some(rest)) => {
94
+ output.push_str(&format!(" {BOLD}{cmd}{RESET} \x1b[36m{sub}{RESET} {rest}\n"));
95
+ }
96
+
97
+ (Some(cmd), Some(sub), None) => {
98
+ output.push_str(&format!(" {BOLD}{cmd}{RESET} \x1b[36m{sub}{RESET}\n"));
99
+ }
100
+
101
+ _ => {
102
+ output.push_str(&format!(" {trimmed}\n"));
103
+ }
104
+ }
105
+
106
+ continue;
107
+ }
108
+
109
+ let mut columns: Vec<&str> = Vec::new();
110
+ let mut rest = trimmed;
111
+
112
+ while !rest.is_empty() {
113
+ let column_end = rest.find(" ").unwrap_or(rest.len());
114
+ let column = rest[..column_end].trim();
115
+
116
+ if !column.is_empty() {
117
+ columns.push(column);
118
+ }
119
+
120
+ if column_end >= rest.len() {
121
+ break;
122
+ }
123
+
124
+ rest = rest[column_end..].trim_start();
125
+ }
126
+
127
+ if columns.len() == 3 {
128
+ output.push_str(&format!(
129
+ " \x1b[36m{:<20}{RESET} {:<21} {DIM}{}{RESET}\n",
130
+ columns[0], columns[1], columns[2]
131
+ ));
132
+ } else if columns.len() == 2 {
133
+ output.push_str(&format!(" \x1b[36m{:<20}{RESET} {}\n", columns[0], columns[1]));
134
+ } else {
135
+ output.push_str(&format!(" {trimmed}\n"));
136
+ }
137
+ }
138
+
139
+ output.trim_end().to_string()
140
+ }
141
+
142
+ #[derive(Subcommand)]
143
+ pub enum Command {
144
+ Get(get::Args),
145
+ Set(set::Args),
146
+ Insert(insert::Args),
147
+ Rename(rename::Args),
148
+ Delete(delete::Args),
149
+ Remove(remove::Args),
150
+ Move(move_item::Args),
151
+ MoveKey(move_key::Args),
152
+ SortKeys(sort_keys::Args),
153
+ Sort(sort::Args),
154
+ QuoteStyle(quote_style::Args),
155
+ BlankLines(blank_lines::Args),
156
+ #[command(about = "Create a new Yerbafile in the current directory")]
157
+ Init,
158
+ #[command(about = "Apply all rules from the Yerbafile and write changes")]
159
+ Apply,
160
+ #[command(about = "Check if all files match Yerbafile rules (exits 1 if not)")]
161
+ Check,
162
+ #[command(about = "Print the yerba version")]
163
+ Version,
164
+ #[command(about = "\u{1f9c9}")]
165
+ Mate,
166
+ }
167
+
168
+ impl Command {
169
+ pub fn run(self) {
170
+ match self {
171
+ Command::Get(args) => args.run(),
172
+ Command::Set(args) => args.run(),
173
+ Command::Insert(args) => args.run(),
174
+ Command::Rename(args) => args.run(),
175
+ Command::Delete(args) => args.run(),
176
+ Command::Remove(args) => args.run(),
177
+ Command::Move(args) => args.run(),
178
+ Command::MoveKey(args) => args.run(),
179
+ Command::SortKeys(args) => args.run(),
180
+ Command::Sort(args) => args.run(),
181
+ Command::QuoteStyle(args) => args.run(),
182
+ Command::BlankLines(args) => args.run(),
183
+ Command::Init => init::run(),
184
+ Command::Apply => apply::run(),
185
+ Command::Check => check::run(),
186
+ Command::Version => version::run(),
187
+ Command::Mate => mate::run(),
188
+ }
189
+ }
190
+ }
191
+
192
+ pub(crate) fn run_yerbafile(write: bool) {
193
+ use color::*;
194
+
195
+ let yerbafile_path = yerba::Yerbafile::find().unwrap_or_else(|| {
196
+ eprintln!("{RED}No Yerbafile found.{RESET} Run {BOLD}yerba init{RESET} to create one.");
197
+ process::exit(1);
198
+ });
199
+
200
+ let yerbafile = yerba::Yerbafile::load(&yerbafile_path).unwrap_or_else(|error| {
201
+ eprintln!("{RED}Error loading {}:{RESET} {}", yerbafile_path.display(), error);
202
+ process::exit(1);
203
+ });
204
+
205
+ eprintln!("🧉 {BOLD}Using{RESET} {}", yerbafile_path.display());
206
+
207
+ let results = yerbafile.apply(write);
208
+ let mut has_changes = false;
209
+ let mut has_errors = false;
210
+
211
+ for result in &results {
212
+ if let Some(error) = &result.error {
213
+ eprintln!(" {RED}error:{RESET} {} {DIM}—{RESET} {}", result.file, error);
214
+ has_errors = true;
215
+ } else if result.changed {
216
+ if write {
217
+ eprintln!(" {GREEN}updated:{RESET} {}", result.file);
218
+ } else {
219
+ eprintln!(" {YELLOW}would change:{RESET} {}", result.file);
220
+ }
221
+
222
+ has_changes = true;
223
+ }
224
+ }
225
+
226
+ if !has_changes && !has_errors {
227
+ eprintln!(" {GREEN}All files match the rules.{RESET}");
228
+ }
229
+
230
+ if !write && has_changes {
231
+ process::exit(1);
232
+ }
233
+
234
+ if has_errors {
235
+ process::exit(1);
236
+ }
237
+ }
238
+
239
+ pub(crate) fn resolve_move_indexes(
240
+ document: &yerba::Document,
241
+ path: &str,
242
+ item: &str,
243
+ before: Option<String>,
244
+ after: Option<String>,
245
+ to: Option<usize>,
246
+ resolve: impl Fn(&yerba::Document, &str, &str) -> Result<usize, yerba::YerbaError>,
247
+ ) -> (usize, usize) {
248
+ use color::*;
249
+
250
+ let from_index = resolve(document, path, item).unwrap_or_else(|error| {
251
+ eprintln!("{RED}Error:{RESET} {}", error);
252
+ process::exit(1);
253
+ });
254
+
255
+ let to_index = if let Some(index) = to {
256
+ index
257
+ } else if let Some(target) = &before {
258
+ let target_index = resolve(document, path, target).unwrap_or_else(|error| {
259
+ eprintln!("{RED}Error:{RESET} {}", error);
260
+ process::exit(1);
261
+ });
262
+
263
+ if from_index < target_index {
264
+ target_index - 1
265
+ } else {
266
+ target_index
267
+ }
268
+ } else if let Some(target) = &after {
269
+ let target_index = resolve(document, path, target).unwrap_or_else(|error| {
270
+ eprintln!("{RED}Error:{RESET} {}", error);
271
+ process::exit(1);
272
+ });
273
+
274
+ if from_index <= target_index {
275
+ target_index
276
+ } else {
277
+ target_index + 1
278
+ }
279
+ } else {
280
+ eprintln!("{RED}Error:{RESET} specify --before, --after, or --to");
281
+ process::exit(1);
282
+ };
283
+
284
+ (from_index, to_index)
285
+ }
286
+
287
+ pub(crate) fn resolve_files(pattern: &str) -> Vec<String> {
288
+ use color::*;
289
+
290
+ if pattern.contains('*') || pattern.contains('?') || pattern.contains('[') {
291
+ let paths: Vec<String> = glob::glob(pattern)
292
+ .unwrap_or_else(|error| {
293
+ eprintln!("{RED}Error:{RESET} invalid glob pattern '{}': {}", pattern, error);
294
+ process::exit(1);
295
+ })
296
+ .filter_map(|entry| entry.ok())
297
+ .map(|path| path.to_string_lossy().to_string())
298
+ .collect();
299
+
300
+ if paths.is_empty() {
301
+ eprintln!("{RED}Error:{RESET} no files matched pattern: {}", pattern);
302
+ process::exit(1);
303
+ }
304
+
305
+ paths
306
+ } else {
307
+ vec![pattern.to_string()]
308
+ }
309
+ }
310
+
311
+ pub(crate) fn parse_file(file: &str) -> yerba::Document {
312
+ use color::*;
313
+
314
+ yerba::parse_file(file).unwrap_or_else(|error| {
315
+ match &error {
316
+ yerba::YerbaError::IoError(io_error) => match io_error.kind() {
317
+ std::io::ErrorKind::NotFound => eprintln!("{RED}Error:{RESET} file not found: {}", file),
318
+ std::io::ErrorKind::PermissionDenied => {
319
+ eprintln!("{RED}Error:{RESET} permission denied: {}", file)
320
+ }
321
+ _ => eprintln!("{RED}Error:{RESET} reading {}: {}", file, io_error),
322
+ },
323
+ _ => eprintln!("{RED}Error:{RESET} parsing {}: {}", file, error),
324
+ }
325
+
326
+ process::exit(1);
327
+ })
328
+ }
329
+
330
+ pub(crate) fn run_op(operation: impl FnOnce() -> Result<(), yerba::YerbaError>) {
331
+ use color::*;
332
+
333
+ operation().unwrap_or_else(|error| {
334
+ eprintln!("{RED}Error:{RESET} {}", error);
335
+ process::exit(1);
336
+ });
337
+ }
338
+
339
+ pub(crate) fn output(file: &str, document: &yerba::Document, dry_run: bool) {
340
+ if dry_run {
341
+ println!("--- {}", file);
342
+ print!("{}", document);
343
+ } else {
344
+ fs::write(file, document.to_string()).unwrap_or_else(|error| {
345
+ eprintln!("Error writing {}: {}", file, error);
346
+ process::exit(1);
347
+ });
348
+ }
349
+ }
@@ -0,0 +1,54 @@
1
+ use std::sync::LazyLock;
2
+
3
+ use indoc::indoc;
4
+
5
+ use super::colorize_examples;
6
+ use super::{output, parse_file, resolve_move_indexes, run_op};
7
+
8
+ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
9
+ colorize_examples(indoc! {r#"
10
+ yerba move config.yml "tags" "rust" --before "ruby"
11
+ yerba move config.yml "tags" "rust" --after "yaml"
12
+ yerba move config.yml "tags" 2 --to 0
13
+ yerba move videos.yml "" ".id == talk-2" --after ".id == talk-1"
14
+ "#})
15
+ });
16
+
17
+ #[derive(clap::Args)]
18
+ #[command(
19
+ about = "Move a sequence item to a new position",
20
+ arg_required_else_help = true,
21
+ after_help = EXAMPLES.as_str()
22
+ )]
23
+ pub struct Args {
24
+ file: String,
25
+ selector: String,
26
+ #[arg(help = "Item to move: name, index, or condition (e.g. \".id == talk-1\")")]
27
+ item: String,
28
+ #[arg(long, help = "Move before this item or condition (e.g. \".id == talk-2\")")]
29
+ before: Option<String>,
30
+ #[arg(long, help = "Move after this item or condition (e.g. \".id == talk-2\")")]
31
+ after: Option<String>,
32
+ #[arg(long)]
33
+ to: Option<usize>,
34
+ #[arg(long)]
35
+ dry_run: bool,
36
+ }
37
+
38
+ impl Args {
39
+ pub fn run(self) {
40
+ let mut document = parse_file(&self.file);
41
+ let (from_index, to_index) = resolve_move_indexes(
42
+ &document,
43
+ &self.selector,
44
+ &self.item,
45
+ self.before,
46
+ self.after,
47
+ self.to,
48
+ |document, path, reference| document.resolve_sequence_index(path, reference),
49
+ );
50
+
51
+ run_op(|| document.move_item(&self.selector, from_index, to_index));
52
+ output(&self.file, &document, self.dry_run);
53
+ }
54
+ }
@@ -0,0 +1,87 @@
1
+ use std::process;
2
+ use std::sync::LazyLock;
3
+
4
+ use indoc::indoc;
5
+
6
+ use super::colorize_examples;
7
+ use super::{output, parse_file, resolve_move_indexes, run_op};
8
+
9
+ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
10
+ colorize_examples(indoc! {r#"
11
+ yerba move-key config.yml "database.name" --to 0
12
+ yerba move-key config.yml "database.pool" --before "database.host"
13
+ yerba move-key config.yml "database.pool" --after "database.name"
14
+ "#})
15
+ });
16
+
17
+ #[derive(clap::Args)]
18
+ #[command(
19
+ about = "Move a key to a new position within a map",
20
+ arg_required_else_help = true,
21
+ after_help = EXAMPLES.as_str()
22
+ )]
23
+ pub struct Args {
24
+ file: String,
25
+ selector: String,
26
+ #[arg(long, help = "Move before this key")]
27
+ before: Option<String>,
28
+ #[arg(long, help = "Move after this key")]
29
+ after: Option<String>,
30
+ #[arg(long)]
31
+ to: Option<usize>,
32
+ #[arg(long)]
33
+ dry_run: bool,
34
+ }
35
+
36
+ impl Args {
37
+ pub fn run(self) {
38
+ let (parent_path, key) = self.selector.rsplit_once('.').unwrap_or(("", &self.selector));
39
+
40
+ let mut document = parse_file(&self.file);
41
+
42
+ let before_key = self.before.map(|target| {
43
+ let (target_parent, target_key) = target.rsplit_once('.').unwrap_or(("", &target));
44
+
45
+ if target_parent != parent_path {
46
+ use super::color::*;
47
+ eprintln!(
48
+ "{RED}Error:{RESET} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different path.",
49
+ self.selector, target
50
+ );
51
+
52
+ process::exit(1);
53
+ }
54
+
55
+ target_key.to_string()
56
+ });
57
+
58
+ let after_key = self.after.map(|target| {
59
+ let (target_parent, target_key) = target.rsplit_once('.').unwrap_or(("", &target));
60
+
61
+ if target_parent != parent_path {
62
+ use super::color::*;
63
+ eprintln!(
64
+ "{RED}Error:{RESET} cannot move key across different maps ({} \u{2192} {})\n\n Use 'yerba rename' to relocate keys to a different path.",
65
+ self.selector, target
66
+ );
67
+
68
+ process::exit(1);
69
+ }
70
+
71
+ target_key.to_string()
72
+ });
73
+
74
+ let (from_index, to_index) = resolve_move_indexes(
75
+ &document,
76
+ parent_path,
77
+ key,
78
+ before_key,
79
+ after_key,
80
+ self.to,
81
+ |document, parent_path, reference| document.resolve_key_index(parent_path, reference),
82
+ );
83
+
84
+ run_op(|| document.move_key(parent_path, from_index, to_index));
85
+ output(&self.file, &document, self.dry_run);
86
+ }
87
+ }
@@ -0,0 +1,62 @@
1
+ use std::sync::LazyLock;
2
+
3
+ use indoc::indoc;
4
+
5
+ use super::colorize_examples;
6
+ use super::{output, parse_file, resolve_files};
7
+
8
+ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
9
+ colorize_examples(indoc! {r#"
10
+ yerba quote-style config.yml --values double
11
+ yerba quote-style config.yml --keys plain
12
+ yerba quote-style config.yml --keys plain --values double
13
+ yerba quote-style config.yml "[].speakers" --values plain
14
+ yerba quote-style "data/**/*.yml" --keys plain --values double
15
+ "#})
16
+ });
17
+
18
+ #[derive(clap::Args)]
19
+ #[command(
20
+ about = "Enforce a consistent quote style on keys and/or values",
21
+ arg_required_else_help = true,
22
+ after_help = EXAMPLES.as_str()
23
+ )]
24
+ pub struct Args {
25
+ file: String,
26
+ /// Selector to scope the operation (optional — omit for whole file)
27
+ selector: Option<String>,
28
+ /// Quote style for values (plain, single, double, literal, folded)
29
+ #[arg(long)]
30
+ values: Option<yerba::QuoteStyle>,
31
+ /// Quote style for keys (plain, single, double)
32
+ #[arg(long)]
33
+ keys: Option<yerba::QuoteStyle>,
34
+ #[arg(long)]
35
+ dry_run: bool,
36
+ }
37
+
38
+ impl Args {
39
+ pub fn run(self) {
40
+ if self.values.is_none() && self.keys.is_none() {
41
+ use super::color::*;
42
+ eprintln!("{RED}Error:{RESET} specify --values, --keys, or both");
43
+ std::process::exit(1);
44
+ }
45
+
46
+ let selector = self.selector.as_deref().filter(|s| !s.is_empty());
47
+
48
+ for resolved_file in resolve_files(&self.file) {
49
+ let mut document = parse_file(&resolved_file);
50
+
51
+ if let Some(style) = &self.keys {
52
+ let _ = document.enforce_key_style(style, selector);
53
+ }
54
+
55
+ if let Some(style) = &self.values {
56
+ let _ = document.enforce_quotes_at(style, selector);
57
+ }
58
+
59
+ output(&resolved_file, &document, self.dry_run);
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,35 @@
1
+ use std::sync::LazyLock;
2
+
3
+ use indoc::indoc;
4
+
5
+ use super::colorize_examples;
6
+ use super::{output, parse_file, run_op};
7
+
8
+ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
9
+ colorize_examples(indoc! {r#"
10
+ yerba remove config.yml "tags" "rust"
11
+ yerba remove videos.yml "[0].speakers" "Alice"
12
+ "#})
13
+ });
14
+
15
+ #[derive(clap::Args)]
16
+ #[command(
17
+ about = "Remove an item from a sequence by its value",
18
+ arg_required_else_help = true,
19
+ after_help = EXAMPLES.as_str()
20
+ )]
21
+ pub struct Args {
22
+ file: String,
23
+ selector: String,
24
+ value: String,
25
+ #[arg(long)]
26
+ dry_run: bool,
27
+ }
28
+
29
+ impl Args {
30
+ pub fn run(self) {
31
+ let mut document = parse_file(&self.file);
32
+ run_op(|| document.remove(&self.selector, &self.value));
33
+ output(&self.file, &document, self.dry_run);
34
+ }
35
+ }
@@ -0,0 +1,37 @@
1
+ use std::sync::LazyLock;
2
+
3
+ use indoc::indoc;
4
+
5
+ use super::colorize_examples;
6
+ use super::{output, parse_file, run_op};
7
+
8
+ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
9
+ colorize_examples(indoc! {r#"
10
+ yerba rename config.yml "database.host" "database.hostname"
11
+ yerba rename config.yml "database.host" "hostname"
12
+ yerba rename config.yml "database.host" "settings.db_host"
13
+ yerba rename videos.yml "[0].old_name" "[0].name"
14
+ "#})
15
+ });
16
+
17
+ #[derive(clap::Args)]
18
+ #[command(
19
+ about = "Rename a key in a map (preserves value and position)",
20
+ arg_required_else_help = true,
21
+ after_help = EXAMPLES.as_str()
22
+ )]
23
+ pub struct Args {
24
+ file: String,
25
+ source: String,
26
+ destination: String,
27
+ #[arg(long)]
28
+ dry_run: bool,
29
+ }
30
+
31
+ impl Args {
32
+ pub fn run(self) {
33
+ let mut document = parse_file(&self.file);
34
+ run_op(|| document.rename(&self.source, &self.destination));
35
+ output(&self.file, &document, self.dry_run);
36
+ }
37
+ }
@@ -0,0 +1,59 @@
1
+ use std::sync::LazyLock;
2
+
3
+ use indoc::indoc;
4
+
5
+ use super::colorize_examples;
6
+ use super::{output, parse_file, run_op};
7
+
8
+ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
9
+ colorize_examples(indoc! {r#"
10
+ yerba set config.yml "database.host" "0.0.0.0"
11
+ yerba set config.yml "database.host" "0.0.0.0" --if-exists
12
+ yerba set config.yml "database.host" "0.0.0.0" --condition ".port == 5432"
13
+ yerba set videos.yml "[0].title" "New Title"
14
+ yerba set "data/**/event.yml" "website" "" --if-exists
15
+ "#})
16
+ });
17
+
18
+ #[derive(clap::Args)]
19
+ #[command(
20
+ about = "Update an existing value at a path (preserves quote style)",
21
+ arg_required_else_help = true,
22
+ after_help = EXAMPLES.as_str()
23
+ )]
24
+ pub struct Args {
25
+ file: String,
26
+ selector: String,
27
+ value: String,
28
+ #[arg(long)]
29
+ if_exists: bool,
30
+ #[arg(long)]
31
+ if_missing: bool,
32
+ #[arg(long)]
33
+ condition: Option<String>,
34
+ #[arg(long)]
35
+ dry_run: bool,
36
+ }
37
+
38
+ impl Args {
39
+ pub fn run(self) {
40
+ let mut document = parse_file(&self.file);
41
+ let parent_path = self.selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");
42
+
43
+ let should_set = if self.if_exists {
44
+ document.exists(&self.selector)
45
+ } else if self.if_missing {
46
+ !document.exists(&self.selector)
47
+ } else if let Some(condition) = &self.condition {
48
+ document.evaluate_condition(parent_path, condition)
49
+ } else {
50
+ true
51
+ };
52
+
53
+ if should_set {
54
+ run_op(|| document.set(&self.selector, &self.value));
55
+ }
56
+
57
+ output(&self.file, &document, self.dry_run);
58
+ }
59
+ }