phlex 2.1.0 → 2.2.0
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/lib/phlex/csv.rb +28 -12
- data/lib/phlex/helpers.rb +4 -0
- data/lib/phlex/html/standard_elements.rb +7 -0
- data/lib/phlex/html.rb +0 -3
- data/lib/phlex/sgml/state.rb +12 -15
- data/lib/phlex/sgml.rb +69 -22
- data/lib/phlex/svg.rb +0 -2
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex.rb +14 -29
- metadata +18 -5
- data/lib/phlex/vanish.rb +0 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d3eae91fcc5ec985b668a5e016c518f6392cd91d6d155a1f60552282755ca0e7
|
|
4
|
+
data.tar.gz: 4bac6d025488edbc104135dc3417286cc4782d4f022caff2a8a414afa12d09c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a18175ffb32c8ae170f9fd2c7f4b990d39500cef81db66545c057f7ff6635a215ba257130c3715a82d1322666ec89badc9beb06fae3325d864a1630f685bea2
|
|
7
|
+
data.tar.gz: 4ee950b03d2d8f0b8efe085207ca42b139775dff4cc0a564661648995ef32f01fce22e84537194a7c37a6218031d9ab8a4ab7689b92bfeac53534aee4d2fc919
|
data/lib/phlex/csv.rb
CHANGED
|
@@ -13,6 +13,7 @@ class Phlex::CSV
|
|
|
13
13
|
@collection = collection
|
|
14
14
|
@_row_buffer = []
|
|
15
15
|
@_headers = []
|
|
16
|
+
@_row_appender = nil
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
attr_reader :collection
|
|
@@ -49,13 +50,7 @@ class Phlex::CSV
|
|
|
49
50
|
MESSAGE
|
|
50
51
|
end
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
if has_yielder
|
|
54
|
-
yielder(record) { |*a, **k| row = row_template(*a, **k) }
|
|
55
|
-
else
|
|
56
|
-
around_row(record)
|
|
57
|
-
end
|
|
58
|
-
|
|
53
|
+
row_appender = -> {
|
|
59
54
|
row = row_buffer
|
|
60
55
|
|
|
61
56
|
if first_row
|
|
@@ -108,13 +103,28 @@ class Phlex::CSV
|
|
|
108
103
|
buffer << "\n"
|
|
109
104
|
|
|
110
105
|
row_buffer.clear
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if has_yielder
|
|
109
|
+
each_item do |record|
|
|
110
|
+
yielder(record) do |*a, **k|
|
|
111
|
+
row_template(*a, **k)
|
|
112
|
+
row_appender.call
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
else
|
|
116
|
+
@row_appender = row_appender
|
|
117
|
+
each_item do |record|
|
|
118
|
+
around_row(record)
|
|
119
|
+
end
|
|
111
120
|
end
|
|
112
121
|
|
|
113
122
|
buffer
|
|
114
123
|
end
|
|
115
124
|
|
|
116
|
-
def around_row(
|
|
117
|
-
row_template(
|
|
125
|
+
def around_row(...)
|
|
126
|
+
row_template(...)
|
|
127
|
+
@row_appender.call
|
|
118
128
|
end
|
|
119
129
|
|
|
120
130
|
def filename
|
|
@@ -168,7 +178,9 @@ class Phlex::CSV
|
|
|
168
178
|
value = value.strip
|
|
169
179
|
|
|
170
180
|
if escape_csv_injection
|
|
171
|
-
if
|
|
181
|
+
if value.empty?
|
|
182
|
+
buffer << value
|
|
183
|
+
elsif FORMULA_PREFIXES_MAP[value.getbyte(0)]
|
|
172
184
|
value.gsub!('"', '""')
|
|
173
185
|
buffer << '"\'' << value << '"'
|
|
174
186
|
elsif value.match?(escape_regex)
|
|
@@ -184,7 +196,9 @@ class Phlex::CSV
|
|
|
184
196
|
if escape_csv_injection
|
|
185
197
|
first_byte = value.getbyte(0)
|
|
186
198
|
|
|
187
|
-
if
|
|
199
|
+
if value.empty?
|
|
200
|
+
buffer << '""'
|
|
201
|
+
elsif FORMULA_PREFIXES_MAP[first_byte]
|
|
188
202
|
buffer << '"\'' << value.gsub('"', '""') << '"'
|
|
189
203
|
elsif value.match?(escape_regex)
|
|
190
204
|
buffer << '"' << value.gsub('"', '""') << '"'
|
|
@@ -192,7 +206,9 @@ class Phlex::CSV
|
|
|
192
206
|
buffer << value
|
|
193
207
|
end
|
|
194
208
|
else # not escaping CSV injection
|
|
195
|
-
if value.
|
|
209
|
+
if value.empty?
|
|
210
|
+
buffer << '""'
|
|
211
|
+
elsif value.match?(escape_regex)
|
|
196
212
|
buffer << '"' << value.gsub('"', '""') << '"'
|
|
197
213
|
else
|
|
198
214
|
buffer << value
|
data/lib/phlex/helpers.rb
CHANGED
|
@@ -472,6 +472,13 @@ module Phlex::HTML::StandardElements
|
|
|
472
472
|
# [Spec](https://html.spec.whatwg.org/#the-select-element)
|
|
473
473
|
register_element def select(**attributes, &content) = nil
|
|
474
474
|
|
|
475
|
+
# [EXPERIMENTAL] Outputs a `<selectedcontent>` tag.
|
|
476
|
+
#
|
|
477
|
+
# [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/selectedcontent)
|
|
478
|
+
# [Draft Spec](https://github.com/whatwg/html/pull/10633)
|
|
479
|
+
# [Can I Use?](https://caniuse.com/mdn-html_elements_selectedcontent)
|
|
480
|
+
register_element def selectedcontent(**attributes, &content) = nil
|
|
481
|
+
|
|
475
482
|
# Outputs a `<slot>` tag.
|
|
476
483
|
#
|
|
477
484
|
# [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/slot)
|
data/lib/phlex/html.rb
CHANGED
data/lib/phlex/sgml/state.rb
CHANGED
|
@@ -14,16 +14,7 @@ class Phlex::SGML::State
|
|
|
14
14
|
|
|
15
15
|
attr_accessor :capturing, :user_context
|
|
16
16
|
|
|
17
|
-
attr_reader :fragments, :fragment_depth, :output_buffer
|
|
18
|
-
|
|
19
|
-
def buffer
|
|
20
|
-
case @buffer
|
|
21
|
-
when Proc
|
|
22
|
-
@buffer.call
|
|
23
|
-
else
|
|
24
|
-
@buffer
|
|
25
|
-
end
|
|
26
|
-
end
|
|
17
|
+
attr_reader :fragments, :fragment_depth, :output_buffer, :buffer
|
|
27
18
|
|
|
28
19
|
def around_render(component)
|
|
29
20
|
stack = @stack
|
|
@@ -85,17 +76,23 @@ class Phlex::SGML::State
|
|
|
85
76
|
end
|
|
86
77
|
|
|
87
78
|
def caching(&)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
result = nil
|
|
80
|
+
|
|
81
|
+
capture do
|
|
82
|
+
@cache_stack.push([buffer, {}].freeze)
|
|
83
|
+
yield
|
|
84
|
+
result = @cache_stack.pop
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
result
|
|
92
88
|
end
|
|
93
89
|
|
|
94
90
|
def caching?
|
|
95
91
|
@cache_stack.length > 0
|
|
96
92
|
end
|
|
97
93
|
|
|
98
|
-
def
|
|
94
|
+
def capture
|
|
95
|
+
new_buffer = +""
|
|
99
96
|
original_buffer = @buffer
|
|
100
97
|
original_capturing = @capturing
|
|
101
98
|
original_fragments = @fragments
|
data/lib/phlex/sgml.rb
CHANGED
|
@@ -5,10 +5,16 @@ class Phlex::SGML
|
|
|
5
5
|
UNSAFE_ATTRIBUTES = Set.new(%w[srcdoc sandbox http-equiv]).freeze
|
|
6
6
|
REF_ATTRIBUTES = Set.new(%w[href src action formaction lowsrc dynsrc background ping]).freeze
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
ERBCompiler = ERB::Compiler.new("<>").tap do |compiler|
|
|
9
|
+
compiler.pre_cmd = [""]
|
|
10
|
+
compiler.put_cmd = "@_state.buffer.<<"
|
|
11
|
+
compiler.insert_cmd = "__implicit_output__"
|
|
12
|
+
compiler.post_cmd = ["nil"]
|
|
13
|
+
|
|
14
|
+
def compiler.add_insert_cmd(out, content)
|
|
15
|
+
out.push("#{@insert_cmd}((#{content}))")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
12
18
|
|
|
13
19
|
include Phlex::Helpers
|
|
14
20
|
|
|
@@ -29,6 +35,40 @@ class Phlex::SGML
|
|
|
29
35
|
super
|
|
30
36
|
end
|
|
31
37
|
end
|
|
38
|
+
|
|
39
|
+
def erb(method_name, erb = nil, locals: nil, &block)
|
|
40
|
+
loc = caller_locations(1, 1)[0]
|
|
41
|
+
path = loc.path.delete_suffix(".rb")
|
|
42
|
+
file = loc.path
|
|
43
|
+
line = loc.lineno - 1
|
|
44
|
+
|
|
45
|
+
unless erb
|
|
46
|
+
method_path = "#{path}/#{method_name}.html.erb"
|
|
47
|
+
sidecar_path = "#{path}.html.erb"
|
|
48
|
+
|
|
49
|
+
if File.exist?(method_path)
|
|
50
|
+
erb = File.read(method_path)
|
|
51
|
+
file = method_path
|
|
52
|
+
line = 1
|
|
53
|
+
elsif method_name == :view_template && File.exist?(sidecar_path)
|
|
54
|
+
erb = File.read(sidecar_path)
|
|
55
|
+
file = sidecar_path
|
|
56
|
+
line = 1
|
|
57
|
+
else
|
|
58
|
+
raise Phlex::RuntimeError.new(<<~MESSAGE)
|
|
59
|
+
No ERB template found for #{method_name}
|
|
60
|
+
MESSAGE
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
code, _enc = ERBCompiler.compile(erb)
|
|
65
|
+
|
|
66
|
+
class_eval(<<~RUBY, file, line)
|
|
67
|
+
def #{method_name} #{locals}
|
|
68
|
+
#{code}
|
|
69
|
+
end
|
|
70
|
+
RUBY
|
|
71
|
+
end
|
|
32
72
|
end
|
|
33
73
|
|
|
34
74
|
def view_template
|
|
@@ -172,9 +212,9 @@ class Phlex::SGML
|
|
|
172
212
|
return "" unless block
|
|
173
213
|
|
|
174
214
|
if args.length > 0
|
|
175
|
-
@_state.
|
|
215
|
+
@_state.capture { __yield_content_with_args__(*args, &block) }
|
|
176
216
|
else
|
|
177
|
-
@_state.
|
|
217
|
+
@_state.capture { __yield_content__(&block) }
|
|
178
218
|
end
|
|
179
219
|
end
|
|
180
220
|
|
|
@@ -253,6 +293,8 @@ class Phlex::SGML
|
|
|
253
293
|
].freeze
|
|
254
294
|
|
|
255
295
|
low_level_cache(full_key, **, &content)
|
|
296
|
+
|
|
297
|
+
nil
|
|
256
298
|
end
|
|
257
299
|
|
|
258
300
|
# Cache a block of content where you control the entire cache key.
|
|
@@ -286,6 +328,8 @@ class Phlex::SGML
|
|
|
286
328
|
end
|
|
287
329
|
end
|
|
288
330
|
end
|
|
331
|
+
|
|
332
|
+
nil
|
|
289
333
|
end
|
|
290
334
|
|
|
291
335
|
def json_escape(string)
|
|
@@ -308,15 +352,8 @@ class Phlex::SGML
|
|
|
308
352
|
false
|
|
309
353
|
end
|
|
310
354
|
|
|
311
|
-
def vanish(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if args.length > 0
|
|
315
|
-
@_state.capturing_into(Phlex::Vanish) { yield(*args) }
|
|
316
|
-
else
|
|
317
|
-
@_state.capturing_into(Phlex::Vanish) { yield(self) }
|
|
318
|
-
end
|
|
319
|
-
|
|
355
|
+
def vanish(...)
|
|
356
|
+
capture(...)
|
|
320
357
|
nil
|
|
321
358
|
end
|
|
322
359
|
|
|
@@ -524,14 +561,20 @@ class Phlex::SGML
|
|
|
524
561
|
attributes.each do |k, v|
|
|
525
562
|
next unless v
|
|
526
563
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
564
|
+
if (root_key = (:_ == k))
|
|
565
|
+
name = ""
|
|
566
|
+
original_base_name = base_name
|
|
567
|
+
base_name = base_name.delete_suffix("-")
|
|
568
|
+
else
|
|
569
|
+
name = case k
|
|
570
|
+
when String then k
|
|
571
|
+
when Symbol then k.name.tr("_", "-")
|
|
572
|
+
else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols")
|
|
573
|
+
end
|
|
532
574
|
|
|
533
|
-
|
|
534
|
-
|
|
575
|
+
if name.match?(/[<>&"']/)
|
|
576
|
+
raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
|
|
577
|
+
end
|
|
535
578
|
end
|
|
536
579
|
|
|
537
580
|
case v
|
|
@@ -555,6 +598,10 @@ class Phlex::SGML
|
|
|
555
598
|
raise Phlex::ArgumentError.new("Invalid attribute value #{v.inspect}.")
|
|
556
599
|
end
|
|
557
600
|
|
|
601
|
+
if root_key
|
|
602
|
+
base_name = original_base_name
|
|
603
|
+
end
|
|
604
|
+
|
|
558
605
|
buffer
|
|
559
606
|
end
|
|
560
607
|
end
|
data/lib/phlex/svg.rb
CHANGED
data/lib/phlex/version.rb
CHANGED
data/lib/phlex.rb
CHANGED
|
@@ -2,26 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
4
|
require "set"
|
|
5
|
+
require "zeitwerk"
|
|
5
6
|
|
|
6
7
|
module Phlex
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
autoload :Error, "phlex/error"
|
|
21
|
-
autoload :NameError, "phlex/errors/name_error"
|
|
22
|
-
autoload :RuntimeError, "phlex/errors/runtime_error"
|
|
23
|
-
autoload :ArgumentError, "phlex/errors/argument_error"
|
|
24
|
-
autoload :DoubleRenderError, "phlex/errors/double_render_error"
|
|
8
|
+
Loader = Zeitwerk::Loader.for_gem.tap do |loader|
|
|
9
|
+
loader.push_dir("lib/phlex/errors", namespace: Phlex)
|
|
10
|
+
loader.inflector.inflect(
|
|
11
|
+
"csv" => "CSV",
|
|
12
|
+
"fifo" => "FIFO",
|
|
13
|
+
"fifo_cache_store" => "FIFOCacheStore",
|
|
14
|
+
"html" => "HTML",
|
|
15
|
+
"sgml" => "SGML",
|
|
16
|
+
"svg" => "SVG",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
loader.setup
|
|
20
|
+
end
|
|
25
21
|
|
|
26
22
|
Escape = ERB::Escape
|
|
27
23
|
|
|
@@ -36,17 +32,6 @@ module Phlex
|
|
|
36
32
|
end
|
|
37
33
|
end
|
|
38
34
|
|
|
39
|
-
def self.eager_load
|
|
40
|
-
queue = [self]
|
|
41
|
-
|
|
42
|
-
while (mod = queue.shift)
|
|
43
|
-
mod.constants.each do |const_name|
|
|
44
|
-
const = mod.const_get(const_name)
|
|
45
|
-
queue << const if Module === const
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
35
|
# Generate an HTML string using Phlex’ HTML DSL
|
|
51
36
|
def self.html(&block)
|
|
52
37
|
HTML.call do |component|
|
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: phlex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joel Drapper
|
|
8
8
|
- Will Cosgrove
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
12
|
-
dependencies:
|
|
11
|
+
date: 2025-04-04 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: zeitwerk
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
13
27
|
description: Build HTML, SVG and CSV views with Ruby classes.
|
|
14
28
|
email:
|
|
15
29
|
- joel@drapper.me
|
|
@@ -40,7 +54,6 @@ files:
|
|
|
40
54
|
- lib/phlex/sgml/state.rb
|
|
41
55
|
- lib/phlex/svg.rb
|
|
42
56
|
- lib/phlex/svg/standard_elements.rb
|
|
43
|
-
- lib/phlex/vanish.rb
|
|
44
57
|
- lib/phlex/version.rb
|
|
45
58
|
homepage: https://www.phlex.fun
|
|
46
59
|
licenses:
|
|
@@ -65,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
65
78
|
- !ruby/object:Gem::Version
|
|
66
79
|
version: '0'
|
|
67
80
|
requirements: []
|
|
68
|
-
rubygems_version: 3.6.
|
|
81
|
+
rubygems_version: 3.6.6
|
|
69
82
|
specification_version: 4
|
|
70
83
|
summary: Object-oriented views in Ruby.
|
|
71
84
|
test_files: []
|
data/lib/phlex/vanish.rb
DELETED