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.
data/README.md ADDED
@@ -0,0 +1,492 @@
1
+ # Cecil
2
+
3
+ An experimental templating library for generating source code.
4
+
5
+ Cecil templates look like the source code you want to generate thanks to Ruby's flexible syntax.
6
+
7
+ ## Features
8
+
9
+ ### Write templates in plain Ruby
10
+
11
+ Call `Cecil::Code.generate_string` and pass it a block. Inside the block, add lines of code via backticks (or use `src` if you prefer). Cecil returns your generated source code as a string.
12
+
13
+ #### Example
14
+
15
+ ```ruby
16
+ model_code = Cecil::Code.generate_string do
17
+ # Use backticks to add lines of code
18
+ `import Model from '../model'`
19
+
20
+ # Multi-line strings work, too.
21
+ # Cecil preserves indentation.
22
+ `class User extends Model {
23
+ id: number
24
+ name: string
25
+ companyId: number | undefined
26
+ }`
27
+
28
+ # use #src if you prefer to avoid backticks
29
+ src "export type Username = User['name']"
30
+ end
31
+
32
+ puts model_code
33
+ ```
34
+
35
+ Returns:
36
+
37
+ ```typescript
38
+ import Model from '../model'
39
+ class User extends Model {
40
+ id: number
41
+ name: string
42
+ companyId: number | undefined
43
+ }
44
+ export type Username = User['name']
45
+ ```
46
+
47
+ ### Interpolate values with Cecil's low-noise syntax
48
+
49
+ Use `#[]` on the backticks to replace placeholders with actual values.
50
+
51
+ By default, placeholders start with `$` and are followed by an identifier.
52
+
53
+ Positional arguments match up with placeholders in order. Named arguments match placeholders by name.
54
+
55
+ #### Example
56
+
57
+ ```ruby
58
+ field = "user"
59
+ types = ["string", "string[]"]
60
+ default_value = ["SilentHaiku", "DriftingSnowfall"]
61
+ field_class = "Model"
62
+
63
+ Cecil::Code.generate_string do
64
+ # positional arguments match placeholders by position
65
+ `let $field: $FieldType = $default`[field, types.join('|'), default_value.sort.to_json]
66
+
67
+ # named arguments match placeholders by name
68
+ `let $field: $FieldClass<$Types> = new $FieldClass($default)`[
69
+ field: field,
70
+ FieldClass: field_class,
71
+ Types: types.join('|'),
72
+ default: default_value.sort.to_json
73
+ ]
74
+ end
75
+ ```
76
+
77
+ Returns:
78
+
79
+ ```typescript
80
+ let user: string|string[] = ["DriftingSnowfall","SilentHaiku"]
81
+ let user: Model<string|string[]> = new Model(["DriftingSnowfall","SilentHaiku"])
82
+ ```
83
+
84
+
85
+ #### "Doesn't Ruby already have string interpolation?"
86
+
87
+ Yes, but compare the readability of these two approaches:
88
+
89
+ ```ruby
90
+ `let $field: $FieldClass<$Types> = new $FieldClass($default)`[
91
+ field: field,
92
+ FieldClass: field_class,
93
+ Types: types.join('|'),
94
+ default: default_value.sort.to_json
95
+ ]
96
+
97
+ # vs
98
+
99
+ field_types = types.join('|'),
100
+ default_json = default_value.sort.to_json
101
+ "let #{field}: #{field_class}<#{field_types}> = new #{field_class}(#{default_json})"
102
+ ```
103
+
104
+ ### Indents code blocks & closes brackets automatically
105
+
106
+ Pass a block to `#[]` gets indented and open brackets get closed automatically.
107
+
108
+ #### Example
109
+
110
+ ```ruby
111
+ model = "User"
112
+ field_name = "name"
113
+ field_default = "Unnamed"
114
+
115
+ Cecil::Code.generate_string do
116
+ `class $Class extends Model {`[model] do
117
+ # indentation is preserved
118
+ `id: number`
119
+
120
+ `override get $field() {`[field_name] do
121
+ `return super.$field ?? $defaultValue`[field_name, field_default.to_json]
122
+ end
123
+ end # the open bracket from `... Model {` gets closed with "}"
124
+ end
125
+ ```
126
+
127
+ Returns:
128
+
129
+ ```typescript
130
+ class User extends Model {
131
+ id: number
132
+ override get name() {
133
+ return super.name ?? "Unnamed"
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### Emit source code to other locations
139
+
140
+ When generating source code, things like functions, parameters, classes, etc, often need to be declared, imported, or otherwise setup or before being used.
141
+
142
+ `content_for` can be used to add content to a different location of your file.
143
+
144
+ Call `content_for(some_key) { ... }` with key and a block to store content under the key you provide. Call `content_for(some_key)` with the key and *no* block to insert your stored content at that location.
145
+
146
+ #### Example
147
+
148
+ ```ruby
149
+ models = [
150
+ { name: 'User', inherits: 'AuthModel' },
151
+ { name: 'Company', inherits: 'Model' },
152
+ ]
153
+
154
+ Cecil::Code.generate_string do
155
+ # insert content collected for :imports
156
+ content_for :imports
157
+
158
+ models.each do |model|
159
+ ``
160
+ `class $Class extends $SuperClass {`[model[:name], model[:inherits]] do
161
+ `id: number`
162
+ end
163
+
164
+ content_for :imports do
165
+ # this gets inserted above
166
+ `import $SuperClass from '../models/$SuperClass'`[SuperClass: model[:inherits]]
167
+ end
168
+
169
+ content_for :registrations do
170
+ # this gets inserted below
171
+ `$SuperClass.registerAncestor($Class)`[model[:inherits], model[:name]]
172
+ end
173
+ end
174
+
175
+ ``
176
+ # insert content collected for :registrations
177
+ content_for :registrations
178
+ end
179
+ ```
180
+
181
+ Returns:
182
+
183
+ ```typescript
184
+ import AuthModel from '../models/AuthModel'
185
+ import Model from '../models/Model'
186
+
187
+ class User extends AuthModel {
188
+ id: number
189
+ }
190
+
191
+ class Company extends Model {
192
+ id: number
193
+ }
194
+
195
+ AuthModel.registerAncestor(User)
196
+ Model.registerAncestor(Company)
197
+ ```
198
+
199
+ ### Collect data as you go then use it earlier in the document
200
+
201
+ The `#defer` method takes a block and waits to call it until the rest of the template is evaluated. The block's result is inserted at the location where `#defer` was called.
202
+
203
+ This gives a similar ability to `#content_for`, but is more flexible because you can collect any kind of data, not just source code.
204
+
205
+ #### Example
206
+
207
+ ```ruby
208
+ models = [
209
+ { name: 'User', inherits: 'AuthModel' },
210
+ { name: 'Company', inherits: 'Model' },
211
+ { name: 'Candidate', inherits: 'AuthModel' },
212
+ ]
213
+
214
+ Cecil::Code.generate_string do
215
+ superclasses = []
216
+
217
+ defer do
218
+ # This block gets called after the rest of the parent block is finished.
219
+ #
220
+ # By the time this block is called, the `superclasses` array is full of data
221
+ #
222
+ # Even though this block is called later, the output is added at the location where `defer` was called
223
+ `import { $SuperClasses } from '../models'`[superclasses.uniq.sort.join(', ')]
224
+ ``
225
+ end
226
+
227
+ models.each do |model|
228
+ superclasses << model[:inherits] # add more strings to `superclasses`, which is used in the block above
229
+
230
+ `class $Class extends $SuperClass {}`[model[:name], model[:inherits]]
231
+ end
232
+ end
233
+ ```
234
+
235
+ Returns:
236
+
237
+ ```typescript
238
+ import { AuthModel, Model } from '../models'
239
+
240
+ class User extends AuthModel {}
241
+ class Company extends Model {}
242
+ class Candidate extends AuthModel {}
243
+ ```
244
+
245
+ ### Customizable syntax and behaviors
246
+
247
+ Easily customize the following features to make Cecil suit your needs/preferences:
248
+
249
+ - placeholder syntax
250
+ - auto-closing brackets
251
+ - indentation
252
+
253
+ Customizations are performed by subclassing [`Cecil::Code`][{Code}] and overriding the relevant methods.
254
+
255
+ For example, Cecil comes with [`Cecil::Lang::TypeScript`][{Lang::TypeScript}] that you can use instead of of `Cecil::Code`. It has a few JavaScript/TypeScript-specific customizations. It's a subclass of `Cecil::Code` so it can be used the same way:
256
+
257
+ ```ruby
258
+ Cecil::Lang::TypeScript.generate_string do
259
+ # ...
260
+ end
261
+ ```
262
+
263
+ ## Use cases
264
+
265
+ Things I've personally used Cecil to generate:
266
+
267
+ - **serialization/deserialization code** generated from from specs (e.g. OpenAPI)
268
+ - **diagrams** (e.g. Mermaid, PlantUML, Dot/Graphviz)
269
+ - ERDs/schemas
270
+ - state machine diagrams
271
+ - graphs
272
+ - data visualizations
273
+ - **state machines** generated from a list of states and transitions
274
+ - **test cases** generated from data that describes inputs/setup and expected outputs; because parameterized tests can be very hard to debug
275
+ - **complex types** because meta-programming in TypeScript can get complex quickly
276
+
277
+ ## Quick Reference
278
+
279
+ Reference documentation is on RubyDoc.info:
280
+ [gem](https://www.rubydoc.info/gems/cecil)
281
+ |
282
+ [repo](https://www.rubydoc.info/github/nicholaides/cecil/main)
283
+
284
+ ### Calling Cecil
285
+
286
+ Call
287
+ [`Cecil::Code.generate`][{Code.generate}] /
288
+ [`generate_string`][{Code.generate_string}]
289
+ with a block and inside the block, use backticks or `#src` to emit lines of source code.
290
+ E.g.
291
+
292
+ ```ruby
293
+ # returns a string
294
+ Cecil::Code.generate_string do
295
+ `function greet() {}`
296
+ `function respond() {}`
297
+ end
298
+
299
+ # outputs to $stdout
300
+ Cecil::Code.generate do
301
+ `function greet() {}`
302
+ `function respond() {}`
303
+ end
304
+ ```
305
+
306
+ See: [Methods available inside a Cecil block][{BlockContext}]
307
+
308
+ ### Emitting source code
309
+
310
+ - [backticks/``` #`` ```/`#src`][{BlockContext#src}] emit source code.
311
+ E.g.:
312
+ ```ruby
313
+ Cecil::Code.generate_string do
314
+ `function greet() {}`
315
+ `function respond() {}`
316
+ src "function ask() {}"
317
+ end
318
+ # outputs:
319
+ # function greet() {}
320
+ # function respond() {}
321
+ # function ask() {}
322
+ ```
323
+
324
+ - [`#[]`][{Node#with}] interpolates data into placeholders. E.g.
325
+ ```ruby
326
+ Cecil::Code.generate_string do
327
+ `function $fn() {}`["greet"]
328
+ `function $fn() {}`[fn: "respond"]
329
+ end
330
+ # outputs:
331
+ # function greet() {}
332
+ # function respond() {}
333
+ ```
334
+ - [`#[]`][{Node#with}]`{ ... }` given a block, interpolates and indents the code emitted in its block.
335
+ E.g.
336
+ ```ruby
337
+ Cecil::Code.generate_string do
338
+ `function $fn() {`["greet"] do
339
+ `console.log("hello")`
340
+ end
341
+ end
342
+ # outputs:
343
+ # function greet() {
344
+ # console.log("hello")
345
+ # }
346
+ ```
347
+ - [`#<<`][{Node#<<}] adds code the last line of the block.
348
+ E.g.
349
+ ```ruby
350
+ Cecil::Code.generate_string do
351
+ `(function ${fn}Now() {`["greet"] do
352
+ `console.log("hello")`
353
+ end << ')()'
354
+ end
355
+ # outputs:
356
+ # (function greetNow() {
357
+ # console.log("hello")
358
+ # })()
359
+ ```
360
+ - [`#content_for`][{BlockContext#content_for}] emits source code to different locations
361
+ - [`#defer`][{BlockContext#defer}] for waits to emit the given source until after data has been gathered
362
+
363
+ ### Customizing behavior for the language of the source code you're generating
364
+
365
+ Many of Cecil's defaults can be customized by creating a subclass of [`Cecil::Code`][{Code}] and overriding methods to customize syntax and behavior of:
366
+ - placeholder syntax
367
+ - indentation
368
+ - auto-closing brackets
369
+
370
+ Currently, Cecil comes with:
371
+ - [`Cecil::Code`][{Code}] for generic code
372
+ - [`Cecil::Lang::TypeScript`][{Lang::TypeScript}] for JavaScript and TypeScript
373
+
374
+
375
+ ### Auto-closing brackets
376
+
377
+ > Customize which opening brackets are auto-closed by overriding [`Cecil::Code#block_ending_pairs`][{Code#block_ending_pairs}] in a subclass.
378
+
379
+ When nesting code blocks with `#[] { ... }`, open brackets at the end of the string get closed automatically.
380
+
381
+ For example, notice how we don't have to manually provide a closing `}` in the following:
382
+
383
+ ```ruby
384
+ `$var = {`[var: "user"] do
385
+ `id: 42`
386
+ end
387
+ ```
388
+ becomes
389
+ ```javascript
390
+ user = {
391
+ id: 42
392
+ }
393
+ ```
394
+
395
+ #### Multiple brackets
396
+
397
+ Every consecutive closing bracket at the end of the string gets closed. E.g.
398
+
399
+ ```ruby
400
+ `$var = [{(`[var: "user"] do
401
+ `id: 42`
402
+ end
403
+ ```
404
+
405
+ becomes
406
+
407
+ ```javascript
408
+ user = ([{
409
+ id: 42
410
+ )}]
411
+ ```
412
+
413
+ Currently, the algorithm is simplistic, so open brackets that aren't at the end of the string will *not* get closed.
414
+
415
+ In this example, the `(` in `test(` needs to be closed manually:
416
+
417
+ ```ruby
418
+ `test("getter $fn", () => {`[fn: 'getUsername'] do
419
+ `assert(false)`
420
+ end << `)'
421
+ ```
422
+
423
+ ```javascript
424
+ test("getter getUsername", () => {
425
+ assert(false)
426
+ })
427
+ ```
428
+
429
+ ### Placeholder syntax
430
+
431
+ Default placeholder rules:
432
+ - start with `$`-- e.g. `$foo`
433
+ - named with alpha-numeric and underscore -- e.g. `$foo_bar123`
434
+ - names can optionally be surrounded by optional brackets -- e.g `${my_placeholder}`
435
+
436
+ Surrounding with brackets can be useful to separate a placeholder from subsequent characters that would otherwise get parsed as a placeholder.
437
+
438
+ E.g. `function ${fn}Sync()`-- without curly brackets, the placeholder would be parsed as `fnSync`.
439
+
440
+ Customize placeholder syntax by subclassing [`Cecil::Code`][{Code}]
441
+ and overriding [placeholder-related methods][{Code}].
442
+
443
+
444
+ ### Helper methods
445
+
446
+ If you use your generator frequently it can be helpful to define reusable helper methods on a subclass of [`Cecil::Code`][{Code}].
447
+
448
+ For example, the [`Cecil::Lang::TypeScript`][{Lang::TypeScript}] subclass defines several [helper methods][{Lang::TypeScript::Helpers}] for generating TypeScript code.
449
+
450
+ [{BlockContext#content_for}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/BlockContext#content_for-instance_method
451
+ [{BlockContext#defer}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/BlockContext#defer-instance_method
452
+ [{BlockContext#src}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/BlockContext#src-instance_method
453
+ [{BlockContext}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/BlockContext
454
+ [{Code.generate_string}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code#generate_string-class_method
455
+ [{Code.generate}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code#generate-class_method
456
+ [{Code}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code
457
+ [{Code#block_ending_pairs}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code#block_ending_pairs-instance_method
458
+ [{Lang::TypeScript}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Lang/TypeScript
459
+ [{Lang::TypeScript::Helpers}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Lang/TypeScript/Helpers
460
+ [{Lang::TypeScript.generate_string}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Lang/TypeScript#generate_string-class_method
461
+ [{Lang::TypeScript.generate}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code#generate-class_method
462
+ [{Node#<<}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Node#<<-instance_method
463
+ [{Node#with}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Node#with-instance_method
464
+
465
+ ## Installation
466
+
467
+ Gem can be installed from github. Once I'm ready to bother with version numbers and releases and such, then I'll publish to Rubygems.
468
+
469
+ From your shell:
470
+
471
+ ```sh
472
+ bundle add cecil --github=nicholaides/cecil
473
+ ```
474
+
475
+ Add it to your Gemfile like:
476
+
477
+ ```ruby
478
+ gem 'cecil', github: 'nicholaides/cecil'
479
+ ```
480
+ ## Development
481
+
482
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
483
+
484
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
485
+
486
+ ## Contributing
487
+
488
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nicholaides/cecil.
489
+
490
+ ## License
491
+
492
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ require "rake/clean"
13
+ CLEAN.include ".yardoc/"
14
+
15
+ require "yard"
16
+ YARD::Rake::YardocTask.new do |t|
17
+ t.options = %w[--no-cache --fail-on-warning]
18
+ end
19
+
20
+ directory ".yard"
21
+ CLEAN.include ".yard/"
22
+
23
+ def normalize_yard_ref(str)
24
+ if str.start_with?("Cecil::")
25
+ str
26
+ else
27
+ "Cecil::#{str}"
28
+ end
29
+ end
30
+
31
+ def convert_markdown_yardoc_links_to_yardoc(str)
32
+ str.gsub(/\[(.+)\]\[\{([^\}\]]+)\}\]/) { "{#{normalize_yard_ref(Regexp.last_match(2))} #{Regexp.last_match(1)}}" }
33
+ end
34
+
35
+ file ".yard/README.md" => ["README.md", ".yard"] do |t|
36
+ File.write t.name, convert_markdown_yardoc_links_to_yardoc(File.read("README.md"))
37
+ end
38
+ task yard: ".yard/README.md"
39
+
40
+ task :ensure_yard_readme_is_up_to_date do
41
+ if File.read(".yard/README.md") != convert_markdown_yardoc_links_to_yardoc(File.read("README.md"))
42
+ raise ".yard/README.md is not up-to-date. Run `rake` before committing."
43
+ end
44
+ end
45
+
46
+ task default: %i[spec yard rubocop]
@@ -0,0 +1,150 @@
1
+ require "forwardable"
2
+ require "delegate"
3
+
4
+ module Cecil
5
+ # The {BlockContext} contains methods available to you inside a Cecil block.
6
+ #
7
+ # Methods available in the scope of a Cecil block are:
8
+ #
9
+ # - **Methods & variables from local scope**
10
+ # - **{BlockContext} instance methods** for emitting code (listed below)
11
+ # - **Helper methods** in your {Code} subclass' `Helpers`. See {Code} for defining your own helper methods.
12
+ #
13
+ # @example Methods available in a Cecil block's scope
14
+ # def has_data?(first_name) = File.exist?("data/#{first_name}.json")
15
+ #
16
+ # name = "Bob"
17
+ # last_names = ["McTesterson", "Rickenbacker"]
18
+ #
19
+ # Cecil::Lang::TypeScript.generate_string do
20
+ # content_for :imports # `content_for` is a BlockContext instance method
21
+ #
22
+ # `let firstName = "$username"`[name] # `name` is a local variable
23
+ #
24
+ # `let lastNames = $lastNames`[j last_names] # `j` helper via Cecil::Lang::TypeScript::Helpers
25
+ #
26
+ # if has_data?(name) # has_data? comes from local scope
27
+ # content_for :imports do
28
+ # `import userData from './data/$first_name.json`[s name]
29
+ # end
30
+ # end
31
+ #
32
+ # end
33
+ class BlockContext < SimpleDelegator
34
+ # @!visibility private
35
+ def initialize(receiver, builder, helpers)
36
+ super(receiver)
37
+ @builder = builder
38
+ extend helpers
39
+ end
40
+
41
+ # @!visibility private
42
+ # Override from Delegator. This allows methods in the global scope to be accessed, b/c otherwise they are private
43
+ # and Delegator won't pick them up.
44
+ def target_respond_to?(target, method_name, _include_private) = super(target, method_name, true)
45
+
46
+ extend Forwardable
47
+
48
+ # @!method src(source_string)
49
+ # Inserts a node with the given source string.
50
+ #
51
+ # The inserted node can be modified by calling {Node#with}/{Node#[]}
52
+ #
53
+ # @return [Node] the inserted node
54
+ #
55
+ # @overload src(source_string)
56
+ # @overload `(source_string)
57
+ def_delegator :@builder, :src
58
+
59
+ # Alias for {#src}
60
+ def `(source_string) = @builder.src(source_string)
61
+
62
+ # @!method defer(&)
63
+ # Defer execution of the the given block until the rest of the document is evaluated and insert any content in the
64
+ # document where this method was called.
65
+ #
66
+ # @return [Node::Deferred]
67
+ def_delegator :@builder, :defer
68
+
69
+ # @!method content_for(key, &)
70
+ # Stores content for the given key to be insert at a different location in the document.
71
+ #
72
+ # If a block is passed, it will be executed and the result stored. If no block is passed but the key already has
73
+ # content, it will be retrieved. Otherwise, content rendering will be deferred until later.
74
+ #
75
+ # @param [#hash] key Any hashable object to identify the content but can
76
+ # be anything that works as a hash key
77
+ #
78
+ # @return [nil]
79
+ #
80
+ # @example Storing content for earlier insertion
81
+ # content_for :imports # inserts `import { Component } from 'react'` here
82
+ # # ...
83
+ # content_for :imports do # store
84
+ # `import { Component } from 'react'`
85
+ # end
86
+ #
87
+ # @example Storing content for later insertion
88
+ # `job1 = new Job()`
89
+ # content_for :run_jobs do # store
90
+ # `job1.run()`
91
+ # end
92
+ #
93
+ # `job2 = new Job()`
94
+ # content_for :run_jobs do # store
95
+ # `job2.run()`
96
+ # end
97
+ # # ...
98
+ # content_for :run_jobs # adds `job1.run()` and `job2.run()`
99
+ #
100
+ # @example Storing multiple lines
101
+ # content_for :functions
102
+ #
103
+ # content_for :functions do
104
+ # `function $fnName() {`[fn_name] do
105
+ # `api.fetch('$fnName', $fn_arg)`[fn_name, fn_arg.to_json]
106
+ # end
107
+ # `function undo$fnName() {`[fn_name] do
108
+ # `api.fetch('undo$fnName', $fn_arg)`[fn_name, fn_arg.to_json]
109
+ # end
110
+ # end
111
+ #
112
+ # @example Using different types for keys
113
+ # content_for :imports
114
+ # content_for "imports"
115
+ # content_for ["imports", :secion1]
116
+ #
117
+ # user = User.find(1)
118
+ # content_for user
119
+ #
120
+ # @overload content_for(key)
121
+ # Insert the stored content for the given key
122
+ # @return [nil] A node of stored content for the given key
123
+ #
124
+ # @overload content_for(key, &)
125
+ # Store content to be be inserted at a different position in the file
126
+ # @yield The content in the block is evaluated immediately and stored for later insertion
127
+ # @return [nil]
128
+ def_delegator :@builder, :content_for
129
+
130
+ # @!method content_for?(key)
131
+ # Returns whether there is any content stored for the given key.
132
+ #
133
+ # This method returns immediately and will return
134
+ # false even if `#content_for(key) { ... }` is called later.
135
+ #
136
+ # @param [#hash] key Any hashable object to identify the content
137
+ # @return [Boolean] whether any content is stored for the given key
138
+ def_delegator :@builder, :content_for?
139
+
140
+ # @!method content_for!(key)
141
+ # Returns the content stored for the given key, and raises an exception if there is no content stored. Calling
142
+ # {#content_for!} is evaluated immeditately and will raise an exception even if `#content_for(key) { ... }` is
143
+ # called later.
144
+ #
145
+ # @param [#hash] key Any hashable object to identify the content
146
+ # @return [Array<Node::Detached>] A node of stored content for the given key
147
+ # @raise [Exception] Throws an execption if there is no content stored at the given key
148
+ def_delegator :@builder, :content_for!
149
+ end
150
+ end