yerba 0.3.0-x86_64-linux-gnu → 0.4.1-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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +93 -8
  3. data/exe/x86_64-linux-gnu/yerba +0 -0
  4. data/ext/yerba/extconf.rb +22 -3
  5. data/ext/yerba/include/yerba.h +32 -9
  6. data/ext/yerba/yerba.c +133 -25
  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 +35 -0
  12. data/lib/yerba/document.rb +45 -3
  13. data/lib/yerba/query_result.rb +50 -0
  14. data/lib/yerba/sequence.rb +187 -1
  15. data/lib/yerba/version.rb +1 -1
  16. data/lib/yerba/yerbafile.rb +45 -0
  17. data/lib/yerba.rb +2 -0
  18. data/rust/Cargo.lock +3 -1
  19. data/rust/Cargo.toml +3 -2
  20. data/rust/cbindgen.toml +1 -0
  21. data/rust/rustfmt.toml +1 -1
  22. data/rust/src/commands/apply.rs +11 -2
  23. data/rust/src/commands/blank_lines.rs +1 -4
  24. data/rust/src/commands/check.rs +11 -2
  25. data/rust/src/commands/directives.rs +61 -0
  26. data/rust/src/commands/get.rs +5 -22
  27. data/rust/src/commands/mod.rs +16 -18
  28. data/rust/src/commands/quote_style.rs +12 -6
  29. data/rust/src/commands/selectors.rs +3 -19
  30. data/rust/src/commands/sort.rs +22 -157
  31. data/rust/src/didyoumean.rs +2 -4
  32. data/rust/src/document/condition.rs +139 -0
  33. data/rust/src/document/delete.rs +91 -0
  34. data/rust/src/document/get.rs +262 -0
  35. data/rust/src/document/insert.rs +384 -0
  36. data/rust/src/document/mod.rs +784 -0
  37. data/rust/src/document/set.rs +100 -0
  38. data/rust/src/document/sort.rs +639 -0
  39. data/rust/src/document/style.rs +473 -0
  40. data/rust/src/error.rs +24 -6
  41. data/rust/src/ffi.rs +272 -518
  42. data/rust/src/json.rs +1 -7
  43. data/rust/src/lib.rs +88 -2
  44. data/rust/src/main.rs +2 -0
  45. data/rust/src/quote_style.rs +83 -7
  46. data/rust/src/selector.rs +2 -7
  47. data/rust/src/syntax.rs +41 -21
  48. data/rust/src/yerbafile.rs +86 -19
  49. metadata +13 -3
  50. data/rust/src/document.rs +0 -2304
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6987f6cac6021ba8070b123a736689284d81b7e23e1287b38683350d1b227460
4
- data.tar.gz: 5766c81708ef47e7c4961232bbf7550162299fdd8a050cadf8e753d78c00a66b
3
+ metadata.gz: 3c63edced27fb5d271865615abddb05ee8c028d4205fe01ddf1debfef105b962
4
+ data.tar.gz: 56f52e28f3360c7dcec764e1878b68f90b3d80c4b24ab59e15e664da3c9e848c
5
5
  SHA512:
6
- metadata.gz: 22768d0681ed5510368c4b6251dc7a416bfaf792cb593a945ce94797cdb0cdccb866a22503ec70711e1ecc6632c8b706a71610d2051682a92ad8875b89bdaa4c
7
- data.tar.gz: caa0f0761b9e5eabe6c062991a03cec1d7a052f310de78dbd93cae3adb50f94764368c1c2aa8ccba2dc6aba48b12a08920574561f387ee040750865dd38a72cb
6
+ metadata.gz: 4b5257dcd6171cb8982c2a2839cc79faf0020bd39f740e6d4646e3c293f2b067f7effe497e7385632c35f9a55ac00edfd8eb21f3121a47b413bb350270091f55
7
+ data.tar.gz: 56377b990d5379a88233a2d50f39952662d295f919011f1480ae4137e85f9e1d03c5830784e9a4f088295c5f04a8e003e332c8e946bc0158f198db3594f06991
data/README.md CHANGED
@@ -47,13 +47,15 @@ Use `yerba` as a library in your Rust project:
47
47
 
48
48
  ```toml
49
49
  [dependencies]
50
- yerba = "0.3"
50
+ yerba = "0.4"
51
51
  ```
52
52
 
53
53
  ```rust
54
54
  let mut document = yerba::parse_file("config.yml")?;
55
55
  document.set("database.host", "0.0.0.0")?;
56
- document.save()?;
56
+
57
+ document.save()?; // saves to original path
58
+ document.save_to("output.yml")?; // saves to new path
57
59
  ```
58
60
 
59
61
  ### Ruby Gem
@@ -286,7 +288,7 @@ yerba sort-keys "data/**/videos.yml" "[]" "id,title,speakers"
286
288
 
287
289
  ### `quote-style`
288
290
 
289
- Enforce a consistent quote style across keys and/or values. Available styles are `plain`, `single`, and `double`:
291
+ Enforce a consistent quote style across keys and/or values:
290
292
 
291
293
  ```bash
292
294
  yerba quote-style config.yml --values double
@@ -301,6 +303,36 @@ yerba quote-style config.yml "[].speakers" --values plain
301
303
  yerba quote-style "data/**/*.yml" --keys plain --values double
302
304
  ```
303
305
 
306
+ Use block scalar styles to enforce multiline formatting on specific fields:
307
+
308
+ ```bash
309
+ yerba quote-style videos.yml "[].description" --values literal
310
+ ```
311
+
312
+ **Key styles** (`--keys`):
313
+
314
+ | Style | Symbol | Example |
315
+ |-------|--------|---------|
316
+ | `plain` | — | `host: value` |
317
+ | `single` | `'` | `'host': value` |
318
+ | `double` | `"` | `"host": value` |
319
+
320
+ **Value styles** (`--values`):
321
+
322
+ | Style | Symbol | Example | Behavior |
323
+ |-------|--------|---------|----------|
324
+ | `plain` | — | `host: localhost` | Unquoted |
325
+ | `single` | `'` | `host: 'localhost'` | Single-quoted |
326
+ | `double` | `"` | `host: "localhost"` | Double-quoted, supports `\n` escapes |
327
+ | `literal` | `\|-` | Preserves newlines | Strip trailing newline |
328
+ | `literal-clip` | `\|` | Preserves newlines | Keep one trailing newline |
329
+ | `literal-keep` | `\|+` | Preserves newlines | Keep all trailing newlines |
330
+ | `folded` | `>-` | Folds newlines to spaces | Strip trailing newline |
331
+ | `folded-clip` | `>` | Folds newlines to spaces | Keep one trailing newline |
332
+ | `folded-keep` | `>+` | Folds newlines to spaces | Keep all trailing newlines |
333
+
334
+ Block scalars are only converted when scoped to a specific selector. An unscoped `--values double` will not touch existing block scalars.
335
+
304
336
  ### `blank-lines`
305
337
 
306
338
  Enforce a consistent number of blank lines between sequence entries:
@@ -311,6 +343,16 @@ yerba blank-lines videos.yml "[]" 1
311
343
  yerba blank-lines config.yml "tags" 0
312
344
  ```
313
345
 
346
+ ### `directives`
347
+
348
+ Add or remove the document start marker (`---`):
349
+
350
+ ```bash
351
+ yerba directives config.yml --ensure
352
+ yerba directives config.yml --remove
353
+ yerba directives "data/**/*.yml" --ensure
354
+ ```
355
+
314
356
  ### `selectors`
315
357
 
316
358
  Show all valid selectors for a YAML file. Useful for discovering the structure of a file and knowing which selectors you can use with other commands:
@@ -371,7 +413,10 @@ Use `yerba init` to create one, then `yerba apply` to apply all rules, or `yerba
371
413
  ```bash
372
414
  yerba init
373
415
  yerba apply
416
+ yerba apply path/to/file.yml
417
+
374
418
  yerba check
419
+ yerba check path/to/file.yml
375
420
  ```
376
421
 
377
422
  Each rule specifies a file glob and a list of steps to run in order:
@@ -425,6 +470,7 @@ Available pipeline steps:
425
470
  - `delete` Remove a key (supports conditions)
426
471
  - `rename` Rename a key
427
472
  - `remove` Remove an item from a sequence
473
+ - `directives` Add or remove the document start marker (`---`)
428
474
  - `get` Read a value and store it as a variable for subsequent steps
429
475
 
430
476
  This makes it easy to enforce project-wide YAML conventions in CI:
@@ -514,7 +560,44 @@ tags = document["tags"]
514
560
  tags << "yaml"
515
561
  tags << { name: "Rust", version: "1.80" }
516
562
  tags.remove("obsolete")
517
- tags.sort(by: "name")
563
+ ```
564
+
565
+ ### Sorting
566
+
567
+ Sort sequences in place. Works on both the document and sequence level:
568
+
569
+ ```ruby
570
+ document.sort(by: :name)
571
+ document.sort(by: :name, order: :desc)
572
+ document.sort(by: :name, order: ["Charlie", "Bob", "Alice"])
573
+ document.sort("tags")
574
+ document.sort("tags", order: :desc)
575
+ document.sort("tags", order: ["rust", "ruby", "go"])
576
+ ```
577
+
578
+ The `by:` option accepts symbols, strings, or dot-prefixed strings (`:name`, `"name"`, `".name"`).
579
+
580
+ ### Querying
581
+
582
+ Find and filter items in sequences with `find_by`, `where`, and `pluck`:
583
+
584
+ ```ruby
585
+ document.find_by(name: "Alice")
586
+ document.where(role: "admin")
587
+ document.pluck(:name)
588
+
589
+ document.find_by(speakers: { name: "Alice" })
590
+ document.where(tags: ["ruby"])
591
+ document.find_by("database.host": "localhost")
592
+ ```
593
+
594
+ These methods work on `Document` (delegates to root), `Sequence`, and `Collection` (searches across files):
595
+
596
+ ```ruby
597
+ collection = Yerba.files("data/**/*.yml")
598
+ collection.find_by(name: "Alice")
599
+ collection.where(kind: "talk")
600
+ collection.pluck(:name)
518
601
  ```
519
602
 
520
603
  ### Quote Style Control
@@ -523,7 +606,7 @@ Read and set the quote style on individual scalars:
523
606
 
524
607
  ```ruby
525
608
  scalar = document["database.host"]
526
- scalar.quote_style # => :double
609
+ scalar.quote_style # => :double
527
610
  scalar.quote_style = :single
528
611
  ```
529
612
 
@@ -547,6 +630,10 @@ collection.each do |document|
547
630
  puts document.get("[0].title")
548
631
  end
549
632
 
633
+ collection.find_by(name: "Alice")
634
+ collection.where(kind: "talk")
635
+ collection.pluck(:name)
636
+
550
637
  collection.apply! do |document|
551
638
  document.set("status", "published")
552
639
  end
@@ -572,10 +659,9 @@ After checking out the repo, run `bundle install` to install Ruby dependencies,
572
659
 
573
660
  ### Building from source
574
661
 
575
- The Rust core is in the `rust/` directory:
662
+ The Rust core is in the `rust/` directory, with a workspace `Cargo.toml` at the root so all cargo commands work from the project root:
576
663
 
577
664
  ```bash
578
- cd rust
579
665
  cargo build
580
666
  cargo test
581
667
  ```
@@ -591,7 +677,6 @@ cargo run -- get config.yml "database.host"
591
677
  Or build a release binary:
592
678
 
593
679
  ```bash
594
- cd rust
595
680
  cargo build --release
596
681
  ./target/release/yerba --help
597
682
  ```
Binary file
data/ext/yerba/extconf.rb CHANGED
@@ -44,22 +44,41 @@ if cross_compiling && target_platform.nil?
44
44
  end
45
45
  end
46
46
 
47
+ workspace_target_dir = File.join(root_dir, "target")
48
+ crate_target_dir = File.join(rust_dir, "target")
49
+
47
50
  if target_platform
48
51
  puts "yerba: Cross-compiling Rust for target: #{target_platform}"
49
52
  system("rustup target add #{target_platform}") || warn("yerba: Failed to add Rust target #{target_platform}")
50
53
 
51
54
  cargo_args = "--release --target #{target_platform}"
52
- lib_dir = File.join(rust_dir, "target", target_platform, "release")
55
+ lib_dir = [workspace_target_dir, crate_target_dir]
56
+ .map { |dir| File.join(dir, target_platform, "release") }
57
+ .find { |dir| Dir.exist?(dir) } || File.join(crate_target_dir, target_platform, "release")
53
58
  else
54
59
  puts "yerba: Compiling Rust library for native platform..."
60
+
55
61
  cargo_args = "--release"
56
- lib_dir = File.join(rust_dir, "target", "release")
62
+
63
+ lib_dir = [workspace_target_dir, crate_target_dir]
64
+ .map { |dir| File.join(dir, "release") }
65
+ .find { |dir| Dir.exist?(dir) } || File.join(crate_target_dir, "release")
57
66
  end
58
67
 
59
- unless system("cd #{rust_dir} && cargo build #{cargo_args}")
68
+ unless system("cd #{root_dir} && cargo build #{cargo_args}")
60
69
  abort "ERROR: Failed to compile yerba from Rust source."
61
70
  end
62
71
 
72
+ lib_dir = if target_platform
73
+ [workspace_target_dir, crate_target_dir]
74
+ .map { |dir| File.join(dir, target_platform, "release") }
75
+ .find { |dir| Dir.exist?(dir) } || lib_dir
76
+ else
77
+ [workspace_target_dir, crate_target_dir]
78
+ .map { |dir| File.join(dir, "release") }
79
+ .find { |dir| Dir.exist?(dir) } || lib_dir
80
+ end
81
+
63
82
  if target_platform
64
83
  platform_key = ENV.fetch("RCD_PLATFORM", "")
65
84
  else
@@ -7,12 +7,12 @@
7
7
  #ifndef YERBA_H
8
8
  #define YERBA_H
9
9
 
10
- typedef enum YerbaNodeType {
11
- YERBA_NODE_TYPE_SCALAR = 0,
12
- YERBA_NODE_TYPE_MAP = 1,
13
- YERBA_NODE_TYPE_SEQUENCE = 2,
14
- YERBA_NODE_TYPE_NOT_FOUND = 3,
15
- } YerbaNodeType;
10
+ typedef enum NodeType {
11
+ NODE_TYPE_SCALAR = 0,
12
+ NODE_TYPE_MAP = 1,
13
+ NODE_TYPE_SEQUENCE = 2,
14
+ NODE_TYPE_NOT_FOUND = 3,
15
+ } NodeType;
16
16
 
17
17
  typedef enum YerbaValueType {
18
18
  YERBA_VALUE_TYPE_NULL = 0,
@@ -50,7 +50,7 @@ typedef struct YerbaLocation {
50
50
 
51
51
  typedef struct YerbaGetResult {
52
52
  bool is_list;
53
- enum YerbaNodeType node_type;
53
+ enum NodeType node_type;
54
54
  struct YerbaTypedValue single;
55
55
  struct YerbaTypedList list;
56
56
  struct YerbaLocation location;
@@ -82,11 +82,11 @@ char *yerba_document_get_value(const struct Document *document, const char *path
82
82
  */
83
83
  char *yerba_document_get_values(const struct Document *document, const char *path);
84
84
 
85
- int32_t yerba_document_get_quote_style(const struct Document *document, const char *path);
85
+ char *yerba_document_get_quote_style(const struct Document *document, const char *path);
86
86
 
87
87
  struct YerbaResult yerba_document_set_quote_style(struct Document *document,
88
88
  const char *path,
89
- int32_t style);
89
+ const char *style);
90
90
 
91
91
  bool yerba_document_evaluate_condition(const struct Document *document,
92
92
  const char *parent_path,
@@ -121,6 +121,10 @@ struct YerbaResult yerba_document_insert_object(struct Document *document,
121
121
 
122
122
  struct YerbaResult yerba_document_delete(struct Document *document, const char *path);
123
123
 
124
+ struct YerbaResult yerba_document_insert_objects(struct Document *document,
125
+ const char *path,
126
+ const char *json);
127
+
124
128
  struct YerbaResult yerba_document_remove(struct Document *document,
125
129
  const char *path,
126
130
  const char *value);
@@ -129,6 +133,11 @@ struct YerbaResult yerba_document_remove_at(struct Document *document,
129
133
  const char *path,
130
134
  uintptr_t index);
131
135
 
136
+ struct YerbaResult yerba_document_move_item(struct Document *document,
137
+ const char *path,
138
+ uintptr_t from,
139
+ uintptr_t to);
140
+
132
141
  struct YerbaResult yerba_document_rename(struct Document *document,
133
142
  const char *source,
134
143
  const char *dest);
@@ -138,6 +147,11 @@ struct YerbaResult yerba_document_sort(struct Document *document,
138
147
  const char *by,
139
148
  bool case_sensitive);
140
149
 
150
+ struct YerbaResult yerba_document_reorder(struct Document *document,
151
+ const char *path,
152
+ const char *by,
153
+ const char *order_csv);
154
+
141
155
  struct YerbaResult yerba_document_sort_keys(struct Document *document,
142
156
  const char *path,
143
157
  const char *order);
@@ -151,6 +165,15 @@ struct YerbaResult yerba_document_blank_lines(struct Document *document,
151
165
  const char *path,
152
166
  uintptr_t count);
153
167
 
168
+ /**
169
+ * Caller must free with yerba_string_free.
170
+ */
171
+ char *yerba_yerbafile_find(const char *directory);
172
+
173
+ struct YerbaResult yerba_document_apply_yerbafile(struct Document *document,
174
+ const char *file_path,
175
+ const char *yerbafile_path);
176
+
154
177
  char *yerba_document_to_string(const struct Document *document);
155
178
 
156
179
  void yerba_string_free(char *s);
data/ext/yerba/yerba.c CHANGED
@@ -230,7 +230,7 @@ static VALUE document_bracket(VALUE self, VALUE path) {
230
230
  }
231
231
 
232
232
  switch (result.node_type) {
233
- case YERBA_NODE_TYPE_SCALAR: {
233
+ case NODE_TYPE_SCALAR: {
234
234
  VALUE klass = rb_path2class("Yerba::Scalar");
235
235
  VALUE value = typed_value_to_ruby(result.single);
236
236
  yerba_get_result_free(result);
@@ -240,7 +240,7 @@ static VALUE document_bracket(VALUE self, VALUE path) {
240
240
  return instance;
241
241
  }
242
242
 
243
- case YERBA_NODE_TYPE_MAP: {
243
+ case NODE_TYPE_MAP: {
244
244
  yerba_get_result_free(result);
245
245
  VALUE klass = rb_path2class("Yerba::Map");
246
246
 
@@ -249,7 +249,7 @@ static VALUE document_bracket(VALUE self, VALUE path) {
249
249
  return instance;
250
250
  }
251
251
 
252
- case YERBA_NODE_TYPE_SEQUENCE: {
252
+ case NODE_TYPE_SEQUENCE: {
253
253
  yerba_get_result_free(result);
254
254
  VALUE klass = rb_path2class("Yerba::Sequence");
255
255
 
@@ -298,35 +298,35 @@ static VALUE document_get_values(VALUE self, VALUE path) {
298
298
  return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
299
299
  }
300
300
 
301
- /* document.get_quote_style(path) → :plain, :single, :double, or nil */
301
+ /* document.get_quote_style(path) → :plain, :single, :double, :literal, etc. or nil */
302
302
  static VALUE document_get_quote_style(VALUE self, VALUE path) {
303
303
  struct Document *document = get_document(self);
304
- int style = yerba_document_get_quote_style(document, StringValueCStr(path));
304
+ char *style = yerba_document_get_quote_style(document, StringValueCStr(path));
305
305
 
306
- switch (style) {
307
- case 0: return ID2SYM(rb_intern("plain"));
308
- case 1: return ID2SYM(rb_intern("single"));
309
- case 2: return ID2SYM(rb_intern("double"));
310
- default: return Qnil;
311
- }
306
+ if (style == NULL) return Qnil;
307
+
308
+ VALUE symbol = ID2SYM(rb_intern(style));
309
+
310
+ yerba_string_free(style);
311
+
312
+ return symbol;
312
313
  }
313
314
 
314
315
  /* document.set_quote_style(path, style) */
315
316
  static VALUE document_set_quote_style(VALUE self, VALUE path, VALUE style) {
316
317
  struct Document *document = get_document(self);
317
318
 
318
- int style_int;
319
+ const char *style_string;
320
+
319
321
  if (RB_TYPE_P(style, T_SYMBOL)) {
320
- ID style_id = SYM2ID(style);
321
- if (style_id == rb_intern("plain")) style_int = 0;
322
- else if (style_id == rb_intern("single")) style_int = 1;
323
- else if (style_id == rb_intern("double")) style_int = 2;
324
- else rb_raise(rb_eError, "Invalid quote style (use :plain, :single, or :double)");
322
+ style_string = rb_id2name(SYM2ID(style));
323
+ } else if (RB_TYPE_P(style, T_STRING)) {
324
+ style_string = StringValueCStr(style);
325
325
  } else {
326
- style_int = NUM2INT(style);
326
+ rb_raise(rb_eError, "Invalid quote style (expected Symbol or String)");
327
327
  }
328
328
 
329
- YerbaResult result = yerba_document_set_quote_style(document, StringValueCStr(path), style_int);
329
+ YerbaResult result = yerba_document_set_quote_style(document, StringValueCStr(path), style_string);
330
330
 
331
331
  check_result(result);
332
332
 
@@ -488,6 +488,17 @@ static VALUE document_insert_object(int argc, VALUE *argv, VALUE self) {
488
488
  return self;
489
489
  }
490
490
 
491
+ /* document.insert_objects(path, array) */
492
+ static VALUE document_insert_objects(VALUE self, VALUE path, VALUE array) {
493
+ struct Document *document = get_document(self);
494
+ VALUE json_string = rb_funcall(rb_path2class("JSON"), rb_intern("generate"), 1, array);
495
+
496
+ YerbaResult result = yerba_document_insert_objects(document, StringValueCStr(path), StringValueCStr(json_string));
497
+ check_result(result);
498
+
499
+ return self;
500
+ }
501
+
491
502
  /* document.delete(path, condition: nil) */
492
503
  static VALUE document_delete(int argc, VALUE *argv, VALUE self) {
493
504
  VALUE path, opts;
@@ -533,24 +544,83 @@ static VALUE document_rename(VALUE self, VALUE source, VALUE destination) {
533
544
  return self;
534
545
  }
535
546
 
536
- /* document.sort(path, by: nil, case_sensitive: false) */
547
+ /* document.sort(path = "", by: nil, order: nil, case_sensitive: false) */
537
548
  static VALUE document_sort(int argc, VALUE *argv, VALUE self) {
538
549
  VALUE path, opts;
539
- rb_scan_args(argc, argv, "1:", &path, &opts);
550
+ rb_scan_args(argc, argv, "01:", &path, &opts);
551
+
552
+ if (NIL_P(path)) path = rb_str_new_cstr("");
540
553
 
541
554
  const char *by = NULL;
542
555
  bool case_sensitive = false;
556
+ VALUE v_order = Qnil;
543
557
 
544
558
  if (!NIL_P(opts)) {
545
559
  VALUE v_by = rb_hash_aref(opts, ID2SYM(rb_intern("by")));
560
+ v_order = rb_hash_aref(opts, ID2SYM(rb_intern("order")));
546
561
  VALUE v_case_sensitive = rb_hash_aref(opts, ID2SYM(rb_intern("case_sensitive")));
547
562
 
548
- if (!NIL_P(v_by)) by = StringValueCStr(v_by);
549
- if (RTEST(v_case_sensitive)) case_sensitive = true;
563
+ if (SYMBOL_P(v_by)) {
564
+ VALUE by_string = rb_sym2str(v_by);
565
+ by = StringValueCStr(by_string);
566
+ } else if (!NIL_P(v_by)) {
567
+ by = StringValueCStr(v_by);
568
+ }
569
+
570
+ if (RTEST(v_case_sensitive)) {
571
+ case_sensitive = true;
572
+ }
550
573
  }
551
574
 
552
575
  struct Document *document = get_document(self);
553
- YerbaResult result = yerba_document_sort(document, StringValueCStr(path), by, case_sensitive);
576
+ const char *path_string = StringValueCStr(path);
577
+
578
+ if (RB_TYPE_P(v_order, T_ARRAY)) {
579
+ VALUE order_csv = rb_ary_join(v_order, rb_str_new_cstr(","));
580
+ const char *order_string = StringValueCStr(order_csv);
581
+ const char *reorder_path = StringValueCStr(path);
582
+ const char *reorder_by;
583
+
584
+ if (by) {
585
+ VALUE reorder_by_value = rb_hash_aref(opts, ID2SYM(rb_intern("by")));
586
+
587
+ if (SYMBOL_P(reorder_by_value)) {
588
+ reorder_by_value = rb_sym2str(reorder_by_value);
589
+ }
590
+
591
+ reorder_by = StringValueCStr(reorder_by_value);
592
+ } else {
593
+ reorder_by = ".";
594
+ }
595
+
596
+ YerbaResult result = yerba_document_reorder(document, reorder_path, reorder_by, order_string);
597
+ check_result(result);
598
+
599
+ return self;
600
+ }
601
+
602
+ const char *order = NULL;
603
+
604
+ if (SYMBOL_P(v_order)) {
605
+ VALUE order_string = rb_sym2str(v_order);
606
+ order = StringValueCStr(order_string);
607
+ } else if (!NIL_P(v_order)) {
608
+ order = StringValueCStr(v_order);
609
+ }
610
+
611
+ VALUE by_with_order = Qnil;
612
+
613
+ if (order && strcmp(order, "desc") == 0) {
614
+ if (by) {
615
+ by_with_order = rb_sprintf("%s:desc", by);
616
+ } else {
617
+ by_with_order = rb_str_new_cstr(":desc");
618
+ }
619
+
620
+ by = StringValueCStr(by_with_order);
621
+ }
622
+
623
+ YerbaResult result = yerba_document_sort(document, path_string, by, case_sensitive);
554
624
 
555
625
  check_result(result);
556
626
 
@@ -612,6 +682,23 @@ static VALUE document_blank_lines(VALUE self, VALUE path, VALUE count) {
612
682
  return self;
613
683
  }
614
684
 
685
+ /* document.apply_yerbafile(yerbafile_path = nil) */
686
+ static VALUE document_apply_yerbafile(int argc, VALUE *argv, VALUE self) {
687
+ VALUE yerbafile_path;
688
+ rb_scan_args(argc, argv, "01", &yerbafile_path);
689
+
690
+ struct Document *document = get_document(self);
691
+ VALUE file_path = rb_iv_get(self, "@path");
692
+
693
+ const char *file_path_str = NIL_P(file_path) ? "" : StringValueCStr(file_path);
694
+ const char *yerbafile_path_str = NIL_P(yerbafile_path) ? NULL : StringValueCStr(yerbafile_path);
695
+
696
+ YerbaResult result = yerba_document_apply_yerbafile(document, file_path_str, yerbafile_path_str);
697
+ check_result(result);
698
+
699
+ return self;
700
+ }
701
+
615
702
  /* document.to_s */
616
703
  static VALUE document_to_s(VALUE self) {
617
704
  struct Document *document = get_document(self);
@@ -714,6 +801,22 @@ static VALUE collection_s_find(int argc, VALUE *argv, VALUE self) {
714
801
  return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
715
802
  }
716
803
 
804
+ /* Yerbafile.locate(directory = nil) → path string or nil */
805
+ static VALUE yerbafile_s_locate(int argc, VALUE *argv, VALUE klass) {
806
+ VALUE directory;
807
+ rb_scan_args(argc, argv, "01", &directory);
808
+
809
+ const char *dir = NIL_P(directory) ? NULL : StringValueCStr(directory);
810
+ char *result = yerba_yerbafile_find(dir);
811
+
812
+ if (!result) return Qnil;
813
+
814
+ VALUE path = make_utf8_string(result);
815
+ yerba_string_free(result);
816
+
817
+ return path;
818
+ }
819
+
717
820
  void Init_yerba(void) {
718
821
  rb_require("json");
719
822
 
@@ -744,6 +847,7 @@ void Init_yerba(void) {
744
847
  rb_define_method(rb_cDocument, "set", document_set, -1);
745
848
  rb_define_method(rb_cDocument, "insert", document_insert, -1);
746
849
  rb_define_method(rb_cDocument, "insert_object", document_insert_object, -1);
850
+ rb_define_method(rb_cDocument, "insert_objects", document_insert_objects, 2);
747
851
  rb_define_method(rb_cDocument, "delete", document_delete, -1);
748
852
  rb_define_method(rb_cDocument, "remove", document_remove, 2);
749
853
  rb_define_method(rb_cDocument, "remove_at", document_remove_at, 2);
@@ -752,8 +856,12 @@ void Init_yerba(void) {
752
856
  rb_define_method(rb_cDocument, "sort_keys", document_sort_keys, 2);
753
857
  rb_define_method(rb_cDocument, "quote_style", document_quote_style, -1);
754
858
  rb_define_method(rb_cDocument, "blank_lines", document_blank_lines, 2);
859
+ rb_define_method(rb_cDocument, "apply_yerbafile", document_apply_yerbafile, -1);
755
860
  rb_define_method(rb_cDocument, "to_s", document_to_s, 0);
756
- rb_define_method(rb_cDocument, "save!", document_save, 0);
861
+ rb_define_method(rb_cDocument, "write!", document_save, 0);
757
862
  rb_define_method(rb_cDocument, "changed?", document_changed_p, 0);
758
863
  rb_define_method(rb_cDocument, "path", document_path, 0);
864
+
865
+ VALUE rb_cYerbafile = rb_define_class_under(rb_mYerba, "Yerbafile", rb_cObject);
866
+ rb_define_singleton_method(rb_cYerbafile, "locate", yerbafile_s_locate, -1);
759
867
  }
Binary file
Binary file
Binary file
Binary file
@@ -20,6 +20,41 @@ module Yerba
20
20
  self.class.find(@glob, path, condition: condition, select: select)
21
21
  end
22
22
 
23
+ def find_by(...)
24
+ each do |document|
25
+ next unless document.sequence?
26
+
27
+ result = document.root.find_by(...)
28
+ return result if result
29
+ end
30
+
31
+ nil
32
+ end
33
+
34
+ def where(...)
35
+ results = []
36
+
37
+ each do |document|
38
+ next unless document.sequence?
39
+
40
+ results.concat(document.root.where(...).to_a)
41
+ end
42
+
43
+ results
44
+ end
45
+
46
+ def pluck(...)
47
+ results = []
48
+
49
+ each do |document|
50
+ next unless document.sequence?
51
+
52
+ results.concat(document.root.pluck(...))
53
+ end
54
+
55
+ results
56
+ end
57
+
23
58
  def apply!
24
59
  each do |document|
25
60
  yield document