yerba 0.7.6 → 0.8.1
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.
- checksums.yaml +4 -4
- data/README.md +66 -2
- data/ext/yerba/include/yerba.h +8 -0
- data/ext/yerba/yerba.c +70 -8
- data/lib/yerba/map.rb +15 -10
- data/lib/yerba/node.rb +2 -2
- data/lib/yerba/sequence.rb +13 -1
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.toml +15 -6
- data/rust/src/commands/blank_lines.rs +2 -2
- data/rust/src/commands/directives.rs +4 -4
- data/rust/src/commands/get.rs +17 -10
- data/rust/src/commands/init.rs +4 -4
- data/rust/src/commands/insert.rs +6 -6
- data/rust/src/commands/location.rs +2 -2
- data/rust/src/commands/mate.rs +7 -7
- data/rust/src/commands/mod.rs +119 -90
- data/rust/src/commands/move_key.rs +9 -6
- data/rust/src/commands/quote_style.rs +4 -5
- data/rust/src/commands/rename.rs +3 -3
- data/rust/src/commands/schema.rs +6 -6
- data/rust/src/commands/selectors.rs +2 -2
- data/rust/src/commands/set.rs +12 -1
- data/rust/src/commands/sort.rs +36 -42
- data/rust/src/commands/sort_keys.rs +2 -2
- data/rust/src/commands/ui.rs +157 -0
- data/rust/src/commands/unique.rs +7 -7
- data/rust/src/commands/version.rs +13 -3
- data/rust/src/document/condition.rs +89 -32
- data/rust/src/document/delete.rs +6 -1
- data/rust/src/document/get.rs +94 -26
- data/rust/src/document/insert.rs +26 -0
- data/rust/src/document/mod.rs +73 -20
- data/rust/src/document/set.rs +91 -2
- data/rust/src/error.rs +17 -0
- data/rust/src/ffi.rs +76 -9
- data/rust/src/json.rs +10 -1
- data/rust/src/lib.rs +23 -7
- data/rust/src/main.rs +1 -0
- data/rust/src/quote_style.rs +12 -11
- data/rust/src/selector.rs +19 -3
- data/rust/src/syntax.rs +178 -26
- data/rust/src/yaml_writer.rs +4 -4
- data/rust/src/yerbafile.rs +30 -6
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a769e82895bc0bea8b8e25391f39cdbf8e915a46d524efd05af951738b4b046a
|
|
4
|
+
data.tar.gz: 83d41f698f888f6065729b8adb5e6646e14b9832871eafde6af2fa60bfd4b862
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 64e3fe5431316bc5faf03187419f234f52c9d8f04710d06afdd6bf13dc5342a80dadae3a4345ee610f4f307976d45520f0cc6b4bcc7a7959e21a9902ae071343
|
|
7
|
+
data.tar.gz: 3f12d6fa79ed13905bca60481eb3a87caffa46fd8205b6a9d56a8fcf250ce708698e218a4cec7126294940eb6ce8f44e02abf445144c12b9f39ad16f417a1da7
|
data/README.md
CHANGED
|
@@ -47,7 +47,7 @@ Use `yerba` as a library in your Rust project:
|
|
|
47
47
|
|
|
48
48
|
```toml
|
|
49
49
|
[dependencies]
|
|
50
|
-
yerba = "0.
|
|
50
|
+
yerba = "0.8"
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
```rust
|
|
@@ -96,8 +96,11 @@ Selectors let you address any node in a YAML document:
|
|
|
96
96
|
| `key.nested` | Nested key path | `"database.settings.pool"` |
|
|
97
97
|
| `[]` | All items in array | `"[].title"` |
|
|
98
98
|
| `[N]` | Item at index | `"[0].title"` |
|
|
99
|
+
| `*` | All values in a map | `"database.*"` |
|
|
99
100
|
| `[].key[].nested` | Nested array access | `"[].speakers[].name"` |
|
|
100
101
|
|
|
102
|
+
`[]` and `*` are the same idea applied to the two collection types: `[]` matches every item of a sequence, `*` matches every value of a map. Both can be combined with the other patterns, as in `"[].*"` or `"*.city"`.
|
|
103
|
+
|
|
101
104
|
### Conditions
|
|
102
105
|
|
|
103
106
|
Conditions filter which items a command operates on:
|
|
@@ -109,6 +112,8 @@ Conditions filter which items a command operates on:
|
|
|
109
112
|
| `.key contains val` | Substring or member | `".title contains Ruby"` |
|
|
110
113
|
| `.key not_contains val` | Negated contains | `".title not_contains test"` |
|
|
111
114
|
|
|
115
|
+
The selector is relative to each item and starts with `.`. A condition that is malformed, or that uses an absolute selector where each item is tested, is rejected rather than matching nothing, so a typo is an error instead of a command that quietly does nothing.
|
|
116
|
+
|
|
112
117
|
---
|
|
113
118
|
|
|
114
119
|
### `get`
|
|
@@ -701,6 +706,12 @@ Set all matching nodes at once with `all: true`:
|
|
|
701
706
|
document.set("[].description", "", all: true)
|
|
702
707
|
```
|
|
703
708
|
|
|
709
|
+
Setting a scalar over a key whose value is a collection replaces the collection:
|
|
710
|
+
|
|
711
|
+
```ruby
|
|
712
|
+
document.root["venue"] = "none" # => venue: none
|
|
713
|
+
```
|
|
714
|
+
|
|
704
715
|
Insert new keys with positional control:
|
|
705
716
|
|
|
706
717
|
```ruby
|
|
@@ -829,6 +840,24 @@ document["database"].collection_style = :flow # => database: {host: localhost,
|
|
|
829
840
|
document["database"].collection_style = :block # => database:\n host: localhost\n port: 5432
|
|
830
841
|
```
|
|
831
842
|
|
|
843
|
+
Flow collections are read and iterated like block ones, and their scalars can be
|
|
844
|
+
changed in place:
|
|
845
|
+
|
|
846
|
+
```ruby
|
|
847
|
+
document = Yerba::Document.parse("tags: [ruby, rails]\n")
|
|
848
|
+
|
|
849
|
+
document["tags"].map(&:value) # => ["ruby", "rails"]
|
|
850
|
+
document["tags[1]"].value = "crystal" # => tags: [ruby, crystal]
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
Adding or removing entries is not supported yet, and raises rather than
|
|
854
|
+
reformatting the collection. Switch to block style first:
|
|
855
|
+
|
|
856
|
+
```ruby
|
|
857
|
+
document["tags"].collection_style = :block
|
|
858
|
+
document.delete("tags[0]")
|
|
859
|
+
```
|
|
860
|
+
|
|
832
861
|
### Sequence Indent
|
|
833
862
|
|
|
834
863
|
Control whether sequence items are indented under their key or at the same level:
|
|
@@ -893,17 +922,52 @@ document.locations("[].speakers[]")
|
|
|
893
922
|
|
|
894
923
|
### Wildcard Access
|
|
895
924
|
|
|
896
|
-
When `[]` receives a wildcard selector
|
|
925
|
+
When `[]` receives a wildcard selector, it returns an array of nodes instead of a single node. `[]` matches every item of a sequence and `*` matches every value of a map:
|
|
897
926
|
|
|
898
927
|
```ruby
|
|
899
928
|
document["[].title"] # => [Yerba::Scalar, Yerba::Scalar, ...]
|
|
900
929
|
document["[].speakers[]"] # => [Yerba::Scalar, Yerba::Scalar, ...]
|
|
901
930
|
document["items[].name"] # => [Yerba::Scalar, Yerba::Scalar, ...]
|
|
902
931
|
|
|
932
|
+
document["database.*"] # => [Yerba::Scalar, Yerba::Scalar, ...]
|
|
933
|
+
document["*"] # => every value of the root map
|
|
934
|
+
document["[].*"] # => every value of every item
|
|
935
|
+
|
|
903
936
|
document["[].title"].each { |scalar| puts scalar.value }
|
|
904
937
|
document["[].title"].each { |scalar| scalar.value = "Updated" }
|
|
905
938
|
```
|
|
906
939
|
|
|
940
|
+
Each node knows the key it belongs to, which is how a map value can be traced back to its name:
|
|
941
|
+
|
|
942
|
+
```ruby
|
|
943
|
+
document["database.*"].map { |node| [node.key.value, node.value] }
|
|
944
|
+
# => [["host", "localhost"], ["port", 5432]]
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
`get_all` resolves the same selectors in a single walk of the document, rather than looking each node up separately. It is the faster choice when reading many nodes, and returns nodes that are still connected to the document:
|
|
948
|
+
|
|
949
|
+
```ruby
|
|
950
|
+
document.get_all("[].title") # => [Yerba::Scalar, Yerba::Scalar, ...]
|
|
951
|
+
document.get_all("database.*") # => [Yerba::Scalar, Yerba::Scalar, ...]
|
|
952
|
+
|
|
953
|
+
document.get_all("[].title").first.value = "Updated"
|
|
954
|
+
document.save!
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
Reading values rather than nodes keeps a positional `nil` where a branch does not match, while resolving nodes skips it:
|
|
958
|
+
|
|
959
|
+
```ruby
|
|
960
|
+
document.value_at("*.city") # => [nil, "Berlin", nil]
|
|
961
|
+
document.get_all("*.city").map(&:value) # => ["Berlin"]
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
Wildcards address more than one node, so they cannot be written through. `set`, `insert` and `delete` raise rather than change every match at once:
|
|
965
|
+
|
|
966
|
+
```ruby
|
|
967
|
+
document.set("database.*", "x") # => raises Yerba::Error
|
|
968
|
+
document.delete("database.*") # => raises Yerba::Error
|
|
969
|
+
```
|
|
970
|
+
|
|
907
971
|
### Collections
|
|
908
972
|
|
|
909
973
|
Operate on multiple files matching a glob pattern:
|
data/ext/yerba/include/yerba.h
CHANGED
|
@@ -89,6 +89,10 @@ char *yerba_document_selectors(const struct Document *document);
|
|
|
89
89
|
|
|
90
90
|
char *yerba_document_resolve_selectors(const struct Document *document, const char *path);
|
|
91
91
|
|
|
92
|
+
bool yerba_selector_has_wildcard(const char *path);
|
|
93
|
+
|
|
94
|
+
char *yerba_document_keys(const struct Document *document, const char *path);
|
|
95
|
+
|
|
92
96
|
char *yerba_document_get_quote_style(const struct Document *document, const char *path);
|
|
93
97
|
|
|
94
98
|
struct YerbaResult yerba_document_set_quote_style(struct Document *document,
|
|
@@ -107,6 +111,8 @@ struct YerbaResult yerba_document_set_sequence_indent(struct Document *document,
|
|
|
107
111
|
const char *path,
|
|
108
112
|
const char *style);
|
|
109
113
|
|
|
114
|
+
char *yerba_validate_condition(const char *condition, bool item_context);
|
|
115
|
+
|
|
110
116
|
bool yerba_document_evaluate_condition(const struct Document *document,
|
|
111
117
|
const char *parent_path,
|
|
112
118
|
const char *condition);
|
|
@@ -213,6 +219,8 @@ void yerba_get_result_free(struct YerbaGetResult result);
|
|
|
213
219
|
|
|
214
220
|
struct YerbaTypedList yerba_glob_get(const char *glob_pattern, const char *path);
|
|
215
221
|
|
|
222
|
+
struct YerbaTypedList yerba_document_get_all(const struct Document *document, const char *path);
|
|
223
|
+
|
|
216
224
|
struct YerbaTypedList yerba_glob_find(const char *glob_pattern,
|
|
217
225
|
const char *path,
|
|
218
226
|
const char *condition,
|
data/ext/yerba/yerba.c
CHANGED
|
@@ -98,6 +98,19 @@ static VALUE typed_value_to_ruby(YerbaTypedValue typed_value) {
|
|
|
98
98
|
return result;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
static void check_condition(const char *condition, bool item_context) {
|
|
102
|
+
if (!condition) return;
|
|
103
|
+
|
|
104
|
+
char *error = yerba_validate_condition(condition, item_context);
|
|
105
|
+
|
|
106
|
+
if (!error) return;
|
|
107
|
+
|
|
108
|
+
VALUE message = make_utf8_string(error);
|
|
109
|
+
yerba_string_free(error);
|
|
110
|
+
|
|
111
|
+
rb_raise(rb_eError, "%s", StringValueCStr(message));
|
|
112
|
+
}
|
|
113
|
+
|
|
101
114
|
static int should_proceed(struct Document *document, VALUE opts) {
|
|
102
115
|
if (NIL_P(opts)) return 1;
|
|
103
116
|
VALUE v_condition = rb_hash_aref(opts, ID2SYM(rb_intern("condition")));
|
|
@@ -106,6 +119,8 @@ static int should_proceed(struct Document *document, VALUE opts) {
|
|
|
106
119
|
VALUE v_path = rb_hash_aref(opts, ID2SYM(rb_intern("condition_path")));
|
|
107
120
|
const char *parent_path = NIL_P(v_path) ? "" : StringValueCStr(v_path);
|
|
108
121
|
|
|
122
|
+
check_condition(StringValueCStr(v_condition), parent_path[0] != '\0');
|
|
123
|
+
|
|
109
124
|
return yerba_document_evaluate_condition(document, parent_path, StringValueCStr(v_condition));
|
|
110
125
|
}
|
|
111
126
|
|
|
@@ -256,8 +271,7 @@ static VALUE document_bracket(VALUE self, VALUE path) {
|
|
|
256
271
|
|
|
257
272
|
const char *selector = StringValueCStr(path);
|
|
258
273
|
|
|
259
|
-
|
|
260
|
-
if (strstr(selector, "[]") != NULL) {
|
|
274
|
+
if (yerba_selector_has_wildcard(selector)) {
|
|
261
275
|
char *json = yerba_document_resolve_selectors(document, selector);
|
|
262
276
|
|
|
263
277
|
if (!json) return rb_ary_new();
|
|
@@ -338,6 +352,19 @@ static VALUE document_selectors(VALUE self) {
|
|
|
338
352
|
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
339
353
|
}
|
|
340
354
|
|
|
355
|
+
/* document.keys_at(selector) → ["host", "port", ...] */
|
|
356
|
+
static VALUE document_keys_at(VALUE self, VALUE path) {
|
|
357
|
+
struct Document *document = get_document(self);
|
|
358
|
+
char *json = yerba_document_keys(document, StringValueCStr(path));
|
|
359
|
+
|
|
360
|
+
if (!json) return rb_ary_new();
|
|
361
|
+
|
|
362
|
+
VALUE json_string = make_utf8_string(json);
|
|
363
|
+
yerba_string_free(json);
|
|
364
|
+
|
|
365
|
+
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
366
|
+
}
|
|
367
|
+
|
|
341
368
|
/* document.locations(selector) → [Yerba::Location, ...] */
|
|
342
369
|
static VALUE document_locations(VALUE self, VALUE path) {
|
|
343
370
|
struct Document *document = get_document(self);
|
|
@@ -508,6 +535,8 @@ static VALUE document_condition_p(int argc, VALUE *argv, VALUE self) {
|
|
|
508
535
|
|
|
509
536
|
struct Document *document = get_document(self);
|
|
510
537
|
|
|
538
|
+
check_condition(StringValueCStr(condition), parent_path[0] != '\0');
|
|
539
|
+
|
|
511
540
|
return yerba_document_evaluate_condition(document, parent_path, StringValueCStr(condition)) ? Qtrue : Qfalse;
|
|
512
541
|
}
|
|
513
542
|
|
|
@@ -528,6 +557,9 @@ static VALUE document_find(int argc, VALUE *argv, VALUE self) {
|
|
|
528
557
|
}
|
|
529
558
|
|
|
530
559
|
struct Document *document = get_document(self);
|
|
560
|
+
|
|
561
|
+
check_condition(condition, true);
|
|
562
|
+
|
|
531
563
|
char *json = yerba_document_find(document, StringValueCStr(path), condition, select);
|
|
532
564
|
|
|
533
565
|
if (!json) return rb_ary_new();
|
|
@@ -959,12 +991,7 @@ static VALUE document_path(VALUE self) {
|
|
|
959
991
|
return rb_iv_get(self, "@path");
|
|
960
992
|
}
|
|
961
993
|
|
|
962
|
-
|
|
963
|
-
static VALUE collection_s_get(VALUE self, VALUE pattern, VALUE path) {
|
|
964
|
-
(void) self;
|
|
965
|
-
|
|
966
|
-
YerbaTypedList result = yerba_glob_get(StringValueCStr(pattern), StringValueCStr(path));
|
|
967
|
-
|
|
994
|
+
static VALUE located_list_to_ruby(YerbaTypedList result, VALUE document) {
|
|
968
995
|
if (!result.json) return rb_ary_new();
|
|
969
996
|
|
|
970
997
|
VALUE json_string = make_utf8_string(result.json);
|
|
@@ -992,6 +1019,23 @@ static VALUE collection_s_get(VALUE self, VALUE pattern, VALUE path) {
|
|
|
992
1019
|
if (!NIL_P(file_path)) rb_hash_aset(kwargs, ID2SYM(rb_intern("file_path")), file_path);
|
|
993
1020
|
if (!NIL_P(line)) rb_hash_aset(kwargs, ID2SYM(rb_intern("line")), line);
|
|
994
1021
|
if (!NIL_P(location)) rb_hash_aset(kwargs, ID2SYM(rb_intern("location")), location);
|
|
1022
|
+
if (!NIL_P(document)) rb_hash_aset(kwargs, ID2SYM(rb_intern("document")), document);
|
|
1023
|
+
|
|
1024
|
+
VALUE key_name = rb_hash_aref(item, rb_str_new_cstr("key_name"));
|
|
1025
|
+
|
|
1026
|
+
if (!NIL_P(key_name)) {
|
|
1027
|
+
VALUE key_location = rb_hash_aref(item, rb_str_new_cstr("key_location"));
|
|
1028
|
+
VALUE key_kwargs = rb_hash_new();
|
|
1029
|
+
|
|
1030
|
+
rb_hash_aset(key_kwargs, ID2SYM(rb_intern("selector")), Qnil);
|
|
1031
|
+
rb_hash_aset(key_kwargs, ID2SYM(rb_intern("value")), key_name);
|
|
1032
|
+
if (!NIL_P(key_location)) rb_hash_aset(key_kwargs, ID2SYM(rb_intern("location")), key_location);
|
|
1033
|
+
|
|
1034
|
+
VALUE key_args[1] = { key_kwargs };
|
|
1035
|
+
VALUE key = rb_funcallv_kw(rb_path2class("Yerba::Scalar"), rb_intern("from"), 1, key_args, RB_PASS_KEYWORDS);
|
|
1036
|
+
|
|
1037
|
+
rb_hash_aset(kwargs, ID2SYM(rb_intern("key")), key);
|
|
1038
|
+
}
|
|
995
1039
|
|
|
996
1040
|
if (strcmp(type_str, "scalar") == 0) {
|
|
997
1041
|
VALUE text = rb_hash_aref(item, rb_str_new_cstr("text"));
|
|
@@ -1015,6 +1059,20 @@ static VALUE collection_s_get(VALUE self, VALUE pattern, VALUE path) {
|
|
|
1015
1059
|
return array;
|
|
1016
1060
|
}
|
|
1017
1061
|
|
|
1062
|
+
/* Collection.get(glob, selector) → [Yerba::Scalar|Map|Sequence, ...] */
|
|
1063
|
+
static VALUE collection_s_get(VALUE self, VALUE pattern, VALUE path) {
|
|
1064
|
+
(void) self;
|
|
1065
|
+
|
|
1066
|
+
return located_list_to_ruby(yerba_glob_get(StringValueCStr(pattern), StringValueCStr(path)), Qnil);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/* document.get_all(selector) → [Yerba::Scalar|Map|Sequence, ...] */
|
|
1070
|
+
static VALUE document_get_all(VALUE self, VALUE path) {
|
|
1071
|
+
struct Document *document = get_document(self);
|
|
1072
|
+
|
|
1073
|
+
return located_list_to_ruby(yerba_document_get_all(document, StringValueCStr(path)), self);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1018
1076
|
/* Collection.find(glob, path, condition: nil, select: nil) */
|
|
1019
1077
|
static VALUE collection_s_find(int argc, VALUE *argv, VALUE self) {
|
|
1020
1078
|
(void)self;
|
|
@@ -1032,6 +1090,8 @@ static VALUE collection_s_find(int argc, VALUE *argv, VALUE self) {
|
|
|
1032
1090
|
if (!NIL_P(v_select)) select = StringValueCStr(v_select);
|
|
1033
1091
|
}
|
|
1034
1092
|
|
|
1093
|
+
check_condition(condition, true);
|
|
1094
|
+
|
|
1035
1095
|
YerbaTypedList result = yerba_glob_find(StringValueCStr(pattern), StringValueCStr(path), condition, select);
|
|
1036
1096
|
|
|
1037
1097
|
if (!result.json) return rb_ary_new();
|
|
@@ -1080,9 +1140,11 @@ void Init_yerba(void) {
|
|
|
1080
1140
|
rb_define_method(rb_cDocument, "[]", document_bracket, 1);
|
|
1081
1141
|
rb_define_method(rb_cDocument, "node_at", document_bracket, 1);
|
|
1082
1142
|
rb_define_method(rb_cDocument, "value_at", document_get_value, 1);
|
|
1143
|
+
rb_define_method(rb_cDocument, "get_all", document_get_all, 1);
|
|
1083
1144
|
rb_define_method(rb_cDocument, "location", document_location, -1);
|
|
1084
1145
|
rb_define_method(rb_cDocument, "locations", document_locations, 1);
|
|
1085
1146
|
rb_define_method(rb_cDocument, "selectors", document_selectors, 0);
|
|
1147
|
+
rb_define_method(rb_cDocument, "keys_at", document_keys_at, 1);
|
|
1086
1148
|
rb_define_method(rb_cDocument, "resolve_selectors", document_resolve_selectors, 1);
|
|
1087
1149
|
rb_define_method(rb_cDocument, "get_quote_style", document_get_quote_style, 1);
|
|
1088
1150
|
rb_define_method(rb_cDocument, "set_quote_style", document_set_quote_style, 2);
|
data/lib/yerba/map.rb
CHANGED
|
@@ -77,24 +77,29 @@ module Yerba
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def keys
|
|
80
|
-
|
|
81
|
-
results = document.find(@selector)
|
|
82
|
-
return [] unless results.is_a?(Array) && results.first.is_a?(Hash)
|
|
80
|
+
return @data.keys unless connected?
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
else
|
|
86
|
-
@data.keys
|
|
87
|
-
end
|
|
82
|
+
document.keys_at(@selector)
|
|
88
83
|
end
|
|
89
84
|
|
|
90
85
|
def each(&)
|
|
91
86
|
return enum_for(:each) unless block_given?
|
|
92
87
|
|
|
93
|
-
|
|
94
|
-
keys.each { |key| yield key, self[key] }
|
|
95
|
-
else
|
|
88
|
+
unless connected?
|
|
96
89
|
@data.each(&)
|
|
90
|
+
return self
|
|
97
91
|
end
|
|
92
|
+
|
|
93
|
+
names = keys
|
|
94
|
+
values = document.get_all(@selector.empty? ? "*" : "#{@selector}.*")
|
|
95
|
+
|
|
96
|
+
if values.length == names.length && values.all?(&:key)
|
|
97
|
+
values.each { |value| yield value.key.value, value }
|
|
98
|
+
else
|
|
99
|
+
names.each { |key| yield key, self[key] }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
self
|
|
98
103
|
end
|
|
99
104
|
|
|
100
105
|
def fetch(key)
|
data/lib/yerba/node.rb
CHANGED
|
@@ -36,10 +36,10 @@ module Yerba
|
|
|
36
36
|
instance
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def from(
|
|
39
|
+
def from(selector:, file_path: nil, line: nil, location: nil, document: nil, key: nil, **attributes)
|
|
40
40
|
instance = allocate
|
|
41
41
|
|
|
42
|
-
instance.send(:init_node,
|
|
42
|
+
instance.send(:init_node, document, selector, coerce_location(location), key, file_path, line)
|
|
43
43
|
instance.send(:init_from, **attributes) if instance.respond_to?(:init_from, true)
|
|
44
44
|
|
|
45
45
|
instance
|
data/lib/yerba/sequence.rb
CHANGED
|
@@ -90,10 +90,22 @@ module Yerba
|
|
|
90
90
|
self
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
-
def each
|
|
93
|
+
def each(&)
|
|
94
94
|
return enum_for(:each) unless block_given?
|
|
95
95
|
|
|
96
|
+
if connected?
|
|
97
|
+
items = document.get_all("#{@selector}[]")
|
|
98
|
+
|
|
99
|
+
if items.length == length
|
|
100
|
+
items.each(&)
|
|
101
|
+
|
|
102
|
+
return self
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
96
106
|
length.times { |index| yield self[index] }
|
|
107
|
+
|
|
108
|
+
self
|
|
97
109
|
end
|
|
98
110
|
|
|
99
111
|
def find_by(selector = nil, value = nil, **criteria)
|
data/lib/yerba/version.rb
CHANGED
data/rust/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "yerba"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.1"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Marco Roth <marco.roth@intergga.ch>"]
|
|
6
6
|
description = "YAML Editing and Refactoring with Better Accuracy"
|
|
@@ -18,21 +18,30 @@ crate-type = ["cdylib", "staticlib", "rlib"]
|
|
|
18
18
|
[[bin]]
|
|
19
19
|
name = "yerba"
|
|
20
20
|
path = "src/main.rs"
|
|
21
|
+
required-features = ["cli"]
|
|
22
|
+
|
|
23
|
+
[features]
|
|
24
|
+
default = ["cli"]
|
|
25
|
+
cli = ["dep:clap", "dep:indoc", "dep:anstyle-query", "glob", "schema"]
|
|
26
|
+
glob = ["dep:glob", "dep:rayon"]
|
|
27
|
+
schema = ["dep:jsonschema"]
|
|
21
28
|
|
|
22
29
|
[dependencies]
|
|
23
30
|
yaml_parser = "0.3"
|
|
24
31
|
rowan = "0.16"
|
|
25
|
-
glob = "0.3"
|
|
32
|
+
glob = { version = "0.3", optional = true }
|
|
26
33
|
serde = { version = "1", features = ["derive"] }
|
|
27
34
|
yaml_serde = "0.10"
|
|
28
35
|
serde_json = { version = "1", features = ["preserve_order"] }
|
|
29
|
-
clap = { version = "4", features = ["derive"] }
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
clap = { version = "4", features = ["derive"], optional = true }
|
|
37
|
+
anstyle-query = { version = "1", optional = true }
|
|
38
|
+
indoc = { version = "2", optional = true }
|
|
39
|
+
rayon = { version = "1", optional = true }
|
|
40
|
+
jsonschema = { version = "0.29", default-features = false, optional = true }
|
|
33
41
|
|
|
34
42
|
[build-dependencies]
|
|
35
43
|
cbindgen = "0.28"
|
|
36
44
|
|
|
37
45
|
[dev-dependencies]
|
|
46
|
+
indoc = "2"
|
|
38
47
|
tempfile = "3"
|
|
@@ -37,9 +37,9 @@ impl Args {
|
|
|
37
37
|
} else if let Ok(count) = self.first.parse::<usize>() {
|
|
38
38
|
("", count)
|
|
39
39
|
} else {
|
|
40
|
-
use super::
|
|
40
|
+
use super::ui;
|
|
41
41
|
|
|
42
|
-
eprintln!("{
|
|
42
|
+
eprintln!("{} expected a number for blank line count, got '{}'", ui::failure("Error:"), self.first);
|
|
43
43
|
|
|
44
44
|
std::process::exit(1);
|
|
45
45
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use super::ui;
|
|
1
2
|
use std::sync::LazyLock;
|
|
2
3
|
|
|
3
4
|
use indoc::indoc;
|
|
@@ -35,14 +36,13 @@ pub struct Args {
|
|
|
35
36
|
impl Args {
|
|
36
37
|
pub fn run(self) {
|
|
37
38
|
if !self.ensure && !self.remove {
|
|
38
|
-
use super::
|
|
39
|
-
eprintln!("{
|
|
39
|
+
use super::ui;
|
|
40
|
+
eprintln!("{} specify --ensure or --remove", ui::failure("Error:"));
|
|
40
41
|
std::process::exit(1);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
if self.ensure && self.remove {
|
|
44
|
-
|
|
45
|
-
eprintln!("{RED}Error:{RESET} --ensure and --remove are mutually exclusive");
|
|
45
|
+
eprintln!("{} --ensure and --remove are mutually exclusive", ui::failure("Error:"));
|
|
46
46
|
std::process::exit(1);
|
|
47
47
|
}
|
|
48
48
|
|
data/rust/src/commands/get.rs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use super::ui;
|
|
1
2
|
use std::process;
|
|
2
3
|
use std::sync::LazyLock;
|
|
3
4
|
|
|
@@ -62,6 +63,16 @@ impl Args {
|
|
|
62
63
|
condition.clone()
|
|
63
64
|
});
|
|
64
65
|
|
|
66
|
+
if let Some(condition) = &normalized_condition {
|
|
67
|
+
if let Err(error) = yerba::validate_item_condition(condition) {
|
|
68
|
+
use super::ui;
|
|
69
|
+
|
|
70
|
+
eprintln!("{} {}", ui::failure("Error:"), error);
|
|
71
|
+
|
|
72
|
+
process::exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
let search_path_string = search_path.to_selector_string();
|
|
66
77
|
let mut all_results: Vec<serde_json::Value> = Vec::new();
|
|
67
78
|
let files = resolve_files(&self.file);
|
|
@@ -75,9 +86,7 @@ impl Args {
|
|
|
75
86
|
continue;
|
|
76
87
|
}
|
|
77
88
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
eprintln!("{RED}Error:{RESET} selector \"{}\" not found in {}", self.selector, self.file);
|
|
89
|
+
eprintln!("{} selector \"{}\" not found in {}", ui::failure("Error:"), self.selector, self.file);
|
|
81
90
|
|
|
82
91
|
show_similar_selectors(&self.file, &document, &self.selector);
|
|
83
92
|
process::exit(1);
|
|
@@ -100,8 +109,7 @@ impl Args {
|
|
|
100
109
|
break;
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
|
|
104
|
-
eprintln!("{RED}Error:{RESET} select field \"{}\" not found in {}", field.trim(), self.file);
|
|
112
|
+
eprintln!("{} select field \"{}\" not found in {}", ui::failure("Error:"), field.trim(), self.file);
|
|
105
113
|
show_similar_selectors(&self.file, &document, &full_selector);
|
|
106
114
|
process::exit(1);
|
|
107
115
|
}
|
|
@@ -114,7 +122,7 @@ impl Args {
|
|
|
114
122
|
|
|
115
123
|
let (values, selectors, lines): (Vec<yaml_serde::Value>, Vec<String>, Vec<usize>) = if select_fields.is_some() {
|
|
116
124
|
if let Some(condition) = &normalized_condition {
|
|
117
|
-
let triples = document.filter_with_selectors(&search_path_string, condition);
|
|
125
|
+
let triples = document.filter_with_selectors(&search_path_string, condition).unwrap_or_default();
|
|
118
126
|
let (values, rest): (Vec<_>, Vec<_>) = triples.into_iter().map(|(v, s, l)| (v, (s, l))).unzip();
|
|
119
127
|
let (selectors, lines): (Vec<_>, Vec<_>) = rest.into_iter().unzip();
|
|
120
128
|
|
|
@@ -129,7 +137,7 @@ impl Args {
|
|
|
129
137
|
(values, selectors, lines)
|
|
130
138
|
}
|
|
131
139
|
} else if let Some(condition) = &normalized_condition {
|
|
132
|
-
(document.filter(&search_path_string, condition), Vec::new(), Vec::new())
|
|
140
|
+
(document.filter(&search_path_string, condition).unwrap_or_default(), Vec::new(), Vec::new())
|
|
133
141
|
} else {
|
|
134
142
|
(document.get_values(&search_path_string), Vec::new(), Vec::new())
|
|
135
143
|
};
|
|
@@ -180,10 +188,9 @@ impl Args {
|
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
if is_glob && all_results.is_empty() && normalized_condition.is_none() {
|
|
183
|
-
use super::color::*;
|
|
184
|
-
|
|
185
191
|
eprintln!(
|
|
186
|
-
"{
|
|
192
|
+
"{} selector \"{}\" not found in any of the {} files matching \"{}\"",
|
|
193
|
+
ui::failure("Error:"),
|
|
187
194
|
self.selector,
|
|
188
195
|
files.len(),
|
|
189
196
|
self.file
|
data/rust/src/commands/init.rs
CHANGED
|
@@ -3,13 +3,13 @@ use std::process;
|
|
|
3
3
|
|
|
4
4
|
use indoc::indoc;
|
|
5
5
|
|
|
6
|
-
use super::
|
|
6
|
+
use super::ui;
|
|
7
7
|
|
|
8
8
|
pub fn run() {
|
|
9
9
|
let filename = "Yerbafile";
|
|
10
10
|
|
|
11
11
|
if Path::new(filename).exists() {
|
|
12
|
-
eprintln!("🧉 {
|
|
12
|
+
eprintln!("🧉 {}", ui::pending("Yerbafile already exists."));
|
|
13
13
|
process::exit(1);
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -81,9 +81,9 @@ pub fn run() {
|
|
|
81
81
|
"#};
|
|
82
82
|
|
|
83
83
|
std::fs::write(filename, content).unwrap_or_else(|error| {
|
|
84
|
-
eprintln!("{
|
|
84
|
+
eprintln!("{} writing Yerbafile: {}", ui::failure("Error:"), error);
|
|
85
85
|
process::exit(1);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
eprintln!("🧉 {
|
|
88
|
+
eprintln!("🧉 {}", ui::success("Created Yerbafile"));
|
|
89
89
|
}
|
data/rust/src/commands/insert.rs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use super::ui;
|
|
1
2
|
use std::sync::LazyLock;
|
|
2
3
|
|
|
3
4
|
use indoc::indoc;
|
|
@@ -28,6 +29,7 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
|
|
|
28
29
|
pub struct Args {
|
|
29
30
|
file: String,
|
|
30
31
|
selector: String,
|
|
32
|
+
#[arg(allow_hyphen_values = true)]
|
|
31
33
|
value: Option<String>,
|
|
32
34
|
#[arg(long, help = "Read value from a file (use - for stdin)")]
|
|
33
35
|
from: Option<String>,
|
|
@@ -49,8 +51,8 @@ impl Args {
|
|
|
49
51
|
let mut buffer = String::new();
|
|
50
52
|
|
|
51
53
|
std::io::stdin().read_to_string(&mut buffer).unwrap_or_else(|error| {
|
|
52
|
-
use super::
|
|
53
|
-
eprintln!("{
|
|
54
|
+
use super::ui;
|
|
55
|
+
eprintln!("{} reading stdin: {}", ui::failure("Error:"), error);
|
|
54
56
|
|
|
55
57
|
std::process::exit(1);
|
|
56
58
|
});
|
|
@@ -59,8 +61,7 @@ impl Args {
|
|
|
59
61
|
} else {
|
|
60
62
|
std::fs::read_to_string(&from_path)
|
|
61
63
|
.unwrap_or_else(|error| {
|
|
62
|
-
|
|
63
|
-
eprintln!("{RED}Error:{RESET} reading {}: {}", from_path, error);
|
|
64
|
+
eprintln!("{} reading {}: {}", ui::failure("Error:"), from_path, error);
|
|
64
65
|
std::process::exit(1);
|
|
65
66
|
})
|
|
66
67
|
.trim()
|
|
@@ -69,8 +70,7 @@ impl Args {
|
|
|
69
70
|
} else if let Some(val) = self.value {
|
|
70
71
|
val
|
|
71
72
|
} else {
|
|
72
|
-
|
|
73
|
-
eprintln!("{RED}Error:{RESET} either a value argument or --from is required");
|
|
73
|
+
eprintln!("{} either a value argument or --from is required", ui::failure("Error:"));
|
|
74
74
|
|
|
75
75
|
std::process::exit(1);
|
|
76
76
|
};
|
|
@@ -28,13 +28,13 @@ pub struct Args {
|
|
|
28
28
|
|
|
29
29
|
impl Args {
|
|
30
30
|
pub fn run(self) {
|
|
31
|
-
use super::
|
|
31
|
+
use super::ui;
|
|
32
32
|
|
|
33
33
|
let document = parse_file(&self.file);
|
|
34
34
|
let info = document.get_node_info(&self.selector);
|
|
35
35
|
|
|
36
36
|
if info.location.start_line == 0 && info.location.end_line == 0 {
|
|
37
|
-
eprintln!("{
|
|
37
|
+
eprintln!("{} selector \"{}\" not found in {}", ui::failure("Error:"), self.selector, self.file);
|
|
38
38
|
|
|
39
39
|
super::show_similar_selectors(&self.file, &document, &self.selector);
|
|
40
40
|
process::exit(1);
|
data/rust/src/commands/mate.rs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
use super::
|
|
1
|
+
use super::ui;
|
|
2
2
|
|
|
3
3
|
pub fn run() {
|
|
4
|
-
let g =
|
|
5
|
-
let b =
|
|
6
|
-
let d =
|
|
7
|
-
let i =
|
|
8
|
-
let y =
|
|
9
|
-
let r =
|
|
4
|
+
let g = ui::raw::green();
|
|
5
|
+
let b = ui::raw::bold();
|
|
6
|
+
let d = ui::raw::dim();
|
|
7
|
+
let i = ui::raw::italic();
|
|
8
|
+
let y = ui::raw::yellow();
|
|
9
|
+
let r = ui::raw::reset();
|
|
10
10
|
let hr = format!(" {d}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}{r}");
|
|
11
11
|
|
|
12
12
|
println!();
|