rubyoshka 0.1 → 0.2
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 +244 -20
- data/lib/rubyoshka/version.rb +1 -1
- data/lib/rubyoshka.rb +32 -10
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff842a3a48141bcef19e1298cb26ff59cd9dbb0a58075efe50b3f73892a75c2f
|
4
|
+
data.tar.gz: 4556f2fc468d48d9a98b4582ab25f0068d814d933e0e0c198d45fce9f692960b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79e2fd4e9f970201191e576f1b4bd971bef99cd1a7fbcbaee2102d6be8b8e0ec59e6ad0d26f6770ba756bd2e7b92ab84128a6bded62219417af94e1f9dbce159
|
7
|
+
data.tar.gz: 9d78ee6402ddc3e873d7b47073c655cb6501c46f91e8f4f6bb2041581367ee7cc5c661cc63e6f356e17a8d9cab59f9cf719e4497166f3fa890b4b320c62473c0
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
[INSTALL](#installing-rubyoshka) |
|
4
4
|
[TUTORIAL](#getting-started) |
|
5
|
-
[EXAMPLES](examples)
|
5
|
+
[EXAMPLES](examples) |
|
6
|
+
[REFERENCE](reference)
|
6
7
|
|
7
8
|
## What is Rubyoshka
|
8
9
|
|
@@ -11,14 +12,20 @@ features:
|
|
11
12
|
|
12
13
|
- HTML templating using plain Ruby syntax
|
13
14
|
- Minimal boilerplate
|
15
|
+
- Mix logic and tags freely
|
16
|
+
- Use global and local contexts to pass values to reusable components
|
14
17
|
- Automatic HTML escaping
|
15
18
|
- Composable nested components
|
16
19
|
- Access to a global context from anywhere in the component hierarchy
|
17
|
-
-
|
20
|
+
- About 4 times faster than ERubis (see [benchmark](examples/perf.rb)).
|
21
|
+
|
22
|
+
> **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!
|
18
24
|
|
19
25
|
With Rubyoshka you can structure your templates like a Russian doll, each
|
20
|
-
component containing any number of nested components, in a similar
|
21
|
-
React. The name *Rubyoshka* is a nod to Matryoshka, the Russian
|
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
|
28
|
+
nesting doll.
|
22
29
|
|
23
30
|
## Installing Rubyoshka
|
24
31
|
|
@@ -49,7 +56,9 @@ html = H {
|
|
49
56
|
}
|
50
57
|
```
|
51
58
|
|
52
|
-
|
59
|
+
## Rendering a template
|
60
|
+
|
61
|
+
To render a Rubyoshka template use the `#render` method:
|
53
62
|
|
54
63
|
```ruby
|
55
64
|
H { span 'best span' }.render #=> "<span>best span</span>"
|
@@ -102,14 +111,121 @@ H { img src: '/my.gif' }.render #=> "<img src="/my.gif"/>
|
|
102
111
|
H { p "foobar", class: 'important' }.render #=> "<p class=\"important\">foobar</p>"
|
103
112
|
```
|
104
113
|
|
114
|
+
## Logic in templates
|
115
|
+
|
116
|
+
Since Rubyoshka templates are just a bunch of Ruby, you can easily write your
|
117
|
+
view logic right in the template:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
def user_message(user)
|
121
|
+
H {
|
122
|
+
if user
|
123
|
+
span "Hello, #{user.name}!"
|
124
|
+
else
|
125
|
+
span "Hello, guest!"
|
126
|
+
end
|
127
|
+
}
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
## Local context
|
132
|
+
|
133
|
+
When writing logic and referring to application values in you templates, there
|
134
|
+
are some ground rules to obey. Since the template code is evaluated using
|
135
|
+
`#instance_eval` that means that you will not be able to directly use instance
|
136
|
+
variables or do unqualified method calls (calls to `self`).
|
137
|
+
|
138
|
+
In order to facilitate exposing values to your template logic, Rubyoshka
|
139
|
+
provides an API for setting a local context. The local context is simply a set
|
140
|
+
of values that are accessible for a given block of code, and to any nested
|
141
|
+
blocks within it. The local context is primarily set using the `#with` method:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
H {
|
145
|
+
with(name: 'world') {
|
146
|
+
div {
|
147
|
+
span "Hello, #{name}"
|
148
|
+
}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
```
|
152
|
+
|
153
|
+
The local context can alternatively be set by passing hash values to `#H`:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
H(name: 'world') {
|
157
|
+
div { span "Hello, #{name}" }
|
158
|
+
}
|
159
|
+
```
|
160
|
+
|
161
|
+
A local context can also be set for a component (see the next section) simply by
|
162
|
+
passing arguments to the component call:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
Greeting = H { span "Hello, #{name}" }
|
166
|
+
|
167
|
+
H {
|
168
|
+
div {
|
169
|
+
Greeting(name: 'world')
|
170
|
+
}
|
171
|
+
}
|
172
|
+
```
|
173
|
+
|
174
|
+
### Tip: accessing `self` and instance variables from a template
|
175
|
+
|
176
|
+
In order to be able to access the object in the context of which the template is
|
177
|
+
defined or any of its methods, you can pass it in the local context:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class User
|
181
|
+
...
|
182
|
+
def greeting_template
|
183
|
+
H(user: self) {
|
184
|
+
...
|
185
|
+
span "Hello, #{user.name}"
|
186
|
+
span "your email: #{user.email}"
|
187
|
+
}
|
188
|
+
end
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
Instance variables can be passed to the template in a similar fashion:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
H(name: @name) { span "Hello, #{name}" }
|
196
|
+
```
|
197
|
+
|
198
|
+
## Global context
|
199
|
+
|
200
|
+
In addition to the local context, Rubyoshka also provides a way to set a global
|
201
|
+
context, accessible from anywhere in the template, and also in sub-components
|
202
|
+
used in the template.
|
203
|
+
|
204
|
+
The global context is a simple hash that can be accessed from within the
|
205
|
+
template with the `#context` method:
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
greeting = H { span "Hello, #{context[:name]}" }
|
209
|
+
```
|
210
|
+
|
211
|
+
The global context can be set upon rendering the template:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
greeting.render(name: 'world')
|
215
|
+
```
|
216
|
+
|
105
217
|
## Templates as components
|
106
218
|
|
107
|
-
Rubyoshka makes it easy to compose multiple templates into a whole HTML
|
219
|
+
Rubyoshka makes it easy to compose multiple separate templates into a whole HTML
|
108
220
|
document. Each template can be defined as a self-contained component that can
|
109
221
|
be reused inside other components. Components should be defined as constants,
|
110
|
-
either in the global namespace, or on the Rubyoshka namespace
|
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:
|
111
225
|
|
112
226
|
```ruby
|
227
|
+
Title = H { h1 title }
|
228
|
+
|
113
229
|
# Item is actually a Proc that returns a template
|
114
230
|
Item = ->(id:, text:, checked:) {
|
115
231
|
H {
|
@@ -122,6 +238,7 @@ Item = ->(id:, text:, checked:) {
|
|
122
238
|
|
123
239
|
def render_items(items)
|
124
240
|
html = H {
|
241
|
+
Title()
|
125
242
|
ul {
|
126
243
|
items.each { |id, attributes|
|
127
244
|
Item id: id, text: attributes[:text], checked: attributes[:active]
|
@@ -131,26 +248,45 @@ def render_items(items)
|
|
131
248
|
end
|
132
249
|
```
|
133
250
|
|
134
|
-
|
251
|
+
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
|
253
|
+
above.
|
135
254
|
|
136
|
-
|
137
|
-
|
255
|
+
In addition to using components defined as constants, you can also use
|
256
|
+
non-constant components by invoking the `#emit` method:
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
greeting = H { span "Hello, world" }
|
260
|
+
|
261
|
+
H {
|
262
|
+
div {
|
263
|
+
emit greeting
|
264
|
+
}
|
265
|
+
}
|
266
|
+
```
|
267
|
+
|
268
|
+
## Wrapping arbitrary HTML with a component
|
269
|
+
|
270
|
+
Components can also be used to wrap arbitrary HTML with addional markup. This is
|
271
|
+
done by implementing the component as a `proc` that takes a block:
|
138
272
|
|
139
273
|
```ruby
|
140
274
|
Header = ->(&inner_html) {
|
141
275
|
header {
|
142
|
-
h1 'title'
|
276
|
+
h1 'This is a title'
|
143
277
|
emit inner_html
|
144
278
|
}
|
145
279
|
}
|
146
280
|
|
147
|
-
H {
|
281
|
+
Greeting = H { span "Hello, #{name}" }
|
282
|
+
|
283
|
+
H { Header { Greeting(name: 'world') }.render #=> "<header><h1>This is a title</h1><span>Hello, world</span></header>"
|
148
284
|
```
|
149
285
|
|
150
286
|
## Some interesting use cases
|
151
287
|
|
152
288
|
Rubyoshka opens up all kinds of new possibilities when it comes to putting
|
153
|
-
together pieces of HTML. Feel free to explore the
|
289
|
+
together pieces of HTML. Feel free to explore the API!
|
154
290
|
|
155
291
|
### Routing in the view
|
156
292
|
|
@@ -163,12 +299,7 @@ Router = ->(path) {
|
|
163
299
|
when '/'
|
164
300
|
PostIndex()
|
165
301
|
when /^posts\/(.+)$/
|
166
|
-
|
167
|
-
if post
|
168
|
-
Post(post)
|
169
|
-
else
|
170
|
-
ErrorPage(404)
|
171
|
-
end
|
302
|
+
Post(get_post($1))
|
172
303
|
end
|
173
304
|
}
|
174
305
|
|
@@ -184,4 +315,97 @@ Blog = H {
|
|
184
315
|
}
|
185
316
|
}
|
186
317
|
}
|
187
|
-
```
|
318
|
+
```
|
319
|
+
|
320
|
+
### A general purpose router
|
321
|
+
|
322
|
+
A more flexible, reusable approach could be achieved by implementing a
|
323
|
+
higher-order routing component, in a similar fashion to
|
324
|
+
[React Router](https://reacttraining.com/react-router/web/guides/quick-start):
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
Route = ->(path, &block) {
|
328
|
+
match = path.is_a?(Regexp) ?
|
329
|
+
context[:path] =~ path : context[:path] == /^#{path}/
|
330
|
+
emit block if match
|
331
|
+
}
|
332
|
+
|
333
|
+
Blog = H {
|
334
|
+
html {
|
335
|
+
head {
|
336
|
+
title: 'My blog'
|
337
|
+
}
|
338
|
+
body {
|
339
|
+
Topbar()
|
340
|
+
Sidebar()
|
341
|
+
div id: 'content' {
|
342
|
+
Route '/' { PostIndex() }
|
343
|
+
Route /^posts\/(.+)$/ { Post(get_post($1)) }
|
344
|
+
}
|
345
|
+
}
|
346
|
+
}
|
347
|
+
}
|
348
|
+
```
|
349
|
+
|
350
|
+
## Reference
|
351
|
+
|
352
|
+
#### `Rubyoshka#initialize(**context, &block)` a.k.a. `Kernel#H`
|
353
|
+
|
354
|
+
- `context`: local context hash
|
355
|
+
- `block`: template block
|
356
|
+
|
357
|
+
Initializes a new Rubyoshka instance. This method takes a block of template
|
358
|
+
code, and an optional [local context](#local-context) in the form of a hash.
|
359
|
+
The `Kernel#H` method serves as a shortcut for creating Rubyoshka instances.
|
360
|
+
|
361
|
+
#### `Rubyoshka#render(**context)`
|
362
|
+
|
363
|
+
- `context`: global context hash
|
364
|
+
|
365
|
+
Renders the template with an optional [global context](#global-context)
|
366
|
+
hash.
|
367
|
+
|
368
|
+
#### Methods accessible inside template blocks
|
369
|
+
|
370
|
+
#### `#<tag/component>(*args, **props, &block)`
|
371
|
+
|
372
|
+
- `args`: tag arguments. For an HTML tag Rubyoshka expects a single `String`
|
373
|
+
argument containing the inner text of the tag.
|
374
|
+
- `props`: hash of tag attributes
|
375
|
+
- `block`: inner HTML block
|
376
|
+
|
377
|
+
Adds a tag or component to the current template. If the method name starts with
|
378
|
+
an upper-case letter, it is considered a [component](#templates-as-components).
|
379
|
+
|
380
|
+
If a text argument is given for a tag, it will be escaped.
|
381
|
+
|
382
|
+
#### `#context`
|
383
|
+
|
384
|
+
Accesses the [global context](#global-context).
|
385
|
+
|
386
|
+
#### `#emit(object)` a.k.a. `#e(object)`
|
387
|
+
|
388
|
+
- `object`: `Proc`, `Rubyoshka` instance or `String`
|
389
|
+
|
390
|
+
Adds the given object to the current template. If a `String` is given, it is
|
391
|
+
rendered verbatim, i.e. without escaping.
|
392
|
+
|
393
|
+
#### `html5(&block)`
|
394
|
+
|
395
|
+
- `block`: inner HTML block
|
396
|
+
|
397
|
+
Adds an HTML5 `doctype` tag, followed by an `html` tag with the given block.
|
398
|
+
|
399
|
+
#### `#text(data)`
|
400
|
+
|
401
|
+
- `data` - text to add
|
402
|
+
|
403
|
+
Adds text without wrapping it in a tag. The text will be escaped.
|
404
|
+
|
405
|
+
#### `#with(**context, &block)`
|
406
|
+
|
407
|
+
- `context`: local context hash
|
408
|
+
- `block`: HTML block
|
409
|
+
|
410
|
+
Sets a [local context](#local-context) for use inside the given block. The
|
411
|
+
previous local context will be restored upon exiting the given block.
|
data/lib/rubyoshka/version.rb
CHANGED
data/lib/rubyoshka.rb
CHANGED
@@ -41,16 +41,22 @@ class Rubyoshka
|
|
41
41
|
# @param block [Proc] block passed to method call
|
42
42
|
# @return [void]
|
43
43
|
def method_missing(sym, *args, &block)
|
44
|
+
value = @local && @local[sym]
|
45
|
+
return value if value
|
46
|
+
|
44
47
|
if sym =~ R_CONST_SYM
|
45
48
|
o = instance_eval(sym.to_s) rescue Rubyoshka.const_get(sym) \
|
46
49
|
rescue Object.const_get(sym)
|
47
50
|
case o
|
48
51
|
when ::Proc
|
49
|
-
self.class.define_method(sym) { |*a, &b| emit
|
50
|
-
emit
|
52
|
+
self.class.define_method(sym) { |*a, &b| emit(o.(*a, &b)) }
|
53
|
+
emit(o.(*args, &block))
|
51
54
|
when Rubyoshka
|
52
|
-
self.class.define_method(sym) {
|
53
|
-
|
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)
|
54
60
|
when ::String
|
55
61
|
@buffer << o
|
56
62
|
else
|
@@ -73,6 +79,7 @@ class Rubyoshka
|
|
73
79
|
instance_eval(&o)
|
74
80
|
when Rubyoshka
|
75
81
|
instance_eval(&o.block)
|
82
|
+
when nil
|
76
83
|
else
|
77
84
|
@buffer << o.to_s
|
78
85
|
end
|
@@ -160,30 +167,45 @@ class Rubyoshka
|
|
160
167
|
# Emits text into the rendering buffer
|
161
168
|
# @param data [String] text
|
162
169
|
def text(data)
|
163
|
-
@buffer << E.escape_html(
|
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
|
164
182
|
end
|
165
183
|
end
|
166
184
|
|
167
185
|
attr_reader :block
|
168
186
|
|
169
187
|
# Initializes a Rubyoshka with the given block
|
188
|
+
# @param ctx [Hash] local context
|
170
189
|
# @param block [Proc] nested HTML block
|
171
190
|
# @param [void]
|
172
|
-
def initialize(&block)
|
173
|
-
@block = block
|
191
|
+
def initialize(**ctx, &block)
|
192
|
+
@block = ctx.empty? ? block : proc { with(ctx, &block) }
|
174
193
|
end
|
175
194
|
|
176
195
|
# Renders the associated block and returns the string result
|
177
196
|
# @param context [Hash] context
|
178
197
|
# @return [String]
|
179
|
-
def render(context
|
198
|
+
def render(**context)
|
180
199
|
Rendering.new(context, &block).to_s
|
181
200
|
end
|
182
201
|
end
|
183
202
|
|
184
203
|
module ::Kernel
|
185
204
|
# Convenience method for creating a new Rubyoshka
|
186
|
-
|
187
|
-
|
205
|
+
# @param ctx [Hash] local context
|
206
|
+
# @param block [Proc] nested block
|
207
|
+
# @return [Rubyoshka] Rubyoshka template
|
208
|
+
def H(**ctx, &block)
|
209
|
+
Rubyoshka.new(ctx, &block)
|
188
210
|
end
|
189
211
|
end
|
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.2'
|
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-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: modulation
|
@@ -115,8 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
115
|
- !ruby/object:Gem::Version
|
116
116
|
version: '0'
|
117
117
|
requirements: []
|
118
|
-
|
119
|
-
rubygems_version: 2.7.3
|
118
|
+
rubygems_version: 3.0.1
|
120
119
|
signing_key:
|
121
120
|
specification_version: 4
|
122
121
|
summary: 'Rubyoshka: composable HTML templating for Ruby'
|