yerba 0.4.2-arm64-darwin → 0.5.0-arm64-darwin

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +241 -53
  3. data/exe/arm64-darwin/yerba +0 -0
  4. data/ext/yerba/include/yerba.h +13 -1
  5. data/ext/yerba/yerba.c +239 -113
  6. data/lib/yerba/3.2/yerba.bundle +0 -0
  7. data/lib/yerba/3.3/yerba.bundle +0 -0
  8. data/lib/yerba/3.4/yerba.bundle +0 -0
  9. data/lib/yerba/4.0/yerba.bundle +0 -0
  10. data/lib/yerba/document.rb +54 -18
  11. data/lib/yerba/map.rb +55 -43
  12. data/lib/yerba/node.rb +58 -0
  13. data/lib/yerba/scalar.rb +20 -23
  14. data/lib/yerba/sequence.rb +88 -55
  15. data/lib/yerba/version.rb +1 -1
  16. data/lib/yerba.rb +2 -0
  17. data/rust/Cargo.lock +1110 -25
  18. data/rust/Cargo.toml +2 -1
  19. data/rust/src/commands/delete.rs +1 -1
  20. data/rust/src/commands/get.rs +47 -12
  21. data/rust/src/commands/insert.rs +1 -1
  22. data/rust/src/commands/location.rs +56 -0
  23. data/rust/src/commands/mod.rs +33 -5
  24. data/rust/src/commands/remove.rs +1 -1
  25. data/rust/src/commands/rename.rs +1 -1
  26. data/rust/src/commands/schema.rs +84 -0
  27. data/rust/src/commands/set.rs +1 -1
  28. data/rust/src/commands/unique.rs +80 -0
  29. data/rust/src/document/condition.rs +17 -1
  30. data/rust/src/document/get.rs +254 -23
  31. data/rust/src/document/mod.rs +90 -12
  32. data/rust/src/document/schema.rs +73 -0
  33. data/rust/src/document/set.rs +1 -1
  34. data/rust/src/document/sort.rs +19 -13
  35. data/rust/src/document/style.rs +3 -3
  36. data/rust/src/document/unique.rs +86 -0
  37. data/rust/src/error.rs +78 -0
  38. data/rust/src/ffi.rs +89 -9
  39. data/rust/src/lib.rs +5 -10
  40. data/rust/src/main.rs +2 -0
  41. data/rust/src/schema.rs +93 -0
  42. data/rust/src/syntax.rs +91 -31
  43. data/rust/src/yerbafile.rs +107 -18
  44. metadata +9 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f8cb33d0155704a579fff462e53265bdbfc406516c12e6f63a9108559f46e75
4
- data.tar.gz: c967042ee83b8da7b62933b5ec6f2b8308a5ed26234105daddf2172945e7e438
3
+ metadata.gz: 8bd1db1a4b741b9e303af8f7d649298d9db34d93f60d7e56993494e020d55565
4
+ data.tar.gz: 3b627ab3bbd1ab2a60a46b719a2ec33400b3ef1e213c8bae7f3f9a7355b6147b
5
5
  SHA512:
6
- metadata.gz: 39013e6138e7f21a67125c2b08864547faf85266fbb39e2bbf8292ca1e32c3fce5f6c2562a229929949d49df5ec215c46674ad09924eee5c93cedeebfecc508b
7
- data.tar.gz: 3c1ff0c3089a246f14af2eb6f23afabda9fff279b9d046275373621bf02086c123f04b4ec12bafccad65f5022c2db72ce182ebe612154250efa1cba4a08dfef4
6
+ metadata.gz: dc785c52e0510ceae9747b84630ea9b8436317a81942e8c65484a0c189516747a75fdb07a44a7591740a7a568f199184e1704c4ef2b303f7d88c7d492fefa734
7
+ data.tar.gz: 13b70ee5dd1416ba67334ba487b5126df6cbb5609f37230d5671e20f80f66bdea024c6a49c0d4999575d35e1bc56c0c0ffe69e662963400aa22a699e264f68f0
data/README.md CHANGED
@@ -90,24 +90,24 @@ Selectors use dot-notation for nested keys, brackets for array access, and suppo
90
90
 
91
91
  Selectors let you address any node in a YAML document:
92
92
 
93
- | Pattern | Meaning | Example |
94
- |---------|---------|---------|
95
- | `key` | A single key | `"database.host"` |
96
- | `key.nested` | Nested key path | `"database.settings.pool"` |
97
- | `[]` | All items in array | `"[].title"` |
98
- | `[N]` | Item at index | `"[0].title"` |
99
- | `[].key[].nested` | Nested array access | `"[].speakers[].name"` |
93
+ | Pattern | Meaning | Example |
94
+ |-------------------|---------------------|----------------------------|
95
+ | `key` | A single key | `"database.host"` |
96
+ | `key.nested` | Nested key path | `"database.settings.pool"` |
97
+ | `[]` | All items in array | `"[].title"` |
98
+ | `[N]` | Item at index | `"[0].title"` |
99
+ | `[].key[].nested` | Nested array access | `"[].speakers[].name"` |
100
100
 
101
101
  ### Conditions
102
102
 
103
103
  Conditions filter which items a command operates on:
104
104
 
105
- | Syntax | Meaning | Example |
106
- |--------|---------|---------|
107
- | `.key == value` | Equality | `".kind == keynote"` |
108
- | `.key != value` | Inequality | `".status != draft"` |
109
- | `.key contains val` | Substring or member | `".title contains Ruby"` |
110
- | `.key not_contains val` | Negated contains | `".title not_contains test"` |
105
+ | Syntax | Meaning | Example |
106
+ |-------------------------|---------------------|------------------------------|
107
+ | `.key == value` | Equality | `".kind == keynote"` |
108
+ | `.key != value` | Inequality | `".status != draft"` |
109
+ | `.key contains val` | Substring or member | `".title contains Ruby"` |
110
+ | `.key not_contains val` | Negated contains | `".title not_contains test"` |
111
111
 
112
112
  ---
113
113
 
@@ -311,25 +311,25 @@ yerba quote-style videos.yml "[].description" --values literal
311
311
 
312
312
  **Key styles** (`--keys`):
313
313
 
314
- | Style | Symbol | Example |
315
- |-------|--------|---------|
316
- | `plain` | — | `host: value` |
317
- | `single` | `'` | `'host': value` |
318
- | `double` | `"` | `"host": value` |
314
+ | Style | Symbol | Example |
315
+ |----------|--------|-----------------|
316
+ | `plain` | — | `host: value` |
317
+ | `single` | `'` | `'host': value` |
318
+ | `double` | `"` | `"host": value` |
319
319
 
320
320
  **Value styles** (`--values`):
321
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 |
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
333
 
334
334
  Block scalars are only converted when scoped to a specific selector. An unscoped `--values double` will not touch existing block scalars.
335
335
 
@@ -353,6 +353,63 @@ yerba directives config.yml --remove
353
353
  yerba directives "data/**/*.yml" --ensure
354
354
  ```
355
355
 
356
+ ### `unique`
357
+
358
+ Find or remove duplicate items in a sequence. Use `--by` to specify which field determines uniqueness:
359
+
360
+ ```bash
361
+ yerba unique videos.yml --by ".id"
362
+ yerba unique speakers.yml --by ".name"
363
+ yerba unique config.yml "tags" --by "."
364
+ ```
365
+
366
+ By default, duplicates are reported but not removed. Use `--remove` to remove them (keeps the first occurrence):
367
+
368
+ ```bash
369
+ yerba unique videos.yml --by ".id" --remove
370
+ yerba unique speakers.yml --by ".name" --remove --dry-run
371
+ ```
372
+
373
+ ### `location`
374
+
375
+ Show the location (line, column, byte offset) of a selector in a YAML file:
376
+
377
+ ```bash
378
+ yerba location config.yml "database.host"
379
+ yerba location videos.yml "[0].title"
380
+ yerba location videos.yml "[0]"
381
+ ```
382
+
383
+ Output:
384
+ ```json
385
+ {
386
+ "selector": "[0].title",
387
+ "file": "videos.yml",
388
+ "start_line": 2,
389
+ "start_column": 9,
390
+ "end_line": 2,
391
+ "end_column": 19,
392
+ "start_offset": 22,
393
+ "end_offset": 32
394
+ }
395
+ ```
396
+
397
+ ### `schema`
398
+
399
+ Validate YAML files against a JSON schema:
400
+
401
+ ```bash
402
+ yerba schema data/speakers.yml --schema lib/schemas/speaker_schema.json
403
+ yerba schema "data/**/videos.yml" --schema lib/schemas/video_schema.json
404
+ ```
405
+
406
+ Use `--path` to scope validation to a specific selector (e.g. validate each item in an array):
407
+
408
+ ```bash
409
+ yerba schema data/speakers.yml --schema speaker_schema.json --selector "[]"
410
+ yerba schema data/sponsors.yml --schema tier_schema.json --selector "tiers[]"
411
+ ```
412
+
356
413
  ### `selectors`
357
414
 
358
415
  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:
@@ -471,6 +528,8 @@ Available pipeline steps:
471
528
  - `rename` Rename a key
472
529
  - `remove` Remove an item from a sequence
473
530
  - `directives` Add or remove the document start marker (`---`)
531
+ - `unique` Find or remove duplicate items in a sequence
532
+ - `schema` Validate against a JSON schema (with optional `path` for scoping)
474
533
  - `get` Read a value and store it as a variable for subsequent steps
475
534
 
476
535
  This makes it easy to enforce project-wide YAML conventions in CI:
@@ -491,47 +550,74 @@ Create a document from a file path or from a string:
491
550
  require "yerba"
492
551
 
493
552
  document = Yerba.parse_file("config.yml")
494
- document = Yerba.parse("database:\n host: localhost\n port: 5432\n")
553
+
554
+ document = Yerba.parse(<<~YAML)
555
+ database:
556
+ host: localhost
557
+ port: 5432
558
+ YAML
495
559
  ```
496
560
 
497
561
  ### Reading Values
498
562
 
499
- Use `get` to retrieve the raw value at a path. Values are returned with their YAML types, strings, integers, booleans, and nil are all mapped to their Ruby equivalents:
563
+ Use bracket notation (`[]`) to navigate the document. Returns typed node objects (`Scalar`, `Map`, or `Sequence`) that are live references mutations flow back to the document.
564
+
565
+ All access methods (`[]`, `fetch`, `dig`, `value_at`) accept full selector strings like `"database.host"`, `"[0].title"`, or `"[].speakers[].name"`. In the examples below we prefer the more idiomatic chained bracket style, but the two forms are equivalent:
500
566
 
501
567
  ```ruby
502
- document.get("database.host") # => "localhost"
503
- document.get("database.port") # => 5432
504
- document.get("database.ssl") # => false
568
+ document["database"]["host"].value # => "localhost"
569
+ document["database.host"].value # => "localhost" (same thing)
505
570
  ```
506
571
 
507
- ### Structured Navigation
572
+ The returned object type depends on what's at the path:
508
573
 
509
- Use bracket notation to get typed wrapper objects (`Scalar`, `Map`, or `Sequence`) representing nodes in the document. These are live references, mutations flow back to the document.
574
+ ```ruby
575
+ document["database"] # => Yerba::Map
576
+ document["database"]["host"] # => Yerba::Scalar
577
+ document["tags"] # => Yerba::Sequence
578
+ ```
510
579
 
511
- You can use a full dot-path in a single bracket call, or chain brackets to navigate one level at a time:
580
+ Scalars expose their value and quote style:
512
581
 
513
582
  ```ruby
514
- document["database.host"].value # => "localhost"
515
- document["database"]["host"].value # => "localhost"
516
- document["database"]["port"].value # => 5432
583
+ scalar = document["database"]["host"]
584
+ scalar.value # => "localhost"
585
+ scalar.quote_style # => :double
517
586
  ```
518
587
 
519
- The returned object type depends on what's at the path:
588
+ Use `fetch` for strict access, it raises `Yerba::SelectorNotFoundError` with "did you mean?" suggestions if the selector doesn't exist:
520
589
 
521
590
  ```ruby
522
- document["database"] # => Yerba::Map
523
- document["database.host"] # => Yerba::Scalar
524
- document["tags"] # => Yerba::Sequence
591
+ document.fetch("database.host") # => Yerba::Scalar
592
+ document.fetch("databse.host") # => raises SelectorNotFoundError: ... Did you mean: database.host?
525
593
  ```
526
594
 
527
- Scalars expose their value and quote style:
595
+ Use `dig` to traverse multiple levels, returning `nil` for missing paths:
596
+
597
+ ```ruby
598
+ document.dig("database", "host") # => Yerba::Scalar
599
+ document.dig("items", 0, "name") # => Yerba::Scalar
600
+ document.dig("database", "missing") # => nil
601
+ ```
602
+
603
+ Use `value_at` to get the plain Ruby value (String, Integer, Hash, Array, etc.) instead of a node object:
528
604
 
529
605
  ```ruby
530
- scalar = document["database.host"]
531
- scalar.value # => "localhost"
532
- scalar.quote_style # => :double
606
+ document.value_at("database.host") # => "localhost"
607
+ document.value_at("database.port") # => 5432
608
+ document.value_at("database") # => {"host" => "localhost", "port" => 5432}
609
+ document.value_at("[].title") # => ["First Talk", "Second Talk"]
533
610
  ```
534
611
 
612
+ Summary of access methods:
613
+
614
+ | Method | Not found | Returns |
615
+ |------------|--------------------------------|------------------------------------|
616
+ | `[]` | `nil` | `Scalar` / `Map` / `Sequence` node |
617
+ | `fetch` | raises `SelectorNotFoundError` | `Scalar` / `Map` / `Sequence` node |
618
+ | `dig` | `nil` | `Scalar` / `Map` / `Sequence` node |
619
+ | `value_at` | `nil` | plain Ruby value |
620
+
535
621
  ### Mutations
536
622
 
537
623
  Modify values in place. The original formatting is preserved:
@@ -600,23 +686,99 @@ collection.where(kind: "talk")
600
686
  collection.pluck(:name)
601
687
  ```
602
688
 
689
+ ### Schema Validation
690
+
691
+ Validate documents against JSON schemas from Ruby:
692
+
693
+ ```ruby
694
+ schema = {
695
+ type: "object",
696
+ properties: { name: { type: "string" }, slug: { type: "string" } },
697
+ required: ["name", "slug"]
698
+ }
699
+
700
+ document.valid?(schema) # => true/false
701
+ document.valid?(schema, selector: "[]") # validate each array item
702
+
703
+ errors = document.validate(schema, selector: "[]")
704
+
705
+ errors.each do |error|
706
+ puts "#{error["message"]} at #{error["path"]} (line #{error["line"]})"
707
+ end
708
+ ```
709
+
710
+ Also accepts a JSON string:
711
+
712
+ ```ruby
713
+ document.valid?('{"type":"object","required":["name"]}')
714
+ ```
715
+
603
716
  ### Quote Style Control
604
717
 
605
718
  Read and set the quote style on individual scalars:
606
719
 
607
720
  ```ruby
608
- scalar = document["database.host"]
721
+ scalar = document["database"]["host"]
609
722
  scalar.quote_style # => :double
610
723
  scalar.quote_style = :single
611
724
  ```
612
725
 
726
+ ### Location
727
+
728
+ Get the precise location (line, column, byte offset) of any selector in a document:
729
+
730
+ ```ruby
731
+ loc = document[0]["title"].location
732
+ loc.start_line # => 2
733
+ loc.start_column # => 9
734
+ loc.end_line # => 2
735
+ loc.end_column # => 19
736
+ loc.start_offset # => 22
737
+ loc.end_offset # => 32
738
+ ```
739
+
740
+ You can also get a location by selector string:
741
+
742
+ ```ruby
743
+ document.location("[0].title")
744
+ ```
745
+
746
+ The above is the same as:
747
+
748
+ ```ruby
749
+ document[0]["title"].location
750
+ document["[0].title"].location
751
+ ```
752
+
753
+ Omit the selector to get the whole document's location:
754
+
755
+ ```ruby
756
+ document.location # => #<Yerba::Location start_line=1, ...>
757
+ ```
758
+
759
+ Returns `nil` for non-existent selectors. Use `locations` for wildcard selectors that match multiple nodes:
760
+
761
+ ```ruby
762
+ locs = document.locations("[].title")
763
+ locs.each { |loc| puts "line #{loc.start_line}" }
764
+ # line 2
765
+ # line 4
766
+
767
+ document.locations("[]")
768
+ document.locations("[].speakers[]")
769
+ ```
770
+
613
771
  ### Wildcard Access
614
772
 
615
- Use `at_path` to access all items matching a wildcard selector:
773
+ When `[]` receives a wildcard selector (containing `[]`), it returns an array of nodes instead of a single node:
616
774
 
617
775
  ```ruby
618
- titles = document.at_path("[].title")
619
- titles.each { |scalar| puts scalar.value }
776
+ document["[].title"] # => [Yerba::Scalar, Yerba::Scalar, ...]
777
+ document["[].speakers[]"] # => [Yerba::Scalar, Yerba::Scalar, ...]
778
+ document["items[].name"] # => [Yerba::Scalar, Yerba::Scalar, ...]
779
+
780
+ document["[].title"].each { |scalar| puts scalar.value }
781
+ document["[].title"].each { |scalar| scalar.value = "Updated" }
620
782
  ```
621
783
 
622
784
  ### Collections
@@ -627,7 +789,7 @@ Operate on multiple files matching a glob pattern:
627
789
  collection = Yerba.files("data/**/videos.yml")
628
790
 
629
791
  collection.each do |document|
630
- puts document.get("[0].title")
792
+ puts document[0]["title"].value
631
793
  end
632
794
 
633
795
  collection.find_by(name: "Alice")
@@ -639,6 +801,32 @@ collection.apply! do |document|
639
801
  end
640
802
  ```
641
803
 
804
+ Use `Collection.get` to retrieve nodes across all matching files in parallel. Returns `Scalar`, `Map`, or `Sequence` objects with `file_path`, `line`, and `selector`:
805
+
806
+ ```ruby
807
+ speakers = Yerba::Collection.get("data/**/videos.yml", "[].speakers[]")
808
+
809
+ speakers.each do |scalar|
810
+ puts "#{scalar.value} in #{scalar.file_path}:#{scalar.line}"
811
+ end
812
+
813
+ maps = Yerba::Collection.get("data/**/videos.yml", "[]")
814
+ maps.first.class
815
+ # => Yerba::Map
816
+
817
+ sequences = Yerba::Collection.get("data/**/videos.yml", "[].speakers")
818
+ sequences.first.class
819
+ # => Yerba::Sequence
820
+ ```
821
+
822
+ Nodes returned by `Collection.get` lazily load their `Document` on first mutation, so reads are fast and writes work transparently:
823
+
824
+ ```ruby
825
+ scalars = Yerba::Collection.get("data/**/videos.yml", "[].title")
826
+ scalars.first.value = "New Title"
827
+ scalars.first.document.save!
828
+ ```
829
+
642
830
  ### Saving
643
831
 
644
832
  Write changes back to the original file:
Binary file
@@ -80,7 +80,9 @@ char *yerba_document_get_value(const struct Document *document, const char *path
80
80
  /**
81
81
  * Caller must free with yerba_string_free.
82
82
  */
83
- char *yerba_document_get_values(const struct Document *document, const char *path);
83
+ char *yerba_document_selectors(const struct Document *document);
84
+
85
+ char *yerba_document_resolve_selectors(const struct Document *document, const char *path);
84
86
 
85
87
  char *yerba_document_get_quote_style(const struct Document *document, const char *path);
86
88
 
@@ -94,6 +96,8 @@ bool yerba_document_evaluate_condition(const struct Document *document,
94
96
 
95
97
  bool yerba_document_exists(const struct Document *document, const char *path);
96
98
 
99
+ bool yerba_document_valid_selector(const struct Document *document, const char *path);
100
+
97
101
  char *yerba_document_find(const struct Document *document,
98
102
  const char *path,
99
103
  const char *condition,
@@ -108,6 +112,7 @@ struct YerbaResult yerba_document_set(struct Document *document,
108
112
  struct YerbaResult yerba_document_insert(struct Document *document,
109
113
  const char *path,
110
114
  const char *value,
115
+ enum YerbaValueType value_type,
111
116
  const char *before,
112
117
  const char *after,
113
118
  int64_t at);
@@ -165,6 +170,13 @@ struct YerbaResult yerba_document_blank_lines(struct Document *document,
165
170
  const char *path,
166
171
  uintptr_t count);
167
172
 
173
+ /**
174
+ * Caller must free with yerba_string_free.
175
+ */
176
+ char *yerba_document_validate_schema(const struct Document *document,
177
+ const char *schema_json,
178
+ const char *selector);
179
+
168
180
  /**
169
181
  * Caller must free with yerba_string_free.
170
182
  */