phlex 1.4.0 β†’ 1.5.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8a6c8dfab161746b6b4e04d403fead1f2477f195ff27e72365838f169ecc287
4
- data.tar.gz: e9e9da3414eba5fc53d5440b16c0eecf47d4fc5d09a178fe44c9acab3f554119
3
+ metadata.gz: 4a1ec24fb4717ac1f83f1d43f87db1d9674cfdc7ab1ad2ca5479d1918a7da14d
4
+ data.tar.gz: 0e3bc60aff556fcf4e2005dc059033f9bbf45f168a0f7626aaa351cf87a75df0
5
5
  SHA512:
6
- metadata.gz: 043c059dd8ce067a50df40c293d61475892acc934a4efdd18bedae7094ebaea3c9dd11a4bc5e5b7779120d38966964a2725dcd17de3dca2de48c50e837e2e8fd
7
- data.tar.gz: 00edaf160d669c8f62f40c4f66faf17a6b76b2428648be0d7c7977ad629b119a146cfacffc8636312f87e46b77cdfc3074fe39ade0b4ff45ee10e874f0459a31
6
+ metadata.gz: 44d1682ad2d052e8394420dccfaf3ff286d293f2a86d7bc685c704e5d2f2fcbd9f50bb06a189a8e6407bc2886bdc5f9de9e62b61aa79c8c82226ff65c719c950
7
+ data.tar.gz: 38d9e4e9665c67479da8dd6633613807f38b69a40c24cc1e15583c6a89f34ef1f37edadee244325389d6bd7f11442b9fa977a65a783293a34e509b713ebd8819
data/Gemfile CHANGED
@@ -7,10 +7,7 @@ gemspec
7
7
 
8
8
  gem "rubocop"
9
9
  gem "sus"
10
- gem "zeitwerk"
11
10
  gem "benchmark-ips"
12
- gem "erb"
13
- gem "ruby-lsp"
14
11
 
15
12
  group :test do
16
13
  gem "i18n"
data/README.md CHANGED
@@ -10,6 +10,17 @@ Documentation can be found at [www.phlex.fun](https://www.phlex.fun).
10
10
 
11
11
  If you run into any trouble, please [start a discussion](https://github.com/joeldrapper/phlex/discussions/new), or [open an issue](https://github.com/joeldrapper/phlex/issues/new) if you think you’ve found a bug.
12
12
 
13
+ ### Ecosystem 🌱
14
+ - [joeldrapper/phlex-rails](https://github.com/joeldrapper/phlex-rails) β€” Ruby on Rails integration
15
+ - [joeldrapper/phlex.fun](https://github.com/joeldrapper/phlex.fun) β€” Docs Website
16
+ - [joeldrapper/phlex-markdown](https://github.com/joeldrapper/phlex-markdown) β€” Markdown to HTML using Phlex
17
+ - [joeldrapper/phlex-compiler](https://github.com/joeldrapper/phlex-compiler) β€” A compiler for Phlex
18
+ - [joeldrapper/phlex-translation](https://github.com/joeldrapper/phlex-translation) β€” I18n Support for Phlex
19
+ - [joeldrapper/phlex-testing-nokogiri](https://github.com/joeldrapper/phlex-testing-nokogiri) β€” Nokogiri support
20
+ - [joeldrapper/phlex-testing-capybara](https://github.com/joeldrapper/phlex-testing-capybara) β€” Capybara support
21
+ - [marcoroth/phlexing](https://github.com/marcoroth/phlexing) β€” ERB β†’ Phlex converter
22
+ - [ViewComponent/lookbook](https://github.com/ViewComponent/lookbook) β€”Β Document and preview Phlex components in Rails (v2 beta only)
23
+
13
24
  ### Community πŸ™Œ
14
25
 
15
26
  Everyone interacting in Phlex codebases, issue trackers or chat rooms is expected to follow the [code of conduct](https://github.com/joeldrapper/phlex/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module ElementClobberingGuard
5
+ def method_added(method_name)
6
+ if method_name[0] == "_" && private_instance_methods.include?(:"__phlex#{method_name}__")
7
+ raise NameError, "πŸ‘‹ Redefining the method `#{name}##{method_name}` is not a good idea."
8
+ end
9
+
10
+ super
11
+ end
12
+ end
13
+ end
@@ -5,25 +5,33 @@ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
5
5
  end
6
6
 
7
7
  module Phlex::Elements
8
+ private def slow_registered_elements
9
+ private_instance_methods
10
+ .lazy
11
+ .map(&:to_s)
12
+ .select { |m| m.start_with?("__phlex_") }
13
+ .map { |m| m[8...-2].to_sym }
14
+ end
15
+
8
16
  def register_element(element, tag: element.name.tr("_", "-"))
9
17
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
10
18
  # frozen_string_literal: true
11
19
 
12
- def #{element}(**attributes, &block)
13
- if attributes.length > 0
14
- if block_given?
15
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || __attributes__(**attributes)) << ">"
20
+ def __phlex_#{element}__(**attributes, &block)
21
+ if attributes.length > 0 # with attributes
22
+ if block_given? # with content block
23
+ @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << ">"
16
24
  yield_content(&block)
17
25
  @_target << "</#{tag}>"
18
- else
19
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || __attributes__(**attributes)) << "></#{tag}>"
26
+ else # without content block
27
+ @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << "></#{tag}>"
20
28
  end
21
- else
22
- if block_given?
29
+ else # without attributes
30
+ if block_given? # with content block
23
31
  @_target << "<#{tag}>"
24
32
  yield_content(&block)
25
33
  @_target << "</#{tag}>"
26
- else
34
+ else # without content block
27
35
  @_target << "<#{tag}></#{tag}>"
28
36
  end
29
37
  end
@@ -31,11 +39,11 @@ module Phlex::Elements
31
39
  nil
32
40
  end
33
41
 
34
- alias_method :_#{element}, :#{element}
42
+ alias_method :_#{element}, :__phlex_#{element}__
43
+ alias_method :#{element}, :__phlex_#{element}__
44
+ private :__phlex_#{element}__
35
45
  RUBY
36
46
 
37
- self::REGISTERED_ELEMENTS[element] = tag
38
-
39
47
  element
40
48
  end
41
49
 
@@ -43,21 +51,21 @@ module Phlex::Elements
43
51
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
44
52
  # frozen_string_literal: true
45
53
 
46
- def #{element}(**attributes)
47
- if attributes.length > 0
48
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || __attributes__(**attributes)) << ">"
49
- else
54
+ def __phlex_#{element}__(**attributes)
55
+ if attributes.length > 0 # with attributes
56
+ @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << ">"
57
+ else # without attributes
50
58
  @_target << "<#{tag}>"
51
59
  end
52
60
 
53
61
  nil
54
62
  end
55
63
 
56
- alias_method :_#{element}, :#{element}
64
+ alias_method :_#{element}, :__phlex_#{element}__
65
+ alias_method :#{element}, :__phlex_#{element}__
66
+ private :__phlex_#{element}__
57
67
  RUBY
58
68
 
59
- self::REGISTERED_ELEMENTS[element] = tag
60
-
61
69
  element
62
70
  end
63
71
  end
data/lib/phlex/helpers.rb CHANGED
@@ -5,6 +5,7 @@ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
5
5
  end
6
6
 
7
7
  module Phlex::Helpers
8
+ # @return [String]
8
9
  private def tokens(*tokens, **conditional_tokens)
9
10
  conditional_tokens.each do |condition, token|
10
11
  truthy = case condition
@@ -31,6 +32,7 @@ module Phlex::Helpers
31
32
  tokens
32
33
  end
33
34
 
35
+ # @api private
34
36
  private def __append_token__(tokens, token)
35
37
  case token
36
38
  when nil then nil
@@ -42,6 +44,7 @@ module Phlex::Helpers
42
44
  end
43
45
  end
44
46
 
47
+ # @return [Hash]
45
48
  private def classes(*tokens, **conditional_tokens)
46
49
  tokens = self.tokens(*tokens, **conditional_tokens)
47
50
 
@@ -52,6 +55,7 @@ module Phlex::Helpers
52
55
  end
53
56
  end
54
57
 
58
+ # @return [Hash]
55
59
  private def mix(*args)
56
60
  args.each_with_object({}) do |object, result|
57
61
  result.merge!(object) do |_key, old, new|
@@ -3,8 +3,6 @@
3
3
  module Phlex::HTML::StandardElements
4
4
  extend Phlex::Elements
5
5
 
6
- REGISTERED_ELEMENTS = Concurrent::Map.new
7
-
8
6
  # @!method a(**attributes, &content)
9
7
  # Outputs an <code>a</code> tag
10
8
  # @return [nil]
@@ -3,8 +3,6 @@
3
3
  module Phlex::HTML::VoidElements
4
4
  extend Phlex::Elements
5
5
 
6
- REGISTERED_ELEMENTS = Concurrent::Map.new
7
-
8
6
  # @!method area(**attributes, &content)
9
7
  # Outputs an <code>area</code> tag
10
8
  # @return [nil]
data/lib/phlex/html.rb CHANGED
@@ -29,6 +29,12 @@ module Phlex
29
29
  nil
30
30
  end
31
31
 
32
+ # @deprecated use {#plain} instead.
33
+ def text(...)
34
+ warn "DEPRECATED: The `text` method has been deprecated in favour of `plain`. Please use `plain` instead. The `text` method will be removed in a future version of Phlex. Called from: #{caller.first}"
35
+ plain(...)
36
+ end
37
+
32
38
  def svg(...)
33
39
  super do
34
40
  render Phlex::SVG.new do |svg|
@@ -42,13 +48,7 @@ module Phlex
42
48
  self.class.__unbuffered_class__.new(self)
43
49
  end
44
50
 
45
- # This should be the last method defined
46
- def self.method_added(method_name)
47
- if method_name[0] == "_" && Phlex::HTML.instance_methods.include?(method_name) && instance_method(method_name).owner != Phlex::HTML
48
- raise NameError, "πŸ‘‹ Redefining the method `#{name}##{method_name}` is not a good idea."
49
- end
50
-
51
- super
52
- end
51
+ # This should be extended after all method definitions
52
+ extend ElementClobberingGuard
53
53
  end
54
54
  end
data/lib/phlex/sgml.rb CHANGED
@@ -14,6 +14,8 @@ module Phlex
14
14
 
15
15
  alias_method :render, :call
16
16
 
17
+ # Create a new instance of the component.
18
+ # @note The block will not be delegated to the initializer. Instead, it will be provided to `template` when rendering.
17
19
  def new(*args, **kwargs, &block)
18
20
  if block
19
21
  object = super(*args, **kwargs, &nil)
@@ -32,21 +34,21 @@ module Phlex
32
34
  end
33
35
 
34
36
  # Renders the view and returns the buffer. The default buffer is a mutable String.
35
- def call(buffer = +"", view_context: nil, parent: nil, &block)
36
- __final_call__(buffer, view_context: view_context, parent: parent, &block).tap do
37
+ def call(buffer = nil, target: +"", view_context: nil, parent: nil, &block)
38
+ __final_call__(buffer, target: target, view_context: view_context, parent: parent, &block).tap do
37
39
  self.class.rendered_at_least_once!
38
40
  end
39
41
  end
40
42
 
41
43
  # @api private
42
- def __final_call__(buffer = +"", view_context: nil, parent: nil, &block)
43
- @_target = buffer
44
+ def __final_call__(buffer = nil, target: +"", view_context: nil, parent: nil, &block)
45
+ @_target = target
44
46
  @_view_context = view_context
45
47
  @_parent = parent
46
48
 
47
49
  block ||= @_content_block
48
50
 
49
- return buffer unless render?
51
+ return buffer || target unless render?
50
52
 
51
53
  around_template do
52
54
  if block
@@ -67,19 +69,19 @@ module Phlex
67
69
  end
68
70
  end
69
71
 
70
- buffer
72
+ buffer ? (buffer << target) : target
71
73
  end
72
74
 
73
75
  # Render another view
74
- # @param renderable [Phlex::HTML] the other view to render
75
- # @return [void]
76
+ # @param renderable [Phlex::SGML]
77
+ # @return [nil]
76
78
  def render(renderable, &block)
77
79
  case renderable
78
80
  when Phlex::SGML
79
- renderable.call(@_target, view_context: @_view_context, parent: self, &block)
81
+ renderable.call(target: @_target, view_context: @_view_context, parent: self, &block)
80
82
  when Class
81
83
  if renderable < Phlex::SGML
82
- renderable.new.call(@_target, view_context: @_view_context, parent: self, &block)
84
+ renderable.new.call(target: @_target, view_context: @_view_context, parent: self, &block)
83
85
  end
84
86
  else
85
87
  raise ArgumentError, "You can't render a #{renderable}."
@@ -89,20 +91,28 @@ module Phlex
89
91
  end
90
92
 
91
93
  # Output text content. The text will be HTML-escaped.
92
- def text(content)
93
- @_target << ERB::Util.html_escape(
94
- case content
95
- when String then content
96
- when Symbol then content.name
97
- when Integer then content.to_s
98
- else format_object(content) || content.to_s
94
+ # @return [nil]
95
+ def plain(content)
96
+ case content
97
+ when String
98
+ @_target << ERB::Escape.html_escape(content)
99
+ when Symbol
100
+ @_target << ERB::Escape.html_escape(content.name)
101
+ when Integer
102
+ @_target << ERB::Escape.html_escape(content.to_s)
103
+ when nil
104
+ nil
105
+ else
106
+ if (formatted_object = format_object(content))
107
+ @_target << ERB::Escape.html_escape(formatted_object)
99
108
  end
100
- )
109
+ end
101
110
 
102
111
  nil
103
112
  end
104
113
 
105
114
  # Output a whitespace character. This is useful for getting inline elements to wrap. If you pass a block, a whitespace will be output before and after yielding the block.
115
+ # @return [nil]
106
116
  def whitespace
107
117
  @_target << " "
108
118
 
@@ -115,6 +125,7 @@ module Phlex
115
125
  end
116
126
 
117
127
  # Output an HTML comment.
128
+ # @return [nil]
118
129
  def comment(&block)
119
130
  @_target << "<!-- "
120
131
  yield_content(&block)
@@ -136,23 +147,25 @@ module Phlex
136
147
  # Capture a block of output as a String.
137
148
  # @return [String]
138
149
  def capture(&block)
139
- return unless block_given?
150
+ return "" unless block_given?
140
151
 
141
- original_buffer = @_target
142
- new_buffer = +""
152
+ original_buffer_content = @_target.dup
153
+ @_target.clear
143
154
 
144
155
  begin
145
- @_target = new_buffer
146
156
  yield_content(&block)
157
+ new_buffer_content = @_target.dup
147
158
  ensure
148
- @_target = original_buffer
159
+ @_target.clear
160
+ @_target << original_buffer_content
149
161
  end
150
162
 
151
- new_buffer
163
+ new_buffer_content
152
164
  end
153
165
 
154
166
  # Like `capture` but the output is vanished into a BlackHole buffer.
155
167
  # Because the BlackHole does nothing with the output, this should be faster.
168
+ # @return [nil]
156
169
  private def __vanish__(*args)
157
170
  return unless block_given?
158
171
 
@@ -169,10 +182,13 @@ module Phlex
169
182
  end
170
183
 
171
184
  # Default render predicate can be overridden to prevent rendering
185
+ # @return [bool]
172
186
  private def render?
173
187
  true
174
188
  end
175
189
 
190
+ # Format the object for output
191
+ # @return [String]
176
192
  private def format_object(object)
177
193
  case object
178
194
  when Float
@@ -181,70 +197,48 @@ module Phlex
181
197
  end
182
198
 
183
199
  # Override this method to hook in around a template render. You can do things before and after calling <code>super</code> to render the template. You should always call <code>super</code> so that callbacks can be added at different layers of the inheritance tree.
200
+ # @return [nil]
184
201
  private def around_template
185
202
  before_template
186
203
  yield
187
204
  after_template
205
+
206
+ nil
188
207
  end
189
208
 
190
209
  # Override this method to hook in right before a template is rendered. Please remember to call <code>super</code> so that callbacks can be added at different layers of the inheritance tree.
210
+ # @return [nil]
191
211
  private def before_template
192
212
  nil
193
213
  end
194
214
 
195
215
  # Override this method to hook in right after a template is rendered. Please remember to call <code>super</code> so that callbacks can be added at different layers of the inheritance tree.
216
+ # @return [nil]
196
217
  private def after_template
197
218
  nil
198
219
  end
199
220
 
200
221
  # Yields the block and checks if it buffered anything. If nothing was buffered, the return value is treated as text.
222
+ # @return [nil]
201
223
  private def yield_content
202
224
  return unless block_given?
203
225
 
204
226
  original_length = @_target.length
205
227
  content = yield(self)
206
- unchanged = (original_length == @_target.length)
207
228
 
208
- if unchanged
209
- case content
210
- when String
211
- @_target << ERB::Util.html_escape(content)
212
- when Symbol
213
- @_target << ERB::Util.html_escape(content.name)
214
- when Integer
215
- @_target << ERB::Util.html_escape(content.to_s)
216
- else
217
- if (formatted_object = format_object(content))
218
- @_target << ERB::Util.html_escape(formatted_object)
219
- end
220
- end
221
- end
229
+ plain(content) if original_length == @_target.length
222
230
 
223
231
  nil
224
232
  end
225
233
 
226
234
  # Same as <code>yield_content</code> but accepts a splat of arguments to yield. This is slightly slower than <code>yield_content</code>, which is why it's defined as a different method because we don't always need arguments so we can usually use <code>yield_content</code> instead.
235
+ # @return [nil]
227
236
  private def yield_content_with_args(*args)
228
237
  return unless block_given?
229
238
 
230
239
  original_length = @_target.length
231
240
  content = yield(*args)
232
- unchanged = (original_length == @_target.length)
233
-
234
- if unchanged
235
- case content
236
- when String
237
- @_target << ERB::Util.html_escape(content)
238
- when Symbol
239
- @_target << ERB::Util.html_escape(content.name)
240
- when Integer, Float
241
- @_target << ERB::Util.html_escape(content.to_s)
242
- else
243
- if (formatted_object = format_object(content))
244
- @_target << ERB::Util.html_escape(formatted_object)
245
- end
246
- end
247
- end
241
+ plain(content) if original_length == @_target.length
248
242
 
249
243
  nil
250
244
  end
@@ -252,12 +246,16 @@ module Phlex
252
246
  # @api private
253
247
  private def __attributes__(**attributes)
254
248
  __final_attributes__(**attributes).tap do |buffer|
255
- Phlex::ATTRIBUTE_CACHE[attributes.hash] = buffer.freeze
249
+ Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] = buffer.freeze
256
250
  end
257
251
  end
258
252
 
259
253
  # @api private
260
254
  private def __final_attributes__(**attributes)
255
+ if respond_to?(:process_attributes)
256
+ attributes = process_attributes(**attributes)
257
+ end
258
+
261
259
  if attributes[:href]&.start_with?(/\s*javascript:/)
262
260
  attributes.delete(:href)
263
261
  end
@@ -292,9 +290,9 @@ module Phlex
292
290
  when true
293
291
  buffer << " " << name
294
292
  when String
295
- buffer << " " << name << '="' << ERB::Util.html_escape(v) << '"'
293
+ buffer << " " << name << '="' << ERB::Escape.html_escape(v) << '"'
296
294
  when Symbol
297
- buffer << " " << name << '="' << ERB::Util.html_escape(v.name) << '"'
295
+ buffer << " " << name << '="' << ERB::Escape.html_escape(v.name) << '"'
298
296
  when Hash
299
297
  __build_attributes__(
300
298
  v.transform_keys { |subkey|
@@ -305,7 +303,7 @@ module Phlex
305
303
  }, buffer: buffer
306
304
  )
307
305
  else
308
- buffer << " " << name << '="' << ERB::Util.html_escape(v.to_s) << '"'
306
+ buffer << " " << name << '="' << ERB::Escape.html_escape(v.to_s) << '"'
309
307
  end
310
308
  end
311
309
 
@@ -3,8 +3,6 @@
3
3
  module Phlex::SVG::StandardElements
4
4
  extend Phlex::Elements
5
5
 
6
- REGISTERED_ELEMENTS = Concurrent::Map.new
7
-
8
6
  # @!method a(**attributes, &content)
9
7
  # Outputs an <code>a</code> tag
10
8
  # @return [nil]
data/lib/phlex/svg.rb CHANGED
@@ -7,5 +7,8 @@ module Phlex
7
7
  def template
8
8
  yield
9
9
  end
10
+
11
+ # This should be extended after all method definitions
12
+ extend ElementClobberingGuard
10
13
  end
11
14
  end
data/lib/phlex/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- VERSION = "1.4.0"
4
+ VERSION = "1.5.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Drapper
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-17 00:00:00.000000000 Z
11
+ date: 2023-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: erb
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '4'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: zeitwerk
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -63,6 +77,7 @@ files:
63
77
  - lib/phlex/black_hole.rb
64
78
  - lib/phlex/callable.rb
65
79
  - lib/phlex/deferred_render.rb
80
+ - lib/phlex/element_clobbering_guard.rb
66
81
  - lib/phlex/elements.rb
67
82
  - lib/phlex/helpers.rb
68
83
  - lib/phlex/html.rb