phlex 2.0.0.rc1 → 2.0.0.rc2

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.
@@ -11,7 +11,6 @@ module Phlex::HTML::VoidElements
11
11
  id: nil,
12
12
  **attributes
13
13
  ) = nil
14
-
15
14
  # Outputs a `<base>` tag.
16
15
  # See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
17
16
  __register_void_element__ def base(
@@ -21,7 +20,6 @@ module Phlex::HTML::VoidElements
21
20
  target: nil,
22
21
  **attributes
23
22
  ) = nil
24
-
25
23
  # Outputs a `<br>` tag.
26
24
  # See https://developer.mozilla.org/docs/Web/HTML/Element/br
27
25
  __register_void_element__ def br(
@@ -29,7 +27,6 @@ module Phlex::HTML::VoidElements
29
27
  id: nil,
30
28
  **attributes
31
29
  ) = nil
32
-
33
30
  # Outputs a `<col>` tag.
34
31
  # See https://developer.mozilla.org/docs/Web/HTML/Element/col
35
32
  __register_void_element__ def col(
@@ -37,7 +34,6 @@ module Phlex::HTML::VoidElements
37
34
  id: nil,
38
35
  **attributes
39
36
  ) = nil
40
-
41
37
  # Outputs an `<embed>` tag.
42
38
  # See https://developer.mozilla.org/docs/Web/HTML/Element/embed
43
39
  __register_void_element__ def embed(
@@ -45,7 +41,6 @@ module Phlex::HTML::VoidElements
45
41
  id: nil,
46
42
  **attributes
47
43
  ) = nil
48
-
49
44
  # Outputs an `<hr>` tag.
50
45
  # See https://developer.mozilla.org/docs/Web/HTML/Element/hr
51
46
  __register_void_element__ def hr(
@@ -53,7 +48,6 @@ module Phlex::HTML::VoidElements
53
48
  id: nil,
54
49
  **attributes
55
50
  ) = nil
56
-
57
51
  # Outputs an `<img>` tag.
58
52
  # See https://developer.mozilla.org/docs/Web/HTML/Element/img
59
53
  __register_void_element__ def img(
@@ -63,7 +57,6 @@ module Phlex::HTML::VoidElements
63
57
  src: nil,
64
58
  **attributes
65
59
  ) = nil
66
-
67
60
  # Outputs an `<input>` tag.
68
61
  # See https://developer.mozilla.org/docs/Web/HTML/Element/input
69
62
  __register_void_element__ def input(
@@ -73,7 +66,6 @@ module Phlex::HTML::VoidElements
73
66
  type: nil,
74
67
  **attributes
75
68
  ) = nil
76
-
77
69
  # Outputs a `<link>` tag.
78
70
  # See https://developer.mozilla.org/docs/Web/HTML/Element/link
79
71
  __register_void_element__ def link(
@@ -81,7 +73,6 @@ module Phlex::HTML::VoidElements
81
73
  id: nil,
82
74
  **attributes
83
75
  ) = nil
84
-
85
76
  # Outputs a `<meta>` tag.
86
77
  # See https://developer.mozilla.org/docs/Web/HTML/Element/meta
87
78
  __register_void_element__ def meta(
@@ -91,7 +82,6 @@ module Phlex::HTML::VoidElements
91
82
  name: nil,
92
83
  **attributes
93
84
  ) = nil
94
-
95
85
  # Outputs a `<source>` tag.
96
86
  # See https://developer.mozilla.org/docs/Web/HTML/Element/source
97
87
  __register_void_element__ def source(
@@ -99,7 +89,6 @@ module Phlex::HTML::VoidElements
99
89
  id: nil,
100
90
  **attributes
101
91
  ) = nil
102
-
103
92
  # Outputs a `<track>` tag.
104
93
  # See https://developer.mozilla.org/docs/Web/HTML/Element/track
105
94
  __register_void_element__ def track(
data/lib/phlex/html.rb CHANGED
@@ -9,10 +9,10 @@ class Phlex::HTML < Phlex::SGML
9
9
 
10
10
  # Output an HTML doctype.
11
11
  def doctype
12
- context = @_context
13
- return if context.fragments && !context.in_target_fragment
12
+ state = @_state
13
+ return unless state.should_render?
14
14
 
15
- context.buffer << "<!doctype html>"
15
+ state.buffer << "<!doctype html>"
16
16
  nil
17
17
  end
18
18
 
data/lib/phlex/kit.rb CHANGED
@@ -49,27 +49,32 @@ module Phlex::Kit
49
49
  me = self
50
50
  constant = const_get(name)
51
51
 
52
- if Class === constant && constant < Phlex::SGML
53
- constant.include(self)
52
+ case constant
53
+ when Class
54
+ if constant < Phlex::SGML
55
+ constant.include(self)
54
56
 
55
- constant = nil
57
+ constant = nil
56
58
 
57
- define_method(name) do |*args, **kwargs, &block|
58
- constant = me.const_get(name)
59
- render(constant.new(*args, **kwargs), &block)
60
- end
59
+ define_method(name) do |*args, **kwargs, &block|
60
+ constant = me.const_get(name)
61
+ render(constant.new(*args, **kwargs), &block)
62
+ end
61
63
 
62
- define_singleton_method(name) do |*args, **kwargs, &block|
63
- component, fiber_id = Thread.current[:__phlex_component__]
64
- if (component && fiber_id == Fiber.current.object_id)
65
- component.instance_exec do
66
- constant = me.const_get(name)
67
- render(constant.new(*args, **kwargs), &block)
64
+ define_singleton_method(name) do |*args, **kwargs, &block|
65
+ component, fiber_id = Thread.current[:__phlex_component__]
66
+ if (component && fiber_id == Fiber.current.object_id)
67
+ component.instance_exec do
68
+ constant = me.const_get(name)
69
+ render(constant.new(*args, **kwargs), &block)
70
+ end
71
+ else
72
+ raise "You can't call `#{name}' outside of a Phlex rendering context."
68
73
  end
69
- else
70
- raise "You can't call `#{name}' outside of a Phlex rendering context."
71
74
  end
72
75
  end
76
+ when Module
77
+ constant.extend(Phlex::Kit)
73
78
  end
74
79
 
75
80
  super
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex::NullCacheStore
4
+ extend self
5
+
6
+ def fetch(key)
7
+ yield
8
+ end
9
+ end
@@ -6,30 +6,17 @@ module Phlex::SGML::Elements
6
6
  end
7
7
 
8
8
  def register_element(method_name, tag: method_name.name.tr("_", "-"))
9
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
9
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
10
10
  # frozen_string_literal: true
11
11
 
12
12
  def #{method_name}(**attributes)
13
- context = @_context
14
- buffer = context.buffer
15
- fragment = context.fragments
16
- target_found = false
13
+ state = @_state
14
+ buffer = state.buffer
17
15
  block_given = block_given?
18
16
 
19
- if fragment
20
- return if fragment.length == 0 # we found all our fragments already
21
-
22
- id = attributes[:id]
23
-
24
- if !context.in_target_fragment
25
- if fragment[id]
26
- context.begin_target(id)
27
- target_found = true
28
- else
29
- yield(self) if block_given
30
- return nil
31
- end
32
- end
17
+ unless state.should_render?
18
+ yield(self) if block_given
19
+ return nil
33
20
  end
34
21
 
35
22
  if attributes.length > 0 # with attributes
@@ -40,14 +27,14 @@ module Phlex::SGML::Elements
40
27
  content = yield(self)
41
28
  if original_length == buffer.bytesize
42
29
  case content
43
- when ::Phlex::SGML::SafeObject
44
- buffer << content.to_s
30
+ when nil
31
+ nil
45
32
  when String
46
33
  buffer << ::Phlex::Escape.html_escape(content)
47
34
  when Symbol
48
35
  buffer << ::Phlex::Escape.html_escape(content.name)
49
- when nil
50
- nil
36
+ when ::Phlex::SGML::SafeObject
37
+ buffer << content.to_s
51
38
  else
52
39
  if (formatted_object = format_object(content))
53
40
  buffer << ::Phlex::Escape.html_escape(formatted_object)
@@ -67,14 +54,14 @@ module Phlex::SGML::Elements
67
54
  content = yield(self)
68
55
  if original_length == buffer.bytesize
69
56
  case content
70
- when ::Phlex::SGML::SafeObject
71
- buffer << content.to_s
57
+ when nil
58
+ nil
72
59
  when String
73
60
  buffer << ::Phlex::Escape.html_escape(content)
74
61
  when Symbol
75
62
  buffer << ::Phlex::Escape.html_escape(content.name)
76
- when nil
77
- nil
63
+ when ::Phlex::SGML::SafeObject
64
+ buffer << content.to_s
78
65
  else
79
66
  if (formatted_object = format_object(content))
80
67
  buffer << ::Phlex::Escape.html_escape(formatted_object)
@@ -90,8 +77,6 @@ module Phlex::SGML::Elements
90
77
 
91
78
  #{'flush' if tag == 'head'}
92
79
 
93
- context.end_target if target_found
94
-
95
80
  nil
96
81
  end
97
82
  RUBY
@@ -102,41 +87,22 @@ module Phlex::SGML::Elements
102
87
  end
103
88
 
104
89
  def __register_void_element__(method_name, tag: method_name.name.tr("_", "-"))
105
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
90
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
106
91
  # frozen_string_literal: true
107
92
 
108
93
  def #{method_name}(**attributes)
109
- context = @_context
110
- buffer = context.buffer
111
- fragment = context.fragments
94
+ state = @_state
112
95
 
113
- if fragment
114
- return if fragment.length == 0 # we found all our fragments already
115
-
116
- id = attributes[:id]
117
-
118
- if !context.in_target_fragment
119
- if fragment[id]
120
- context.begin_target(id)
121
- target_found = true
122
- else
123
- return nil
124
- end
125
- end
126
- end
96
+ return unless state.should_render?
127
97
 
128
98
  if attributes.length > 0 # with attributes
129
- buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
99
+ state.buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
130
100
  else # without attributes
131
- buffer << "<#{tag}>"
101
+ state.buffer << "<#{tag}>"
132
102
  end
133
103
 
134
- context.end_target if target_found
135
-
136
104
  nil
137
105
  end
138
-
139
- alias_method :_#{method_name}, :#{method_name}
140
106
  RUBY
141
107
 
142
108
  __registered_elements__[method_name] = tag
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Phlex::SGML::State
4
+ def initialize(user_context: {}, view_context: nil, output_buffer:, fragments:)
5
+ @buffer = +""
6
+ @capturing = false
7
+ @user_context = user_context
8
+ @fragments = fragments
9
+ @fragment_depth = 0
10
+ @cache_stack = []
11
+ @halt_signal = nil
12
+ @view_context = view_context
13
+ @output_buffer = output_buffer
14
+ end
15
+
16
+ attr_accessor :buffer, :capturing, :user_context
17
+
18
+ attr_reader :fragments, :fragment_depth, :view_context, :output_buffer
19
+
20
+ def around_render(component)
21
+ stack = @stack
22
+
23
+ if !@fragments || @halt_signal
24
+ yield
25
+ else
26
+ catch do |signal|
27
+ @halt_signal = signal
28
+ yield
29
+ end
30
+ end
31
+ end
32
+
33
+ def should_render?
34
+ !@fragments || @fragment_depth > 0
35
+ end
36
+
37
+ def begin_fragment(id)
38
+ @fragment_depth += 1 if @fragments&.include?(id)
39
+
40
+ if caching?
41
+ current_byte_offset = 0 # Start tracking the byte offset of this fragment from the start of the cache buffer
42
+ @cache_stack.reverse_each do |(cache_buffer, fragment_map)| # We'll iterate deepest to shallowest
43
+ current_byte_offset += cache_buffer.bytesize # Add the length of the cache buffer to the current byte offset
44
+ fragment_map[id] = [current_byte_offset, nil, []] # Record the byte offset, length, and store a list of the nested fragments
45
+
46
+ fragment_map.each do |name, (_offset, length, nested_fragments)| # Iterate over the other fragments
47
+ next if name == id || length # Skip if it's the current fragment, or if the fragment has already ended
48
+ nested_fragments << id # Add the current fragment to the list of nested fragments
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def end_fragment(id)
55
+ if caching?
56
+ byte_length = nil
57
+ @cache_stack.reverse_each do |(cache_buffer, fragment_map)| # We'll iterate deepest to shallowest
58
+ byte_length ||= cache_buffer.bytesize - fragment_map[id][0] # The byte length is the difference between the current byte offset and the byte offset of the fragment
59
+ fragment_map[id][1] = byte_length # All cache contexts will use the same by
60
+ end
61
+ end
62
+
63
+ return unless @fragments&.include?(id)
64
+
65
+ @fragments.delete(id)
66
+ @fragment_depth -= 1
67
+ throw @halt_signal if @fragments.length == 0
68
+ end
69
+
70
+ def record_fragment(id, offset, length, nested_fragments)
71
+ return unless caching?
72
+
73
+ @cache_stack.reverse_each do |(cache_buffer, fragment_map)|
74
+ offset += cache_buffer.bytesize
75
+ fragment_map[id] = [offset, length, nested_fragments]
76
+ end
77
+ end
78
+
79
+ def caching(&)
80
+ buffer = +""
81
+ @cache_stack.push([buffer, {}].freeze)
82
+ capturing_into(buffer, &)
83
+ @cache_stack.pop
84
+ end
85
+
86
+ def caching?
87
+ @cache_stack.length > 0
88
+ end
89
+
90
+ def capturing_into(new_buffer)
91
+ original_buffer = @buffer
92
+ original_capturing = @capturing
93
+ original_fragments = @fragments
94
+
95
+ begin
96
+ @buffer = new_buffer
97
+ @capturing = true
98
+ @fragments = nil
99
+ yield
100
+ ensure
101
+ @buffer = original_buffer
102
+ @capturing = original_capturing
103
+ @fragments = original_fragments
104
+ end
105
+
106
+ new_buffer
107
+ end
108
+
109
+ def flush
110
+ return if capturing
111
+
112
+ buffer = @buffer
113
+ @output_buffer << buffer.dup
114
+
115
+ buffer.clear
116
+ nil
117
+ end
118
+ end