yerba 0.6.1 → 0.7.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 +148 -20
- data/ext/yerba/yerba.c +39 -5
- data/lib/yerba/document.rb +31 -0
- data/lib/yerba/formatting.rb +11 -0
- data/lib/yerba/version.rb +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/document/insert.rs +135 -8
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b0696495b8437f9deea398cba7c4e6af00a8983ffa0cb519492aca22426ba46
|
|
4
|
+
data.tar.gz: 42daabe671e951b350286a5ec6a63690466aff8f8b8828ebd8e570e8fc8758d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e245101d150f74c63f49a8caaa7e78f7966f25934ee6678a42629b4633758d6f74e3e638892dd4972ec11f0f2c734f93df85debebe809abcbaad19ca67380f5
|
|
7
|
+
data.tar.gz: 289f195705e6d427bb6f263b8dc826b1107923cdd7022594d2806e47906f6acac3011ae9fb40802e6313bda281d66662168a93c525d5868e14e9435a99758f8b
|
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.7"
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
```rust
|
|
@@ -476,49 +476,58 @@ yerba check
|
|
|
476
476
|
yerba check path/to/file.yml
|
|
477
477
|
```
|
|
478
478
|
|
|
479
|
-
|
|
479
|
+
A Yerbafile supports a global `pipeline` that runs on all matching files, plus per-rule pipelines for file-specific steps. Global steps run first, then per-rule steps refine or override:
|
|
480
480
|
|
|
481
481
|
```yaml
|
|
482
|
+
files: "data/**/*.yml"
|
|
483
|
+
|
|
484
|
+
pipeline:
|
|
485
|
+
- directives:
|
|
486
|
+
max: 1
|
|
487
|
+
ensure: true
|
|
488
|
+
|
|
489
|
+
- collection_style:
|
|
490
|
+
style: block
|
|
491
|
+
|
|
492
|
+
- sequence_indent:
|
|
493
|
+
style: indented
|
|
494
|
+
|
|
495
|
+
- quote_style:
|
|
496
|
+
key_style: plain
|
|
497
|
+
value_style: double
|
|
498
|
+
|
|
482
499
|
rules:
|
|
483
|
-
- files: "
|
|
500
|
+
- files: "data/**/videos.yml"
|
|
484
501
|
pipeline:
|
|
485
502
|
- quote_style:
|
|
486
|
-
|
|
487
|
-
value_style:
|
|
503
|
+
path: "[].speakers"
|
|
504
|
+
value_style: plain
|
|
488
505
|
|
|
489
506
|
- sort_keys:
|
|
490
|
-
path: ""
|
|
507
|
+
path: "[]"
|
|
491
508
|
order:
|
|
492
509
|
- id
|
|
493
510
|
- title
|
|
494
|
-
-
|
|
495
|
-
|
|
496
|
-
- blank_lines:
|
|
497
|
-
count: 1
|
|
511
|
+
- speakers
|
|
498
512
|
|
|
499
513
|
- files: "data/speakers.yml"
|
|
500
514
|
pipeline:
|
|
501
|
-
- quote_style:
|
|
502
|
-
key_style: plain
|
|
503
|
-
value_style: double
|
|
504
|
-
|
|
505
515
|
- sort_keys:
|
|
506
|
-
path: ""
|
|
516
|
+
path: "[]"
|
|
507
517
|
order:
|
|
508
518
|
- name
|
|
509
519
|
- slug
|
|
510
520
|
- github
|
|
511
|
-
- twitter
|
|
512
|
-
- website
|
|
513
521
|
|
|
514
522
|
- sort:
|
|
515
|
-
path: ""
|
|
516
523
|
by: name
|
|
517
524
|
```
|
|
518
525
|
|
|
519
526
|
Available pipeline steps:
|
|
520
527
|
|
|
521
528
|
- `quote_style` Enforce quote style on keys and/or values, optionally scoped by path
|
|
529
|
+
- `collection_style` Enforce flow or block style on collections
|
|
530
|
+
- `sequence_indent` Enforce compact or indented sequence style
|
|
522
531
|
- `sort_keys` Reorder keys to match a predefined list
|
|
523
532
|
- `sort` Sort sequence items by field(s)
|
|
524
533
|
- `blank_lines` Enforce blank lines between sequence entries
|
|
@@ -527,10 +536,9 @@ Available pipeline steps:
|
|
|
527
536
|
- `delete` Remove a key (supports conditions)
|
|
528
537
|
- `rename` Rename a key
|
|
529
538
|
- `remove` Remove an item from a sequence
|
|
530
|
-
- `directives` Add or remove the document start marker (`---`)
|
|
539
|
+
- `directives` Add or remove the document start marker (`---`), with optional `max` validation
|
|
531
540
|
- `unique` Find or remove duplicate items in a sequence
|
|
532
541
|
- `schema` Validate against a JSON schema (with optional `path` for scoping)
|
|
533
|
-
- `get` Read a value and store it as a variable for subsequent steps
|
|
534
542
|
|
|
535
543
|
This makes it easy to enforce project-wide YAML conventions in CI:
|
|
536
544
|
|
|
@@ -558,6 +566,65 @@ document = Yerba.parse(<<~YAML)
|
|
|
558
566
|
YAML
|
|
559
567
|
```
|
|
560
568
|
|
|
569
|
+
### Creating Documents
|
|
570
|
+
|
|
571
|
+
Build documents from Ruby objects:
|
|
572
|
+
|
|
573
|
+
```ruby
|
|
574
|
+
document = Yerba::Document.from({ name: "Alice", tags: ["ruby", "rails"] })
|
|
575
|
+
document = Yerba::Document.from([{ id: "talk-1", title: "First Talk" }])
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Build incrementally using `Document.new` and `root=`:
|
|
579
|
+
|
|
580
|
+
```ruby
|
|
581
|
+
document = Yerba::Document.new
|
|
582
|
+
document.root = {}
|
|
583
|
+
|
|
584
|
+
document["name"] = "Event 123"
|
|
585
|
+
document["kind"] = "conference"
|
|
586
|
+
document["tags"] = ["ruby", "rails"]
|
|
587
|
+
document["config"] = { host: "localhost", port: 5432 }
|
|
588
|
+
|
|
589
|
+
document.save_to!("event.yml")
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
```yaml
|
|
593
|
+
---
|
|
594
|
+
name: Event 123
|
|
595
|
+
kind: conference
|
|
596
|
+
tags:
|
|
597
|
+
- ruby
|
|
598
|
+
- rails
|
|
599
|
+
config:
|
|
600
|
+
host: localhost
|
|
601
|
+
port: 5432
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Or start with `Document.from` and keep building:
|
|
605
|
+
|
|
606
|
+
```ruby
|
|
607
|
+
document = Yerba::Document.from({ name: "Event 123" })
|
|
608
|
+
|
|
609
|
+
document["tags"] = []
|
|
610
|
+
document["tags"] << "ruby"
|
|
611
|
+
document["tags"] << "rails"
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Build a sequence document and append entries:
|
|
615
|
+
|
|
616
|
+
```ruby
|
|
617
|
+
document = Yerba::Document.new
|
|
618
|
+
document.root = []
|
|
619
|
+
|
|
620
|
+
document << { id: "talk-1", title: "First Talk", speakers: ["Alice"] }
|
|
621
|
+
document << { id: "talk-2", title: "Second Talk", speakers: ["Bob", "Carol"] }
|
|
622
|
+
|
|
623
|
+
document.save_to!("videos.yml")
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
`document["key"]` and `document << item` are shortcuts for `document.root["key"]` and `document.root << item`.
|
|
627
|
+
|
|
561
628
|
### Reading Values
|
|
562
629
|
|
|
563
630
|
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.
|
|
@@ -639,6 +706,27 @@ Insert new keys with positional control:
|
|
|
639
706
|
document["database"].insert("ssl", true, after: "host")
|
|
640
707
|
```
|
|
641
708
|
|
|
709
|
+
Set arrays and hashes as values, they default to block style:
|
|
710
|
+
|
|
711
|
+
```ruby
|
|
712
|
+
document["database"]["tags"] = ["ruby", "rails"]
|
|
713
|
+
# tags:
|
|
714
|
+
# - ruby
|
|
715
|
+
# - rails
|
|
716
|
+
|
|
717
|
+
document["database"]["settings"] = { pool: 5, timeout: 30 }
|
|
718
|
+
# settings:
|
|
719
|
+
# pool: 5
|
|
720
|
+
# timeout: 30
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
Use `set` with `style: :flow` for inline formatting:
|
|
724
|
+
|
|
725
|
+
```ruby
|
|
726
|
+
document.root.set("tags", ["ruby", "rails"], style: :flow)
|
|
727
|
+
# tags: [ruby, rails]
|
|
728
|
+
```
|
|
729
|
+
|
|
642
730
|
Work with sequences using familiar Ruby patterns:
|
|
643
731
|
|
|
644
732
|
```ruby
|
|
@@ -723,6 +811,40 @@ scalar.quote_style # => :double
|
|
|
723
811
|
scalar.quote_style = :single
|
|
724
812
|
```
|
|
725
813
|
|
|
814
|
+
### Collection Style
|
|
815
|
+
|
|
816
|
+
Read and change how collections are rendered, flow (inline) or block (multi-line):
|
|
817
|
+
|
|
818
|
+
```ruby
|
|
819
|
+
document["tags"].collection_style # => :block or :flow
|
|
820
|
+
document["tags"].collection_style = :flow # => tags: [ruby, rails]
|
|
821
|
+
document["tags"].collection_style = :block # => tags:\n - ruby\n - rails
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
Works on both sequences and maps:
|
|
825
|
+
|
|
826
|
+
```ruby
|
|
827
|
+
document["database"].collection_style = :flow # => database: {host: localhost, port: 5432}
|
|
828
|
+
document["database"].collection_style = :block # => database:\n host: localhost\n port: 5432
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
### Sequence Indent
|
|
832
|
+
|
|
833
|
+
Control whether sequence items are indented under their key or at the same level:
|
|
834
|
+
|
|
835
|
+
```ruby
|
|
836
|
+
document["tags"].sequence_indent # => :indented or :compact
|
|
837
|
+
document["tags"].sequence_indent = :compact # compact style
|
|
838
|
+
document["tags"].sequence_indent = :indented # indented style
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
```yaml
|
|
842
|
+
# :compact # :indented
|
|
843
|
+
tags: tags:
|
|
844
|
+
- ruby - ruby
|
|
845
|
+
- rails - rails
|
|
846
|
+
```
|
|
847
|
+
|
|
726
848
|
### Location
|
|
727
849
|
|
|
728
850
|
Get the precise location (line, column, byte offset) of any selector in a document:
|
|
@@ -835,6 +957,12 @@ Write changes back to the original file:
|
|
|
835
957
|
document.save!
|
|
836
958
|
```
|
|
837
959
|
|
|
960
|
+
Save to a new path:
|
|
961
|
+
|
|
962
|
+
```ruby
|
|
963
|
+
document.save_to!("output.yml")
|
|
964
|
+
```
|
|
965
|
+
|
|
838
966
|
Or render the document as a string without writing to disk:
|
|
839
967
|
|
|
840
968
|
```ruby
|
data/ext/yerba/yerba.c
CHANGED
|
@@ -110,9 +110,20 @@ static int should_proceed(struct Document *document, VALUE opts) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/* Document.new(path) */
|
|
113
|
-
static VALUE document_initialize(VALUE
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
static VALUE document_initialize(int argc, VALUE *argv, VALUE self) {
|
|
114
|
+
VALUE path;
|
|
115
|
+
rb_scan_args(argc, argv, "01", &path);
|
|
116
|
+
|
|
117
|
+
YerbaParseResult result;
|
|
118
|
+
|
|
119
|
+
if (NIL_P(path)) {
|
|
120
|
+
result = yerba_document_parse("---\n");
|
|
121
|
+
rb_iv_set(self, "@path", Qnil);
|
|
122
|
+
} else {
|
|
123
|
+
const char *file_path = StringValueCStr(path);
|
|
124
|
+
result = yerba_document_parse_file(file_path);
|
|
125
|
+
rb_iv_set(self, "@path", path);
|
|
126
|
+
}
|
|
116
127
|
|
|
117
128
|
if (!result.document) {
|
|
118
129
|
VALUE message = make_utf8_string(result.error);
|
|
@@ -122,7 +133,29 @@ static VALUE document_initialize(VALUE self, VALUE path) {
|
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
RTYPEDDATA_DATA(self) = result.document;
|
|
125
|
-
|
|
136
|
+
|
|
137
|
+
return self;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* document.replace_content!(content) — re-parse from YAML string, keeping the same Ruby object */
|
|
141
|
+
static VALUE document_replace_content(VALUE self, VALUE content) {
|
|
142
|
+
const char *yaml_content = StringValueCStr(content);
|
|
143
|
+
YerbaParseResult result = yerba_document_parse(yaml_content);
|
|
144
|
+
|
|
145
|
+
if (!result.document) {
|
|
146
|
+
VALUE message = make_utf8_string(result.error);
|
|
147
|
+
yerba_string_free(result.error);
|
|
148
|
+
|
|
149
|
+
rb_raise(rb_eParseError, "%s", StringValueCStr(message));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
struct Document *old_document = get_document(self);
|
|
153
|
+
|
|
154
|
+
if (old_document) {
|
|
155
|
+
yerba_document_free(old_document);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
RTYPEDDATA_DATA(self) = result.document;
|
|
126
159
|
|
|
127
160
|
return self;
|
|
128
161
|
}
|
|
@@ -1024,7 +1057,8 @@ void Init_yerba(void) {
|
|
|
1024
1057
|
rb_cDocument = rb_define_class_under(rb_mYerba, "Document", rb_cObject);
|
|
1025
1058
|
|
|
1026
1059
|
rb_define_alloc_func(rb_cDocument, document_alloc);
|
|
1027
|
-
rb_define_method(rb_cDocument, "initialize", document_initialize, 1);
|
|
1060
|
+
rb_define_method(rb_cDocument, "initialize", document_initialize, -1);
|
|
1061
|
+
rb_define_method(rb_cDocument, "replace_content!", document_replace_content, 1);
|
|
1028
1062
|
rb_define_singleton_method(rb_cDocument, "parse", document_s_parse, 1);
|
|
1029
1063
|
rb_define_method(rb_cDocument, "[]", document_bracket, 1);
|
|
1030
1064
|
rb_define_method(rb_cDocument, "node_at", document_bracket, 1);
|
data/lib/yerba/document.rb
CHANGED
|
@@ -12,6 +12,12 @@ module Yerba
|
|
|
12
12
|
@cache = nil
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
def self.from(object, path: nil)
|
|
16
|
+
document = parse(Formatting.to_yaml_document(object))
|
|
17
|
+
document.instance_variable_set(:@path, path) if path
|
|
18
|
+
document
|
|
19
|
+
end
|
|
20
|
+
|
|
15
21
|
def selector
|
|
16
22
|
ROOT_SELECTOR
|
|
17
23
|
end
|
|
@@ -20,7 +26,19 @@ module Yerba
|
|
|
20
26
|
self[ROOT_SELECTOR]
|
|
21
27
|
end
|
|
22
28
|
|
|
29
|
+
def root=(value)
|
|
30
|
+
replace_content!(Formatting.to_yaml_document(value))
|
|
31
|
+
end
|
|
32
|
+
|
|
23
33
|
def []=(key, value)
|
|
34
|
+
if root.is_a?(Scalar)
|
|
35
|
+
raise Error, "document root is not set. Use `document.root = {}` or `document.root = []` first"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if root.is_a?(Sequence)
|
|
39
|
+
raise Error, "document root is a Sequence, not a Map. Use `document << item` to append, or `document.root = {}` to switch to a Map"
|
|
40
|
+
end
|
|
41
|
+
|
|
24
42
|
root[key] = value
|
|
25
43
|
end
|
|
26
44
|
|
|
@@ -44,6 +62,11 @@ module Yerba
|
|
|
44
62
|
to_s
|
|
45
63
|
end
|
|
46
64
|
|
|
65
|
+
def save_to!(path)
|
|
66
|
+
@path = path
|
|
67
|
+
save!
|
|
68
|
+
end
|
|
69
|
+
|
|
47
70
|
def dig(*keys)
|
|
48
71
|
keys.reduce(self) { |node, key| node.nil? ? nil : node[key] }
|
|
49
72
|
end
|
|
@@ -67,6 +90,14 @@ module Yerba
|
|
|
67
90
|
end
|
|
68
91
|
|
|
69
92
|
def <<(item)
|
|
93
|
+
if root.is_a?(Scalar)
|
|
94
|
+
raise Error, "document root is not set. Use `document.root = []` first"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if root.is_a?(Map)
|
|
98
|
+
raise Error, "document root is a Map, not a Sequence. Use `document[\"key\"] = value` to set keys, or `document.root = []` to switch to a Sequence"
|
|
99
|
+
end
|
|
100
|
+
|
|
70
101
|
root << item
|
|
71
102
|
end
|
|
72
103
|
|
data/lib/yerba/formatting.rb
CHANGED
|
@@ -75,5 +75,16 @@ module Yerba
|
|
|
75
75
|
else value.to_s
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
|
+
|
|
79
|
+
def self.to_yaml_document(value)
|
|
80
|
+
case value
|
|
81
|
+
when Array
|
|
82
|
+
value.empty? ? "---\n[]\n" : "---\n#{to_block_yaml_value(value)}\n"
|
|
83
|
+
when Hash
|
|
84
|
+
value.empty? ? "---\n{}\n" : "---\n#{to_block_yaml_value(value)}\n"
|
|
85
|
+
else
|
|
86
|
+
raise ArgumentError, "expected Array or Hash, got #{value.class}"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
78
89
|
end
|
|
79
90
|
end
|
data/lib/yerba/version.rb
CHANGED
data/rust/Cargo.toml
CHANGED
data/rust/src/document/insert.rs
CHANGED
|
@@ -154,7 +154,22 @@ impl Document {
|
|
|
154
154
|
|
|
155
155
|
let map = match node.descendants().find_map(BlockMap::cast) {
|
|
156
156
|
Some(map) => map,
|
|
157
|
-
None =>
|
|
157
|
+
None => {
|
|
158
|
+
let has_empty_flow_map = node
|
|
159
|
+
.descendants()
|
|
160
|
+
.any(|descendant| descendant.kind() == SyntaxKind::FLOW_MAP && !descendant.descendants().any(|child| child.kind() == SyntaxKind::FLOW_MAP_ENTRY));
|
|
161
|
+
|
|
162
|
+
if has_empty_flow_map {
|
|
163
|
+
let selectors = self.resolve_selectors(parent_path);
|
|
164
|
+
|
|
165
|
+
if let Some(selector) = selectors.get(i) {
|
|
166
|
+
self.replace_empty_inline_map(selector, key, value)?;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
158
173
|
};
|
|
159
174
|
|
|
160
175
|
if find_entry_by_key(&map, key).is_some() {
|
|
@@ -178,7 +193,35 @@ impl Document {
|
|
|
178
193
|
};
|
|
179
194
|
|
|
180
195
|
let indent = " ".repeat(start_col);
|
|
181
|
-
|
|
196
|
+
|
|
197
|
+
let is_block_value = value.contains('\n') || value.starts_with("- ");
|
|
198
|
+
let new_entry_text = if is_block_value {
|
|
199
|
+
let value_indent = format!("{} ", indent);
|
|
200
|
+
let lines: Vec<&str> = value.lines().collect();
|
|
201
|
+
|
|
202
|
+
let min_indent = lines
|
|
203
|
+
.iter()
|
|
204
|
+
.filter(|line| !line.trim().is_empty())
|
|
205
|
+
.map(|line| line.len() - line.trim_start().len())
|
|
206
|
+
.min()
|
|
207
|
+
.unwrap_or(0);
|
|
208
|
+
|
|
209
|
+
let indented_lines: Vec<String> = lines
|
|
210
|
+
.iter()
|
|
211
|
+
.map(|line| {
|
|
212
|
+
if line.trim().is_empty() {
|
|
213
|
+
String::new()
|
|
214
|
+
} else {
|
|
215
|
+
let relative = &line[min_indent..];
|
|
216
|
+
format!("{}{}", value_indent, relative)
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
.collect();
|
|
220
|
+
|
|
221
|
+
format!("{}:\n{}", key, indented_lines.join("\n"))
|
|
222
|
+
} else {
|
|
223
|
+
format!("{}: {}", key, value)
|
|
224
|
+
};
|
|
182
225
|
|
|
183
226
|
match &position {
|
|
184
227
|
InsertPosition::After(target_key) => {
|
|
@@ -370,10 +413,22 @@ impl Document {
|
|
|
370
413
|
fn insert_map_key(&mut self, dot_path: &str, key: &str, value: &str, position: InsertPosition) -> Result<(), YerbaError> {
|
|
371
414
|
let current_node = self.navigate(dot_path)?;
|
|
372
415
|
|
|
373
|
-
let map = current_node
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
416
|
+
let map = match current_node.descendants().find_map(BlockMap::cast) {
|
|
417
|
+
Some(map) => map,
|
|
418
|
+
None => {
|
|
419
|
+
let flow_map = current_node.descendants().find(|descendant| descendant.kind() == SyntaxKind::FLOW_MAP);
|
|
420
|
+
|
|
421
|
+
if let Some(flow_map_node) = flow_map {
|
|
422
|
+
let is_empty = !flow_map_node.descendants().any(|descendant| descendant.kind() == SyntaxKind::FLOW_MAP_ENTRY);
|
|
423
|
+
|
|
424
|
+
if is_empty {
|
|
425
|
+
return self.replace_empty_inline_map(dot_path, key, value);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return Err(YerbaError::SelectorNotFound(dot_path.to_string()));
|
|
430
|
+
}
|
|
431
|
+
};
|
|
377
432
|
|
|
378
433
|
let entries: Vec<_> = map.entries().collect();
|
|
379
434
|
|
|
@@ -495,21 +550,93 @@ impl Document {
|
|
|
495
550
|
}
|
|
496
551
|
}
|
|
497
552
|
|
|
553
|
+
fn replace_empty_inline_map(&mut self, dot_path: &str, key: &str, value: &str) -> Result<(), YerbaError> {
|
|
554
|
+
let current_node = self.navigate(dot_path)?;
|
|
555
|
+
|
|
556
|
+
if dot_path.is_empty() {
|
|
557
|
+
let flow_map = current_node
|
|
558
|
+
.descendants()
|
|
559
|
+
.find(|descendant| descendant.kind() == SyntaxKind::FLOW_MAP)
|
|
560
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
561
|
+
|
|
562
|
+
let range = flow_map.text_range();
|
|
563
|
+
|
|
564
|
+
let source = self.to_string();
|
|
565
|
+
let start: usize = range.start().into();
|
|
566
|
+
let before = &source[..start];
|
|
567
|
+
let trimmed_length = before.trim_end_matches([' ', '\n']).len();
|
|
568
|
+
|
|
569
|
+
let adjusted_range = TextRange::new(rowan::TextSize::from(trimmed_length as u32), range.end());
|
|
570
|
+
let replacement = format!("\n{}: {}", key, value);
|
|
571
|
+
|
|
572
|
+
return self.apply_edit(adjusted_range, &replacement);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let (parent_path, map_key) = dot_path.rsplit_once('.').unwrap_or(("", dot_path));
|
|
576
|
+
let parent_node = self.navigate(parent_path)?;
|
|
577
|
+
|
|
578
|
+
let map = parent_node
|
|
579
|
+
.descendants()
|
|
580
|
+
.find_map(BlockMap::cast)
|
|
581
|
+
.ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
582
|
+
|
|
583
|
+
let entry = find_entry_by_key(&map, map_key).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
584
|
+
let entry_indent = preceding_whitespace_indent(entry.syntax());
|
|
585
|
+
let child_indent = format!("{} ", entry_indent);
|
|
586
|
+
let new_entry_text = format!("{}{}: {}", child_indent, key, value);
|
|
587
|
+
|
|
588
|
+
let mut range = current_node.text_range();
|
|
589
|
+
|
|
590
|
+
if let Some(previous) = current_node.prev_sibling_or_token().and_then(|element| element.into_token()) {
|
|
591
|
+
if previous.kind() == SyntaxKind::WHITESPACE && !previous.text().contains('\n') {
|
|
592
|
+
range = TextRange::new(previous.text_range().start(), range.end());
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
let replacement = format!("\n{}", new_entry_text);
|
|
597
|
+
|
|
598
|
+
self.apply_edit(range, &replacement)
|
|
599
|
+
}
|
|
600
|
+
|
|
498
601
|
fn replace_empty_inline_sequence(&mut self, dot_path: &str, value: &str) -> Result<(), YerbaError> {
|
|
602
|
+
if dot_path.is_empty() {
|
|
603
|
+
let current_node = self.navigate(dot_path)?;
|
|
604
|
+
let flow_seq = current_node
|
|
605
|
+
.descendants()
|
|
606
|
+
.find(|descendant| descendant.kind() == SyntaxKind::FLOW_SEQ)
|
|
607
|
+
.ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
608
|
+
|
|
609
|
+
let new_item = Self::format_sequence_item(value, "");
|
|
610
|
+
let range = flow_seq.text_range();
|
|
611
|
+
let source = self.to_string();
|
|
612
|
+
let start: usize = range.start().into();
|
|
613
|
+
let before = &source[..start];
|
|
614
|
+
|
|
615
|
+
let trimmed_length = before.trim_end_matches([' ', '\n']).len();
|
|
616
|
+
let adjusted_start = trimmed_length;
|
|
617
|
+
|
|
618
|
+
let adjusted_range = TextRange::new(rowan::TextSize::from(adjusted_start as u32), range.end());
|
|
619
|
+
let replacement = format!("\n{}", new_item);
|
|
620
|
+
|
|
621
|
+
return self.apply_edit(adjusted_range, &replacement);
|
|
622
|
+
}
|
|
623
|
+
|
|
499
624
|
let (parent_path, key) = dot_path.rsplit_once('.').unwrap_or(("", dot_path));
|
|
500
625
|
let parent_node = self.navigate(parent_path)?;
|
|
626
|
+
|
|
501
627
|
let map = parent_node
|
|
502
628
|
.descendants()
|
|
503
629
|
.find_map(BlockMap::cast)
|
|
504
630
|
.ok_or_else(|| YerbaError::NotASequence(dot_path.to_string()))?;
|
|
505
|
-
let entry = find_entry_by_key(&map, key).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
506
631
|
|
|
632
|
+
let entry = find_entry_by_key(&map, key).ok_or_else(|| YerbaError::SelectorNotFound(dot_path.to_string()))?;
|
|
507
633
|
let item_indent = format!("{} ", preceding_whitespace_indent(entry.syntax()));
|
|
508
634
|
let new_item = Self::format_sequence_item(value, &item_indent);
|
|
509
635
|
let current_node = self.navigate(dot_path)?;
|
|
510
|
-
let mut range = current_node.text_range();
|
|
511
636
|
let inline_comment = self.trailing_inline_comment(¤t_node);
|
|
512
637
|
|
|
638
|
+
let mut range = current_node.text_range();
|
|
639
|
+
|
|
513
640
|
if let Some(previous) = current_node.prev_sibling_or_token().and_then(|element| element.into_token()) {
|
|
514
641
|
if previous.kind() == SyntaxKind::WHITESPACE && !previous.text().contains('\n') {
|
|
515
642
|
range = TextRange::new(previous.text_range().start(), range.end());
|