drawght 0.1.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 776b1ea99f39fc1716543bd837504d10ca2648babb6e283c0bbf0dcc6db10b5f
4
- data.tar.gz: 0aa2b64aaa564b8194266ac18d37627ae384ba3cf4e39d7ad84a3a5c342ef36c
3
+ metadata.gz: 9ed4619bbd991ccf58bd7e7c89407879181c656e98667b933a25cdf751a9c273
4
+ data.tar.gz: ae541e430afcc2ee112cd78d0549767845fbb1a8def0204de1d29e4db14baef9
5
5
  SHA512:
6
- metadata.gz: 73a0b217f9ba7ecca5aaad07a182bc9756714245bd12af06a75b288b781f62bbbb6cce10013dcbcce66cc4e7a3631f62c07ed7ea9a0a50edf4525eb29dd3fd45
7
- data.tar.gz: 96895069175bf3326d10e4a40f6276120043b650d77211ed00dca259c00f4ddedeaa2d28622c35a70c12e15c02a42db5e4dd1dbda936ae1d2398b3b8c092cf0b
6
+ metadata.gz: 704e351d12da61369698dd568740d3943ee9542ac01c3d5e52c9bad514063ea6e1d395ba503df518a91605d44fb1bb7df0b68893527fa036ea74132d6680cafa
7
+ data.tar.gz: e10c78a814d001c3c7baa5eec7b748c5e34256b4a1dc7206d109372ec815279ba658f2ed1017838e7e233bf62befbe847b9df1af9fa4a4bc7b74090c9651b40a
data/CHANGELOG.yaml ADDED
@@ -0,0 +1,32 @@
1
+ - version: 1.1.0
2
+ date: 2025-12-22
3
+ summary:
4
+ Syntax validation and new expression.
5
+ changes:
6
+ - Syntax validation.
7
+ - Refined extension methods have been moved to their respective modules.
8
+ - Syntax for getting the length of a collection.
9
+
10
+ - version: 1.0.0
11
+ date: 2025-12-03
12
+ summary:
13
+ The compiler has finished.
14
+ changes:
15
+ - New compiler process.
16
+ - Refined extension methods have been added.
17
+ - Test coverage.
18
+ - Syntax revision for nested values in collections.
19
+
20
+ - version: 0.2.0
21
+ date: 2024-08-30
22
+ summary:
23
+ Tests and tests.
24
+ changes:
25
+ - Tests have been added to test variables, objects and lists.
26
+
27
+ - version: 0.1.0
28
+ date: 2021-07-11
29
+ summary:
30
+ Work in progress! Syntax and compiler have been coded.
31
+ changes:
32
+ - Process variables, objects and lists.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright © 2021, Hallison Batista
1
+ Copyright © 2021-2024, Hallison Batista
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,211 +1,389 @@
1
- # Drawght
2
-
3
- Drawght is a data handler for texts without logical statements. The goal is
4
- to use a dataset (such as the subject of a text) to draft a document
5
- template. It can be considered a mini template processor.
6
-
7
- Data is accessed through `{}` braces, replaced by their respective values.
8
-
9
- Considering the following data:
10
-
11
- ```yaml
12
- title: Drawght is a very useful sketch
13
- author:
14
- name: Hallison Batista
15
- email: email@hallison.dev.br
16
- networks:
17
- - name: Github
18
- url: //github.com/hallison
19
- - name: Twitter
20
- url: //twitter.com/hallison
21
- creation-date: 2021-06-28
22
- publishing date: 2021-07-01
23
- references:
24
- - name: Mustache
25
- url: //mustache.github.io
26
- - name: Handlebars
27
- url: //handlebarsjs.com
28
- tags:
29
- - Template
30
- - Draft
31
- ```
32
-
33
- Note that the `creation-date` and `publishing date` fields are normally
34
- identified by the parser.
35
-
36
- In a template written in Markdown:
37
-
38
- ```markdown
39
- # {title}
40
-
41
- Drawght is a good tool for writing draft documents using datasets without
42
- logical statements.
43
-
44
- Written by {author.name} <{author.email}>, created in {creation-date},
45
- published in {publishing date} and tagged by {tags#1}.
46
-
47
- - [{author.networks:name}]({author.networks:url})
48
-
49
- Follow the news on [{author.networks#1.name}]({author.networks#1.url}).
50
-
51
- The syntax was inspired by:
52
-
53
- - [{references:name}]({references:url})
54
-
55
- Tags:
56
-
57
- - {tags} (tagged by {author.name}).
58
- ```
59
-
60
- The Drawght processing returns the following result:
61
-
62
- ```markdown
63
- # Drawght is a very useful sketch
64
-
65
- Drawght is a good tool for writing draft documents using datasets without
66
- logical statements.
67
-
68
- Written by Hallison Batista <email@hallison.dev.br>, created in 2021-06-28,
69
- published in 2021-07-01 and tagged by Template.
70
-
71
- - [Dev.to](//dev.to/hallison)
72
- - [Github](//github.com/hallison)
73
- - [Twitter](//twitter.com/hallison)
74
-
75
- Follow the news on [Dev.to](//dev.to/hallison).
76
-
77
- The syntax was inspired by:
78
-
79
- - [Mustache](//mustache.github.io)
80
- - [Handlebars](//handlebarsjs.com)
81
-
82
- Tags:
83
-
84
- - Template (tagged by Hallison Batista).
85
- - Draf (tagged by Hallison Batista).
86
- ```
87
-
88
- In a template written in HTML:
89
-
90
- ```html
91
- <h1>{title}</h1>
92
-
93
- <p>
94
- Drawght is a good tool for writing draft documents using datasets without
95
- logical statements.
96
- </p>
97
-
98
- <p>
99
- Written by <a href="mailto:{author.email}">{author.name}</a>, created
100
- published in {publishing date} and tagged by {tags#1}.
101
- </p>
102
-
103
- <ul>
104
- <li><a href="{author.networks:url}">{author.networks:name}</a></li>
105
- </ul>
106
-
107
- <p>
108
- Follow the news on <a href="{author.networks#1.url}">author.networks#1.name</a>.
109
- </p>
110
-
111
- <p>
112
- The syntax was inspired by:
113
- </p>
114
-
115
- <ul>
116
- <a href="{references:url}">{references:name}</a>
117
- </ul>
118
-
119
- <p>
120
- Tags:
121
- </p>
122
-
123
- <ul>
124
- <li>{tags} (tagged by {author.name}).</li>
125
- </ul>
126
- ```
127
-
128
- The Drawght processing returns the following result:
129
-
130
- ```html
131
- <h1>Drawght is a very useful sketch</h1>
132
-
133
- <p>
134
- Drawght is a good tool for writing draft documents using datasets without
135
- logical statements.
136
- </p>
137
-
138
- <p>
139
- Written by <a href="mailto:email@hallison.dev.br">Hallison Batista</a>,
140
- created published in 2021-07-01 and tagged by Template.
141
- </p>
142
-
143
- <ul>
144
- <li><a href="//dev.to/hallison">Dev.to</a></li>
145
- <li><a href="//github.com/hallison">Github</a></li>
146
- <li><a href="//twitter.com/hallison">Twitter</a></li>
147
- </ul>
148
-
149
- <p>
150
- Follow the news on <a href="//dev.to/hallison">Dev.to</a>.
151
- </p>
152
-
153
- <p>
154
- The syntax was inspired by:
155
- </p>
156
-
157
- <ul>
158
- <a href="//mustache.github.io">Mustache</a>
159
- <a href="//handlebarsjs.com">Handlebars</a>
160
- </ul>
161
-
162
- <p>
163
- Tags:
164
- </p>
165
-
166
- <ul>
167
- <li>Template (tagged by Hallison Batista).</li>
168
- <li>Draf (tagged by Hallison Batista).</li>
169
- </ul>
170
- ```
171
-
172
- ## Syntax
173
-
174
- Drawght has a simple syntax:
175
-
176
- - `{key}`: converts `key` to its respective value. If the value is a list, then
177
- the row will be replicated and converted with the respective values.
178
-
179
- - `{object.key}`: converts `key` to its respective value inside `object`,
180
- assuming the same behavior as `{key}`.
181
-
182
- - `{list:key}`: selects `list`, replicates the line for each item in the list,
183
- and converts `key` to its respective value contained in an object within
184
- `list`. The `list` key can also be accessed by `object.list`, just as `key`
185
- can also be accessed by `object.key` which will be converted following the
186
- same process in case it is a list.
187
-
188
- - `{list#n.key}`: selects item object `n` (from 1) from `list` and converts
189
- `key` to its respective value. The whole process is similar to `object.key`.
190
-
191
- ## License (MIT)
192
-
193
- ### Copyright (c) 2021, Hallison Batista
194
-
195
- Permission is hereby granted, free of charge, to any person obtaining a copy of
196
- this software and associated documentation files (the "Software"), to deal in
197
- the Software without restriction, including without limitation the rights to
198
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
199
- of the Software, and to permit persons to whom the Software is furnished to do
200
- so, subject to the following conditions:
201
-
202
- The above copyright notice and this permission notice shall be included in all
203
- copies or substantial portions of the Software.
204
-
205
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
206
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
207
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
208
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
209
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
210
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
211
- SOFTWARE.
1
+ # Drawght
2
+
3
+ Drawght (like draft/draught) is a data handler for texts without logical
4
+ statements. The goal is to use a dataset (such as the subject of a text) to
5
+ draft a document template. It can be considered a mini template processor.
6
+
7
+ Data is accessed through `{}` braces, replaced by their respective values.
8
+
9
+ Considering the following data:
10
+
11
+ ```yaml
12
+ title: Drawght is a very useful sketch
13
+ author:
14
+ name: Hallison Batista
15
+ email: email@hallison.dev.br
16
+ networks:
17
+ - name: Github
18
+ url: //github.com/hallison
19
+ - name: Twitter
20
+ url: //twitter.com/hallison
21
+ creation-date: 2021-06-28
22
+ publishing date: 2021-07-01
23
+ references:
24
+ - name: Mustache
25
+ url: //mustache.github.io
26
+ - name: Handlebars
27
+ url: //handlebarsjs.com
28
+ tags:
29
+ - Template
30
+ - Draft
31
+ ```
32
+
33
+ Note that the `creation-date` and `publishing date` fields are normally
34
+ identified by the parser.
35
+
36
+ In a template written in Markdown:
37
+
38
+ ```markdown
39
+ # {title}
40
+
41
+ Drawght is a good tool for writing draft documents using datasets without
42
+ logical statements.
43
+
44
+ Written by {author.name} <{author.email}>, created in {creation-date},
45
+ published in {publishing date} and tagged by {tags#1}.
46
+
47
+ - [{author.networks:name}]({author.networks:url})
48
+
49
+ Follow the news on [{author.networks#1.name}]({author.networks#1.url}).
50
+
51
+ The syntax was inspired by:
52
+
53
+ - [{references:name}]({references:url})
54
+
55
+ Tags:
56
+
57
+ - {tags} (tagged by {author.name}).
58
+ ```
59
+
60
+ The Drawght processing returns the following result:
61
+
62
+ ```markdown
63
+ # Drawght is a very useful sketch
64
+
65
+ Drawght is a good tool for writing draft documents using datasets without
66
+ logical statements.
67
+
68
+ Written by Hallison Batista <email@hallison.dev.br>, created in 2021-06-28,
69
+ published in 2021-07-01 and tagged by Template.
70
+
71
+ - [Dev.to](//dev.to/hallison)
72
+ - [Github](//github.com/hallison)
73
+ - [Twitter](//twitter.com/hallison)
74
+
75
+ Follow the news on [Dev.to](//dev.to/hallison).
76
+
77
+ The syntax was inspired by:
78
+
79
+ - [Mustache](//mustache.github.io)
80
+ - [Handlebars](//handlebarsjs.com)
81
+
82
+ Tags:
83
+
84
+ - Template (tagged by Hallison Batista).
85
+ - Draf (tagged by Hallison Batista).
86
+ ```
87
+
88
+ In a template written in HTML:
89
+
90
+ ```html
91
+ <h1>{title}</h1>
92
+
93
+ <p>
94
+ Drawght is a good tool for writing draft documents using datasets without
95
+ logical statements.
96
+ </p>
97
+
98
+ <p>
99
+ Written by <a href="mailto:{author.email}">{author.name}</a>, created
100
+ published in {publishing date} and tagged by {tags#1}.
101
+ </p>
102
+
103
+ <ul>
104
+ <li><a href="{author.networks:url}">{author.networks:name}</a></li>
105
+ </ul>
106
+
107
+ <p>
108
+ Follow the news on <a href="{author.networks#1.url}">author.networks#1.name</a>.
109
+ </p>
110
+
111
+ <p>
112
+ The syntax was inspired by:
113
+ </p>
114
+
115
+ <ul>
116
+ <a href="{references:url}">{references:name}</a>
117
+ </ul>
118
+
119
+ <p>
120
+ Tags:
121
+ </p>
122
+
123
+ <ul>
124
+ <li>{tags} (tagged by {author.name}).</li>
125
+ </ul>
126
+ ```
127
+
128
+ The Drawght processing returns the following result:
129
+
130
+ ```html
131
+ <h1>Drawght is a very useful sketch</h1>
132
+
133
+ <p>
134
+ Drawght is a good tool for writing draft documents using datasets without
135
+ logical statements.
136
+ </p>
137
+
138
+ <p>
139
+ Written by <a href="mailto:email@hallison.dev.br">Hallison Batista</a>,
140
+ created published in 2021-07-01 and tagged by Template.
141
+ </p>
142
+
143
+ <ul>
144
+ <li><a href="//dev.to/hallison">Dev.to</a></li>
145
+ <li><a href="//github.com/hallison">Github</a></li>
146
+ <li><a href="//twitter.com/hallison">Twitter</a></li>
147
+ </ul>
148
+
149
+ <p>
150
+ Follow the news on <a href="//dev.to/hallison">Dev.to</a>.
151
+ </p>
152
+
153
+ <p>
154
+ The syntax was inspired by:
155
+ </p>
156
+
157
+ <ul>
158
+ <a href="//mustache.github.io">Mustache</a>
159
+ <a href="//handlebarsjs.com">Handlebars</a>
160
+ </ul>
161
+
162
+ <p>
163
+ Tags:
164
+ </p>
165
+
166
+ <ul>
167
+ <li>Template (tagged by Hallison Batista).</li>
168
+ <li>Draf (tagged by Hallison Batista).</li>
169
+ </ul>
170
+ ```
171
+
172
+ ## Install
173
+
174
+ ```bash
175
+ gem install drawght
176
+ ```
177
+
178
+ ## Usage
179
+
180
+ ```ruby
181
+ require "drawght"
182
+
183
+ template = "{package.name} v{package.version}"
184
+ result = Drawght.compile template, {
185
+ package: {
186
+ name: "Drawght",
187
+ version: "1.0.0",
188
+ }
189
+ }
190
+
191
+ puts result
192
+ # Drawght v1.0.0
193
+ ```
194
+
195
+ ## Syntax
196
+
197
+ Drawght has a simple syntax:
198
+
199
+ - `{identifier}`: converts `identifier` to its respective value. If the value
200
+ is a collection, then the row will be replicated and converted with the
201
+ respective values.
202
+
203
+ - `{structure.attribute}`: converts `attribute` to its respective value inside
204
+ `structure`, assuming the same behavior as `{identifier}`.
205
+
206
+ - `{collection:attribute}`: selects `collection`, replicates the line for each
207
+ item in the collection, and converts `attribute` to its respective value
208
+ contained in an structure within `collection`. The `collection` key can also
209
+ be accessed by `structure.collection`, just as `attribute` can also be
210
+ accessed by `structure.attribute` which will be converted following the same
211
+ process in case it is a collection.
212
+
213
+ - `{collection#nth.attribute}`: selects the `nth` (start from 1) item from
214
+ `collection` and converts `attribute` to its respective value. The whole
215
+ process is similar to `structure.attribute`.
216
+
217
+ - `{collection#$.attribute}`: selects the `nth` (start from 1) item from
218
+ `collection` and converts `attribute` to its respective value. The whole
219
+ process is similar to `structure.attribute`.
220
+
221
+ ### EBNF
222
+
223
+ ```ebnf
224
+ identifier ::= initial_name { compound_name | space compound_name } ;
225
+
226
+ initial_name ::= letter | "_" ;
227
+
228
+ compound_name ::= letter | digit | "_" | "-" ;
229
+
230
+ letter ::= "A"…"Z" | "a"…"z" ;
231
+
232
+ digit ::= "0"…"9" ;
233
+
234
+ space ::= " " ;
235
+
236
+ expression ::= path [ length ] ;
237
+
238
+ path ::= [ structural_path ] { sequential_path } ;
239
+
240
+ structural_path ::= attribute { "." attribute } ;
241
+
242
+ scoped_path ::= ":" structural_path ;
243
+
244
+ attribute ::= element | item ;
245
+
246
+ element ::= identifier [ index ] ;
247
+
248
+ item ::= index ;
249
+
250
+ index ::= "#" ( number | "$" ) ;
251
+
252
+ number ::= digit { digit } ;
253
+
254
+ length ::= "#&" ;
255
+ ```
256
+
257
+ ## How it works
258
+
259
+ ### Variables and attributes
260
+
261
+ #### Definition
262
+
263
+ ```yaml
264
+ Author:
265
+ Name: John Scalzi
266
+
267
+ Protagonist: John Perry
268
+ ```
269
+
270
+ #### Usage
271
+
272
+ ```
273
+ {Protagonist}
274
+ {Author.Name}
275
+ ```
276
+
277
+ ### Collections
278
+
279
+ #### Definition
280
+
281
+ ```yaml
282
+ Book Titles:
283
+ - Old Man's War
284
+ - The Ghost Brigades
285
+ - The Last Colony
286
+
287
+ # Or
288
+
289
+ Books:
290
+ - Title: Old Man's War
291
+ - Title: The Ghost Brigades
292
+ - Title: The Last Colony
293
+ ```
294
+
295
+ #### Usage
296
+
297
+ For value sequencing.
298
+
299
+ ```
300
+ {Book Titles}
301
+ {Books:Title}
302
+ ```
303
+
304
+ For value straightly.
305
+
306
+ ```
307
+ {Book Titles#1}
308
+ {Book Titles#2}
309
+ {Book Titles#3}
310
+
311
+ {Books#1.Title}
312
+ {Books#2.Title}
313
+ {Books#3.Title}
314
+ ```
315
+
316
+ ### Nested collections
317
+
318
+ #### Definition
319
+
320
+ ```yaml
321
+ Books:
322
+ - Title: Old Man's War
323
+ ISBN: 0-7653-0940-8
324
+
325
+ - Title: The Ghost Brigades
326
+ ISBN: 0-7653-1502-5
327
+
328
+ - Title: The Last Colony
329
+ ISBN: 0-7653-1697-8
330
+
331
+ Series:
332
+ - Name: Old Man's War
333
+ Books:
334
+ - Title: Old Man's War
335
+ ISBN: 0-7653-0940-8
336
+
337
+ - Title: The Ghost Brigades
338
+ ISBN: 0-7653-1502-5
339
+
340
+ - Title: The Last Colony
341
+ ISBN: 0-7653-1697-8
342
+
343
+ - Name: Lock In
344
+ Books:
345
+ - Title: Lock In
346
+ ISBN: 978-0-7653-7586-5
347
+ - Title: Head On
348
+ ISBN: 978-0-7653-8891-9
349
+ ```
350
+
351
+ #### Usage
352
+
353
+ ```
354
+ {Books:Title}
355
+
356
+ {Books#1.Title}
357
+
358
+ {Series:Name}
359
+
360
+ {Series:Books:Title}
361
+
362
+ {Series#1.Name}
363
+
364
+ {Series#1.Books:Title}
365
+
366
+ {Series#2.Books#2.Title}
367
+ ```
368
+
369
+ ## License (MIT)
370
+
371
+ ### Copyright (c) 2021-2025, Hallison Batista
372
+
373
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
374
+ this software and associated documentation files (the "Software"), to deal in
375
+ the Software without restriction, including without limitation the rights to
376
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
377
+ of the Software, and to permit persons to whom the Software is furnished to do
378
+ so, subject to the following conditions:
379
+
380
+ The above copyright notice and this permission notice shall be included in all
381
+ copies or substantial portions of the Software.
382
+
383
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
384
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
385
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
386
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
387
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
388
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
389
+ SOFTWARE.
@@ -0,0 +1,94 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Drawght
5
+
6
+ module HashExtensions
7
+ refine Hash do
8
+ def deep_stringify_keys!
9
+ transform_keys! do |key|
10
+ case value = fetch(key)
11
+ when Hash then value.deep_stringify_keys!
12
+ when Array then
13
+ value.map! do |item|
14
+ (item.is_a? Hash) ? item.deep_stringify_keys! : item
15
+ end
16
+ end
17
+
18
+ key.to_s
19
+ end
20
+
21
+ self
22
+ end
23
+ end
24
+ end
25
+
26
+ class Compiler
27
+ using HashExtensions
28
+
29
+ include Parser
30
+ include Tracker
31
+
32
+ attr_reader :template, :dataset, :result
33
+
34
+ def initialize template, dataset = nil
35
+ @template = template
36
+ @result = template.dup
37
+ @dataset = dataset.deep_stringify_keys! if dataset
38
+ end
39
+
40
+ def compile dataset
41
+ @dataset ||= dataset.deep_stringify_keys!
42
+ compile!
43
+ end
44
+
45
+ def compile!
46
+ convert
47
+ result
48
+ end
49
+
50
+ private
51
+
52
+ def convert
53
+ lines = template.lines.map do |line|
54
+ next line unless has_placeholders? line
55
+
56
+ mapping_placeholders_from line
57
+
58
+ newline = convert_structural_placeholders_from line
59
+
60
+ convert_sequential_placeholders_from newline
61
+ end
62
+
63
+ result.replace lines.join
64
+ end
65
+
66
+ def convert_structural_placeholders_from string
67
+ structural_placeholders.reduce string.dup do |template, expression|
68
+ matter = pathing dataset, *pathkeys_from(expression)
69
+
70
+ converted = [matter].flatten.map do |value|
71
+ template.dup.gsub! pathize(expression), value.to_s
72
+ end
73
+
74
+ template.replace converted.join
75
+ end
76
+ end
77
+
78
+ def convert_sequential_placeholders_from string
79
+ sequential_placeholders.reduce string.dup do |template, (attribute, mappings)|
80
+ matter = pathing dataset, *pathkeys_from(attribute)
81
+
82
+ converted = matter.map do |values|
83
+ mappings.inject template.dup do |partial, (expression, key)|
84
+ value = pathing(values, *pathkeys_from(key)) || key
85
+ partial.dup.gsub! pathize(expression), value.to_s
86
+ end
87
+ end
88
+
89
+ template.replace converted.join
90
+ end
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Drawght
5
+ module Mapper
6
+
7
+ end
8
+ end
@@ -1,68 +1,201 @@
1
- class Drawght::Parser
2
- PREFIX, ATTRIBUTE, QUERY, ITEM, SUFFIX, = "{", ".", ":", "#", "}"
3
- KEY, LIST, INDEX = "(?<key>.*?)", "(?<list>.*?)", "(?<index>.*?)"
4
- EOL = /\r?\n/
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
5
3
 
6
- KEY_PATTERN = Regexp.new "#{PREFIX}#{KEY}#{SUFFIX}"
7
- LIST_PATTERN = Regexp.new "#{PREFIX}#{LIST}#{QUERY}#{KEY}#{SUFFIX}"
8
- ITEM_PATTERN = Regexp.new "#{LIST}#{ITEM}#{INDEX}([#{ATTRIBUTE}]#{KEY})?$"
9
-
10
- def initialize(template)
11
- @template = template
4
+ module Drawght
5
+ module ArrayExtensions
6
+ refine Array do
7
+ def add_unique item
8
+ push item unless include? item
9
+ self
10
+ end
11
+ end
12
12
  end
13
13
 
14
- def parse(data)
15
- self.parse_keys(self.parse_queries(@template, data), data)
16
- end
14
+ module Parser
15
+ using ArrayExtensions
17
16
 
18
- def parse_queries(template, data)
19
- template.split(EOL).map do |line|
20
- result = line
21
- line.scan LIST_PATTERN do |(list, key)|
22
- value = value_from_key(list, data)
23
- if value && (value.kind_of? Array) && key
24
- partial = line.gsub("#{list}#{QUERY}", "")
25
- parsed_lines = value.map { |item| parse_template(partial, item) }
26
- result = result.gsub(line, parsed_lines.join("\n"))
27
- end
17
+ TOKENS = [
18
+ PREFIX = '{',
19
+ SUFFIX = '}',
20
+ ACCESSOR = '.',
21
+ INDEX = '#',
22
+ SCOPE = ':',
23
+ CONTEXT = '@',
24
+ LAST = '$',
25
+ LENGTH = '&',
26
+ ]
27
+
28
+ # Identifiers/name matches:
29
+ #
30
+ # - "name"
31
+ # - "variable name"
32
+ # - "variable-name"
33
+ # - "variable_name"
34
+ # - "_name_"
35
+ # - "name_"
36
+ # - "_name"
37
+ IDENTIFIER_MATCHES = "[A-Za-z_][A-Za-z0-9_\\-]*(?: [A-Za-z0-9_\\-]+)*"
38
+
39
+ # Index/item matches:
40
+ #
41
+ # - "#1" for first item.
42
+ # - "#n" for n-th item.
43
+ # - "#$" for last item.
44
+ INDEX_MATCHES = "\\#{INDEX}(?:\\d+|\\#{LAST})"
45
+
46
+ # Element matches:
47
+ #
48
+ # - "items#1" for first named item.
49
+ # - "items#n" for n-th named item.
50
+ # - "items#$" for last named item.
51
+ #
52
+ # Matches with IDENTIFIER_MATCHES and INDEX_MATCHES.
53
+ ELEMENT_MATCHES = "#{IDENTIFIER_MATCHES}(?:#{INDEX_MATCHES})?"
54
+
55
+ # Attribute matches.
56
+ ATTRIBUTE_MATCHES = "\\#{ACCESSOR}(?:#{ELEMENT_MATCHES})"
57
+
58
+ # Validation
59
+ #
60
+ # Structural matches:
61
+ #
62
+ # - "struct.attribute" for attribute value from struct.
63
+ # - "struct.substruct.attribute" for attribute value of the substruct from
64
+ # struct.
65
+ # - "struct.items#1" for first item value in items of the struct.
66
+ # - "struct.items#1.attribute" for attribute value of the first item in
67
+ # items of the struct.
68
+ # - "#1.attribute" for attribute value of the first item.
69
+ # - "#1.struct.attribute" for attribute value of the struct from the first item.
70
+ # - "items#1.attribute" for attribute value of the first item in items.
71
+ # - "items#1.struct.attribute" for attribute value of the struct of the
72
+ # first item in items.
73
+ # - "items#1.subitems#1" for subitem value of the first item in items.
74
+ # - "items#1.subitems#1.attribute" for attribute value of the subitem from
75
+ # first item in items.
76
+ #
77
+ # Scoped matches:
78
+ #
79
+ # ":attribute"
80
+ # ":collection:attribute"
81
+ # "collection:attribute"
82
+ # "collection:subcollection:attribute"
83
+ # "items#1:attribute"
84
+ # "struct.collection:attribute"
85
+ # "items#1.collection:attribute"
86
+ # "items#1.subitems#1:attribute"
87
+ # "items#1.subitems#1.collection:attribute"
88
+ # "struct.collection:noitcelloc.tcurts:attribute"
89
+ VALIDATION_PATTERN = Regexp.new "^(?=.*[A-Za-z_\\#{INDEX}])(?:(?:#{ELEMENT_MATCHES}|#{INDEX_MATCHES})(?:#{ATTRIBUTE_MATCHES})*)?(?:\\#{SCOPE}(?:#{ELEMENT_MATCHES})(?:#{ATTRIBUTE_MATCHES})*)*(?:\\#{INDEX}\\#{LENGTH})?$"
90
+
91
+ PLACEHOLDERS_PATTERN = Regexp.new "\\#{PREFIX}([^\\#{SUFFIX}]+)\\#{SUFFIX}"
92
+ STRUCTURE_TOKEN_PATTERN = Regexp.new "(\\#{ACCESSOR}|\\#{INDEX}|\\#{LENGTH}$)"
93
+ SCOPE_TOKEN_PATTERN = Regexp.new "(\\#{SCOPE})"
94
+ SCOPE_PATH_PATTERN = Regexp.new "^(.*)#{SCOPE_TOKEN_PATTERN}(.*)$"
95
+
96
+ NUMBER_PATTERN = /^\d+/
97
+
98
+ using ArrayExtensions
99
+
100
+ class SyntaxError < StandardError
101
+ def initialize syntax
102
+ super "Invalid syntax for \"#{syntax}\""
28
103
  end
29
- result
30
- end.join("\n")
31
- end
104
+ end
32
105
 
33
- def parse_keys(template, data)
34
- template.split(EOL).map do |line|
35
- parse_template(line, data)
36
- end.join("\n");
37
- end
106
+ def pathkeys_from string
107
+ raise SyntaxError.new string unless syntax_valid? string
38
108
 
39
- def parse_template(template, data)
40
- result = template
41
- template.scan KEY_PATTERN do |(key)|
42
- template_key = "#{PREFIX}#{key}#{SUFFIX}"
43
- value = value_from_key(key, data) || template_key
44
- if value.kind_of? Array
45
- result = value.map { |item| template.gsub(template_key, item) }.join("\n")
46
- else
47
- result = result.gsub(template_key, value.to_s)
109
+ path = case string
110
+ when STRUCTURE_TOKEN_PATTERN then
111
+ pathkeys_for_structure string
112
+ when SCOPE_TOKEN_PATTERN then
113
+ pathkeys_for_collection string
114
+ else
115
+ [string]
48
116
  end
117
+
118
+ path.flatten
49
119
  end
50
- result
51
- end
52
120
 
53
- private
121
+ def syntax_valid? string
122
+ string.match? VALIDATION_PATTERN
123
+ end
124
+
125
+ def placeholders_from string
126
+ return [] unless string =~ PLACEHOLDERS_PATTERN
127
+
128
+ string.scan(PLACEHOLDERS_PATTERN).flatten
129
+ end
130
+
131
+ def mapping_placeholders_from string
132
+ clear_placeholder_mappings!
133
+
134
+ placeholders_from(string).map do |placeholder|
135
+ if placeholder =~ SCOPE_TOKEN_PATTERN
136
+ placeholder.scan SCOPE_PATH_PATTERN do |pathkeys, _delimiter, attribute|
137
+ (sequential_placeholders[pathkeys] ||= {}).update placeholder => attribute
138
+ end
139
+ else
140
+ structural_placeholders.add_unique placeholder
141
+ end
54
142
 
55
- def value_from_key(nested_key, data)
56
- item = nested_key.match ITEM_PATTERN
57
- if item
58
- list, index, key = item.captures
59
- index = index && (index.to_i)
60
- value = data.dig *list.split(ATTRIBUTE)
61
- if value && (value.kind_of? Array) && index
62
- key ? value[index - 1][key] : value[index - 1]
143
+ placeholder
63
144
  end
64
- else
65
- data.dig *nested_key.split(ATTRIBUTE)
145
+ end
146
+
147
+ def structural_placeholders
148
+ @structural_placeholders ||= []
149
+ end
150
+
151
+ def sequential_placeholders
152
+ @sequential_placeholders ||= {}
153
+ end
154
+
155
+ def has_placeholders? string
156
+ string.match? PLACEHOLDERS_PATTERN
157
+ end
158
+
159
+ def pathize string
160
+ "#{PREFIX}#{string}#{SUFFIX}"
161
+ end
162
+
163
+ private
164
+
165
+ def pathkeys_for_structure placeholder
166
+ placeholder
167
+ .split(STRUCTURE_TOKEN_PATTERN)
168
+ .reject{ |expression| [ACCESSOR, INDEX].include? expression }
169
+ .map do |expression|
170
+ value = zeroize expression
171
+
172
+ if value =~ SCOPE_TOKEN_PATTERN
173
+ pathkeys_for_collection expression
174
+ else
175
+ value =~ NUMBER_PATTERN ? value.to_i - 1 : expression unless value.empty?
176
+ end
177
+ end.compact
178
+ end
179
+
180
+ def zeroize value, token: LAST
181
+ value.to_s.sub token, '0'
182
+ end
183
+
184
+ def pathkeys_for_collection placeholder
185
+ placeholder.split(SCOPE_TOKEN_PATTERN).map do |expression|
186
+ case expression.to_s
187
+ when STRUCTURE_TOKEN_PATTERN then
188
+ pathkeys_for_structure expression
189
+ when SCOPE then
190
+ CONTEXT
191
+ else
192
+ expression
193
+ end
194
+ end
195
+ end
196
+
197
+ def clear_placeholder_mappings!
198
+ structural_placeholders.clear && sequential_placeholders.clear
66
199
  end
67
200
  end
68
201
  end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module Drawght
4
+
5
+ module Tracker
6
+ LENGTH = Parser::LENGTH
7
+ CONTEXT = Parser::CONTEXT
8
+
9
+ def pathing hash, *pathkeys
10
+ if i = pathkeys.index CONTEXT
11
+ prefixes, suffixes = pathkeys.slice(0..i - 1), pathkeys.slice(i + 1..-1)
12
+ result = pathing(hash, *prefixes).map{ |item| pathing item, *suffixes }
13
+ suffixes.last == LENGTH ? result.reduce(&:+) : result
14
+ elsif pathkeys.last == LENGTH
15
+ hash.dig(*pathkeys.slice(0..-2)).length
16
+ else
17
+ hash.dig *pathkeys
18
+ end
19
+ rescue => error
20
+ raise error, "The pathkeys \"#{pathkeys.join ' '}\" is a not valid path"
21
+ end
22
+ end
23
+
24
+ end # Drawght
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ module Drawght
4
+ VERSION = "1.1.0"
5
+ RELEASE_DATE = "2025-12-22"
6
+ CHANGESET = [
7
+ "Syntax validation.",
8
+ "Refined extension methods have been moved to their respective modules.",
9
+ "Syntax for getting the length of a collection.",
10
+ ]
11
+ end
data/lib/drawght.rb CHANGED
@@ -1,7 +1,17 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
1
4
  module Drawght
2
5
  require_relative "drawght/parser"
6
+ require_relative "drawght/tracker"
7
+ require_relative "drawght/compiler"
8
+ require_relative "drawght/version"
9
+
10
+ def self.load_template(template)
11
+ Compiler.new template
12
+ end
3
13
 
4
- def self.new(template)
5
- Drawght::Parser.new template
14
+ def self.compile(template, data)
15
+ load_template(template).compile data
6
16
  end
7
17
  end
metadata CHANGED
@@ -1,34 +1,44 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: drawght
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hallison Batista
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-07-11 00:00:00.000000000 Z
10
+ date: 2025-12-22 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: |
13
+ Drawght v1.1.0 (2025-12-22)
14
+
14
15
  Drawght is a data handler for texts without logical statements. The goal is
15
16
  to use a dataset (such as the subject of a text) to draft a document
16
17
  template. It can be considered a mini template processor.
18
+
19
+ Latest changes:
20
+
21
+ - Syntax validation.
22
+ - Refined extension methods have been moved to their respective modules.
23
+ - Syntax for getting the length of a collection.
17
24
  email: email@hallison.dev.br
18
25
  executables: []
19
26
  extensions: []
20
27
  extra_rdoc_files: []
21
28
  files:
29
+ - CHANGELOG.yaml
22
30
  - LICENSE
23
31
  - README.md
24
- - drawght.gemspec
25
32
  - lib/drawght.rb
33
+ - lib/drawght/compiler.rb
34
+ - lib/drawght/mapper.rb
26
35
  - lib/drawght/parser.rb
27
- homepage: https://github.com/drawght/drawght-ruby
36
+ - lib/drawght/tracker.rb
37
+ - lib/drawght/version.rb
38
+ homepage: https://drawght.github.io
28
39
  licenses:
29
40
  - MIT
30
41
  metadata: {}
31
- post_install_message:
32
42
  rdoc_options: []
33
43
  require_paths:
34
44
  - lib
@@ -43,8 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
43
53
  - !ruby/object:Gem::Version
44
54
  version: '0'
45
55
  requirements: []
46
- rubygems_version: 3.1.2
47
- signing_key:
56
+ rubygems_version: 3.6.7
48
57
  specification_version: 4
49
- summary: Drawght parser implementation in Ruby.
58
+ summary: Drawght implementation in Ruby.
50
59
  test_files: []
data/drawght.gemspec DELETED
@@ -1,30 +0,0 @@
1
- Gem::Specification.new do |spec|
2
- spec.name = "drawght"
3
- spec.summary = "Drawght parser implementation in Ruby."
4
- spec.authors = ["Hallison Batista"]
5
- spec.email = "email@hallison.dev.br"
6
- spec.homepage = "https://github.com/drawght/drawght-ruby"
7
- spec.version = %x(git describe --tags --abbrev=0)
8
- spec.date = %x(git log --format='%as' --max-count=1)
9
- spec.licenses = ["MIT"]
10
- spec.platform = Gem::Platform::RUBY
11
-
12
- spec.files = %x(git ls-files).split.reject do |out|
13
- ignore = out =~ /([MR]ake|Gem)file/ || out =~ /^\./
14
- ignore = ignore || out =~ /example\..*/
15
- ignore = ignore || out =~ %r{^doc/api} || out =~ %r{^test/.*}
16
- ignore
17
- end
18
-
19
- spec.test_files = spec.files.select do |path|
20
- path =~ %r{^test/.*}
21
- end
22
-
23
- spec.description = <<-end.gsub /^[ ]{4}/m, ""
24
- Drawght is a data handler for texts without logical statements. The goal is
25
- to use a dataset (such as the subject of a text) to draft a document
26
- template. It can be considered a mini template processor.
27
- end
28
-
29
- spec.require_paths = ["lib"]
30
- end