cecil 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|