cecil 0.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +25 -0
- data/.tool-versions +1 -0
- data/.yard/README.md +492 -0
- data/.yardopts +2 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +81 -0
- data/LICENSE.txt +21 -0
- data/README.md +492 -0
- data/Rakefile +46 -0
- data/lib/cecil/block_context.rb +150 -0
- data/lib/cecil/builder.rb +66 -0
- data/lib/cecil/code.rb +335 -0
- data/lib/cecil/content_for.rb +27 -0
- data/lib/cecil/indentation.rb +131 -0
- data/lib/cecil/lang/typescript.rb +95 -0
- data/lib/cecil/node.rb +397 -0
- data/lib/cecil/placeholder.rb +31 -0
- data/lib/cecil/text.rb +112 -0
- data/lib/cecil/version.rb +5 -0
- data/lib/cecil.rb +11 -0
- data/sig/cecil.rbs +4 -0
- metadata +70 -0
data/lib/cecil/node.rb
ADDED
@@ -0,0 +1,397 @@
|
|
1
|
+
require_relative "content_for"
|
2
|
+
require_relative "text"
|
3
|
+
require_relative "indentation"
|
4
|
+
|
5
|
+
module Cecil
|
6
|
+
class Node
|
7
|
+
# @!visibility private
|
8
|
+
attr_accessor :parent
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
def initialize(parent:)
|
12
|
+
self.parent = parent
|
13
|
+
end
|
14
|
+
|
15
|
+
# @!visibility private
|
16
|
+
def builder = root.builder
|
17
|
+
|
18
|
+
# @!visibility private
|
19
|
+
def root = parent.root
|
20
|
+
|
21
|
+
# @!visibility private
|
22
|
+
def depth = parent.depth + 1
|
23
|
+
|
24
|
+
# @!visibility private
|
25
|
+
def evaluate! = self
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
def replace_child(...) = nil
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
def reattach_to(new_parent)
|
32
|
+
parent.remove_child self
|
33
|
+
|
34
|
+
self.parent = new_parent
|
35
|
+
new_parent.add_child self
|
36
|
+
end
|
37
|
+
|
38
|
+
# @!visibility private
|
39
|
+
def replace_with(node) = builder.replace_node self, node
|
40
|
+
|
41
|
+
# Provide values for placeholders and/or nest a block of code. When called, will replace this node with a {Literal}
|
42
|
+
# or {LiteralWithChildren}.
|
43
|
+
#
|
44
|
+
# Placeholder values can be given as positional arguments or named values, but not both.
|
45
|
+
#
|
46
|
+
# When called with a block, the block is called immediately and any source code emitted is nested under the current
|
47
|
+
# block.
|
48
|
+
#
|
49
|
+
# @return [Node]
|
50
|
+
#
|
51
|
+
# @overload with(*positional_values)
|
52
|
+
# @overload with(**named_values)
|
53
|
+
# @overload with(*positional_values, &)
|
54
|
+
# @overload with(**named_values, &)
|
55
|
+
# @overload with(&)
|
56
|
+
#
|
57
|
+
# @example Positional values are replaced in the order given
|
58
|
+
# `const $field = $value`["user", "Alice".to_json]
|
59
|
+
# # const user = "Alice"
|
60
|
+
#
|
61
|
+
# @example Positional values replace all placeholders with the same name
|
62
|
+
# `const $field: $Namespace.$Class = new $Namespace.$Class()`["user", "Models", "User"]
|
63
|
+
# # const user: Models.User = new Models.User()
|
64
|
+
#
|
65
|
+
# @example Named values replace all placeholders with the given name
|
66
|
+
# `const $field = $value`[field: "user", value: "Alice".to_json]
|
67
|
+
# # const user = "Alice"
|
68
|
+
#
|
69
|
+
# `const $field: $Class = new $Class()`[field: "user", Class: "User"]
|
70
|
+
# # const user: User = new User()
|
71
|
+
#
|
72
|
+
# @example Blocks indent their emitted contents (see {Code#indent_chars})
|
73
|
+
# `class $Class {`["User"] do
|
74
|
+
# `public $field: $type`["name", "string"]
|
75
|
+
#
|
76
|
+
# # multiline nodes still get indented correctly
|
77
|
+
# `get id() {
|
78
|
+
# return this.name
|
79
|
+
# }`
|
80
|
+
#
|
81
|
+
# # nodes can nest arbitrarily deep
|
82
|
+
# `get $field() {`["upperCaseName"] do
|
83
|
+
# `return this.name.toUpperCase()`
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# # class User {
|
88
|
+
# # public name: string
|
89
|
+
# # get id() {
|
90
|
+
# # return this.name
|
91
|
+
# # }
|
92
|
+
# # get upperCaseName() {
|
93
|
+
# # return this.name.toUpperCase()
|
94
|
+
# # }
|
95
|
+
# # }
|
96
|
+
#
|
97
|
+
# @example Blocks close trailing open brackets (defined in {Code#block_ending_pairs})
|
98
|
+
# `ids = new Set([`[] do
|
99
|
+
# `1, 2, 3`
|
100
|
+
# end
|
101
|
+
# # ids = new Set([
|
102
|
+
# # 1, 2, 3
|
103
|
+
# # ])
|
104
|
+
#
|
105
|
+
# @example Can be called with no parameters to nest a block
|
106
|
+
# `ids = new Set([`[] do
|
107
|
+
# `1, 2, 3`
|
108
|
+
# end
|
109
|
+
# @see Code
|
110
|
+
def with(*positional_values, **named_values, &) = raise "Not implemented" # rubocop:disable Lint/UnusedMethodArgument
|
111
|
+
|
112
|
+
# Alias of {#with}
|
113
|
+
# @return [Node]
|
114
|
+
def [](...)
|
115
|
+
# don't use alias/alias_method b/c subclasses overriding `with` need `[]` to call `self.with`
|
116
|
+
with(...)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Append a string or node to the node, without making a new line.
|
120
|
+
#
|
121
|
+
# @param string_or_node [String, Node]
|
122
|
+
# @return [Node]
|
123
|
+
#
|
124
|
+
# @example Append a string to close brackets that aren't closed automatically
|
125
|
+
# `test("quacks like a duck", () => {`[] do
|
126
|
+
# `expect(duck)`
|
127
|
+
# end << ')' # closes open bracket from "test("
|
128
|
+
#
|
129
|
+
# # test("quacks like a duck", () => {
|
130
|
+
# # expect(duck)
|
131
|
+
# # })
|
132
|
+
#
|
133
|
+
# @example Use backticks to append brackets
|
134
|
+
# `test("quacks like a duck", () => {`[] do
|
135
|
+
# `expect(duck)`
|
136
|
+
# end << `)` # closes open bracket from "test("
|
137
|
+
#
|
138
|
+
# # test("quacks like a duck", () => {`
|
139
|
+
# # expect(duck)
|
140
|
+
# # })
|
141
|
+
def <<(string_or_node)
|
142
|
+
SameLineContainer.new(parent:).tap do |container|
|
143
|
+
container.add_child self
|
144
|
+
replace_with container
|
145
|
+
end << string_or_node
|
146
|
+
end
|
147
|
+
|
148
|
+
# @!visibility private
|
149
|
+
module AsParent
|
150
|
+
def self.included(base)
|
151
|
+
base.attr_accessor :children
|
152
|
+
end
|
153
|
+
|
154
|
+
def initialize(**kwargs, &)
|
155
|
+
super(**kwargs)
|
156
|
+
|
157
|
+
self.children = []
|
158
|
+
add_to_root(&)
|
159
|
+
end
|
160
|
+
|
161
|
+
def add_to_root(&) = builder.build_node(self, &)
|
162
|
+
|
163
|
+
def build_child(**kwargs) = root.build_child(**kwargs, parent: self)
|
164
|
+
|
165
|
+
def add_child(child) = children << child
|
166
|
+
|
167
|
+
def evaluate!
|
168
|
+
children&.map!(&:evaluate!)
|
169
|
+
super
|
170
|
+
end
|
171
|
+
|
172
|
+
def remove_child(child) = children.delete(child)
|
173
|
+
|
174
|
+
def replace_child(old_node, new_node)
|
175
|
+
if idx = children.index(old_node)
|
176
|
+
children[idx] = new_node
|
177
|
+
else
|
178
|
+
children.each { _1.replace_child(old_node, new_node) }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def stringify_children = children.map(&:stringify)
|
183
|
+
|
184
|
+
def stringify = stringify_children.join
|
185
|
+
end
|
186
|
+
|
187
|
+
# Node that will be replaced with its children, after the rest of the document is evaluated.
|
188
|
+
#
|
189
|
+
# Created by calling {BlockContext#defer} or by the internal workings of {BlockContext#content_for}.
|
190
|
+
#
|
191
|
+
# @see BlockContext#defer
|
192
|
+
# @see BlockContext#content_for
|
193
|
+
class Deferred < Node
|
194
|
+
# @!visibility private
|
195
|
+
def initialize(**kwargs, &block) # rubocop:disable Style/ArgumentsForwarding,Naming/BlockForwarding
|
196
|
+
super(**kwargs)
|
197
|
+
|
198
|
+
@evaluate = lambda do
|
199
|
+
Container.new(**kwargs, &block) # rubocop:disable Style/ArgumentsForwarding,Naming/BlockForwarding
|
200
|
+
.tap { root.replace_child self, _1 }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# @!visibility private
|
205
|
+
def evaluate!(...) = @evaluate.call(...)
|
206
|
+
end
|
207
|
+
|
208
|
+
# @!visibility private
|
209
|
+
class RootNode < Node
|
210
|
+
include AsParent
|
211
|
+
|
212
|
+
attr_accessor :builder
|
213
|
+
|
214
|
+
def initialize(builder)
|
215
|
+
@builder = builder
|
216
|
+
|
217
|
+
super(parent: nil)
|
218
|
+
end
|
219
|
+
|
220
|
+
def root = self
|
221
|
+
def depth = -1
|
222
|
+
|
223
|
+
def add_to_root(...) = nil
|
224
|
+
|
225
|
+
def build_node(...) = builder.build_node(...)
|
226
|
+
|
227
|
+
def build_child(src:, parent: self) = Template.build(src:, parent:, builder:)
|
228
|
+
end
|
229
|
+
|
230
|
+
# @!visibility private
|
231
|
+
class Container < Node
|
232
|
+
include AsParent
|
233
|
+
|
234
|
+
def depth = parent.depth
|
235
|
+
end
|
236
|
+
|
237
|
+
# Node that contains child nodes that were appended to via {Node#<<}.
|
238
|
+
class SameLineContainer < Container
|
239
|
+
# @!visibility private
|
240
|
+
def initialize(parent:)
|
241
|
+
super(parent:) do
|
242
|
+
yield self if block_given?
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# @!visibility private
|
247
|
+
def stringify
|
248
|
+
*firsts, last = stringify_children
|
249
|
+
firsts_without_trailing_newline = firsts.map { _1.sub(/\R\z/m, "") }
|
250
|
+
[*firsts_without_trailing_newline, last].join
|
251
|
+
end
|
252
|
+
|
253
|
+
# @!visibility private
|
254
|
+
def <<(string_or_node)
|
255
|
+
case string_or_node
|
256
|
+
in Node => node
|
257
|
+
node.reattach_to self
|
258
|
+
in String => string
|
259
|
+
builder.build_node(self) { builder.src string }
|
260
|
+
end
|
261
|
+
|
262
|
+
self
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Node that will be inserted in another location in the document.
|
267
|
+
#
|
268
|
+
# Created by {BlockContext#content_for} with a block.
|
269
|
+
#
|
270
|
+
# @see BlockContext#content_for
|
271
|
+
class Detached < Container
|
272
|
+
# @!visibility private
|
273
|
+
attr_accessor :root
|
274
|
+
|
275
|
+
# @!visibility private
|
276
|
+
def initialize(root, &)
|
277
|
+
@root = root
|
278
|
+
super(parent: nil, &)
|
279
|
+
end
|
280
|
+
|
281
|
+
# @!visibility private
|
282
|
+
def attach_to(new_parent)
|
283
|
+
self.parent = new_parent
|
284
|
+
new_parent.add_child self
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Node with source code, no placeholders, and no child nodes. Created by calling
|
289
|
+
# {BlockContext#src `` #`(code_str) ``} with a string that has no placeholders.
|
290
|
+
#
|
291
|
+
# Will not accept any placeholder values, but can receive children via {#with}/{#[]} and will replace itself with a
|
292
|
+
# {LiteralWithChildren}.
|
293
|
+
class Literal < Node
|
294
|
+
# @!visibility private
|
295
|
+
def self.build(...)
|
296
|
+
klass = block_given? ? LiteralWithChildren : self
|
297
|
+
klass.new(...)
|
298
|
+
end
|
299
|
+
|
300
|
+
# @!visibility private
|
301
|
+
def initialize(src:, **kwargs)
|
302
|
+
super(**kwargs)
|
303
|
+
@src = src
|
304
|
+
end
|
305
|
+
|
306
|
+
# @!visibility private
|
307
|
+
def with(*args, **options, &)
|
308
|
+
raise "This fragement has no placeholders. Fragment:\n#{@src}" if args.any? || options.any?
|
309
|
+
|
310
|
+
raise "This method requires a block" unless block_given?
|
311
|
+
|
312
|
+
self.class.build(src: @src, parent:, &)
|
313
|
+
.tap { builder.replace_node self, _1 }
|
314
|
+
end
|
315
|
+
|
316
|
+
# @!visibility private
|
317
|
+
def stringify_src
|
318
|
+
src = Indentation.reindent(@src, depth, builder.syntax.indent_chars,
|
319
|
+
handle_ambiguity: builder.syntax.handle_ambiguous_indentation)
|
320
|
+
src += "\n" unless src.end_with?("\n")
|
321
|
+
src
|
322
|
+
end
|
323
|
+
|
324
|
+
# @!visibility private
|
325
|
+
alias stringify stringify_src
|
326
|
+
end
|
327
|
+
|
328
|
+
# Node with source code, no placeholders, and child nodes. Created by calling {BlockContext#src `` #`(code_str) ``}
|
329
|
+
# with a string without placeholders and then calling {#with}/{#[]} on it.
|
330
|
+
class LiteralWithChildren < Literal
|
331
|
+
include AsParent
|
332
|
+
|
333
|
+
# @!visibility private
|
334
|
+
def closers
|
335
|
+
closing_brackets = Text.closers(@src.strip, builder.syntax.block_ending_pairs).to_a
|
336
|
+
|
337
|
+
Indentation.reindent("#{closing_brackets.join.strip}\n", depth, builder.syntax.indent_chars,
|
338
|
+
handle_ambiguity: builder.syntax.handle_ambiguous_indentation)
|
339
|
+
end
|
340
|
+
|
341
|
+
# @!visibility private
|
342
|
+
def stringify
|
343
|
+
[
|
344
|
+
stringify_src,
|
345
|
+
*stringify_children,
|
346
|
+
*closers
|
347
|
+
].join
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# A node that has placeholders but does not yet have values or children. Created with backticks or
|
352
|
+
# {BlockContext#src `` #`(code_str) ``}
|
353
|
+
#
|
354
|
+
# When {#with}/{#[]} is called on the node, it will replace itself with a {Literal} or {LiteralWithChildren}
|
355
|
+
class Template < Node
|
356
|
+
# @!visibility private
|
357
|
+
def self.build(src:, builder:, **kwargs)
|
358
|
+
placeholders = builder.syntax.scan_for_placeholders(src)
|
359
|
+
|
360
|
+
if placeholders.any?
|
361
|
+
new(src:, placeholders:, **kwargs)
|
362
|
+
else
|
363
|
+
Literal.new(src:, **kwargs)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# @!visibility private
|
368
|
+
def initialize(src:, placeholders:, **kwargs)
|
369
|
+
super(**kwargs)
|
370
|
+
@src = src
|
371
|
+
@placeholders = placeholders
|
372
|
+
end
|
373
|
+
|
374
|
+
# @see Node#with
|
375
|
+
def with(*positional_values, **named_values, &)
|
376
|
+
src =
|
377
|
+
case [positional_values, named_values, @placeholders]
|
378
|
+
in [], {}, []
|
379
|
+
@src
|
380
|
+
in [], _, _
|
381
|
+
Text.interpolate_named(@src, @placeholders, named_values)
|
382
|
+
in _, {}, _
|
383
|
+
Text.interpolate_positional(@src, @placeholders, positional_values)
|
384
|
+
else
|
385
|
+
raise "Method expects to be called with either named arguments or positional arguments but not both"
|
386
|
+
end
|
387
|
+
|
388
|
+
Literal
|
389
|
+
.build(src:, parent:, &)
|
390
|
+
.tap { builder.replace_node self, _1 }
|
391
|
+
end
|
392
|
+
|
393
|
+
# @!visibility private
|
394
|
+
def stringify = raise "This fragement has placeholders but was not given values. Fragment:\n#{@src}"
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Cecil
|
2
|
+
# Represents the name and location of a placeholder in a string.
|
3
|
+
Placeholder = Struct.new(:ident, :offset_start, :offset_end) do
|
4
|
+
# @!attribute ident
|
5
|
+
# @return [String] the name of this placeholder. E.g. the `ident` of `${my_field}` would be `my_field`
|
6
|
+
|
7
|
+
# @!attribute offset_start
|
8
|
+
# @return [Integer] the offset where this placeholder starts in the
|
9
|
+
# string. This number is usually taken from a Regexp match.
|
10
|
+
|
11
|
+
# @!attribute offset_end
|
12
|
+
# @return [Integer] the offset where this placeholder ends in the
|
13
|
+
# string. This number is usually taken from a Regexp match.
|
14
|
+
|
15
|
+
# Return the range that this placeholder occupies in the string
|
16
|
+
# @return [Range(Integer)]
|
17
|
+
def range = offset_start...offset_end
|
18
|
+
|
19
|
+
# Mimicks Data#with, introduced in Ruby 3.2
|
20
|
+
def with(**kwargs) = self.class.new(*to_h.merge(kwargs).values_at(*members))
|
21
|
+
|
22
|
+
# Create a new {Placeholder} with one member transformed by the given block
|
23
|
+
#
|
24
|
+
# @example Make a new placeholder with ident in uppercase
|
25
|
+
# placeholder.transform_key(:ident, &:upcase)
|
26
|
+
#
|
27
|
+
# @param [Symbol] member
|
28
|
+
# @return [Placeholder]
|
29
|
+
def transform_key(member) = with(**{ member => yield(self[member]) })
|
30
|
+
end
|
31
|
+
end
|
data/lib/cecil/text.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module Cecil
|
4
|
+
# Helper methods for string searching, manipulating, etc.
|
5
|
+
module Text
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# Scan a string for matches on a Regexp and return each MatchObject
|
9
|
+
#
|
10
|
+
# @param str [String] the string to scan
|
11
|
+
# @param regexp [Regexp] the regexp to scan with
|
12
|
+
# @return [Array<MatchData>] The MatchData objects for each instance of the regexp matched in the string
|
13
|
+
def scan_for_re_matches(str, regexp) = str.to_enum(:scan, regexp).map { Regexp.last_match }
|
14
|
+
|
15
|
+
# Interpolate positional placeholder values into a string
|
16
|
+
#
|
17
|
+
# @param template [String]
|
18
|
+
# @param placeholders [Array<Placeholder>]
|
19
|
+
# @param values [Array<#to_s>]
|
20
|
+
# @return [String] `template`, except with placeholders replaced with
|
21
|
+
# provided values
|
22
|
+
def interpolate_positional(template, placeholders, values)
|
23
|
+
match_idents = placeholders.to_set(&:ident)
|
24
|
+
|
25
|
+
if match_idents.size != values.size
|
26
|
+
raise "Mismatch between number of placeholders (#{match_idents.size}) and given values (#{values.size})"
|
27
|
+
end
|
28
|
+
|
29
|
+
replace(template, placeholders, match_idents.zip(values).to_h)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Interpolate named placeholder values into a string
|
33
|
+
#
|
34
|
+
# @param template [String]
|
35
|
+
# @param placeholders [Array<Placeholder>]
|
36
|
+
# @param idents_to_values [Hash{#to_s=>#to_s}]
|
37
|
+
# @return [String] `template`, except with placeholders replaced with
|
38
|
+
# provided values
|
39
|
+
def interpolate_named(template, placeholders, idents_to_values)
|
40
|
+
duplicated_keys = idents_to_values.keys.group_by(&:to_s).values.select { _1.size > 1 }
|
41
|
+
if duplicated_keys.any?
|
42
|
+
keys_list = duplicated_keys.map { "\n - #{_1.map(&:inspect).join(", ")}\n" }.join
|
43
|
+
raise "Duplicate placeholder value keys:#{keys_list}"
|
44
|
+
end
|
45
|
+
|
46
|
+
values_idents = idents_to_values.keys.to_set(&:to_s)
|
47
|
+
match_idents = placeholders.to_set(&:ident)
|
48
|
+
|
49
|
+
if match_idents != values_idents
|
50
|
+
missing_values = match_idents - values_idents
|
51
|
+
extra_values = values_idents - match_idents
|
52
|
+
message = "Mismatch between placeholders and provide values."
|
53
|
+
message << "\n Missing values for placeholders #{missing_values.join(", ")}" if missing_values.any?
|
54
|
+
message << "\n Missing placeholders for values #{extra_values.join(", ")}" if extra_values.any?
|
55
|
+
|
56
|
+
raise message
|
57
|
+
end
|
58
|
+
|
59
|
+
replace(template, placeholders, idents_to_values)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Replace placeholders in the string with provided values
|
63
|
+
#
|
64
|
+
# @param template [String]
|
65
|
+
# @param placeholders [Array<Placeholder>]
|
66
|
+
# @param idents_to_values [Hash{#to_s=>#to_s}]
|
67
|
+
# @return [String] `template`, except with placeholders replaced with
|
68
|
+
# provided values
|
69
|
+
def replace(template, placeholders, idents_to_values)
|
70
|
+
values = idents_to_values.transform_keys(&:to_s)
|
71
|
+
|
72
|
+
template.dup.tap do |new_src|
|
73
|
+
placeholders.reverse.each do |placeholder|
|
74
|
+
value = values.fetch(placeholder.ident)
|
75
|
+
|
76
|
+
new_src[placeholder.range] = value.to_s
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns any closing bracket found
|
82
|
+
#
|
83
|
+
# @param src [String]
|
84
|
+
# @param block_ending_pairs [Hash{String=>String}]
|
85
|
+
def match_ending_pair(src, block_ending_pairs)
|
86
|
+
return if src.empty?
|
87
|
+
|
88
|
+
block_ending_pairs.detect { |opener, _closer| src.end_with?(opener) }
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns or yields each closing bracket.
|
92
|
+
#
|
93
|
+
# @param src [String]
|
94
|
+
# @param block_ending_pairs [Hash{String=>String}]
|
95
|
+
#
|
96
|
+
# @overload closers(src, block_ending_pairs, &)
|
97
|
+
# With block given, behaves like {.each_closer}
|
98
|
+
# @yield [String]
|
99
|
+
#
|
100
|
+
# @overload closers(src, block_ending_pairs)
|
101
|
+
# When no block is given, returns an enumerator of the {.each_closer} method
|
102
|
+
# @return [Enumerator<String>]
|
103
|
+
def closers(src, block_ending_pairs)
|
104
|
+
return enum_for(:closers, src, block_ending_pairs) unless block_given?
|
105
|
+
|
106
|
+
while match_ending_pair(src, block_ending_pairs) in [opener, closer]
|
107
|
+
yield closer
|
108
|
+
src = src[0...-opener.size]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/cecil.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative "cecil/version"
|
2
|
+
require_relative "cecil/builder"
|
3
|
+
require_relative "cecil/block_context"
|
4
|
+
require_relative "cecil/code"
|
5
|
+
|
6
|
+
module Cecil
|
7
|
+
# @!visibility private
|
8
|
+
def self.generate(out:, syntax_class:, &)
|
9
|
+
out << Builder.new(syntax_class).build(&)
|
10
|
+
end
|
11
|
+
end
|
data/sig/cecil.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cecil
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Nicholaides
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-01-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Cecil templates look like the source code you want to generate thanks
|
14
|
+
to Ruby's flexible syntax.
|
15
|
+
email:
|
16
|
+
- mike@nicholaides.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- ".rspec"
|
22
|
+
- ".rubocop.yml"
|
23
|
+
- ".tool-versions"
|
24
|
+
- ".yard/README.md"
|
25
|
+
- ".yardopts"
|
26
|
+
- Gemfile
|
27
|
+
- Gemfile.lock
|
28
|
+
- LICENSE.txt
|
29
|
+
- README.md
|
30
|
+
- Rakefile
|
31
|
+
- lib/cecil.rb
|
32
|
+
- lib/cecil/block_context.rb
|
33
|
+
- lib/cecil/builder.rb
|
34
|
+
- lib/cecil/code.rb
|
35
|
+
- lib/cecil/content_for.rb
|
36
|
+
- lib/cecil/indentation.rb
|
37
|
+
- lib/cecil/lang/typescript.rb
|
38
|
+
- lib/cecil/node.rb
|
39
|
+
- lib/cecil/placeholder.rb
|
40
|
+
- lib/cecil/text.rb
|
41
|
+
- lib/cecil/version.rb
|
42
|
+
- sig/cecil.rbs
|
43
|
+
homepage: https://github.com/nicholaides/cecil
|
44
|
+
licenses:
|
45
|
+
- MIT
|
46
|
+
metadata:
|
47
|
+
allowed_push_host: https://rubygems.org
|
48
|
+
rubygems_mfa_required: 'true'
|
49
|
+
homepage_uri: https://github.com/nicholaides/cecil
|
50
|
+
source_code_uri: https://github.com/nicholaides/cecil
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 3.1.0
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubygems_version: 3.3.26
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: An experimental templating library for generating source code.
|
70
|
+
test_files: []
|