yerba 0.8.1 → 0.9.0
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 +170 -19
- data/ext/yerba/include/yerba.h +7 -0
- data/ext/yerba/yerba.c +54 -5
- data/lib/yerba/formatting.rb +9 -13
- data/lib/yerba/map.rb +11 -8
- data/lib/yerba/sequence.rb +2 -7
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/commands/blank_lines.rs +21 -1
- data/rust/src/commands/init.rs +6 -0
- data/rust/src/commands/insert.rs +12 -6
- data/rust/src/commands/mod.rs +2 -3
- data/rust/src/commands/set.rs +15 -5
- data/rust/src/commands/ui.rs +1 -2
- data/rust/src/commands/version.rs +1 -1
- data/rust/src/document/insert.rs +23 -11
- data/rust/src/document/set.rs +108 -34
- data/rust/src/document/style.rs +131 -11
- data/rust/src/error.rs +24 -0
- data/rust/src/ffi.rs +50 -12
- data/rust/src/lib.rs +5 -3
- data/rust/src/syntax.rs +169 -26
- data/rust/src/validation.rs +30 -0
- data/rust/src/yaml_writer.rs +7 -19
- data/rust/src/yerbafile.rs +76 -4
- 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: be42a92f67226567e1ce9b74e3a2fdba79b06a3dcb4f3a6636ef1b002f7d67c4
|
|
4
|
+
data.tar.gz: 5f1183494d12c2b27bc9658b50368d4fa5280fb676fbe9a11c5cb75b8c3deb74
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7b82a3ee82fa5fbdcf1bf7a937b7859647b73e3a4bf7d6248b5bddd84477e3cd0aa2b5d0f240d37ead5fad419e4b00c87baa6b846d426f23881cc877c65f496
|
|
7
|
+
data.tar.gz: 1a6f516d714bfb6ba41099796ce04e94d9090b6eaff9f47366551c00998cd73ff28eb4907bd3e85aa550e08123e7a0219b028951e3a6a649163ee407d07a97a6
|
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.9"
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
```rust
|
|
@@ -169,34 +169,62 @@ Use `--condition` to only apply the change when a sibling field matches:
|
|
|
169
169
|
yerba set config.yml "database.host" "0.0.0.0" --condition ".port == 5432"
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
+
Over a wildcard selector the condition picks out *which* items to edit, the same way it filters in
|
|
173
|
+
`get`. Items that do not match are left alone:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
yerba set videos.yml "[].title" "New Title" --condition ".id == talk-1"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
If the condition matches more than one item the command stops rather than guessing, so add `--all`
|
|
180
|
+
when editing several is what you meant:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
yerba set videos.yml "[].published" false --plain --condition ".kind == draft" --all
|
|
184
|
+
```
|
|
185
|
+
|
|
172
186
|
Use `--all` to update all nodes matching a wildcard selector:
|
|
173
187
|
|
|
174
188
|
```bash
|
|
175
189
|
yerba set videos.yml "[].description" "" --all
|
|
176
190
|
```
|
|
177
191
|
|
|
192
|
+
Values are written in the quote style already at the path, which keeps string edits tidy. A value is quoted anyway when writing it plain would not read back as the string you passed, so `yes` becomes `"yes"` and `12345` becomes `"12345"` — whatever the field held before. Use `--plain` to write a YAML literal instead:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
yerba set config.yml "database.replica" null --plain
|
|
196
|
+
yerba set config.yml "database.ssl" true --plain
|
|
197
|
+
```
|
|
198
|
+
|
|
178
199
|
### `insert`
|
|
179
200
|
|
|
180
201
|
Insert a new key into a map or a new item into a sequence. By default, new items are appended at the end.
|
|
181
202
|
|
|
182
203
|
```bash
|
|
183
|
-
yerba insert config.yml "database.ssl" true
|
|
204
|
+
yerba insert config.yml "database.ssl" true --plain
|
|
184
205
|
yerba insert config.yml "tags" "yaml"
|
|
185
206
|
```
|
|
186
207
|
|
|
187
208
|
Control placement with `--before`, `--after`, or `--at`:
|
|
188
209
|
|
|
189
210
|
```bash
|
|
190
|
-
yerba insert config.yml "database.ssl" true --after "host"
|
|
191
|
-
yerba insert config.yml "database.ssl" true --before "port"
|
|
211
|
+
yerba insert config.yml "database.ssl" true --plain --after "host"
|
|
212
|
+
yerba insert config.yml "database.ssl" true --plain --before "port"
|
|
192
213
|
yerba insert config.yml "tags" "yaml" --at 0
|
|
193
214
|
yerba insert config.yml "tags" "yaml" --after "ruby"
|
|
194
215
|
```
|
|
195
216
|
|
|
217
|
+
Values are written as strings, and quoted whenever writing them plain would change what they mean. `"Conf 2018: A Talk"` stays one string instead of becoming a nested map, and `"12345"` stays a string instead of becoming a number. Use `--plain` to write a YAML literal or a map or sequence fragment instead:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
yerba insert config.yml "database.ssl" true --plain
|
|
221
|
+
yerba insert speakers.yml "" "name: Bob" --plain --after ".name == Alice"
|
|
222
|
+
```
|
|
223
|
+
|
|
196
224
|
For sequences of maps, use conditions to position relative to other items:
|
|
197
225
|
|
|
198
226
|
```bash
|
|
199
|
-
yerba insert speakers.yml "" "name: Bob" --after ".name == Alice"
|
|
227
|
+
yerba insert speakers.yml "" "name: Bob" --plain --after ".name == Alice"
|
|
200
228
|
yerba insert videos.yml "[0].speakers" "Diana" --before ".name == Charlie"
|
|
201
229
|
```
|
|
202
230
|
|
|
@@ -324,17 +352,17 @@ yerba quote-style videos.yml "[].description" --values literal
|
|
|
324
352
|
|
|
325
353
|
**Value styles** (`--values`):
|
|
326
354
|
|
|
327
|
-
| Style | Symbol | Example | Behavior |
|
|
328
|
-
|
|
329
|
-
| `plain` | — | `host: localhost` | Unquoted |
|
|
330
|
-
| `single` | `'` | `host: 'localhost'` | Single-quoted |
|
|
331
|
-
| `double` | `"` | `host: "localhost"` | Double-quoted, supports `\n` escapes |
|
|
332
|
-
| `literal` |
|
|
333
|
-
| `literal-clip` |
|
|
334
|
-
| `literal-keep` |
|
|
335
|
-
| `folded` | `>-` | Folds newlines to spaces | Strip trailing newline |
|
|
336
|
-
| `folded-clip` | `>` | Folds newlines to spaces | Keep one trailing newline |
|
|
337
|
-
| `folded-keep` | `>+` | Folds newlines to spaces | Keep all trailing newlines |
|
|
355
|
+
| Style | Symbol | Example | Behavior |
|
|
356
|
+
|----------------|--------|--------------------------|--------------------------------------|
|
|
357
|
+
| `plain` | — | `host: localhost` | Unquoted |
|
|
358
|
+
| `single` | `'` | `host: 'localhost'` | Single-quoted |
|
|
359
|
+
| `double` | `"` | `host: "localhost"` | Double-quoted, supports `\n` escapes |
|
|
360
|
+
| `literal` | `\|-` | Preserves newlines | Strip trailing newline |
|
|
361
|
+
| `literal-clip` | `\|` | Preserves newlines | Keep one trailing newline |
|
|
362
|
+
| `literal-keep` | `\|+` | Preserves newlines | Keep all trailing newlines |
|
|
363
|
+
| `folded` | `>-` | Folds newlines to spaces | Strip trailing newline |
|
|
364
|
+
| `folded-clip` | `>` | Folds newlines to spaces | Keep one trailing newline |
|
|
365
|
+
| `folded-keep` | `>+` | Folds newlines to spaces | Keep all trailing newlines |
|
|
338
366
|
|
|
339
367
|
Block scalars are only converted when scoped to a specific selector. An unscoped `--values double` will not touch existing block scalars.
|
|
340
368
|
|
|
@@ -348,6 +376,52 @@ yerba blank-lines videos.yml "[]" 1
|
|
|
348
376
|
yerba blank-lines config.yml "tags" 0
|
|
349
377
|
```
|
|
350
378
|
|
|
379
|
+
By default every entry after the first is adjusted. Use `--before` to set apart only certain sections of a map and leave their neighbours alone:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
yerba blank-lines event.yml "" 1 --before "schedule,speakers"
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
```yaml
|
|
386
|
+
id: rubyconf-2024
|
|
387
|
+
name: RubyConf 2024
|
|
388
|
+
city: Chicago
|
|
389
|
+
|
|
390
|
+
schedule:
|
|
391
|
+
- day: 1
|
|
392
|
+
|
|
393
|
+
speakers:
|
|
394
|
+
- name: Alice
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`--after` is the mirror image, separating a key from what follows it. Listing a key in both sets it apart on both sides:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
yerba blank-lines event.yml "" 1 --before "location" --after "location"
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Keys that are not listed keep whatever spacing they already have, so these flags add separators without reflowing the rest of the document. Sequence entries have no key and are never matched by a filter, which means `--before` and `--after` on a sequence are no-ops.
|
|
404
|
+
|
|
405
|
+
Use `--skip-empty` to leave placeholder entries tucked against their neighbours. An entry counts as empty when its value is `null`, an empty collection, or an empty string:
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
yerba blank-lines event.yml "" 1 --before "location,speakers,sponsors" --after "location" --skip-empty
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
```yaml
|
|
412
|
+
id: rubyconf-2024
|
|
413
|
+
name: RubyConf 2024
|
|
414
|
+
|
|
415
|
+
location:
|
|
416
|
+
city: Chicago
|
|
417
|
+
country: US
|
|
418
|
+
|
|
419
|
+
speakers: []
|
|
420
|
+
sponsors: []
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
The match is ignored when the entry that *triggered* it is empty, not the one on the other side of the gap. That is why `--after "location"` still separates a populated `location:` block from the empty entries below it, while a bare `location: null` would stay packed. Without `--skip-empty`, every listed key gets its blank line whether or not it holds anything.
|
|
424
|
+
|
|
351
425
|
### `directives`
|
|
352
426
|
|
|
353
427
|
Add or remove the document start marker (`---`):
|
|
@@ -535,15 +609,16 @@ Available pipeline steps:
|
|
|
535
609
|
- `sequence_indent` Enforce compact or indented sequence style
|
|
536
610
|
- `sort_keys` Reorder keys to match a predefined list
|
|
537
611
|
- `sort` Sort sequence items by field(s)
|
|
538
|
-
- `blank_lines` Enforce blank lines between sequence entries
|
|
539
|
-
- `set` Set a value (supports conditions)
|
|
540
|
-
- `insert` Insert a new key or sequence item
|
|
612
|
+
- `blank_lines` Enforce blank lines between sequence entries, optionally limited to named map keys with `before` and to non-empty values with `skip_empty`
|
|
613
|
+
- `set` Set a value (supports conditions, and `plain: true` for YAML literals)
|
|
614
|
+
- `insert` Insert a new key or sequence item (`plain: true` for YAML literals and fragments)
|
|
541
615
|
- `delete` Remove a key (supports conditions)
|
|
542
616
|
- `rename` Rename a key
|
|
543
617
|
- `remove` Remove an item from a sequence
|
|
544
618
|
- `directives` Add or remove the document start marker (`---`), with optional `max` validation
|
|
545
619
|
- `unique` Find or remove duplicate items in a sequence
|
|
546
620
|
- `schema` Validate against a JSON schema (with optional `path` for scoping)
|
|
621
|
+
- `escapes` Spell escaped characters literally where the quote style can carry them (with optional `path` for scoping)
|
|
547
622
|
- `final_newline` Enforce exact number of trailing newlines (`count: 1` by default, strips extras)
|
|
548
623
|
|
|
549
624
|
This makes it easy to enforce project-wide YAML conventions in CI:
|
|
@@ -552,6 +627,36 @@ This makes it easy to enforce project-wide YAML conventions in CI:
|
|
|
552
627
|
yerba check
|
|
553
628
|
```
|
|
554
629
|
|
|
630
|
+
The `escapes` step rewrites double-quoted values in the form yerba would write them, so a character the style can carry is spelled out rather than escaped. Text that arrives from an API or a scraper often carries `\U0001F631` where an emoji belongs, or a long value wrapped across two lines. Both read back correctly either way, but neither is pleasant to review in a diff:
|
|
631
|
+
|
|
632
|
+
```yaml
|
|
633
|
+
pipeline:
|
|
634
|
+
- escapes: {}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
```diff
|
|
638
|
+
- title: "Deploying on a Friday \U0001F631"
|
|
639
|
+
+ title: "Deploying on a Friday 😱"
|
|
640
|
+
|
|
641
|
+
- summary: "Why the deploy went out on a Friday, and
|
|
642
|
+
- what we changed afterwards"
|
|
643
|
+
+ summary: "Why the deploy went out on a Friday, and what we changed afterwards"
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
Escapes that have to stay do: a control character, a quote or a backslash is still written escaped, since the value would not read back the same otherwise. Plain, single-quoted and block scalars are left alone, and a `path` scopes the step to part of the document.
|
|
647
|
+
|
|
648
|
+
Every checked file is also scanned for control characters, whatever the pipeline says. Yerba's parser accepts them but libyaml does not, so without this a file could pass `check` in CI and then fail to load in the application that reads it. Offending characters are reported with their line and column:
|
|
649
|
+
|
|
650
|
+
```console
|
|
651
|
+
$ yerba check
|
|
652
|
+
✗ data/rubyconf-taiwan/videos.yml
|
|
653
|
+
found 2 control characters that YAML parsers reject:
|
|
654
|
+
U+0008 at line 152, column 34
|
|
655
|
+
U+0008 at line 154, column 12
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
Tabs, newlines and carriage returns are fine, and so are characters written escaped as `\xNN` inside a double-quoted value — only literal ones are reported.
|
|
659
|
+
|
|
555
660
|
## Ruby API
|
|
556
661
|
|
|
557
662
|
Yerba includes a native C extension (backed by the same Rust core) that provides a full Ruby API for YAML editing.
|
|
@@ -718,6 +823,42 @@ Insert new keys with positional control:
|
|
|
718
823
|
document["database"].insert("ssl", true, after: "host")
|
|
719
824
|
```
|
|
720
825
|
|
|
826
|
+
A `String` is written as a value, not as YAML text, and quoted whenever writing it plain would change what it means. This is the same rule the CLI's `insert` and `set` follow, so `"key: value"` stays one string instead of becoming a nested map:
|
|
827
|
+
|
|
828
|
+
```ruby
|
|
829
|
+
document["notes"] = "key: value" # => notes: "key: value"
|
|
830
|
+
document["lines"] = "line\nbreak" # => lines: "line\nbreak"
|
|
831
|
+
document["item"] = "- a" # => item: "- a"
|
|
832
|
+
document["tags"] = "[a, b]" # => tags: "[a, b]"
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
Other types are written as the YAML literal they stand for, so the value you read back is the value you wrote:
|
|
836
|
+
|
|
837
|
+
```ruby
|
|
838
|
+
document["port"] = 5432 # => port: 5432
|
|
839
|
+
document["ssl"] = true # => ssl: true
|
|
840
|
+
document["replica"] = nil # => replica: null
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
Both `set` and `insert` take `plain: true` when the value is YAML text you built yourself, to write it verbatim instead:
|
|
844
|
+
|
|
845
|
+
```ruby
|
|
846
|
+
document.set("tags", "[a, b]", plain: true) # => tags: [a, b]
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
```ruby
|
|
850
|
+
document.insert("tags", "- ruby\n- rails", plain: true)
|
|
851
|
+
# tags:
|
|
852
|
+
# - ruby
|
|
853
|
+
# - rails
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
Use `style:` to pick the quote style for the inserted value. A style that cannot carry the value falls back to double quotes rather than writing something that reads back differently:
|
|
857
|
+
|
|
858
|
+
```ruby
|
|
859
|
+
document.insert("city", "Berlin", style: :double) # => city: "Berlin"
|
|
860
|
+
```
|
|
861
|
+
|
|
721
862
|
Set arrays and hashes as values, they default to block style:
|
|
722
863
|
|
|
723
864
|
```ruby
|
|
@@ -823,6 +964,16 @@ scalar.quote_style # => :double
|
|
|
823
964
|
scalar.quote_style = :single
|
|
824
965
|
```
|
|
825
966
|
|
|
967
|
+
`Yerba.quote_scalar` writes a value as YAML text without needing a document, using the rule the insert APIs follow. Pass a style to ask for one, and it is honoured unless the value cannot survive it:
|
|
968
|
+
|
|
969
|
+
```ruby
|
|
970
|
+
Yerba.quote_scalar("plain") # => "plain"
|
|
971
|
+
Yerba.quote_scalar("key: value") # => "\"key: value\""
|
|
972
|
+
Yerba.quote_scalar("12345") # => "\"12345\""
|
|
973
|
+
Yerba.quote_scalar("ok", :single) # => "'ok'"
|
|
974
|
+
Yerba.quote_scalar("a\nb", :single) # => "\"a\\nb\""
|
|
975
|
+
```
|
|
976
|
+
|
|
826
977
|
### Collection Style
|
|
827
978
|
|
|
828
979
|
Read and change how collections are rendered, flow (inline) or block (multi-line):
|
data/ext/yerba/include/yerba.h
CHANGED
|
@@ -129,13 +129,18 @@ char *yerba_document_find(const struct Document *document,
|
|
|
129
129
|
struct YerbaResult yerba_document_set(struct Document *document,
|
|
130
130
|
const char *path,
|
|
131
131
|
const char *value,
|
|
132
|
+
uintptr_t value_length,
|
|
132
133
|
enum YerbaValueType value_type,
|
|
134
|
+
bool plain,
|
|
133
135
|
bool all);
|
|
134
136
|
|
|
135
137
|
struct YerbaResult yerba_document_insert(struct Document *document,
|
|
136
138
|
const char *path,
|
|
137
139
|
const char *value,
|
|
140
|
+
uintptr_t value_length,
|
|
138
141
|
enum YerbaValueType value_type,
|
|
142
|
+
bool plain,
|
|
143
|
+
const char *style,
|
|
139
144
|
const char *before,
|
|
140
145
|
const char *after,
|
|
141
146
|
int64_t at);
|
|
@@ -211,6 +216,8 @@ struct YerbaResult yerba_document_apply_yerbafile(struct Document *document,
|
|
|
211
216
|
|
|
212
217
|
char *yerba_document_to_string(const struct Document *document);
|
|
213
218
|
|
|
219
|
+
char *yerba_quote_scalar(const char *value, uintptr_t length, const char *style);
|
|
220
|
+
|
|
214
221
|
void yerba_string_free(char *s);
|
|
215
222
|
|
|
216
223
|
void yerba_result_free(struct YerbaResult result);
|
data/ext/yerba/yerba.c
CHANGED
|
@@ -414,6 +414,31 @@ static VALUE document_resolve_selectors(VALUE self, VALUE path) {
|
|
|
414
414
|
return rb_funcall(rb_path2class("JSON"), rb_intern("parse"), 1, json_string);
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
+
/* Yerba.quote_scalar(value, style = nil) → YAML text for value, quoted when the style cannot carry it. */
|
|
418
|
+
static VALUE yerba_s_quote_scalar(int argc, VALUE *argv, VALUE self) {
|
|
419
|
+
VALUE value, style_value;
|
|
420
|
+
rb_scan_args(argc, argv, "11", &value, &style_value);
|
|
421
|
+
|
|
422
|
+
StringValue(value);
|
|
423
|
+
|
|
424
|
+
const char *style = NULL;
|
|
425
|
+
|
|
426
|
+
if (!NIL_P(style_value)) {
|
|
427
|
+
VALUE style_string = rb_obj_is_kind_of(style_value, rb_cString) ? style_value : rb_sym2str(style_value);
|
|
428
|
+
style = StringValueCStr(style_string);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
char *quoted = yerba_quote_scalar(RSTRING_PTR(value), (size_t)RSTRING_LEN(value), style);
|
|
432
|
+
|
|
433
|
+
if (quoted == NULL) return value;
|
|
434
|
+
|
|
435
|
+
VALUE result = make_utf8_string(quoted);
|
|
436
|
+
|
|
437
|
+
yerba_string_free(quoted);
|
|
438
|
+
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
441
|
+
|
|
417
442
|
/* document.get_quote_style(path) → :plain, :single, :double, :literal, etc. or nil */
|
|
418
443
|
static VALUE document_get_quote_style(VALUE self, VALUE path) {
|
|
419
444
|
struct Document *document = get_document(self);
|
|
@@ -574,6 +599,7 @@ static VALUE document_find(int argc, VALUE *argv, VALUE self) {
|
|
|
574
599
|
The caller must provide a number_buffer of at least 64 bytes. */
|
|
575
600
|
struct TypedValue {
|
|
576
601
|
const char *text;
|
|
602
|
+
size_t length;
|
|
577
603
|
YerbaValueType type;
|
|
578
604
|
};
|
|
579
605
|
|
|
@@ -598,14 +624,21 @@ static struct TypedValue ruby_to_typed_value(VALUE value, char *number_buffer) {
|
|
|
598
624
|
result.text = number_buffer;
|
|
599
625
|
result.type = YERBA_VALUE_TYPE_FLOAT;
|
|
600
626
|
} else {
|
|
601
|
-
|
|
627
|
+
StringValue(value);
|
|
628
|
+
|
|
629
|
+
result.text = RSTRING_PTR(value);
|
|
630
|
+
result.length = (size_t)RSTRING_LEN(value);
|
|
602
631
|
result.type = YERBA_VALUE_TYPE_STRING;
|
|
632
|
+
|
|
633
|
+
return result;
|
|
603
634
|
}
|
|
604
635
|
|
|
636
|
+
result.length = strlen(result.text);
|
|
637
|
+
|
|
605
638
|
return result;
|
|
606
639
|
}
|
|
607
640
|
|
|
608
|
-
/* document.set(path, value, condition: nil, if_exists: false, if_missing: false) */
|
|
641
|
+
/* document.set(path, value, condition: nil, if_exists: false, if_missing: false, plain: false, all: false) */
|
|
609
642
|
static VALUE document_set(int argc, VALUE *argv, VALUE self) {
|
|
610
643
|
VALUE path, value, opts;
|
|
611
644
|
rb_scan_args(argc, argv, "2:", &path, &value, &opts);
|
|
@@ -625,43 +658,58 @@ static VALUE document_set(int argc, VALUE *argv, VALUE self) {
|
|
|
625
658
|
char number_buffer[64];
|
|
626
659
|
struct TypedValue typed_value = ruby_to_typed_value(value, number_buffer);
|
|
627
660
|
bool all = false;
|
|
661
|
+
bool plain = false;
|
|
628
662
|
|
|
629
663
|
if (!NIL_P(opts)) {
|
|
630
664
|
VALUE v_all = rb_hash_aref(opts, ID2SYM(rb_intern("all")));
|
|
665
|
+
VALUE v_plain = rb_hash_aref(opts, ID2SYM(rb_intern("plain")));
|
|
631
666
|
|
|
632
667
|
if (RTEST(v_all)) all = true;
|
|
668
|
+
if (RTEST(v_plain)) plain = true;
|
|
633
669
|
}
|
|
634
670
|
|
|
635
|
-
YerbaResult result = yerba_document_set(document, StringValueCStr(path), typed_value.text, typed_value.type, all);
|
|
671
|
+
YerbaResult result = yerba_document_set(document, StringValueCStr(path), typed_value.text, typed_value.length, typed_value.type, plain, all);
|
|
636
672
|
check_result(result);
|
|
637
673
|
|
|
638
674
|
return self;
|
|
639
675
|
}
|
|
640
676
|
|
|
641
|
-
/* document.insert(path, value, before: nil, after: nil, at: nil) */
|
|
677
|
+
/* document.insert(path, value, before: nil, after: nil, at: nil, plain: false, style: nil) */
|
|
642
678
|
static VALUE document_insert(int argc, VALUE *argv, VALUE self) {
|
|
643
679
|
VALUE path, value, opts;
|
|
644
680
|
rb_scan_args(argc, argv, "2:", &path, &value, &opts);
|
|
645
681
|
|
|
646
682
|
const char *before = NULL;
|
|
647
683
|
const char *after = NULL;
|
|
684
|
+
const char *style = NULL;
|
|
648
685
|
long long at = -1;
|
|
686
|
+
bool plain = false;
|
|
649
687
|
|
|
650
688
|
if (!NIL_P(opts)) {
|
|
651
689
|
VALUE v_before = rb_hash_aref(opts, ID2SYM(rb_intern("before")));
|
|
652
690
|
VALUE v_after = rb_hash_aref(opts, ID2SYM(rb_intern("after")));
|
|
653
691
|
VALUE v_at = rb_hash_aref(opts, ID2SYM(rb_intern("at")));
|
|
692
|
+
VALUE v_plain = rb_hash_aref(opts, ID2SYM(rb_intern("plain")));
|
|
693
|
+
VALUE v_style = rb_hash_aref(opts, ID2SYM(rb_intern("style")));
|
|
654
694
|
|
|
655
695
|
if (!NIL_P(v_before)) before = StringValueCStr(v_before);
|
|
656
696
|
if (!NIL_P(v_after)) after = StringValueCStr(v_after);
|
|
657
697
|
if (!NIL_P(v_at)) at = NUM2LL(v_at);
|
|
698
|
+
if (RTEST(v_plain)) plain = true;
|
|
699
|
+
|
|
700
|
+
if (!NIL_P(v_style)) {
|
|
701
|
+
VALUE style_string = rb_obj_is_kind_of(v_style, rb_cString) ? v_style : rb_sym2str(v_style);
|
|
702
|
+
style = StringValueCStr(style_string);
|
|
703
|
+
}
|
|
658
704
|
}
|
|
659
705
|
|
|
660
706
|
char number_buffer[64];
|
|
661
707
|
struct TypedValue typed_value = ruby_to_typed_value(value, number_buffer);
|
|
662
708
|
|
|
663
709
|
struct Document *document = get_document(self);
|
|
664
|
-
YerbaResult result =
|
|
710
|
+
YerbaResult result =
|
|
711
|
+
yerba_document_insert(document, StringValueCStr(path), typed_value.text, typed_value.length, typed_value.type, plain, style,
|
|
712
|
+
before, after, at);
|
|
665
713
|
check_result(result);
|
|
666
714
|
|
|
667
715
|
return self;
|
|
@@ -1122,6 +1170,7 @@ void Init_yerba(void) {
|
|
|
1122
1170
|
rb_require("json");
|
|
1123
1171
|
|
|
1124
1172
|
rb_mYerba = rb_define_module("Yerba");
|
|
1173
|
+
rb_define_module_function(rb_mYerba, "quote_scalar", yerba_s_quote_scalar, -1);
|
|
1125
1174
|
rb_eError = rb_define_class_under(rb_mYerba, "Error", rb_eStandardError);
|
|
1126
1175
|
rb_ePathNotFoundError = rb_define_class_under(rb_mYerba, "PathNotFoundError", rb_eError);
|
|
1127
1176
|
rb_eParseError = rb_define_class_under(rb_mYerba, "ParseError", rb_eError);
|
data/lib/yerba/formatting.rb
CHANGED
|
@@ -3,16 +3,7 @@
|
|
|
3
3
|
module Yerba
|
|
4
4
|
module Formatting
|
|
5
5
|
def self.quote(value, style)
|
|
6
|
-
|
|
7
|
-
when :double
|
|
8
|
-
escaped = value.to_s.gsub("\\", "\\\\").gsub('"', '\\"')
|
|
9
|
-
"\"#{escaped}\""
|
|
10
|
-
when :single
|
|
11
|
-
escaped = value.to_s.gsub("'", "''")
|
|
12
|
-
"'#{escaped}'"
|
|
13
|
-
else
|
|
14
|
-
value.to_s
|
|
15
|
-
end
|
|
6
|
+
Yerba.quote_scalar(value.to_s, style)
|
|
16
7
|
end
|
|
17
8
|
|
|
18
9
|
def self.to_yaml_value(value)
|
|
@@ -55,9 +46,13 @@ module Yerba
|
|
|
55
46
|
return "{}" if value.empty?
|
|
56
47
|
|
|
57
48
|
value.map { |key, value|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
case value
|
|
50
|
+
when Array, Hash
|
|
51
|
+
if value.empty?
|
|
52
|
+
"#{prefix}#{key}: #{to_yaml_value(value)}"
|
|
53
|
+
else
|
|
54
|
+
"#{prefix}#{key}:\n#{to_block_yaml_value(value, indent + 1)}"
|
|
55
|
+
end
|
|
61
56
|
else
|
|
62
57
|
"#{prefix}#{key}: #{to_scalar_value(value)}"
|
|
63
58
|
end
|
|
@@ -72,6 +67,7 @@ module Yerba
|
|
|
72
67
|
when true then "true"
|
|
73
68
|
when false then "false"
|
|
74
69
|
when nil then "null"
|
|
70
|
+
when String then Yerba.quote_scalar(value)
|
|
75
71
|
else value.to_s
|
|
76
72
|
end
|
|
77
73
|
end
|
data/lib/yerba/map.rb
CHANGED
|
@@ -32,11 +32,11 @@ module Yerba
|
|
|
32
32
|
if connected?
|
|
33
33
|
new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
|
|
34
34
|
coerced = coerce_value(value, style: style)
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
is_collection = value.is_a?(Array) || value.is_a?(Hash)
|
|
37
|
+
is_block_value = is_collection && !coerced.start_with?("{", "[")
|
|
36
38
|
|
|
37
39
|
if document.exists?(new_path) && is_block_value
|
|
38
|
-
# Block-style collections can't replace a scalar via set().
|
|
39
|
-
# Delete the key and re-insert at the same position.
|
|
40
40
|
all_keys = keys
|
|
41
41
|
key_index = all_keys.index(key.to_s)
|
|
42
42
|
after_key = key_index&.positive? ? all_keys[key_index - 1] : nil
|
|
@@ -44,14 +44,14 @@ module Yerba
|
|
|
44
44
|
document.delete(new_path)
|
|
45
45
|
|
|
46
46
|
if after_key
|
|
47
|
-
document.insert(new_path, coerced, after: after_key)
|
|
47
|
+
document.insert(new_path, coerced, after: after_key, plain: true)
|
|
48
48
|
else
|
|
49
|
-
document.insert(new_path, coerced)
|
|
49
|
+
document.insert(new_path, coerced, plain: true)
|
|
50
50
|
end
|
|
51
51
|
elsif document.exists?(new_path)
|
|
52
|
-
document.set(new_path, coerced)
|
|
52
|
+
document.set(new_path, coerced, plain: is_collection)
|
|
53
53
|
else
|
|
54
|
-
document.insert(new_path, coerced)
|
|
54
|
+
document.insert(new_path, coerced, plain: is_collection)
|
|
55
55
|
end
|
|
56
56
|
else
|
|
57
57
|
@data[key] = value
|
|
@@ -62,7 +62,10 @@ module Yerba
|
|
|
62
62
|
if connected?
|
|
63
63
|
new_path = @selector.empty? ? key.to_s : "#{@selector}.#{key}"
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
coerced = coerce_value(value, style: style)
|
|
66
|
+
is_collection = value.is_a?(Array) || value.is_a?(Hash)
|
|
67
|
+
|
|
68
|
+
document.insert(new_path, coerced, before: before, after: after, plain: is_collection)
|
|
66
69
|
else
|
|
67
70
|
@data[key] = value
|
|
68
71
|
end
|
data/lib/yerba/sequence.rb
CHANGED
|
@@ -60,10 +60,9 @@ module Yerba
|
|
|
60
60
|
when Hash
|
|
61
61
|
document.insert_object(@selector, item)
|
|
62
62
|
when Scalar
|
|
63
|
-
document.insert(@selector, item.to_yaml)
|
|
63
|
+
document.insert(@selector, item.to_yaml, plain: true)
|
|
64
64
|
else
|
|
65
|
-
|
|
66
|
-
document.insert(@selector, formatted)
|
|
65
|
+
document.insert(@selector, item.to_s, style: detect_quote_style)
|
|
67
66
|
end
|
|
68
67
|
else
|
|
69
68
|
@data << item
|
|
@@ -425,10 +424,6 @@ module Yerba
|
|
|
425
424
|
current
|
|
426
425
|
end
|
|
427
426
|
|
|
428
|
-
def format_for_insert(value)
|
|
429
|
-
Formatting.quote(value, detect_quote_style)
|
|
430
|
-
end
|
|
431
|
-
|
|
432
427
|
def detect_quote_style
|
|
433
428
|
document.get_quote_style("#{@selector}[0]")
|
|
434
429
|
end
|
data/lib/yerba/version.rb
CHANGED
data/rust/Cargo.toml
CHANGED
|
@@ -11,6 +11,8 @@ static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
|
|
|
11
11
|
yerba blank-lines videos.yml "[]" 1
|
|
12
12
|
yerba blank-lines videos.yml "[].speakers" 1
|
|
13
13
|
yerba blank-lines config.yml "tags" 0
|
|
14
|
+
yerba blank-lines event.yml "" 1 --before "schedule,speakers"
|
|
15
|
+
yerba blank-lines event.yml "" 1 --after "location" --skip-empty
|
|
14
16
|
"#})
|
|
15
17
|
});
|
|
16
18
|
|
|
@@ -26,6 +28,14 @@ pub struct Args {
|
|
|
26
28
|
first: String,
|
|
27
29
|
/// Count (when selector is provided as first positional)
|
|
28
30
|
second: Option<usize>,
|
|
31
|
+
/// Comma-separated map keys to separate from what precedes them
|
|
32
|
+
#[arg(long)]
|
|
33
|
+
before: Option<String>,
|
|
34
|
+
/// Comma-separated map keys to separate from what follows them
|
|
35
|
+
#[arg(long)]
|
|
36
|
+
after: Option<String>,
|
|
37
|
+
#[arg(long)]
|
|
38
|
+
skip_empty: bool,
|
|
29
39
|
#[arg(long)]
|
|
30
40
|
dry_run: bool,
|
|
31
41
|
}
|
|
@@ -44,12 +54,22 @@ impl Args {
|
|
|
44
54
|
std::process::exit(1);
|
|
45
55
|
};
|
|
46
56
|
|
|
57
|
+
let options = yerba::BlankLineOptions {
|
|
58
|
+
before: split_keys(self.before.as_deref()),
|
|
59
|
+
after: split_keys(self.after.as_deref()),
|
|
60
|
+
skip_empty: self.skip_empty,
|
|
61
|
+
};
|
|
62
|
+
|
|
47
63
|
for resolved_file in resolve_files(&self.file) {
|
|
48
64
|
let mut document = parse_file(&resolved_file);
|
|
49
65
|
|
|
50
|
-
if document.
|
|
66
|
+
if document.enforce_blank_lines_with(selector, count, &options).is_ok() {
|
|
51
67
|
output(&resolved_file, &document, self.dry_run);
|
|
52
68
|
}
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
}
|
|
72
|
+
|
|
73
|
+
fn split_keys(keys: Option<&str>) -> Vec<String> {
|
|
74
|
+
keys.map(|keys| keys.split(',').map(|key| key.trim().to_string()).collect()).unwrap_or_default()
|
|
75
|
+
}
|
data/rust/src/commands/init.rs
CHANGED
|
@@ -45,6 +45,12 @@ pub fn run() {
|
|
|
45
45
|
# # Enforce blank lines between sequence entries
|
|
46
46
|
# - blank_lines:
|
|
47
47
|
# count: 1
|
|
48
|
+
# # skip_empty: true # leave null/empty entries packed
|
|
49
|
+
# # before: # separate these keys from what precedes them
|
|
50
|
+
# # - schedule
|
|
51
|
+
# # - speakers
|
|
52
|
+
# # after: # separate these keys from what follows them
|
|
53
|
+
# # - location
|
|
48
54
|
#
|
|
49
55
|
# # Set a value (with optional condition)
|
|
50
56
|
# - set:
|