phlex 1.10.3 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a86bca32b72f150179b586843c71432ebfea0d892ad70b426ef0f8ce6d58f3f
4
- data.tar.gz: 235a24dc555ffcb1598660c9c5d539a5b050780a1ccec4d81b8b5115ea5b7f0e
3
+ metadata.gz: fd808f50305105b7fb8cde4624804427da99fa7a3e59a20f6442e1db35e0983d
4
+ data.tar.gz: f3e7f7b703a27e3a00bbea42749e4682ac0255a3c3c12abca5c94f90de1be23c
5
5
  SHA512:
6
- metadata.gz: 0b4ab7f24ba4955cc0622da21a363436a0b54cae4505ec0fd599dd2ddd53731175386b5fc9f62981807f7836d34edd7de851dd662a18b1e01c3935aa37956454
7
- data.tar.gz: eb0b42b41d70b8fb28ae3c9134f0ecddcc517698ac812c2f1c69b05c4b41d7d9148aca244d88b1bb8e8d817abcfafa9ac6968342890584b9b6cf539b320e2b27
6
+ metadata.gz: d66595c47ec3704a3b06c619e2fe8697252a8e9f5566be7c0c8082c06c69a46361686135411f04268854a1a087a78b24e072e7ca4fa98fd23b64dfaa147f6f07
7
+ data.tar.gz: e93c80673a2ca16d9585d76b8c348ec0a7ddf04013720d083dc064b432aaf7f1642bbe5a6b8622fea6c18eb453f663de0d36929bd903f3b40a7f243987a46dee
data/README.md CHANGED
@@ -1,10 +1,24 @@
1
1
  <a href="https://www.phlex.fun/"><img alt="Phlex logo" src="https://www.phlex.fun/assets/logo.png" width="180" /></a>
2
2
 
3
- Phlex lets you compose web views in pure Ruby — kind of like JSX, but not really anything like JSX. It’s super-fast, thread-safe and supports TruffleRuby v22.2+, JRuby v9.2+ and MRI v2.7+. Phlex currently supports [HTML](https://rubydoc.info/gems/phlex/Phlex/HTML) and [SVG](https://rubydoc.info/gems/phlex/Phlex/SVG) views, and we’re exploring JSON and XML.
3
+ Phlex lets you compose web views in pure Ruby.
4
4
 
5
- Docs and more at [Phlex.fun](https://www.phlex.fun/)
5
+ - [1.0 Stable Docs](https://www.phlex.fun)
6
+ - [2.0 Beta Docs](https://beta.phlex.fun)
6
7
 
7
- ### Prior Art 🎨
8
+ ## Maintenance schedule
9
+
10
+ ### Bug fixes
11
+
12
+ - Only the latest minor version of each major version will receive bug fixes
13
+ - We may choose to fix bugs by releasing a new minor version rather than patching the existing minor version
14
+ - Major versions will stop receiving bug fixes one year after the next major version is released
15
+
16
+ ### Security patches
17
+
18
+ - When a security issue is brought to our attention, we aim to release patches for any minor version that was released in the last year.
19
+ - Additionally, the latest minor version of the latest major version will receive security patches, even if that version is over a year old.
20
+
21
+ ## Prior Art 🎨
8
22
 
9
23
  - [markaby](https://github.com/markaby/markaby)
10
24
  - [erector](https://github.com/erector/erector)
@@ -17,3 +31,7 @@ Docs and more at [Phlex.fun](https://www.phlex.fun/)
17
31
  - [clearwater](https://github.com/clearwater-rb/clearwater)
18
32
  - [paggio](https://github.com/opal/paggio)
19
33
  - [Inesita](https://github.com/inesita-rb/inesita)
34
+ - [compony](https://github.com/kalsan/compony)
35
+ - [tagz](https://github.com/ahoward/tagz)
36
+ - [html](https://github.com/ismasan/html)
37
+ - [fortitude](https://github.com/ageweke/fortitude)
data/lib/phlex/context.rb CHANGED
@@ -15,11 +15,6 @@ class Phlex::Context
15
15
 
16
16
  attr_reader :fragments
17
17
 
18
- # Added for backwards compatibility with phlex-rails. We can remove this with 2.0
19
- def target
20
- @buffer
21
- end
22
-
23
18
  def target_fragments(fragments)
24
19
  @fragments = fragments.to_h { |it| [it, true] }
25
20
  end
data/lib/phlex/csv.rb CHANGED
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Phlex::CSV
4
- include Phlex::Callable
5
-
6
- FORMULA_PREFIXES = ["=", "+", "-", "@", "\t", "\r"].to_h { |prefix| [prefix, true] }.freeze
7
- SPACE_CHARACTERS = [" ", "\t", "\r"].to_h { |char| [char, true] }.freeze
4
+ FORMULA_PREFIXES = Set["=", "+", "-", "@", "\t", "\r"].freeze
5
+ SPACE_CHARACTERS = Set[" ", "\t", "\r"].freeze
8
6
 
9
7
  def initialize(collection)
10
8
  @collection = collection
@@ -84,18 +82,14 @@ class Phlex::CSV
84
82
  @_current_column_index += 1
85
83
  end
86
84
 
87
- def each_item(&block)
88
- collection.each(&block)
85
+ def each_item(&)
86
+ collection.each(&)
89
87
  end
90
88
 
91
89
  def yielder(record)
92
90
  yield(record)
93
91
  end
94
92
 
95
- def template(...)
96
- nil
97
- end
98
-
99
93
  # Override and set to `false` to disable rendering headers.
100
94
  def render_headers?
101
95
  true
@@ -120,11 +114,11 @@ class Phlex::CSV
120
114
  first_char = value[0]
121
115
  last_char = value[-1]
122
116
 
123
- if escape_csv_injection? && FORMULA_PREFIXES[first_char]
117
+ if escape_csv_injection? && FORMULA_PREFIXES.include?(first_char)
124
118
  # Prefix a single quote to prevent Excel, Google Docs, etc. from interpreting the value as a formula.
125
119
  # See https://owasp.org/www-community/attacks/CSV_Injection
126
120
  %("'#{value.gsub('"', '""')}")
127
- elsif (!trim_whitespace? && (SPACE_CHARACTERS[first_char] || SPACE_CHARACTERS[last_char])) || value.include?('"') || value.include?(",") || value.include?("\n")
121
+ elsif (!trim_whitespace? && (SPACE_CHARACTERS.include?(first_char) || SPACE_CHARACTERS.include?(last_char))) || value.include?('"') || value.include?(",") || value.include?("\n")
128
122
  %("#{value.gsub('"', '""')}")
129
123
  else
130
124
  value
@@ -3,8 +3,12 @@
3
3
  # @api private
4
4
  module Phlex::ElementClobberingGuard
5
5
  def method_added(method_name)
6
- if method_name[0] == "_" && element_method?(method_name[1..].to_sym)
7
- raise Phlex::NameError, "👋 Redefining the method `#{name}##{method_name}` is not a good idea."
6
+ if method_name[0] == "_" && __element_method__?(method_name[1..].to_sym)
7
+ raise Phlex::NameError.new("👋 Redefining the method `#{name}##{method_name}` is not a good idea.")
8
+ elsif method_name == :view_template
9
+ Phlex.__expand_attribute_cache__(
10
+ instance_method(method_name).source_location[0],
11
+ )
8
12
  else
9
13
  super
10
14
  end
@@ -28,25 +28,16 @@ module Phlex::Elements
28
28
  # @note The methods defined by this macro depend on other methods from {SGML} so they should always be mixed into an {HTML} or {SVG} component.
29
29
  # @example Register the custom element `<trix-editor>`
30
30
  # register_element :trix_editor
31
- def register_element(method_name, tag: method_name.name.tr("_", "-"), deprecated: false)
32
- if deprecated
33
- deprecation = <<~RUBY
34
- Kernel.warn "#{deprecated}"
35
- RUBY
36
- else
37
- deprecation = ""
38
- end
39
-
31
+ def register_element(method_name, tag: method_name.name.tr("_", "-"))
40
32
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
41
33
  # frozen_string_literal: true
42
34
 
43
- def #{method_name}(**attributes, &block)
44
- #{deprecation}
45
-
35
+ def #{method_name}(**attributes)
46
36
  context = @_context
47
37
  buffer = context.buffer
48
38
  fragment = context.fragments
49
39
  target_found = false
40
+ block_given = block_given?
50
41
 
51
42
  if fragment
52
43
  return if fragment.length == 0 # we found all our fragments already
@@ -58,26 +49,64 @@ module Phlex::Elements
58
49
  context.begin_target(id)
59
50
  target_found = true
60
51
  else
61
- yield(self) if block
52
+ yield(self) if block_given
62
53
  return nil
63
54
  end
64
55
  end
65
56
  end
66
57
 
67
58
  if attributes.length > 0 # with attributes
68
- if block # with content block
69
- buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << ">"
70
- yield_content(&block)
59
+ if block_given # with content block
60
+ buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
61
+
62
+ original_length = buffer.bytesize
63
+ content = yield(self)
64
+ if original_length == buffer.bytesize
65
+ case content
66
+ when String
67
+ buffer << Phlex::Escape.html_escape(content)
68
+ when Symbol
69
+ buffer << Phlex::Escape.html_escape(content.name)
70
+ when nil
71
+ nil
72
+ when Phlex::SGML::SafeObject
73
+ buffer << content.to_s
74
+ else
75
+ if (formatted_object = format_object(content))
76
+ buffer << Phlex::Escape.html_escape(formatted_object)
77
+ end
78
+ end
79
+ end
80
+
71
81
  buffer << "</#{tag}>"
72
- else # without content block
73
- buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << "></#{tag}>"
82
+ else # without content
83
+ buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << "></#{tag}>"
74
84
  end
75
85
  else # without attributes
76
- if block # with content block
86
+ if block_given # with content block
77
87
  buffer << "<#{tag}>"
78
- yield_content(&block)
88
+
89
+ original_length = buffer.bytesize
90
+ content = yield(self)
91
+ if original_length == buffer.bytesize
92
+ case content
93
+ when String
94
+ buffer << Phlex::Escape.html_escape(content)
95
+ when Symbol
96
+ buffer << Phlex::Escape.html_escape(content.name)
97
+ when nil
98
+ nil
99
+ when Phlex::SGML::SafeObject
100
+ buffer << content.to_s
101
+ else
102
+ if (formatted_object = format_object(content))
103
+ buffer << Phlex::Escape.html_escape(formatted_object)
104
+ end
105
+ end
106
+ end
107
+
79
108
  buffer << "</#{tag}>"
80
- else # without content block
109
+ else # without content
81
110
  buffer << "<#{tag}></#{tag}>"
82
111
  end
83
112
  end
@@ -98,20 +127,11 @@ module Phlex::Elements
98
127
  end
99
128
 
100
129
  # @api private
101
- def register_void_element(method_name, tag: method_name.name.tr("_", "-"), deprecated: false)
102
- if deprecated
103
- deprecation = <<~RUBY
104
- Kernel.warn "#{deprecated}"
105
- RUBY
106
- else
107
- deprecation = ""
108
- end
109
-
130
+ def register_void_element(method_name, tag: method_name.name.tr("_", "-"))
110
131
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
111
132
  # frozen_string_literal: true
112
133
 
113
134
  def #{method_name}(**attributes)
114
- #{deprecation}
115
135
  context = @_context
116
136
  buffer = context.buffer
117
137
  fragment = context.fragments
@@ -132,7 +152,7 @@ module Phlex::Elements
132
152
  end
133
153
 
134
154
  if attributes.length > 0 # with attributes
135
- buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << ">"
155
+ buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
136
156
  else # without attributes
137
157
  buffer << "<#{tag}>"
138
158
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex::Error
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Phlex::ArgumentError < ArgumentError
4
+ include Phlex::Error
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Phlex::DoubleRenderError < RuntimeError
4
+ include Phlex::Error
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Phlex::NameError < NameError
4
+ include Phlex::Error
5
+ end
data/lib/phlex/fifo.rb ADDED
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Phlex::FIFO
4
+ def initialize(max_bytesize: 2_000, max_value_bytesize: 2_000)
5
+ @store = {}
6
+ @max_bytesize = max_bytesize
7
+ @max_value_bytesize = max_value_bytesize
8
+ @bytesize = 0
9
+ @mutex = Mutex.new
10
+ end
11
+
12
+ attr_reader :bytesize, :max_bytesize
13
+
14
+ def expand(bytes)
15
+ @mutex.synchronize do
16
+ @max_bytesize += bytes
17
+ end
18
+ end
19
+
20
+ def [](key)
21
+ k, v = @store[key.hash]
22
+ v if k == key
23
+ end
24
+
25
+ def []=(key, value)
26
+ return if value.bytesize > @max_value_bytesize
27
+
28
+ digest = key.hash
29
+
30
+ @mutex.synchronize do
31
+ # Check the key definitely doesn't exist now we have the lock
32
+ return if @store[digest]
33
+
34
+ @store[digest] = [key, value]
35
+ @bytesize += value.bytesize
36
+
37
+ while @bytesize > @max_bytesize
38
+ k, v = @store.shift
39
+ @bytesize -= v[1].bytesize
40
+ end
41
+ end
42
+ end
43
+
44
+ def size
45
+ @store.size
46
+ end
47
+ end
data/lib/phlex/helpers.rb CHANGED
@@ -1,104 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module Phlex::Helpers
6
4
  private
7
5
 
8
- # Tokens
9
- # @return [String]
10
- # @example With Proc conditions
11
- # tokens(
12
- # -> { true } => "a",
13
- # -> { false } => "b"
14
- # )
15
- # @example With method conditions
16
- # tokens(
17
- # active?: "active"
18
- # )
19
- # @example With else condition
20
- # tokens(
21
- # active?: { then: "active", else: "inactive" }
22
- # )
23
- def tokens(*tokens, **conditional_tokens)
24
- conditional_tokens.each do |condition, token|
25
- truthy = case condition
26
- when Symbol then send(condition)
27
- when Proc then condition.call
28
- else raise ArgumentError, "The class condition must be a Symbol or a Proc."
29
- end
30
-
31
- if truthy
32
- case token
33
- when Hash then __append_token__(tokens, token[:then])
34
- else __append_token__(tokens, token)
35
- end
36
- else
37
- case token
38
- when Hash then __append_token__(tokens, token[:else])
39
- end
40
- end
41
- end
42
-
43
- tokens = tokens.select(&:itself).join(" ")
44
- tokens.strip!
45
- tokens.gsub!(/\s+/, " ")
46
- tokens
47
- end
48
-
49
- # @api private
50
- def __append_token__(tokens, token)
51
- case token
52
- when nil then nil
53
- when String then tokens << token
54
- when Symbol then tokens << token.name
55
- when Array then tokens.concat(token)
56
- else raise ArgumentError,
57
- "Conditional classes must be Symbols, Strings, or Arrays of Symbols or Strings."
58
- end
59
- end
60
-
61
- # Like {#tokens} but returns a {Hash} where the tokens are the value for `:class`.
62
- # @return [Hash]
63
- def classes(*tokens, **conditional_tokens)
64
- tokens = self.tokens(*tokens, **conditional_tokens)
65
-
66
- if tokens.empty?
67
- {}
68
- else
69
- { class: tokens }
70
- end
71
- end
72
-
73
6
  # @return [Hash]
74
7
  def mix(*args)
75
8
  args.each_with_object({}) do |object, result|
76
9
  result.merge!(object) do |_key, old, new|
77
- next new if old.nil?
78
-
79
- case new
80
- when Hash
81
- old.is_a?(Hash) ? mix(old, new) : new
82
- when Array
83
- case old
84
- when Array then old + new
85
- when Set then old.to_a + new
86
- when Hash then new
87
- else
88
- [old] + new
89
- end
90
- when Set
91
- case old
92
- when Set then old + new
93
- when Array then old + new.to_a
94
- when Hash then new
95
- else
96
- new + [old]
97
- end
98
- when String
99
- old.is_a?(String) ? "#{old} #{new}" : old + old.class[new]
100
- when nil
101
- old
10
+ case [old, new]
11
+ in [Array, Array] | [Set, Set]
12
+ old + new
13
+ in [Array, Set]
14
+ old + new.to_a
15
+ in [Array, String]
16
+ old + [new]
17
+ in [Hash, Hash]
18
+ mix(old, new)
19
+ in [Set, Array]
20
+ old.to_a + new
21
+ in [Set, String]
22
+ old.to_a + [new]
23
+ in [String, Array]
24
+ [old] + new
25
+ in [String, Set]
26
+ [old] + new.to_a
27
+ in [String, String]
28
+ "#{old} #{new}"
102
29
  else
103
30
  new
104
31
  end
@@ -109,4 +36,12 @@ module Phlex::Helpers
109
36
  end
110
37
  end
111
38
  end
39
+
40
+ def grab(**bindings)
41
+ if bindings.size > 1
42
+ bindings.values
43
+ else
44
+ bindings.values.first
45
+ end
46
+ end
112
47
  end
@@ -639,7 +639,7 @@ module Phlex::HTML::StandardElements
639
639
  # @return [nil]
640
640
  # @yieldparam component [self]
641
641
  # @see https://developer.mozilla.org/docs/Web/HTML/Element/template
642
- register_element :template_tag, tag: "template"
642
+ register_element :template
643
643
 
644
644
  # @!method textarea(**attributes, &content)
645
645
  # Outputs a `<textarea>` tag.
@@ -58,12 +58,6 @@ module Phlex::HTML::VoidElements
58
58
  # @see https://developer.mozilla.org/docs/Web/HTML/Element/meta
59
59
  register_void_element :meta
60
60
 
61
- # @!method param(**attributes, &content)
62
- # Outputs a `<param>` tag.
63
- # @return [nil]
64
- # @see https://developer.mozilla.org/docs/Web/HTML/Element/param
65
- register_void_element :param, deprecated: "⚠️ [DEPRECATION] The <param> tag is deprecated. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param"
66
-
67
61
  # @!method source(**attributes, &content)
68
62
  # Outputs a `<source>` tag.
69
63
  # @return [nil]
data/lib/phlex/html.rb CHANGED
@@ -1,66 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Phlex
4
- # @abstract Subclass and define {#template} to create an HTML component class.
5
- class HTML < SGML
6
- autoload :StandardElements, "phlex/html/standard_elements"
7
- autoload :VoidElements, "phlex/html/void_elements"
3
+ class Phlex::HTML < Phlex::SGML
4
+ autoload :StandardElements, "phlex/html/standard_elements"
5
+ autoload :VoidElements, "phlex/html/void_elements"
8
6
 
9
- # A list of HTML attributes that have the potential to execute unsafe JavaScript.
10
- EVENT_ATTRIBUTES = %w[onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmessage onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel].to_h { [_1, true] }.freeze
7
+ # A list of HTML attributes that have the potential to execute unsafe JavaScript.
8
+ UNSAFE_ATTRIBUTES = Set.new(%w[onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmessage onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel srcdoc]).freeze
11
9
 
12
- UNBUFFERED_MUTEX = Mutex.new
10
+ extend Phlex::Elements
11
+ include VoidElements, StandardElements
13
12
 
14
- class << self
15
- # @api private
16
- def __unbuffered_class__
17
- UNBUFFERED_MUTEX.synchronize do
18
- if defined? @unbuffered_class
19
- @unbuffered_class
20
- else
21
- @unbuffered_class = Class.new(Unbuffered)
22
- end
23
- end
24
- end
25
- end
26
-
27
- extend Elements
28
- include VoidElements, StandardElements
13
+ # Output an HTML doctype.
14
+ def doctype
15
+ context = @_context
16
+ return if context.fragments && !context.in_target_fragment
29
17
 
30
- # Output an HTML doctype.
31
- def doctype
32
- context = @_context
33
- return if context.fragments && !context.in_target_fragment
34
-
35
- context.buffer << "<!DOCTYPE html>"
36
- nil
37
- end
18
+ context.buffer << "<!doctype html>"
19
+ nil
20
+ end
38
21
 
39
- # Outputs an `<svg>` tag
40
- # @return [nil]
41
- # @see https://developer.mozilla.org/docs/Web/SVG/Element/svg
42
- def svg(...)
22
+ # Outputs an `<svg>` tag
23
+ # @return [nil]
24
+ # @see https://developer.mozilla.org/docs/Web/SVG/Element/svg
25
+ def svg(...)
26
+ if block_given?
43
27
  super do
44
28
  render Phlex::SVG.new do |svg|
45
29
  yield(svg)
46
30
  end
47
31
  end
32
+ else
33
+ super
48
34
  end
35
+ end
49
36
 
50
- # @api private
51
- def unbuffered
52
- self.class.__unbuffered_class__.new(self)
53
- end
54
-
55
- def filename
56
- nil
57
- end
58
-
59
- def content_type
60
- "text/html"
61
- end
37
+ def filename
38
+ nil
39
+ end
62
40
 
63
- # This should be extended after all method definitions
64
- extend ElementClobberingGuard
41
+ def content_type
42
+ "text/html"
65
43
  end
44
+
45
+ # This should be extended after all method definitions
46
+ extend Phlex::ElementClobberingGuard
66
47
  end
data/lib/phlex/kit.rb CHANGED
@@ -1,48 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex::Kit
4
- def self.extended(mod)
5
- warn "⚠️ [WARNING] Phlex::Kit is experimental and may be removed from future versions of Phlex."
6
- super
4
+ module LazyLoader
5
+ def method_missing(name, ...)
6
+ if name[0] == name[0].upcase && __phlex_kit_constants__.include?(name) && __get_phlex_kit_constant__(name) && methods.include?(name)
7
+ public_send(name, ...)
8
+ else
9
+ super
10
+ end
11
+ end
12
+
13
+ def respond_to_missing?(name, include_private = false)
14
+ if name[0] == name[0].upcase && __phlex_kit_constants__.include?(name) && __get_phlex_kit_constant__(name) && methods.include?(name)
15
+ true
16
+ else
17
+ super
18
+ end
19
+ end
7
20
  end
8
21
 
9
- # When a kit is included in a module, we need to load all of its components.
10
- def included(mod)
11
- constants.each { |c| const_get(c) if autoload?(c) }
12
- super
22
+ include LazyLoader
23
+
24
+ def self.extended(mod)
25
+ mod.include(LazyLoader)
26
+ mod.define_method(:__phlex_kit_constants__) { mod.__phlex_kit_constants__ }
27
+ mod.define_method(:__get_phlex_kit_constant__) { |name| mod.__get_phlex_kit_constant__(name) }
13
28
  end
14
29
 
15
- def method_missing(name, *args, **kwargs, &block)
16
- if name[0] == name[0].upcase && constants.include?(name) && const_get(name) && methods.include?(name)
17
- public_send(name, *args, **kwargs, &block)
18
- else
19
- super
20
- end
30
+ def __phlex_kit_constants__
31
+ constants
21
32
  end
22
33
 
23
- def respond_to_missing?(name, include_private = false)
24
- if name[0] == name[0].upcase && constants.include?(name) && const_get(name) && methods.include?(name)
25
- true
26
- else
27
- super
28
- end
34
+ def __get_phlex_kit_constant__(name)
35
+ const_get(name)
29
36
  end
30
37
 
31
38
  def const_added(name)
32
39
  return if autoload?(name)
33
40
 
41
+ me = self
34
42
  constant = const_get(name)
35
43
 
36
44
  if Class === constant && constant < Phlex::SGML
37
- if instance_methods.include?(name)
38
- raise NameError, "The instance method `#{name}' is already defined on `#{inspect}`."
39
- elsif methods.include?(name)
40
- raise NameError, "The method `#{name}' is already defined on `#{inspect}`."
41
- end
42
-
43
45
  constant.include(self)
44
46
 
45
47
  define_method(name) do |*args, **kwargs, &block|
48
+ constant = me.const_get(name)
46
49
  render(constant.new(*args, **kwargs), &block)
47
50
  end
48
51
 
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ module Phlex::SGML::SafeObject
5
+ # This is included in objects that are safe to render in an SGML context.
6
+ # They must implement a `to_s` method that returns a string.
7
+ end