rubyoshka 0.2 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff842a3a48141bcef19e1298cb26ff59cd9dbb0a58075efe50b3f73892a75c2f
4
- data.tar.gz: 4556f2fc468d48d9a98b4582ab25f0068d814d933e0e0c198d45fce9f692960b
3
+ metadata.gz: ae561e0250334107b9b82a72fc72ab672622a7f037044e8e18154e79b71a54b5
4
+ data.tar.gz: e5cddea7df6231799b38339bc014373a6c9d6a96bb60d3c7b7107e48321b3c6d
5
5
  SHA512:
6
- metadata.gz: 79e2fd4e9f970201191e576f1b4bd971bef99cd1a7fbcbaee2102d6be8b8e0ec59e6ad0d26f6770ba756bd2e7b92ab84128a6bded62219417af94e1f9dbce159
7
- data.tar.gz: 9d78ee6402ddc3e873d7b47073c655cb6501c46f91e8f4f6bb2041581367ee7cc5c661cc63e6f356e17a8d9cab59f9cf719e4497166f3fa890b4b320c62473c0
6
+ metadata.gz: 675b83551aa791f6bb1539c80c40d267e3caa1aceed3748af04ca1d2a49bcac3ad237a8d8c0f0dd69d24714eadbb47bf4a929af77c3ef2c71155e806ff611c66
7
+ data.tar.gz: 7dd19daa9df716417c894130ff8fdae0505f83a63de17709634b2c7c83384f56453c13adbee109e4961f44a64f1c46e151752d1fc5bf690ad6738402b023f9bf
data/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ 0.6.1 2021-03-03
2
+ ----------------
3
+
4
+ * Remove support for Ruby 2.6
5
+
6
+ 0.6 2021-03-03
7
+ --------------
8
+
9
+ * Fix Rubyoshka on Ruby 3.0
10
+ * Refactor and add more tests
11
+
12
+ 0.5 2021-02-27
13
+ --------------
14
+
15
+ * Add support for rendering XML
16
+ * Add Rubyoshka.component method
17
+ * Remove Modulation dependency
18
+
19
+ 0.4 2019-02-05
20
+ --------------
21
+
22
+ * Add support for emitting component modules
23
+
24
+ 0.3 2019-01-13
25
+ --------------
26
+
27
+ * Implement caching
28
+ * Improve performance
29
+ * Handle attributes with `false` value correctly
30
+
1
31
  0.2 2019-01-07
2
32
  --------------
3
33
 
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [INSTALL](#installing-rubyoshka) |
4
4
  [TUTORIAL](#getting-started) |
5
5
  [EXAMPLES](examples) |
6
- [REFERENCE](reference)
6
+ [REFERENCE](#api-reference)
7
7
 
8
8
  ## What is Rubyoshka
9
9
 
@@ -16,21 +16,30 @@ features:
16
16
  - Use global and local contexts to pass values to reusable components
17
17
  - Automatic HTML escaping
18
18
  - Composable nested components
19
- - Access to a global context from anywhere in the component hierarchy
20
- - About 4 times faster than ERubis (see [benchmark](examples/perf.rb)).
19
+ - Template caching from fragments to whole templates
21
20
 
22
21
  > **Note** Rubyoshka is a new library and as such may be missing features and
23
- > contain bugs. Your issue reports and code conributions are most welcome!
22
+ > contain bugs. Also, its API may change unexpectedly. Your issue reports and
23
+ > code contributions are most welcome!
24
24
 
25
25
  With Rubyoshka you can structure your templates like a Russian doll, each
26
26
  component containing any number of nested components, in a somewhat similar
27
- fashion to React. The name *Rubyoshka* is a nod to Matryoshka, the Russian
27
+ fashion to React. The name *Rubyoshka* is a nod to
28
+ [Matryoshka](https://en.wikipedia.org/wiki/Matryoshka_doll), the Russian
28
29
  nesting doll.
29
30
 
30
31
  ## Installing Rubyoshka
31
32
 
33
+ Using bundler:
34
+
35
+ ```ruby
36
+ gem 'rubyoshka'
37
+ ```
38
+
39
+ Or manually:
40
+
32
41
  ```bash
33
- $ gem install polyphony
42
+ $ gem install rubyoshka
34
43
  ```
35
44
 
36
45
  ## Getting started
@@ -217,16 +226,17 @@ greeting.render(name: 'world')
217
226
  ## Templates as components
218
227
 
219
228
  Rubyoshka makes it easy to compose multiple separate templates into a whole HTML
220
- document. Each template can be defined as a self-contained component that can
221
- be reused inside other components. Components should be defined as constants,
222
- either in the global namespace, or on the `Rubyoshka` namespace. Each component
223
- can be defined as either a Rubyoshka instance (using `#H`) or as a `proc` that
224
- returns a Rubyoshka instance:
229
+ document. Each template can be defined as a self-contained component that can be
230
+ reused inside other components. Components can be defined as either a Rubyoshka
231
+ instance (using `#H`), a `proc` that returns a Rubyoshka instance, or using
232
+ `Rubyoshka.component`:
225
233
 
226
234
  ```ruby
235
+ # Simple component relying on global/local context
227
236
  Title = H { h1 title }
228
237
 
229
- # Item is actually a Proc that returns a template
238
+ # Proc component that returns a template
239
+ # Notice how the lambda expression takes keyword arguments
230
240
  Item = ->(id:, text:, checked:) {
231
241
  H {
232
242
  li {
@@ -236,20 +246,29 @@ Item = ->(id:, text:, checked:) {
236
246
  }
237
247
  }
238
248
 
239
- def render_items(items)
249
+ # Components using Rubyoshka.component (or H.component) are a bit more compact.
250
+ # Any parameters are passed as arguments to the block.
251
+ NavBar = Rubyoshka.component do |links|
252
+ div {
253
+ links.each { |l| a l[:title], href: l[:url] }
254
+ }
255
+ end
256
+
257
+ def render_items(items, links)
240
258
  html = H {
241
259
  Title()
260
+ NavBar(links)
242
261
  ul {
243
262
  items.each { |id, attributes|
244
263
  Item id: id, text: attributes[:text], checked: attributes[:active]
245
264
  }
246
265
  }
247
- }.render
266
+ }.render(title: 'Hello from components')
248
267
  end
249
268
  ```
250
269
 
251
270
  Note that a component is invoked as a method, which means that if no arguments
252
- are passed, you should add an empty pair of parens, as shown in the example
271
+ are passed, you must add an empty pair of parens, as shown in the example
253
272
  above.
254
273
 
255
274
  In addition to using components defined as constants, you can also use
@@ -265,6 +284,54 @@ H {
265
284
  }
266
285
  ```
267
286
 
287
+ ## Fragment caching
288
+
289
+ Any part of a Rubyoshka template can be cached - a fragment, a component, or a
290
+ whole template. It is up to you, the user, to determine which parts of the
291
+ template to cache. By default, a call to `#cache` creates a cache entry based on
292
+ the source location of the cached block:
293
+
294
+ ```ruby
295
+ Head = H {
296
+ cache {
297
+ head {
298
+ title 'My app'
299
+ style "@import '/app.css';"
300
+ }
301
+ }
302
+ }
303
+ ```
304
+
305
+ However, if your template references local or global variables, you'll want to
306
+ take those into account when caching. This is done by passing any variables used
307
+ in the template to `#cache` in order to create separate cache entries for each
308
+ discrete value or combination of values:
309
+
310
+ ```ruby
311
+ Greeting = H {
312
+ cache(name) {
313
+ div {
314
+ span "Hello, #{name}"
315
+ }
316
+ }
317
+ }
318
+
319
+ names = %w{tommy dolly world}
320
+ App = H {
321
+ names.each { |n| Greeting(name: n) }
322
+ }
323
+ ```
324
+
325
+ In the above example a separate cache entry will be created for each name. The
326
+ use of caching in components is especially beneficial since components may be
327
+ reused in multiple different templates in your app.
328
+
329
+ ### Changing the cache store
330
+
331
+ Rubyoshka ships with a naïve in-memory cache store built-in. You can use
332
+ another cache store by overriding the `Rubyoshka.cache` method (see API
333
+ [reference](#rubyoshkacache)).
334
+
268
335
  ## Wrapping arbitrary HTML with a component
269
336
 
270
337
  Components can also be used to wrap arbitrary HTML with addional markup. This is
@@ -347,7 +414,37 @@ Blog = H {
347
414
  }
348
415
  ```
349
416
 
350
- ## Reference
417
+ ### A higher-order list component
418
+
419
+ Here's another demonstration of a higher-order component, a list component that
420
+ takes an item component as an argument. The `List` component can be reused for
421
+ rendering any kind of unordered list, and with any kind of item component:
422
+
423
+ ```ruby
424
+ List = ->(items, item_component) {
425
+ H {
426
+ ul {
427
+ items.each { |item|
428
+ with(item: item) {
429
+ li { emit item_component }
430
+ }
431
+ }
432
+ }
433
+ }
434
+ }
435
+
436
+ TodoItem = H {
437
+ span item.text, class: item.completed ? 'completed' : 'pending'
438
+ }
439
+
440
+ def todo_list(items)
441
+ H {
442
+ div { List(items, TodoItem) }
443
+ }
444
+ end
445
+ ```
446
+
447
+ ## API Reference
351
448
 
352
449
  #### `Rubyoshka#initialize(**context, &block)` a.k.a. `Kernel#H`
353
450
 
@@ -379,6 +476,17 @@ an upper-case letter, it is considered a [component](#templates-as-components).
379
476
 
380
477
  If a text argument is given for a tag, it will be escaped.
381
478
 
479
+ #### `#cache(*vary, &block)`
480
+
481
+ - `vary`: variables used in cached block. The given values will be used to
482
+ create a separate cache entry.
483
+ - `block`: inner HTML block
484
+
485
+ Caches the markup in the given block, storing it in the Rubyoshka cache store.
486
+ If a cache entry for the given block is found, it will be used instead of
487
+ invoking the block. If one or more variables given, those will be used to create
488
+ a separate cache entry.
489
+
382
490
  #### `#context`
383
491
 
384
492
  Accesses the [global context](#global-context).
@@ -409,3 +517,31 @@ Adds text without wrapping it in a tag. The text will be escaped.
409
517
 
410
518
  Sets a [local context](#local-context) for use inside the given block. The
411
519
  previous local context will be restored upon exiting the given block.
520
+
521
+ #### `Rubyoshka.cache`
522
+
523
+ Returns the cache store. A cache store should implement two methods - `#[]` and
524
+ `#[]=`. Here's an example implementing a Redis-based cache store:
525
+
526
+ ```ruby
527
+ class RedisTemplateCache
528
+ def initialize(conn, prefix)
529
+ @conn = conn
530
+ @prefix = prefix
531
+ end
532
+
533
+ def [](key)
534
+ @conn.get("#{prefix}:#{key}")
535
+ end
536
+
537
+ def []=(key, value)
538
+ @conn.set("#{prefix}:#{key}", value)
539
+ end
540
+ end
541
+
542
+ TEMPLATE_CACHE = RedisTemplaceCache.new(redis_conn, "templates:cache")
543
+
544
+ def Rubyoshka.cache
545
+ TEMPLATE_CACHE
546
+ end
547
+ ```
data/lib/rubyoshka.rb CHANGED
@@ -1,204 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'modulation/gem'
4
3
  require 'escape_utils'
5
4
 
6
- export_default :Rubyoshka
5
+ require_relative 'rubyoshka/renderer'
7
6
 
8
7
  # A Rubyoshka is a template representing a piece of HTML
9
8
  class Rubyoshka
10
- # A Rendering is a rendering of a Rubyoshka
11
- class Rendering
12
- attr_reader :context
13
-
14
- # Initializes attributes and renders the given block
15
- # @param context [Hash] rendering context
16
- # @param block [Proc] template block
17
- # @return [void]
18
- def initialize(context, &block)
19
- @context = context
20
- @buffer = +''
21
- instance_eval(&block)
22
- end
23
-
24
- # Returns the result of the rendering
25
- # @return [String]
26
- def to_s
27
- @buffer
28
- end
29
-
30
- S_TAG_METHOD = <<~EOF
31
- def %1$s(*args, &block)
32
- tag(:%1$s, *args, &block)
33
- end
34
- EOF
35
-
36
- R_CONST_SYM = /^[A-Z]/
37
-
38
- # Catches undefined tag method call and handles them by defining the method
39
- # @param sym [Symbol] HTML tag or component identifier
40
- # @param args [Array] method call arguments
41
- # @param block [Proc] block passed to method call
42
- # @return [void]
43
- def method_missing(sym, *args, &block)
44
- value = @local && @local[sym]
45
- return value if value
46
-
47
- if sym =~ R_CONST_SYM
48
- o = instance_eval(sym.to_s) rescue Rubyoshka.const_get(sym) \
49
- rescue Object.const_get(sym)
50
- case o
51
- when ::Proc
52
- self.class.define_method(sym) { |*a, &b| emit(o.(*a, &b)) }
53
- emit(o.(*args, &block))
54
- when Rubyoshka
55
- self.class.define_method(sym) { |**ctx|
56
- ctx.empty? ? emit(o) : with(ctx) { emit(o) }
57
- }
58
- ctx = args.first
59
- Hash === ctx ? with(ctx) { emit(o) } : emit(o)
60
- when ::String
61
- @buffer << o
62
- else
63
- e = StandardError.new "Cannot render #{o.inspect}"
64
- e.set_backtrace(caller)
65
- raise e
66
- end
67
- else
68
- self.class.class_eval(S_TAG_METHOD % sym)
69
- tag(sym, *args, &block)
70
- end
71
- end
72
-
73
- # Emits the given object into the rendering buffer
74
- # @param o [Proc, Rubyoshka, String] emitted object
75
- # @return [void]
76
- def emit(o)
77
- case o
78
- when ::Proc
79
- instance_eval(&o)
80
- when Rubyoshka
81
- instance_eval(&o.block)
82
- when nil
83
- else
84
- @buffer << o.to_s
85
- end
86
- end
87
- alias_method :e, :emit
88
-
89
- S_LT = '<'
90
- S_GT = '>'
91
- S_LT_SLASH = '</'
92
- S_SPACE_LT_SLASH = ' </'
93
- S_SLASH_GT = '/>'
94
- S_SPACE = ' '
95
- S_EQUAL_QUOTE = '="'
96
- S_QUOTE = '"'
97
-
98
- E = EscapeUtils
99
-
100
- # Emits an HTML tag
101
- # @param sym [Symbol] HTML tag
102
- # @param text [String] text content of tag
103
- # @param props [Hash] tag attributes
104
- # @param block [Proc] nested HTML block
105
- # @return [void]
106
- def tag(sym, text = nil, **props, &block)
107
- sym = sym.to_s
108
-
109
- @buffer << S_LT << sym
110
- emit_props(props) unless props.empty?
111
-
112
- if block
113
- @buffer << S_GT
114
- instance_eval(&block)
115
- @buffer << S_LT_SLASH << sym << S_GT
116
- elsif Rubyoshka === text
117
- @buffer << S_GT
118
- emit(text)
119
- @buffer << S_LT_SLASH << sym << S_GT
120
- elsif text
121
- @buffer << S_GT << E.escape_html(text.to_s) <<
122
- S_LT_SLASH << sym << S_GT
123
- else
124
- @buffer << S_SLASH_GT
125
- end
126
- end
127
-
128
- # Emits tag attributes into the rendering buffer
129
- # @param props [Hash] tag attributes
130
- # @return [void]
131
- def emit_props(props)
132
- props.each { |k, v|
133
- case k
134
- when :text
135
- when :src, :href
136
- @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
137
- E.escape_uri(v) << S_QUOTE
138
- else
139
- if v == true
140
- @buffer << S_SPACE << k.to_s
141
- else
142
- @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE << v << S_QUOTE
143
- end
144
- end
145
- }
146
- end
147
-
148
- # Emits the p tag
149
- # @param text [String] text content of tag
150
- # @param props [Hash] tag attributes
151
- # @para block [Proc] nested HTML block
152
- # @return [void]
153
- def p(text = nil, **props, &block)
154
- tag(:p, text, **props, &block)
155
- end
156
-
157
- S_HTML5_DOCTYPE = '<!DOCTYPE html>'
158
-
159
- # Emits an HTML5 doctype tag and an html tag with the given block
160
- # @param block [Proc] nested HTML block
161
- # @return [void]
162
- def html5(&block)
163
- @buffer << S_HTML5_DOCTYPE
164
- self.html(&block)
165
- end
166
-
167
- # Emits text into the rendering buffer
168
- # @param data [String] text
169
- def text(data)
170
- @buffer << E.escape_html(data)
171
- end
172
-
173
- # Sets a local context for the given block
174
- # @param ctx [Hash] context hash
175
- # @param block [Proc] nested HTML block
176
- # @return [void]
177
- def with(**ctx, &block)
178
- old_local, @local = @local, ctx
179
- instance_eval(&block)
180
- ensure
181
- @local = old_local
182
- end
183
- end
184
-
185
9
  attr_reader :block
186
10
 
187
11
  # Initializes a Rubyoshka with the given block
188
12
  # @param ctx [Hash] local context
189
13
  # @param block [Proc] nested HTML block
190
14
  # @param [void]
191
- def initialize(**ctx, &block)
192
- @block = ctx.empty? ? block : proc { with(ctx, &block) }
15
+ def initialize(mode: :html, **ctx, &block)
16
+ @mode = mode
17
+ @block = ctx.empty? ? block : proc { with(**ctx, &block) }
193
18
  end
194
19
 
20
+ H_EMPTY = {}.freeze
21
+
195
22
  # Renders the associated block and returns the string result
196
23
  # @param context [Hash] context
197
24
  # @return [String]
198
- def render(**context)
199
- Rendering.new(context, &block).to_s
25
+ def render(context = H_EMPTY)
26
+ renderer_class.new(context, &block).to_s
27
+ end
28
+
29
+ def renderer_class
30
+ case @mode
31
+ when :html
32
+ HTMLRenderer
33
+ when :xml
34
+ XMLRenderer
35
+ else
36
+ raise "Invalid mode #{@mode.inspect}"
37
+ end
38
+ end
39
+
40
+ @@cache = {}
41
+
42
+ def self.cache
43
+ @@cache
44
+ end
45
+
46
+ def self.component(&block)
47
+ proc { |*args| new { instance_exec(*args, &block) } }
48
+ end
49
+
50
+ def self.xml(**ctx, &block)
51
+ new(mode: :xml, **ctx, &block)
200
52
  end
201
53
  end
54
+ ::H = Rubyoshka
202
55
 
203
56
  module ::Kernel
204
57
  # Convenience method for creating a new Rubyoshka
@@ -206,6 +59,6 @@ module ::Kernel
206
59
  # @param block [Proc] nested block
207
60
  # @return [Rubyoshka] Rubyoshka template
208
61
  def H(**ctx, &block)
209
- Rubyoshka.new(ctx, &block)
62
+ Rubyoshka.new(**ctx, &block)
210
63
  end
211
64
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './html'
4
+
5
+ class Rubyoshka
6
+ # Markup extensions
7
+ module HTML
8
+ # Emits the p tag (overrides Object#p)
9
+ # @param text [String] text content of tag
10
+ # @param props [Hash] tag attributes
11
+ # @para block [Proc] nested HTML block
12
+ # @return [void]
13
+ def p(text = nil, **props, &block)
14
+ method_missing(:p, text, **props, &block)
15
+ end
16
+
17
+ S_HTML5_DOCTYPE = '<!DOCTYPE html>'
18
+
19
+ # Emits an HTML5 doctype tag and an html tag with the given block
20
+ # @param block [Proc] nested HTML block
21
+ # @return [void]
22
+ def html5(&block)
23
+ @buffer << S_HTML5_DOCTYPE
24
+ self.html(&block)
25
+ end
26
+
27
+ def link_stylesheet(href, custom_attributes = nil)
28
+ attributes = {
29
+ rel: 'stylesheet',
30
+ href: href
31
+ }
32
+ if custom_attributes
33
+ attributes = custom_attributes.merge(attributes)
34
+ end
35
+ link(**attributes)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './html'
4
+
5
+ class Rubyoshka
6
+ # A Renderer is a rendering of a Rubyoshka
7
+ class Renderer
8
+ attr_reader :context
9
+
10
+ # Initializes attributes and renders the given block
11
+ # @param context [Hash] rendering context
12
+ # @param block [Proc] template block
13
+ # @return [void]
14
+ def initialize(context, &block)
15
+ @context = context
16
+ @buffer = +''
17
+ instance_eval(&block)
18
+ end
19
+
20
+ # Returns the result of the rendering
21
+ # @return [String]
22
+ def to_s
23
+ @buffer
24
+ end
25
+
26
+ def escape_text(text)
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def escape_uri(uri)
31
+ EscapeUtils.escape_uri(v)
32
+ end
33
+
34
+ S_TAG_METHOD_LINE = __LINE__ + 1
35
+ S_TAG_METHOD = <<~EOF
36
+ S_TAG_%<TAG>s_PRE = '<%<tag>s'
37
+ S_TAG_%<TAG>s_CLOSE = '</%<tag>s>'
38
+
39
+ def %<tag>s(text = nil, **props, &block)
40
+ @buffer << S_TAG_%<TAG>s_PRE
41
+ emit_props(props) unless props.empty?
42
+
43
+ if block
44
+ @buffer << S_GT
45
+ instance_eval(&block)
46
+ @buffer << S_TAG_%<TAG>s_CLOSE
47
+ elsif Rubyoshka === text
48
+ @buffer << S_GT
49
+ emit(text)
50
+ @buffer << S_TAG_%<TAG>s_CLOSE
51
+ elsif text
52
+ @buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
53
+ else
54
+ @buffer << S_SLASH_GT
55
+ end
56
+ end
57
+ EOF
58
+
59
+ R_CONST_SYM = /^[A-Z]/
60
+
61
+ # Catches undefined tag method call and handles them by defining the method
62
+ # @param sym [Symbol] HTML tag or component identifier
63
+ # @param args [Array] method call arguments
64
+ # @param block [Proc] block passed to method call
65
+ # @return [void]
66
+ def method_missing(sym, *args, **opts, &block)
67
+ value = @local && @local[sym]
68
+ return value if value
69
+
70
+ if sym =~ R_CONST_SYM
71
+ # Component reference (capitalized method name)
72
+ o = instance_eval(sym.to_s) rescue Rubyoshka.const_get(sym) \
73
+ rescue Object.const_get(sym)
74
+ case o
75
+ when ::Proc
76
+ self.class.define_method(sym) { |*a, **c, &b| emit(o.(*a, **c, &b)) }
77
+ STDOUT.puts({o: o, args: args, opts: opts, block: block}.inspect)
78
+ emit(o.(*args, **opts, &block))
79
+ when Rubyoshka
80
+ self.class.define_method(sym) do |**ctx|
81
+ ctx.empty? ? emit(o) : with(**ctx) { emit(o) }
82
+ end
83
+ Hash === opts.empty? ? emit(o) : with(**opts) { emit(o) }
84
+ when ::String
85
+ @buffer << o
86
+ else
87
+ e = StandardError.new "Cannot render #{o.inspect}"
88
+ e.set_backtrace(caller)
89
+ raise e
90
+ end
91
+ else
92
+ tag = sym.to_s
93
+ code = S_TAG_METHOD % { tag: tag, TAG: tag.upcase }
94
+ self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
95
+ send(sym, *args, **opts, &block)
96
+ end
97
+ end
98
+
99
+ # Emits the given object into the rendering buffer
100
+ # @param o [Proc, Rubyoshka, Module, String] emitted object
101
+ # @return [void]
102
+ def emit(o)
103
+ case o
104
+ when ::Proc
105
+ instance_eval(&o)
106
+ when Rubyoshka
107
+ instance_eval(&o.block)
108
+ when Module
109
+ # If module is given, the component is expected to be a const inside the module
110
+ emit(o::Component)
111
+ when nil
112
+ else
113
+ @buffer << o.to_s
114
+ end
115
+ end
116
+ alias_method :e, :emit
117
+
118
+ S_LT = '<'
119
+ S_GT = '>'
120
+ S_LT_SLASH = '</'
121
+ S_SPACE_LT_SLASH = ' </'
122
+ S_SLASH_GT = '/>'
123
+ S_SPACE = ' '
124
+ S_EQUAL_QUOTE = '="'
125
+ S_QUOTE = '"'
126
+
127
+ # Emits tag attributes into the rendering buffer
128
+ # @param props [Hash] tag attributes
129
+ # @return [void]
130
+ def emit_props(props)
131
+ props.each { |k, v|
132
+ case k
133
+ when :src, :href
134
+ @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
135
+ EscapeUtils.escape_uri(v) << S_QUOTE
136
+ else
137
+ case v
138
+ when true
139
+ @buffer << S_SPACE << k.to_s
140
+ when false, nil
141
+ # emit nothing
142
+ else
143
+ @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE << v << S_QUOTE
144
+ end
145
+ end
146
+ }
147
+ end
148
+
149
+ # Emits text into the rendering buffer
150
+ # @param data [String] text
151
+ def text(data)
152
+ @buffer << escape_text(data)
153
+ end
154
+
155
+ # Sets a local context for the given block
156
+ # @param ctx [Hash] context hash
157
+ # @param block [Proc] nested HTML block
158
+ # @return [void]
159
+ def with(**ctx, &block)
160
+ old_local, @local = @local, ctx
161
+ instance_eval(&block)
162
+ ensure
163
+ @local = old_local
164
+ end
165
+
166
+ # Caches the given block with the given arguments as cache key
167
+ # @param vary [*Object] cache key
168
+ # @param block [Proc] nested HTML block
169
+ # @return [void]
170
+ def cache(*vary, **opts, &block)
171
+ key = [block.source_location.hash, vary.hash, opts.hash]
172
+
173
+ if (cached = Rubyoshka.cache[key])
174
+ @buffer << cached
175
+ return
176
+ end
177
+
178
+ cache_pos = @buffer.length
179
+ instance_eval(&block)
180
+ diff = @buffer[cache_pos..-1]
181
+ Rubyoshka.cache[key] = diff
182
+ end
183
+ end
184
+
185
+ class HTMLRenderer < Renderer
186
+ include HTML
187
+
188
+ def escape_text(text)
189
+ EscapeUtils.escape_html(text.to_s)
190
+ end
191
+ end
192
+
193
+ class XMLRenderer < Renderer
194
+ def escape_text(text)
195
+ EscapeUtils.escape_xml(text.to_s)
196
+ end
197
+ end
198
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Rubyoshka
4
- VERSION = '0.2'
4
+ VERSION = '0.6.1'
5
5
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyoshka
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.2'
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-07 00:00:00.000000000 Z
11
+ date: 2021-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: modulation
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - '='
18
- - !ruby/object:Gem::Version
19
- version: '0.18'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - '='
25
- - !ruby/object:Gem::Version
26
- version: '0.18'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: escape_utils
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +66,20 @@ dependencies:
80
66
  - - '='
81
67
  - !ruby/object:Gem::Version
82
68
  version: 2.7.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: tilt
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 2.0.9
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 2.0.9
83
83
  description:
84
84
  email: ciconia@gmail.com
85
85
  executables: []
@@ -90,6 +90,8 @@ files:
90
90
  - CHANGELOG.md
91
91
  - README.md
92
92
  - lib/rubyoshka.rb
93
+ - lib/rubyoshka/html.rb
94
+ - lib/rubyoshka/renderer.rb
93
95
  - lib/rubyoshka/version.rb
94
96
  homepage: http://github.com/digital-fabric/rubyoshka
95
97
  licenses:
@@ -108,14 +110,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
110
  requirements:
109
111
  - - ">="
110
112
  - !ruby/object:Gem::Version
111
- version: '0'
113
+ version: '2.7'
112
114
  required_rubygems_version: !ruby/object:Gem::Requirement
113
115
  requirements:
114
116
  - - ">="
115
117
  - !ruby/object:Gem::Version
116
118
  version: '0'
117
119
  requirements: []
118
- rubygems_version: 3.0.1
120
+ rubygems_version: 3.1.4
119
121
  signing_key:
120
122
  specification_version: 4
121
123
  summary: 'Rubyoshka: composable HTML templating for Ruby'