rubyoshka 0.5 → 0.6
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 +6 -0
- data/lib/rubyoshka.rb +9 -213
- data/lib/rubyoshka/html.rb +38 -0
- data/lib/rubyoshka/renderer.rb +197 -0
- data/lib/rubyoshka/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba3dd09fc4ab56c9af5a69c47d9da50949180b0c636c788ed74660bbc04b4b5d
|
4
|
+
data.tar.gz: faf523787f5ff11bb3e0d5346ad684b8e3c22c8fdf522e30ff0f43ff4da40a74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e10f0611cc2596ac27dd3f93df91db2d77e6ba12bc3159cdb3f29ab1b6abd3caedc66161e14a18b776cba72164a7dad725c1d60e8b4a09bcc39f96a34fb1790f
|
7
|
+
data.tar.gz: ad0b35a97f0d76fa7c82fd456751004f75af759b9d98efc824f3ae99d6fdb5d3a6905aecbb083dc5f44d272cd828c6728e27330ceab5355f1d25c24a7090f3fc
|
data/CHANGELOG.md
CHANGED
data/lib/rubyoshka.rb
CHANGED
@@ -2,219 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'escape_utils'
|
4
4
|
|
5
|
+
require_relative 'rubyoshka/renderer'
|
6
|
+
|
5
7
|
# A Rubyoshka is a template representing a piece of HTML
|
6
8
|
class Rubyoshka
|
7
|
-
# A Renderer is a rendering of a Rubyoshka
|
8
|
-
class Renderer
|
9
|
-
attr_reader :context
|
10
|
-
|
11
|
-
# Initializes attributes and renders the given block
|
12
|
-
# @param context [Hash] rendering context
|
13
|
-
# @param block [Proc] template block
|
14
|
-
# @return [void]
|
15
|
-
def initialize(context, &block)
|
16
|
-
@context = context
|
17
|
-
@buffer = +''
|
18
|
-
instance_eval(&block)
|
19
|
-
end
|
20
|
-
|
21
|
-
# Returns the result of the rendering
|
22
|
-
# @return [String]
|
23
|
-
def to_s
|
24
|
-
@buffer
|
25
|
-
end
|
26
|
-
|
27
|
-
def escape_text(text)
|
28
|
-
raise NotImplementedError
|
29
|
-
end
|
30
|
-
|
31
|
-
def escape_uri(uri)
|
32
|
-
EscapeUtils.escape_uri(v)
|
33
|
-
end
|
34
|
-
|
35
|
-
S_TAG_METHOD_LINE = __LINE__ + 1
|
36
|
-
S_TAG_METHOD = <<~EOF
|
37
|
-
S_TAG_%<TAG>s_PRE = '<%<tag>s'
|
38
|
-
S_TAG_%<TAG>s_CLOSE = '</%<tag>s>'
|
39
|
-
|
40
|
-
def %<tag>s(text = nil, **props, &block)
|
41
|
-
@buffer << S_TAG_%<TAG>s_PRE
|
42
|
-
emit_props(props) unless props.empty?
|
43
|
-
|
44
|
-
if block
|
45
|
-
@buffer << S_GT
|
46
|
-
instance_eval(&block)
|
47
|
-
@buffer << S_TAG_%<TAG>s_CLOSE
|
48
|
-
elsif Rubyoshka === text
|
49
|
-
@buffer << S_GT
|
50
|
-
emit(text)
|
51
|
-
@buffer << S_TAG_%<TAG>s_CLOSE
|
52
|
-
elsif text
|
53
|
-
@buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
|
54
|
-
else
|
55
|
-
@buffer << S_SLASH_GT
|
56
|
-
end
|
57
|
-
end
|
58
|
-
EOF
|
59
|
-
|
60
|
-
R_CONST_SYM = /^[A-Z]/
|
61
|
-
|
62
|
-
# Catches undefined tag method call and handles them by defining the method
|
63
|
-
# @param sym [Symbol] HTML tag or component identifier
|
64
|
-
# @param args [Array] method call arguments
|
65
|
-
# @param block [Proc] block passed to method call
|
66
|
-
# @return [void]
|
67
|
-
def method_missing(sym, *args, &block)
|
68
|
-
value = @local && @local[sym]
|
69
|
-
return value if value
|
70
|
-
|
71
|
-
if sym =~ R_CONST_SYM
|
72
|
-
# Component reference (capitalized method name)
|
73
|
-
o = instance_eval(sym.to_s) rescue Rubyoshka.const_get(sym) \
|
74
|
-
rescue Object.const_get(sym)
|
75
|
-
case o
|
76
|
-
when ::Proc
|
77
|
-
self.class.define_method(sym) { |*a, &b| emit(o.(*a, &b)) }
|
78
|
-
emit(o.(*args, &block))
|
79
|
-
when Rubyoshka
|
80
|
-
self.class.define_method(sym) { |**ctx|
|
81
|
-
ctx.empty? ? emit(o) : with(ctx) { emit(o) }
|
82
|
-
}
|
83
|
-
ctx = args.first
|
84
|
-
Hash === ctx ? with(ctx) { emit(o) } : emit(o)
|
85
|
-
when ::String
|
86
|
-
@buffer << o
|
87
|
-
else
|
88
|
-
e = StandardError.new "Cannot render #{o.inspect}"
|
89
|
-
e.set_backtrace(caller)
|
90
|
-
raise e
|
91
|
-
end
|
92
|
-
else
|
93
|
-
tag = sym.to_s
|
94
|
-
code = S_TAG_METHOD % { tag: tag, TAG: tag.upcase }
|
95
|
-
self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
|
96
|
-
send(sym, *args, &block)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Emits the given object into the rendering buffer
|
101
|
-
# @param o [Proc, Rubyoshka, Module, String] emitted object
|
102
|
-
# @return [void]
|
103
|
-
def emit(o)
|
104
|
-
case o
|
105
|
-
when ::Proc
|
106
|
-
instance_eval(&o)
|
107
|
-
when Rubyoshka
|
108
|
-
instance_eval(&o.block)
|
109
|
-
when Module
|
110
|
-
# If module is given, the component is expected to be a const inside the module
|
111
|
-
emit(o::Component)
|
112
|
-
when nil
|
113
|
-
else
|
114
|
-
@buffer << o.to_s
|
115
|
-
end
|
116
|
-
end
|
117
|
-
alias_method :e, :emit
|
118
|
-
|
119
|
-
S_LT = '<'
|
120
|
-
S_GT = '>'
|
121
|
-
S_LT_SLASH = '</'
|
122
|
-
S_SPACE_LT_SLASH = ' </'
|
123
|
-
S_SLASH_GT = '/>'
|
124
|
-
S_SPACE = ' '
|
125
|
-
S_EQUAL_QUOTE = '="'
|
126
|
-
S_QUOTE = '"'
|
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 :src, :href
|
135
|
-
@buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
|
136
|
-
EscapeUtils.escape_uri(v) << S_QUOTE
|
137
|
-
else
|
138
|
-
case v
|
139
|
-
when true
|
140
|
-
@buffer << S_SPACE << k.to_s
|
141
|
-
when false, nil
|
142
|
-
# emit nothing
|
143
|
-
else
|
144
|
-
@buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE << v << S_QUOTE
|
145
|
-
end
|
146
|
-
end
|
147
|
-
}
|
148
|
-
end
|
149
|
-
|
150
|
-
# Emits the p tag (overrides Object#p)
|
151
|
-
# @param text [String] text content of tag
|
152
|
-
# @param props [Hash] tag attributes
|
153
|
-
# @para block [Proc] nested HTML block
|
154
|
-
# @return [void]
|
155
|
-
def p(text = nil, **props, &block)
|
156
|
-
method_missing(:p, text, **props, &block)
|
157
|
-
end
|
158
|
-
|
159
|
-
S_HTML5_DOCTYPE = '<!DOCTYPE html>'
|
160
|
-
|
161
|
-
# Emits an HTML5 doctype tag and an html tag with the given block
|
162
|
-
# @param block [Proc] nested HTML block
|
163
|
-
# @return [void]
|
164
|
-
def html5(&block)
|
165
|
-
@buffer << S_HTML5_DOCTYPE
|
166
|
-
self.html(&block)
|
167
|
-
end
|
168
|
-
|
169
|
-
# Emits text into the rendering buffer
|
170
|
-
# @param data [String] text
|
171
|
-
def text(data)
|
172
|
-
@buffer << escape_text(data)
|
173
|
-
end
|
174
|
-
|
175
|
-
# Sets a local context for the given block
|
176
|
-
# @param ctx [Hash] context hash
|
177
|
-
# @param block [Proc] nested HTML block
|
178
|
-
# @return [void]
|
179
|
-
def with(**ctx, &block)
|
180
|
-
old_local, @local = @local, ctx
|
181
|
-
instance_eval(&block)
|
182
|
-
ensure
|
183
|
-
@local = old_local
|
184
|
-
end
|
185
|
-
|
186
|
-
# Caches the given block with the given arguments as cache key
|
187
|
-
# @param vary [*Object] cache key
|
188
|
-
# @param block [Proc] nested HTML block
|
189
|
-
# @return [void]
|
190
|
-
def cache(*vary, &block)
|
191
|
-
key = [block.source_location, *vary]
|
192
|
-
|
193
|
-
if (cached = Rubyoshka.cache[key])
|
194
|
-
@buffer << cached
|
195
|
-
return
|
196
|
-
end
|
197
|
-
|
198
|
-
cache_pos = @buffer.length
|
199
|
-
instance_eval(&block)
|
200
|
-
diff = @buffer[cache_pos..-1]
|
201
|
-
Rubyoshka.cache[key] = diff
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
class HTMLRenderer < Renderer
|
206
|
-
def escape_text(text)
|
207
|
-
EscapeUtils.escape_html(text.to_s)
|
208
|
-
end
|
209
|
-
|
210
|
-
end
|
211
|
-
|
212
|
-
class XMLRenderer < Renderer
|
213
|
-
def escape_text(text)
|
214
|
-
EscapeUtils.escape_xml(text.to_s)
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
9
|
attr_reader :block
|
219
10
|
|
220
11
|
# Initializes a Rubyoshka with the given block
|
@@ -223,7 +14,7 @@ class Rubyoshka
|
|
223
14
|
# @param [void]
|
224
15
|
def initialize(mode: :html, **ctx, &block)
|
225
16
|
@mode = mode
|
226
|
-
@block = ctx.empty? ? block : proc { with(ctx, &block) }
|
17
|
+
@block = ctx.empty? ? block : proc { with(**ctx, &block) }
|
227
18
|
end
|
228
19
|
|
229
20
|
H_EMPTY = {}.freeze
|
@@ -255,7 +46,12 @@ class Rubyoshka
|
|
255
46
|
def self.component(&block)
|
256
47
|
proc { |*args| new { instance_exec(*args, &block) } }
|
257
48
|
end
|
49
|
+
|
50
|
+
def self.xml(**ctx, &block)
|
51
|
+
new(mode: :xml, **ctx, &block)
|
52
|
+
end
|
258
53
|
end
|
54
|
+
::H = Rubyoshka
|
259
55
|
|
260
56
|
module ::Kernel
|
261
57
|
# Convenience method for creating a new Rubyoshka
|
@@ -263,6 +59,6 @@ module ::Kernel
|
|
263
59
|
# @param block [Proc] nested block
|
264
60
|
# @return [Rubyoshka] Rubyoshka template
|
265
61
|
def H(**ctx, &block)
|
266
|
-
Rubyoshka.new(ctx, &block)
|
62
|
+
Rubyoshka.new(**ctx, &block)
|
267
63
|
end
|
268
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,197 @@
|
|
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
|
+
emit(o.(*args, **opts,&block))
|
78
|
+
when Rubyoshka
|
79
|
+
self.class.define_method(sym) do |**ctx|
|
80
|
+
ctx.empty? ? emit(o) : with(**ctx) { emit(o) }
|
81
|
+
end
|
82
|
+
Hash === opts.empty? ? emit(o) : with(**opts) { emit(o) }
|
83
|
+
when ::String
|
84
|
+
@buffer << o
|
85
|
+
else
|
86
|
+
e = StandardError.new "Cannot render #{o.inspect}"
|
87
|
+
e.set_backtrace(caller)
|
88
|
+
raise e
|
89
|
+
end
|
90
|
+
else
|
91
|
+
tag = sym.to_s
|
92
|
+
code = S_TAG_METHOD % { tag: tag, TAG: tag.upcase }
|
93
|
+
self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
|
94
|
+
send(sym, *args, **opts, &block)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Emits the given object into the rendering buffer
|
99
|
+
# @param o [Proc, Rubyoshka, Module, String] emitted object
|
100
|
+
# @return [void]
|
101
|
+
def emit(o)
|
102
|
+
case o
|
103
|
+
when ::Proc
|
104
|
+
instance_eval(&o)
|
105
|
+
when Rubyoshka
|
106
|
+
instance_eval(&o.block)
|
107
|
+
when Module
|
108
|
+
# If module is given, the component is expected to be a const inside the module
|
109
|
+
emit(o::Component)
|
110
|
+
when nil
|
111
|
+
else
|
112
|
+
@buffer << o.to_s
|
113
|
+
end
|
114
|
+
end
|
115
|
+
alias_method :e, :emit
|
116
|
+
|
117
|
+
S_LT = '<'
|
118
|
+
S_GT = '>'
|
119
|
+
S_LT_SLASH = '</'
|
120
|
+
S_SPACE_LT_SLASH = ' </'
|
121
|
+
S_SLASH_GT = '/>'
|
122
|
+
S_SPACE = ' '
|
123
|
+
S_EQUAL_QUOTE = '="'
|
124
|
+
S_QUOTE = '"'
|
125
|
+
|
126
|
+
# Emits tag attributes into the rendering buffer
|
127
|
+
# @param props [Hash] tag attributes
|
128
|
+
# @return [void]
|
129
|
+
def emit_props(props)
|
130
|
+
props.each { |k, v|
|
131
|
+
case k
|
132
|
+
when :src, :href
|
133
|
+
@buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
|
134
|
+
EscapeUtils.escape_uri(v) << S_QUOTE
|
135
|
+
else
|
136
|
+
case v
|
137
|
+
when true
|
138
|
+
@buffer << S_SPACE << k.to_s
|
139
|
+
when false, nil
|
140
|
+
# emit nothing
|
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 text into the rendering buffer
|
149
|
+
# @param data [String] text
|
150
|
+
def text(data)
|
151
|
+
@buffer << escape_text(data)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Sets a local context for the given block
|
155
|
+
# @param ctx [Hash] context hash
|
156
|
+
# @param block [Proc] nested HTML block
|
157
|
+
# @return [void]
|
158
|
+
def with(**ctx, &block)
|
159
|
+
old_local, @local = @local, ctx
|
160
|
+
instance_eval(&block)
|
161
|
+
ensure
|
162
|
+
@local = old_local
|
163
|
+
end
|
164
|
+
|
165
|
+
# Caches the given block with the given arguments as cache key
|
166
|
+
# @param vary [*Object] cache key
|
167
|
+
# @param block [Proc] nested HTML block
|
168
|
+
# @return [void]
|
169
|
+
def cache(*vary, **opts, &block)
|
170
|
+
key = [block.source_location.hash, vary.hash, opts.hash]
|
171
|
+
|
172
|
+
if (cached = Rubyoshka.cache[key])
|
173
|
+
@buffer << cached
|
174
|
+
return
|
175
|
+
end
|
176
|
+
|
177
|
+
cache_pos = @buffer.length
|
178
|
+
instance_eval(&block)
|
179
|
+
diff = @buffer[cache_pos..-1]
|
180
|
+
Rubyoshka.cache[key] = diff
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class HTMLRenderer < Renderer
|
185
|
+
include HTML
|
186
|
+
|
187
|
+
def escape_text(text)
|
188
|
+
EscapeUtils.escape_html(text.to_s)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class XMLRenderer < Renderer
|
193
|
+
def escape_text(text)
|
194
|
+
EscapeUtils.escape_xml(text.to_s)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
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.6'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: escape_utils
|
@@ -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:
|