phlex 2.0.0.beta2 → 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.
- checksums.yaml +4 -4
- data/README.md +8 -11
- data/lib/phlex/csv.rb +4 -11
- data/lib/phlex/error.rb +1 -0
- data/lib/phlex/fifo.rb +11 -3
- data/lib/phlex/fifo_cache_store.rb +49 -0
- data/lib/phlex/helpers.rb +2 -2
- data/lib/phlex/html/standard_elements.rb +932 -722
- data/lib/phlex/html/void_elements.rb +93 -66
- data/lib/phlex/html.rb +4 -10
- data/lib/phlex/kit.rb +39 -22
- data/lib/phlex/null_cache_store.rb +9 -0
- data/lib/phlex/sgml/elements.rb +112 -0
- data/lib/phlex/sgml/state.rb +118 -0
- data/lib/phlex/sgml.rb +326 -305
- data/lib/phlex/svg/standard_elements.rb +417 -449
- data/lib/phlex/svg.rb +0 -3
- data/lib/phlex/{black_hole.rb → vanish.rb} +1 -1
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex.rb +17 -9
- metadata +9 -12
- data/lib/phlex/context.rb +0 -59
- data/lib/phlex/deferred_render.rb +0 -29
- data/lib/phlex/element_clobbering_guard.rb +0 -18
- data/lib/phlex/elements.rb +0 -172
@@ -2,71 +2,98 @@
|
|
2
2
|
|
3
3
|
# Void HTML elements don't accept content and never have a closing tag.
|
4
4
|
module Phlex::HTML::VoidElements
|
5
|
-
extend Phlex::Elements
|
5
|
+
extend Phlex::SGML::Elements
|
6
6
|
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
#
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
#
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
#
|
70
|
-
#
|
71
|
-
|
7
|
+
# Outputs an `<area>` tag.
|
8
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/area
|
9
|
+
__register_void_element__ def area(
|
10
|
+
class: nil,
|
11
|
+
id: nil,
|
12
|
+
**attributes
|
13
|
+
) = nil
|
14
|
+
# Outputs a `<base>` tag.
|
15
|
+
# See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
16
|
+
__register_void_element__ def base(
|
17
|
+
class: nil,
|
18
|
+
href: nil,
|
19
|
+
id: nil,
|
20
|
+
target: nil,
|
21
|
+
**attributes
|
22
|
+
) = nil
|
23
|
+
# Outputs a `<br>` tag.
|
24
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/br
|
25
|
+
__register_void_element__ def br(
|
26
|
+
class: nil,
|
27
|
+
id: nil,
|
28
|
+
**attributes
|
29
|
+
) = nil
|
30
|
+
# Outputs a `<col>` tag.
|
31
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/col
|
32
|
+
__register_void_element__ def col(
|
33
|
+
class: nil,
|
34
|
+
id: nil,
|
35
|
+
**attributes
|
36
|
+
) = nil
|
37
|
+
# Outputs an `<embed>` tag.
|
38
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/embed
|
39
|
+
__register_void_element__ def embed(
|
40
|
+
class: nil,
|
41
|
+
id: nil,
|
42
|
+
**attributes
|
43
|
+
) = nil
|
44
|
+
# Outputs an `<hr>` tag.
|
45
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/hr
|
46
|
+
__register_void_element__ def hr(
|
47
|
+
class: nil,
|
48
|
+
id: nil,
|
49
|
+
**attributes
|
50
|
+
) = nil
|
51
|
+
# Outputs an `<img>` tag.
|
52
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/img
|
53
|
+
__register_void_element__ def img(
|
54
|
+
alt: nil,
|
55
|
+
class: nil,
|
56
|
+
id: nil,
|
57
|
+
src: nil,
|
58
|
+
**attributes
|
59
|
+
) = nil
|
60
|
+
# Outputs an `<input>` tag.
|
61
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/input
|
62
|
+
__register_void_element__ def input(
|
63
|
+
class: nil,
|
64
|
+
id: nil,
|
65
|
+
name: nil,
|
66
|
+
type: nil,
|
67
|
+
**attributes
|
68
|
+
) = nil
|
69
|
+
# Outputs a `<link>` tag.
|
70
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/link
|
71
|
+
__register_void_element__ def link(
|
72
|
+
class: nil,
|
73
|
+
id: nil,
|
74
|
+
**attributes
|
75
|
+
) = nil
|
76
|
+
# Outputs a `<meta>` tag.
|
77
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/meta
|
78
|
+
__register_void_element__ def meta(
|
79
|
+
charset: nil,
|
80
|
+
class: nil,
|
81
|
+
id: nil,
|
82
|
+
name: nil,
|
83
|
+
**attributes
|
84
|
+
) = nil
|
85
|
+
# Outputs a `<source>` tag.
|
86
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/source
|
87
|
+
__register_void_element__ def source(
|
88
|
+
class: nil,
|
89
|
+
id: nil,
|
90
|
+
**attributes
|
91
|
+
) = nil
|
92
|
+
# Outputs a `<track>` tag.
|
93
|
+
# See https://developer.mozilla.org/docs/Web/HTML/Element/track
|
94
|
+
__register_void_element__ def track(
|
95
|
+
class: nil,
|
96
|
+
id: nil,
|
97
|
+
**attributes
|
98
|
+
) = nil
|
72
99
|
end
|
data/lib/phlex/html.rb
CHANGED
@@ -4,18 +4,15 @@ class Phlex::HTML < Phlex::SGML
|
|
4
4
|
autoload :StandardElements, "phlex/html/standard_elements"
|
5
5
|
autoload :VoidElements, "phlex/html/void_elements"
|
6
6
|
|
7
|
-
|
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
|
9
|
-
|
10
|
-
extend Phlex::Elements
|
7
|
+
extend Phlex::SGML::Elements
|
11
8
|
include VoidElements, StandardElements
|
12
9
|
|
13
10
|
# Output an HTML doctype.
|
14
11
|
def doctype
|
15
|
-
|
16
|
-
return
|
12
|
+
state = @_state
|
13
|
+
return unless state.should_render?
|
17
14
|
|
18
|
-
|
15
|
+
state.buffer << "<!doctype html>"
|
19
16
|
nil
|
20
17
|
end
|
21
18
|
|
@@ -41,7 +38,4 @@ class Phlex::HTML < Phlex::SGML
|
|
41
38
|
def content_type
|
42
39
|
"text/html"
|
43
40
|
end
|
44
|
-
|
45
|
-
# This should be extended after all method definitions
|
46
|
-
extend Phlex::ElementClobberingGuard
|
47
41
|
end
|
data/lib/phlex/kit.rb
CHANGED
@@ -3,7 +3,9 @@
|
|
3
3
|
module Phlex::Kit
|
4
4
|
module LazyLoader
|
5
5
|
def method_missing(name, ...)
|
6
|
-
|
6
|
+
mod = self.class
|
7
|
+
|
8
|
+
if name[0] == name[0].upcase && mod.constants.include?(name) && mod.const_get(name) && methods.include?(name)
|
7
9
|
public_send(name, ...)
|
8
10
|
else
|
9
11
|
super
|
@@ -11,7 +13,9 @@ module Phlex::Kit
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def respond_to_missing?(name, include_private = false)
|
14
|
-
|
16
|
+
mod = self.class
|
17
|
+
|
18
|
+
if name[0] == name[0].upcase && mod.constants.include?(name) && mod.const_get(name) && methods.include?(name)
|
15
19
|
true
|
16
20
|
else
|
17
21
|
super
|
@@ -19,20 +23,24 @@ module Phlex::Kit
|
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
22
|
-
include LazyLoader
|
23
|
-
|
24
26
|
def self.extended(mod)
|
25
27
|
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) }
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
constants
|
30
|
+
def method_missing(name, ...)
|
31
|
+
if name[0] == name[0].upcase && constants.include?(name) && const_get(name) && methods.include?(name)
|
32
|
+
public_send(name, ...)
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
32
36
|
end
|
33
37
|
|
34
|
-
def
|
35
|
-
const_get(name)
|
38
|
+
def respond_to_missing?(name, include_private = false)
|
39
|
+
if name[0] == name[0].upcase && constants.include?(name) && const_get(name) && methods.include?(name)
|
40
|
+
true
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
36
44
|
end
|
37
45
|
|
38
46
|
def const_added(name)
|
@@ -41,23 +49,32 @@ module Phlex::Kit
|
|
41
49
|
me = self
|
42
50
|
constant = const_get(name)
|
43
51
|
|
44
|
-
|
45
|
-
|
52
|
+
case constant
|
53
|
+
when Class
|
54
|
+
if constant < Phlex::SGML
|
55
|
+
constant.include(self)
|
46
56
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
57
|
+
constant = nil
|
58
|
+
|
59
|
+
define_method(name) do |*args, **kwargs, &block|
|
60
|
+
constant = me.const_get(name)
|
61
|
+
render(constant.new(*args, **kwargs), &block)
|
62
|
+
end
|
51
63
|
|
52
|
-
|
53
|
-
|
54
|
-
component.
|
55
|
-
|
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."
|
56
73
|
end
|
57
|
-
else
|
58
|
-
raise "You can't call `#{name}' outside of a Phlex rendering context."
|
59
74
|
end
|
60
75
|
end
|
76
|
+
when Module
|
77
|
+
constant.extend(Phlex::Kit)
|
61
78
|
end
|
62
79
|
|
63
80
|
super
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex::SGML::Elements
|
4
|
+
def __registered_elements__
|
5
|
+
@__registered_elements__ ||= {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def register_element(method_name, tag: method_name.name.tr("_", "-"))
|
9
|
+
class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
10
|
+
# frozen_string_literal: true
|
11
|
+
|
12
|
+
def #{method_name}(**attributes)
|
13
|
+
state = @_state
|
14
|
+
buffer = state.buffer
|
15
|
+
block_given = block_given?
|
16
|
+
|
17
|
+
unless state.should_render?
|
18
|
+
yield(self) if block_given
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
|
22
|
+
if attributes.length > 0 # with attributes
|
23
|
+
if block_given # with content block
|
24
|
+
buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
|
25
|
+
|
26
|
+
original_length = buffer.bytesize
|
27
|
+
content = yield(self)
|
28
|
+
if original_length == buffer.bytesize
|
29
|
+
case content
|
30
|
+
when nil
|
31
|
+
nil
|
32
|
+
when String
|
33
|
+
buffer << ::Phlex::Escape.html_escape(content)
|
34
|
+
when Symbol
|
35
|
+
buffer << ::Phlex::Escape.html_escape(content.name)
|
36
|
+
when ::Phlex::SGML::SafeObject
|
37
|
+
buffer << content.to_s
|
38
|
+
else
|
39
|
+
if (formatted_object = format_object(content))
|
40
|
+
buffer << ::Phlex::Escape.html_escape(formatted_object)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
buffer << "</#{tag}>"
|
46
|
+
else # without content
|
47
|
+
buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << "></#{tag}>"
|
48
|
+
end
|
49
|
+
else # without attributes
|
50
|
+
if block_given # with content block
|
51
|
+
buffer << "<#{tag}>"
|
52
|
+
|
53
|
+
original_length = buffer.bytesize
|
54
|
+
content = yield(self)
|
55
|
+
if original_length == buffer.bytesize
|
56
|
+
case content
|
57
|
+
when nil
|
58
|
+
nil
|
59
|
+
when String
|
60
|
+
buffer << ::Phlex::Escape.html_escape(content)
|
61
|
+
when Symbol
|
62
|
+
buffer << ::Phlex::Escape.html_escape(content.name)
|
63
|
+
when ::Phlex::SGML::SafeObject
|
64
|
+
buffer << content.to_s
|
65
|
+
else
|
66
|
+
if (formatted_object = format_object(content))
|
67
|
+
buffer << ::Phlex::Escape.html_escape(formatted_object)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
buffer << "</#{tag}>"
|
73
|
+
else # without content
|
74
|
+
buffer << "<#{tag}></#{tag}>"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#{'flush' if tag == 'head'}
|
79
|
+
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
RUBY
|
83
|
+
|
84
|
+
__registered_elements__[method_name] = tag
|
85
|
+
|
86
|
+
method_name
|
87
|
+
end
|
88
|
+
|
89
|
+
def __register_void_element__(method_name, tag: method_name.name.tr("_", "-"))
|
90
|
+
class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
91
|
+
# frozen_string_literal: true
|
92
|
+
|
93
|
+
def #{method_name}(**attributes)
|
94
|
+
state = @_state
|
95
|
+
|
96
|
+
return unless state.should_render?
|
97
|
+
|
98
|
+
if attributes.length > 0 # with attributes
|
99
|
+
state.buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
|
100
|
+
else # without attributes
|
101
|
+
state.buffer << "<#{tag}>"
|
102
|
+
end
|
103
|
+
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
RUBY
|
107
|
+
|
108
|
+
__registered_elements__[method_name] = tag
|
109
|
+
|
110
|
+
method_name
|
111
|
+
end
|
112
|
+
end
|
@@ -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
|