phlex 0.5.3 → 1.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of phlex might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +2 -20
- data/config/sus.rb +0 -5
- data/lib/phlex/buffered.rb +2 -0
- data/lib/phlex/collection.rb +2 -0
- data/lib/phlex/elements.rb +1 -5
- data/lib/phlex/experimental.rb +10 -0
- data/lib/phlex/helpers.rb +23 -10
- data/lib/phlex/html.rb +72 -86
- data/lib/phlex/table.rb +1 -0
- data/lib/phlex/turbo/frame.rb +2 -0
- data/lib/phlex/turbo/stream.rb +2 -0
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex.rb +2 -19
- metadata +6 -166
- data/Procfile.dev +0 -3
- data/Rakefile +0 -8
- data/config.ru +0 -9
- data/docs/assets/application.css +0 -32
- data/docs/assets/logo.png +0 -0
- data/docs/build.rb +0 -30
- data/docs/components/callout.rb +0 -9
- data/docs/components/code_block.rb +0 -26
- data/docs/components/code_span.rb +0 -9
- data/docs/components/example.rb +0 -32
- data/docs/components/heading.rb +0 -9
- data/docs/components/layout.rb +0 -91
- data/docs/components/markdown.rb +0 -25
- data/docs/components/nav/item.rb +0 -33
- data/docs/components/nav.rb +0 -6
- data/docs/components/tabs/tab.rb +0 -28
- data/docs/components/tabs.rb +0 -30
- data/docs/components/title.rb +0 -9
- data/docs/page_builder.rb +0 -39
- data/docs/pages/application_page.rb +0 -7
- data/docs/pages/helpers.rb +0 -97
- data/docs/pages/index.rb +0 -27
- data/docs/pages/library/collections.rb +0 -83
- data/docs/pages/rails/getting_started.rb +0 -57
- data/docs/pages/rails/helpers.rb +0 -55
- data/docs/pages/rails/layouts.rb +0 -61
- data/docs/pages/rails/migrating.rb +0 -37
- data/docs/pages/rails/rendering_views.rb +0 -35
- data/docs/pages/rails_integration.rb +0 -58
- data/docs/pages/templates.rb +0 -144
- data/docs/pages/testing/capybara.rb +0 -48
- data/docs/pages/testing/getting_started.rb +0 -44
- data/docs/pages/testing/nokogiri.rb +0 -83
- data/docs/pages/testing/rails.rb +0 -17
- data/docs/pages/translations.rb +0 -81
- data/docs/pages/views.rb +0 -184
- data/fixtures/compiler_test_helpers.rb +0 -19
- data/fixtures/content.rb +0 -60
- data/fixtures/dummy/app/assets/config/manifest.js +0 -0
- data/fixtures/dummy/app/components/comment_component.html.erb +0 -14
- data/fixtures/dummy/app/components/comment_component.rb +0 -8
- data/fixtures/dummy/app/components/reaction_component.html.erb +0 -3
- data/fixtures/dummy/app/components/reaction_component.rb +0 -7
- data/fixtures/dummy/app/controllers/articles_controller.rb +0 -4
- data/fixtures/dummy/app/controllers/comments_controller.rb +0 -4
- data/fixtures/dummy/app/views/application_view.rb +0 -8
- data/fixtures/dummy/app/views/articles/form.rb +0 -15
- data/fixtures/dummy/app/views/articles/index.html.erb +0 -14
- data/fixtures/dummy/app/views/articles/new.html.erb +0 -1
- data/fixtures/dummy/app/views/card.rb +0 -15
- data/fixtures/dummy/app/views/comments/comment.rb +0 -25
- data/fixtures/dummy/app/views/comments/index.html.erb +0 -3
- data/fixtures/dummy/app/views/comments/reaction.rb +0 -17
- data/fixtures/dummy/app/views/comments/show.html.erb +0 -3
- data/fixtures/dummy/app/views/heading.rb +0 -9
- data/fixtures/dummy/config/database.yml +0 -3
- data/fixtures/dummy/config/routes.rb +0 -5
- data/fixtures/dummy/config/storage.yml +0 -3
- data/fixtures/dummy/db/schema.rb +0 -6
- data/fixtures/dummy/log/.gitignore +0 -1
- data/fixtures/dummy/public/favicon.ico +0 -0
- data/fixtures/rails_helper.rb +0 -11
- data/fixtures/standard_element.rb +0 -87
- data/fixtures/void_element.rb +0 -31
- data/lib/generators/phlex/collection/USAGE +0 -8
- data/lib/generators/phlex/collection/collection_generator.rb +0 -13
- data/lib/generators/phlex/collection/templates/collection.rb.erb +0 -16
- data/lib/generators/phlex/controller/USAGE +0 -10
- data/lib/generators/phlex/controller/controller_generator.rb +0 -54
- data/lib/generators/phlex/controller/templates/controller.rb.erb +0 -10
- data/lib/generators/phlex/controller/templates/view.rb.erb +0 -14
- data/lib/generators/phlex/layout/USAGE +0 -8
- data/lib/generators/phlex/layout/layout_generator.rb +0 -13
- data/lib/generators/phlex/layout/templates/layout.rb.erb +0 -31
- data/lib/generators/phlex/page/USAGE +0 -8
- data/lib/generators/phlex/page/page_generator.rb +0 -13
- data/lib/generators/phlex/page/templates/page.rb.erb +0 -13
- data/lib/generators/phlex/table/USAGE +0 -8
- data/lib/generators/phlex/table/table_generator.rb +0 -14
- data/lib/generators/phlex/table/templates/table.rb.erb +0 -11
- data/lib/generators/phlex/view/USAGE +0 -8
- data/lib/generators/phlex/view/templates/view.rb.erb +0 -14
- data/lib/generators/phlex/view/view_generator.rb +0 -21
- data/lib/install/phlex.rb +0 -39
- data/lib/phlex/block.rb +0 -16
- data/lib/phlex/compiler/elements.rb +0 -49
- data/lib/phlex/compiler/formatter.rb +0 -91
- data/lib/phlex/compiler/generators/content.rb +0 -103
- data/lib/phlex/compiler/generators/element.rb +0 -61
- data/lib/phlex/compiler/nodes/base.rb +0 -19
- data/lib/phlex/compiler/nodes/call.rb +0 -9
- data/lib/phlex/compiler/nodes/command.rb +0 -13
- data/lib/phlex/compiler/nodes/fcall.rb +0 -18
- data/lib/phlex/compiler/nodes/method_add_block.rb +0 -33
- data/lib/phlex/compiler/nodes/vcall.rb +0 -9
- data/lib/phlex/compiler/optimizer.rb +0 -66
- data/lib/phlex/compiler/visitors/base.rb +0 -15
- data/lib/phlex/compiler/visitors/file.rb +0 -29
- data/lib/phlex/compiler/visitors/stable_scope.rb +0 -28
- data/lib/phlex/compiler/visitors/statements.rb +0 -36
- data/lib/phlex/compiler/visitors/view.rb +0 -19
- data/lib/phlex/compiler/visitors/view_method.rb +0 -59
- data/lib/phlex/compiler.rb +0 -70
- data/lib/phlex/html/callbacks.rb +0 -11
- data/lib/phlex/markdown.rb +0 -76
- data/lib/phlex/rails/engine.rb +0 -10
- data/lib/phlex/rails/form.rb +0 -67
- data/lib/phlex/rails/helpers.rb +0 -118
- data/lib/phlex/rails/layout.rb +0 -15
- data/lib/phlex/rails.rb +0 -11
- data/lib/phlex/renderable.rb +0 -47
- data/lib/phlex/testing/capybara.rb +0 -25
- data/lib/phlex/testing/nokogiri.rb +0 -24
- data/lib/phlex/testing/rails.rb +0 -19
- data/lib/phlex/translation.rb +0 -23
- data/lib/tasks/phlex_tasks.rake +0 -11
- data/package-lock.json +0 -1195
- data/package.json +0 -5
- data/tailwind.config.js +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72f6baa9bd23ce981517a40897f2bb63a1cbd60fbc87bb127883e8eeb36cc302
|
4
|
+
data.tar.gz: 5abc8c59a81cdff51f5da9250a28d9cc42308b467aa662b155fb1d28eca4a1c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8db8f9df3da1f218bd2029ea22414ea53a910de8b1810d71c70a0e9022a1353dd69557c91eb1ffee5355733c0d8d6878aae9379a75ee0eaa76d05c6ee20abb8e
|
7
|
+
data.tar.gz: 8027722c6fd1592c86f5fa6150814206a007c12a810985c72231195dcb0089aa34fd91a5addbeb9c14a8137a5a2f3fe984c07f554ee6504a4b4c25d6dd216046
|
data/Gemfile
CHANGED
@@ -5,32 +5,14 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|
5
5
|
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem "benchmark-ips"
|
9
|
-
gem "benchmark-memory"
|
10
|
-
gem "capybara"
|
11
|
-
gem "rails"
|
12
8
|
gem "rubocop"
|
13
|
-
gem "solargraph"
|
14
9
|
gem "sus"
|
15
10
|
gem "syntax_suggest"
|
16
11
|
gem "zeitwerk"
|
12
|
+
gem "benchmark-ips"
|
13
|
+
gem "erb"
|
17
14
|
|
18
15
|
group :test do
|
19
16
|
gem "i18n"
|
20
17
|
gem "memory_profiler"
|
21
|
-
gem "covered"
|
22
|
-
end
|
23
|
-
|
24
|
-
group :rails do
|
25
|
-
gem "combustion"
|
26
|
-
end
|
27
|
-
|
28
|
-
group :docs do
|
29
|
-
gem "filewatcher"
|
30
|
-
gem "htmlbeautifier"
|
31
|
-
gem "redcarpet"
|
32
|
-
gem "markly"
|
33
|
-
gem "webrick"
|
34
|
-
gem "rouge"
|
35
|
-
gem "kramdown"
|
36
18
|
end
|
data/config/sus.rb
CHANGED
@@ -1,15 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "covered/sus"
|
4
|
-
include Covered::Sus
|
5
|
-
|
6
3
|
require "phlex"
|
7
4
|
require "bundler"
|
8
|
-
require "view_component"
|
9
5
|
|
10
6
|
Bundler.require :test
|
11
7
|
|
12
8
|
require_relative "../fixtures/view_helper"
|
13
|
-
require_relative "../fixtures/compiler_test_helpers"
|
14
9
|
|
15
10
|
Zeitwerk::Loader.eager_load_all
|
data/lib/phlex/buffered.rb
CHANGED
data/lib/phlex/collection.rb
CHANGED
data/lib/phlex/elements.rb
CHANGED
@@ -10,11 +10,7 @@ module Phlex
|
|
10
10
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
11
11
|
# frozen_string_literal: true
|
12
12
|
|
13
|
-
def #{element}(
|
14
|
-
if content
|
15
|
-
raise ArgumentError, %(👋 You can no longer pass content to #{element} as a positional argument.\n Instead, you can pass it as a block, e.g. #{element} { "Hello" })
|
16
|
-
end
|
17
|
-
|
13
|
+
def #{element}(**attributes, &block)
|
18
14
|
if attributes.length > 0
|
19
15
|
if block_given?
|
20
16
|
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(**attributes)) << ">"
|
data/lib/phlex/helpers.rb
CHANGED
@@ -7,23 +7,36 @@ end
|
|
7
7
|
module Phlex::Helpers
|
8
8
|
def tokens(*tokens, **conditional_tokens)
|
9
9
|
conditional_tokens.each do |condition, token|
|
10
|
-
case condition
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
"The class condition must be a Symbol or a Proc."
|
10
|
+
truthy = case condition
|
11
|
+
when Symbol then send(condition)
|
12
|
+
when Proc then condition.call
|
13
|
+
else raise ArgumentError, "The class condition must be a Symbol or a Proc."
|
15
14
|
end
|
16
15
|
|
17
|
-
|
18
|
-
|
16
|
+
if truthy
|
17
|
+
case token
|
18
|
+
when Hash then _append_token(tokens, token[:then])
|
19
|
+
else _append_token(tokens, token)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
case token
|
23
|
+
when Hash then _append_token(tokens, token[:else])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
tokens.join(" ")
|
29
|
+
end
|
30
|
+
|
31
|
+
def _append_token(tokens, token)
|
32
|
+
case token
|
33
|
+
when nil then nil
|
19
34
|
when String then tokens << token
|
35
|
+
when Symbol then tokens << token.name
|
20
36
|
when Array then tokens.concat(token)
|
21
37
|
else raise ArgumentError,
|
22
38
|
"Conditional classes must be Symbols, Strings, or Arrays of Symbols or Strings."
|
23
|
-
end
|
24
39
|
end
|
25
|
-
|
26
|
-
tokens.compact.join(" ")
|
27
40
|
end
|
28
41
|
|
29
42
|
def classes(*tokens, **conditional_tokens)
|
data/lib/phlex/html.rb
CHANGED
@@ -128,25 +128,9 @@ module Phlex
|
|
128
128
|
|
129
129
|
extend Elements
|
130
130
|
include Helpers
|
131
|
-
include Callable
|
132
|
-
include Renderable
|
133
131
|
|
134
132
|
class << self
|
135
133
|
attr_accessor :rendered_at_least_once
|
136
|
-
|
137
|
-
# def compile
|
138
|
-
# return if @compiled
|
139
|
-
# return unless name
|
140
|
-
# return if name.start_with? "#"
|
141
|
-
#
|
142
|
-
# Compiler.new(self).call
|
143
|
-
#
|
144
|
-
# @compiled = true
|
145
|
-
# end
|
146
|
-
#
|
147
|
-
# def compiled?
|
148
|
-
# !!@compiled
|
149
|
-
# end
|
150
134
|
end
|
151
135
|
|
152
136
|
def call(buffer = +"", view_context: nil, parent: nil, &block)
|
@@ -158,15 +142,30 @@ module Phlex
|
|
158
142
|
@_target = buffer
|
159
143
|
@_view_context = view_context
|
160
144
|
@_parent = parent
|
161
|
-
@output_buffer = self
|
162
145
|
|
163
|
-
template(&block)
|
146
|
+
around_template { template { yield_content(&block) } }
|
164
147
|
|
165
148
|
self.class.rendered_at_least_once ||= true
|
166
149
|
|
167
150
|
buffer
|
168
151
|
end
|
169
152
|
|
153
|
+
def render(renderable, *args, **kwargs, &block)
|
154
|
+
if renderable.is_a?(Phlex::HTML)
|
155
|
+
renderable.call(@_target, view_context: @_view_context, parent: self, &block)
|
156
|
+
elsif renderable.is_a?(Class) && renderable < Phlex::HTML
|
157
|
+
raise ArgumentError, "You tried to render the Phlex view class: #{renderable.name} but you probably meant to render an instance of that class instead."
|
158
|
+
else
|
159
|
+
raise ArgumentError, "You can't render a #{renderable}."
|
160
|
+
end
|
161
|
+
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
|
165
|
+
def format
|
166
|
+
:html
|
167
|
+
end
|
168
|
+
|
170
169
|
def rendered?
|
171
170
|
@_rendered ||= false
|
172
171
|
end
|
@@ -183,37 +182,18 @@ module Phlex
|
|
183
182
|
register_void_element(method_name, tag: tag)
|
184
183
|
end
|
185
184
|
|
186
|
-
def yield_content(&block)
|
187
|
-
return unless block_given?
|
188
|
-
|
189
|
-
original_length = @_target.length
|
190
|
-
output = yield(self)
|
191
|
-
unchanged = (original_length == @_target.length)
|
192
|
-
|
193
|
-
if unchanged
|
194
|
-
case output
|
195
|
-
when String, Symbol, Integer, Float
|
196
|
-
text(output)
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
nil
|
201
|
-
end
|
202
|
-
|
203
185
|
def text(content)
|
204
|
-
@_target <<
|
186
|
+
@_target << ERB::Util.html_escape(
|
187
|
+
case content
|
188
|
+
when String then content
|
189
|
+
when Symbol then content.name
|
190
|
+
else content.to_s
|
191
|
+
end
|
192
|
+
)
|
205
193
|
|
206
194
|
nil
|
207
195
|
end
|
208
196
|
|
209
|
-
def _output(content)
|
210
|
-
case content
|
211
|
-
when String then Hescape.escape_html(content)
|
212
|
-
when Symbol then Hescape.escape_html(content.name)
|
213
|
-
else Hescape.escape_html(content.to_s)
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
197
|
def whitespace
|
218
198
|
@_target << " "
|
219
199
|
|
@@ -225,8 +205,11 @@ module Phlex
|
|
225
205
|
nil
|
226
206
|
end
|
227
207
|
|
228
|
-
def comment(
|
229
|
-
@_target << "<!-- "
|
208
|
+
def comment(&block)
|
209
|
+
@_target << "<!-- "
|
210
|
+
yield_content(&block)
|
211
|
+
@_target << " -->"
|
212
|
+
|
230
213
|
nil
|
231
214
|
end
|
232
215
|
|
@@ -240,34 +223,6 @@ module Phlex
|
|
240
223
|
nil
|
241
224
|
end
|
242
225
|
|
243
|
-
def html_safe?
|
244
|
-
true
|
245
|
-
end
|
246
|
-
|
247
|
-
def safe_append=(value)
|
248
|
-
return unless value
|
249
|
-
|
250
|
-
@_target << case value
|
251
|
-
when String then value
|
252
|
-
when Symbol then value.name
|
253
|
-
else value.to_s
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
def append=(value)
|
258
|
-
return unless value
|
259
|
-
|
260
|
-
if value.html_safe?
|
261
|
-
self.safe_append = value
|
262
|
-
else
|
263
|
-
@_target << case value
|
264
|
-
when String then Hescape.escape_html(value)
|
265
|
-
when Symbol then Hescape.escape_html(value.name)
|
266
|
-
else Hescape.escape_html(value.to_s)
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
226
|
def capture(&block)
|
272
227
|
return unless block_given?
|
273
228
|
|
@@ -279,11 +234,44 @@ module Phlex
|
|
279
234
|
|
280
235
|
@_target = original_buffer
|
281
236
|
|
282
|
-
new_buffer
|
237
|
+
new_buffer
|
283
238
|
end
|
284
239
|
|
285
|
-
|
286
|
-
|
240
|
+
private
|
241
|
+
|
242
|
+
def around_template
|
243
|
+
before_template
|
244
|
+
yield
|
245
|
+
after_template
|
246
|
+
end
|
247
|
+
|
248
|
+
def before_template
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
|
252
|
+
def after_template
|
253
|
+
nil
|
254
|
+
end
|
255
|
+
|
256
|
+
def yield_content(&block)
|
257
|
+
return unless block_given?
|
258
|
+
|
259
|
+
original_length = @_target.length
|
260
|
+
content = yield(self)
|
261
|
+
unchanged = (original_length == @_target.length)
|
262
|
+
|
263
|
+
if unchanged
|
264
|
+
case content
|
265
|
+
when String
|
266
|
+
@_target << ERB::Util.html_escape(content)
|
267
|
+
when Symbol
|
268
|
+
@_target << ERB::Util.html_escape(content.name)
|
269
|
+
when Integer, Float
|
270
|
+
@_target << ERB::Util.html_escape(content.to_s)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
nil
|
287
275
|
end
|
288
276
|
|
289
277
|
def _attributes(**attributes)
|
@@ -306,14 +294,12 @@ module Phlex
|
|
306
294
|
next unless v
|
307
295
|
|
308
296
|
name = case k
|
309
|
-
|
310
|
-
k
|
311
|
-
|
312
|
-
k.name.tr("_", "-")
|
313
|
-
else
|
314
|
-
k.to_s
|
297
|
+
when String then k
|
298
|
+
when Symbol then k.name.tr("_", "-")
|
299
|
+
else k.to_s
|
315
300
|
end
|
316
301
|
|
302
|
+
# Detect unsafe attribute names. Attribute names are considered unsafe if they match an event attribute or include unsafe characters.
|
317
303
|
if HTML::EVENT_ATTRIBUTES[name] || name.match?(/[<>&"']/)
|
318
304
|
raise ArgumentError, "Unsafe attribute name detected: #{k}."
|
319
305
|
end
|
@@ -322,13 +308,13 @@ module Phlex
|
|
322
308
|
when true
|
323
309
|
buffer << " " << name
|
324
310
|
when String
|
325
|
-
buffer << " " << name << '="' <<
|
311
|
+
buffer << " " << name << '="' << ERB::Util.html_escape(v) << '"'
|
326
312
|
when Symbol
|
327
|
-
buffer << " " << name << '="' <<
|
313
|
+
buffer << " " << name << '="' << ERB::Util.html_escape(v.name) << '"'
|
328
314
|
when Hash
|
329
315
|
_build_attributes(v.transform_keys { "#{k}-#{_1.name.tr('_', '-')}" }, buffer: buffer)
|
330
316
|
else
|
331
|
-
buffer << " " << name << '="' <<
|
317
|
+
buffer << " " << name << '="' << ERB::Util.html_escape(v.to_s) << '"'
|
332
318
|
end
|
333
319
|
end
|
334
320
|
|
data/lib/phlex/table.rb
CHANGED
data/lib/phlex/turbo/frame.rb
CHANGED
data/lib/phlex/turbo/stream.rb
CHANGED
data/lib/phlex/version.rb
CHANGED
data/lib/phlex.rb
CHANGED
@@ -1,23 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "erb"
|
4
4
|
require "zeitwerk"
|
5
|
-
require "syntax_tree"
|
6
5
|
|
7
6
|
module Phlex
|
8
7
|
Loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false).tap do |loader|
|
9
|
-
loader.ignore("#{__dir__}/generators")
|
10
|
-
loader.ignore("#{__dir__}/install")
|
11
|
-
|
12
|
-
loader.ignore("#{__dir__}/phlex/testing")
|
13
|
-
loader.ignore("#{__dir__}/phlex/markdown.rb")
|
14
|
-
|
15
|
-
loader.ignore("#{__dir__}/phlex/rails.rb")
|
16
|
-
loader.ignore("#{__dir__}/phlex/rails")
|
17
|
-
|
18
8
|
loader.inflector.inflect("html" => "HTML")
|
19
|
-
loader.
|
20
|
-
loader.inflector.inflect("fcall" => "FCall")
|
9
|
+
loader.ignore("#{__dir__}/phlex/testing")
|
21
10
|
loader.setup
|
22
11
|
end
|
23
12
|
|
@@ -25,12 +14,6 @@ module Phlex
|
|
25
14
|
ArgumentError = Class.new(ArgumentError) { include Error }
|
26
15
|
NameError = Class.new(NameError) { include Error }
|
27
16
|
|
28
|
-
def self.const_missing(name)
|
29
|
-
if name == :View
|
30
|
-
raise NameError, "👋 Phlex::View has been renamed (again 🙄) to Phlex::HTML."
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
17
|
extend self
|
35
18
|
|
36
19
|
ATTRIBUTE_CACHE = {}
|