rubyoshka 0.2 → 0.3
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +131 -6
- data/lib/rubyoshka.rb +57 -38
- data/lib/rubyoshka/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3be682087384cd706b089c77e53334d5e60923106e3fa514bdb298c73aae64fc
|
4
|
+
data.tar.gz: 6efb14adab373708f4a171ed8b07b22b535a71fe05874d26e75f7147f3eefbc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '002368e44d062f4eeb02f8ef33a5eb09ead01b48895bc4fd1b899e88ab99da80ccc3c7a193c814d0b97ac0766245c39dbf1b2ccda96702c58090dc7106442ea5'
|
7
|
+
data.tar.gz: ea84ebb4e043e76ce51d6916d5b814116d6d5dc2ba2c781150e43c052d55c2ef611c8c2444316765a4f20e6798fb474452ab5623fbc221568949e0ca44317788
|
data/CHANGELOG.md
CHANGED
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,19 +16,27 @@ 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)).
|
21
19
|
|
22
20
|
> **Note** Rubyoshka is a new library and as such may be missing features and
|
23
|
-
> contain bugs.
|
21
|
+
> contain bugs. Also, its API may change unexpectedly. Your issue reports and
|
22
|
+
> code contributions are most welcome!
|
24
23
|
|
25
24
|
With Rubyoshka you can structure your templates like a Russian doll, each
|
26
25
|
component containing any number of nested components, in a somewhat similar
|
27
|
-
fashion to React. The name *Rubyoshka* is a nod to
|
26
|
+
fashion to React. The name *Rubyoshka* is a nod to
|
27
|
+
[Matryoshka](https://en.wikipedia.org/wiki/Matryoshka_doll), the Russian
|
28
28
|
nesting doll.
|
29
29
|
|
30
30
|
## Installing Rubyoshka
|
31
31
|
|
32
|
+
Using bundler:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
gem 'polyphony'
|
36
|
+
```
|
37
|
+
|
38
|
+
Or manually:
|
39
|
+
|
32
40
|
```bash
|
33
41
|
$ gem install polyphony
|
34
42
|
```
|
@@ -265,6 +273,54 @@ H {
|
|
265
273
|
}
|
266
274
|
```
|
267
275
|
|
276
|
+
## Fragment caching
|
277
|
+
|
278
|
+
Any part of a Rubyoshka template can be cached - a fragment, a component, or a
|
279
|
+
whole template. It is up to you, the user, to determine which parts of the
|
280
|
+
template to cache. By default, a call to `#cache` creates a cache entry based on
|
281
|
+
the source location of the cached block:
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
Head = H {
|
285
|
+
cache {
|
286
|
+
head {
|
287
|
+
title 'My app'
|
288
|
+
style "@import '/app.css';"
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
```
|
293
|
+
|
294
|
+
However, if your template references local or global variables, you'll want to
|
295
|
+
take those into account when caching. This is done by passing any variables used
|
296
|
+
in the template to `#cache` in order to create separate cache entries for each
|
297
|
+
discrete value or combination of values:
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
Greeting = H {
|
301
|
+
cache(name) {
|
302
|
+
div {
|
303
|
+
span "Hello, #{name}"
|
304
|
+
}
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
names = %w{tommy dolly world}
|
309
|
+
App = H {
|
310
|
+
names.each { |n| Greeting(name: n) }
|
311
|
+
}
|
312
|
+
```
|
313
|
+
|
314
|
+
In the above example a separate cache entry will be created for each name. The
|
315
|
+
use of caching in components is especially beneficial since components may be
|
316
|
+
reused in multiple different templates in your app.
|
317
|
+
|
318
|
+
### Changing the cache store
|
319
|
+
|
320
|
+
Rubyoshka ships with a naïve in-memory cache store built-in. You can use
|
321
|
+
another cache store by overriding the `Rubyoshka.cache_get` and
|
322
|
+
`Rubyoshka.cache_set` methods (see API [reference](#api-reference)).
|
323
|
+
|
268
324
|
## Wrapping arbitrary HTML with a component
|
269
325
|
|
270
326
|
Components can also be used to wrap arbitrary HTML with addional markup. This is
|
@@ -347,7 +403,37 @@ Blog = H {
|
|
347
403
|
}
|
348
404
|
```
|
349
405
|
|
350
|
-
|
406
|
+
### A higher-order list component
|
407
|
+
|
408
|
+
Here's another demonstration of a higher-order component, a list component that
|
409
|
+
takes an item component as an argument. The `List` component can be reused for
|
410
|
+
rendering any kind of unordered list, and with any kind of item component:
|
411
|
+
|
412
|
+
```ruby
|
413
|
+
List = ->(items, item_component) {
|
414
|
+
H {
|
415
|
+
ul {
|
416
|
+
items.each { |item|
|
417
|
+
with(item: item) {
|
418
|
+
li { emit item_component }
|
419
|
+
}
|
420
|
+
}
|
421
|
+
}
|
422
|
+
}
|
423
|
+
}
|
424
|
+
|
425
|
+
TodoItem = H {
|
426
|
+
span item.text, class: item.completed ? 'completed' : 'pending'
|
427
|
+
}
|
428
|
+
|
429
|
+
def todo_list(items)
|
430
|
+
H {
|
431
|
+
div { List(items, TodoItem) }
|
432
|
+
}
|
433
|
+
end
|
434
|
+
```
|
435
|
+
|
436
|
+
## API Reference
|
351
437
|
|
352
438
|
#### `Rubyoshka#initialize(**context, &block)` a.k.a. `Kernel#H`
|
353
439
|
|
@@ -379,6 +465,17 @@ an upper-case letter, it is considered a [component](#templates-as-components).
|
|
379
465
|
|
380
466
|
If a text argument is given for a tag, it will be escaped.
|
381
467
|
|
468
|
+
#### `#cache(*vary, &block)`
|
469
|
+
|
470
|
+
- `vary`: variables used in cached block. The given values will be used to
|
471
|
+
create a separate cache entry.
|
472
|
+
- `block`: inner HTML block
|
473
|
+
|
474
|
+
Caches the markup in the given block, storing it in the Rubyoshka cache store.
|
475
|
+
If a cache entry for the given block is found, it will be used instead of
|
476
|
+
invoking the block. If one or more variables given, those will be used to create
|
477
|
+
a separate cache entry.
|
478
|
+
|
382
479
|
#### `#context`
|
383
480
|
|
384
481
|
Accesses the [global context](#global-context).
|
@@ -409,3 +506,31 @@ Adds text without wrapping it in a tag. The text will be escaped.
|
|
409
506
|
|
410
507
|
Sets a [local context](#local-context) for use inside the given block. The
|
411
508
|
previous local context will be restored upon exiting the given block.
|
509
|
+
|
510
|
+
#### `Rubyoshka.cache`
|
511
|
+
|
512
|
+
Returns the cache store. A cache store should implement two methods - `#[]` and
|
513
|
+
`#[]=`. Here's an example implementing a Redis-based cache store:
|
514
|
+
|
515
|
+
```ruby
|
516
|
+
class RedisTemplateCache
|
517
|
+
def initialize(conn, prefix)
|
518
|
+
@conn = conn
|
519
|
+
@prefix = prefix
|
520
|
+
end
|
521
|
+
|
522
|
+
def [](key)
|
523
|
+
@conn.get("#{prefix}:#{key}")
|
524
|
+
end
|
525
|
+
|
526
|
+
def []=(key, value)
|
527
|
+
@conn.set("#{prefix}:#{key}", value)
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
TEMPLATE_CACHE = RedisTemplaceCache.new(redis_conn, "templates:cache")
|
532
|
+
|
533
|
+
def Rubyoshka.cache
|
534
|
+
TEMPLATE_CACHE
|
535
|
+
end
|
536
|
+
```
|
data/lib/rubyoshka.rb
CHANGED
@@ -26,10 +26,30 @@ class Rubyoshka
|
|
26
26
|
def to_s
|
27
27
|
@buffer
|
28
28
|
end
|
29
|
+
|
30
|
+
E = EscapeUtils
|
29
31
|
|
30
32
|
S_TAG_METHOD = <<~EOF
|
31
|
-
|
32
|
-
|
33
|
+
S_TAG_%<TAG>s_PRE = '<%<tag>s'
|
34
|
+
S_TAG_%<TAG>s_CLOSE = '</%<tag>s>'
|
35
|
+
|
36
|
+
def %<tag>s(text = nil, **props, &block)
|
37
|
+
@buffer << S_TAG_%<TAG>s_PRE
|
38
|
+
emit_props(props) unless props.empty?
|
39
|
+
|
40
|
+
if block
|
41
|
+
@buffer << S_GT
|
42
|
+
instance_eval(&block)
|
43
|
+
@buffer << S_TAG_%<TAG>s_CLOSE
|
44
|
+
elsif Rubyoshka === text
|
45
|
+
@buffer << S_GT
|
46
|
+
emit(text)
|
47
|
+
@buffer << S_TAG_%<TAG>s_CLOSE
|
48
|
+
elsif text
|
49
|
+
@buffer << S_GT << E.escape_html(text.to_s) << S_TAG_%<TAG>s_CLOSE
|
50
|
+
else
|
51
|
+
@buffer << S_SLASH_GT
|
52
|
+
end
|
33
53
|
end
|
34
54
|
EOF
|
35
55
|
|
@@ -65,8 +85,9 @@ class Rubyoshka
|
|
65
85
|
raise e
|
66
86
|
end
|
67
87
|
else
|
68
|
-
|
69
|
-
|
88
|
+
tag = sym.to_s
|
89
|
+
self.class.class_eval(S_TAG_METHOD % { tag: tag, TAG: tag.upcase })
|
90
|
+
send(sym, *args, &block)
|
70
91
|
end
|
71
92
|
end
|
72
93
|
|
@@ -95,49 +116,21 @@ class Rubyoshka
|
|
95
116
|
S_EQUAL_QUOTE = '="'
|
96
117
|
S_QUOTE = '"'
|
97
118
|
|
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
119
|
# Emits tag attributes into the rendering buffer
|
129
120
|
# @param props [Hash] tag attributes
|
130
121
|
# @return [void]
|
131
122
|
def emit_props(props)
|
132
123
|
props.each { |k, v|
|
133
124
|
case k
|
134
|
-
when :text
|
135
125
|
when :src, :href
|
136
126
|
@buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
|
137
127
|
E.escape_uri(v) << S_QUOTE
|
138
128
|
else
|
139
|
-
|
129
|
+
case v
|
130
|
+
when true
|
140
131
|
@buffer << S_SPACE << k.to_s
|
132
|
+
when false, nil
|
133
|
+
# emit nothing
|
141
134
|
else
|
142
135
|
@buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE << v << S_QUOTE
|
143
136
|
end
|
@@ -151,7 +144,7 @@ class Rubyoshka
|
|
151
144
|
# @para block [Proc] nested HTML block
|
152
145
|
# @return [void]
|
153
146
|
def p(text = nil, **props, &block)
|
154
|
-
|
147
|
+
method_missing(:p, text, **props, &block)
|
155
148
|
end
|
156
149
|
|
157
150
|
S_HTML5_DOCTYPE = '<!DOCTYPE html>'
|
@@ -180,6 +173,24 @@ class Rubyoshka
|
|
180
173
|
ensure
|
181
174
|
@local = old_local
|
182
175
|
end
|
176
|
+
|
177
|
+
# Caches the given block with the given arguments as cache key
|
178
|
+
# @param vary [*Object] cache key
|
179
|
+
# @param block [Proc] nested HTML block
|
180
|
+
# @return [void]
|
181
|
+
def cache(*vary, &block)
|
182
|
+
key = [block.source_location, *vary]
|
183
|
+
|
184
|
+
if (cached = Rubyoshka.cache[key])
|
185
|
+
@buffer << cached
|
186
|
+
return
|
187
|
+
end
|
188
|
+
|
189
|
+
cache_pos = @buffer.length
|
190
|
+
instance_eval(&block)
|
191
|
+
diff = @buffer[cache_pos..-1]
|
192
|
+
Rubyoshka.cache[key] = diff
|
193
|
+
end
|
183
194
|
end
|
184
195
|
|
185
196
|
attr_reader :block
|
@@ -192,12 +203,20 @@ class Rubyoshka
|
|
192
203
|
@block = ctx.empty? ? block : proc { with(ctx, &block) }
|
193
204
|
end
|
194
205
|
|
206
|
+
H_EMPTY = {}.freeze
|
207
|
+
|
195
208
|
# Renders the associated block and returns the string result
|
196
209
|
# @param context [Hash] context
|
197
210
|
# @return [String]
|
198
|
-
def render(
|
211
|
+
def render(context = H_EMPTY)
|
199
212
|
Rendering.new(context, &block).to_s
|
200
213
|
end
|
214
|
+
|
215
|
+
@@cache = {}
|
216
|
+
|
217
|
+
def self.cache
|
218
|
+
@@cache
|
219
|
+
end
|
201
220
|
end
|
202
221
|
|
203
222
|
module ::Kernel
|
data/lib/rubyoshka/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyoshka
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.3'
|
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-
|
11
|
+
date: 2019-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: modulation
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - '='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 2.7.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: tilt
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.0.9
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.0.9
|
83
97
|
description:
|
84
98
|
email: ciconia@gmail.com
|
85
99
|
executables: []
|