phlex 2.1.3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58ef5e091c9feed2677149231356e32305898a8d2bb271a0e603bcc208ead0fd
4
- data.tar.gz: bab9a697db8f529f0221afb1fd491e30d2f4b0c702a253f7a0446f8a075ddc82
3
+ metadata.gz: d3eae91fcc5ec985b668a5e016c518f6392cd91d6d155a1f60552282755ca0e7
4
+ data.tar.gz: 4bac6d025488edbc104135dc3417286cc4782d4f022caff2a8a414afa12d09c3
5
5
  SHA512:
6
- metadata.gz: 1e92858e2c2a177a120579e7cbb4c7489456a8faa0a40ecbce9fe6070f7ef01ace1e4f13baf72a9f9baf841ed57ea9b74f9cf2aa262fc774eaa95178a9248b63
7
- data.tar.gz: 8040edc5a9640fb638280bccb4e879ab97d855b739f5eab04fc7ffcf7fcb7d7ca50a06ae808c31e67f9fdb7c286062c35ff40483abfaf42ac9637e2abe32c67e
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
- each_item do |record|
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(item)
117
- row_template(item)
125
+ def around_row(...)
126
+ row_template(...)
127
+ @row_appender.call
118
128
  end
119
129
 
120
130
  def filename
data/lib/phlex/helpers.rb CHANGED
@@ -26,6 +26,10 @@ module Phlex::Helpers
26
26
  [old] + new.to_a
27
27
  in [String, String]
28
28
  "#{old} #{new}"
29
+ in [_, Hash]
30
+ { _: old, **new }
31
+ in [Hash, _]
32
+ { **old, _: new }
29
33
  in [_, nil]
30
34
  old
31
35
  else
@@ -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
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Phlex::HTML < Phlex::SGML
4
- autoload :StandardElements, "phlex/html/standard_elements"
5
- autoload :VoidElements, "phlex/html/void_elements"
6
-
7
4
  extend Phlex::SGML::Elements
8
5
  include VoidElements, StandardElements
9
6
 
@@ -58,7 +55,7 @@ class Phlex::HTML < Phlex::SGML
58
55
  raise Phlex::ArgumentError.new("Expected the tag name to be a Symbol.")
59
56
  end
60
57
 
61
- if (tag = StandardElements.__registered_elements__[name]) || ((tag = name.name.tr("_", "-")).include?("-") && tag.match?(/\A[a-z0-9-]+\z/))
58
+ if (tag = StandardElements.__registered_elements__[name]) || (tag = name.name.tr("_", "-")).include?("-")
62
59
  if attributes.length > 0 # with attributes
63
60
  if block_given # with content block
64
61
  buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
data/lib/phlex/sgml.rb CHANGED
@@ -3,13 +3,7 @@
3
3
  # **Standard Generalized Markup Language** for behaviour common to {HTML} and {SVG}.
4
4
  class Phlex::SGML
5
5
  UNSAFE_ATTRIBUTES = Set.new(%w[srcdoc sandbox http-equiv]).freeze
6
- REF_ATTRIBUTES = Set.new(%w[href src action formaction lowsrc dynsrc background ping xlinkhref]).freeze
7
- NAMED_CHARACTER_REFERENCES = {
8
- "colon" => ":",
9
- "tab" => "\t",
10
- "newline" => "\n",
11
- }.freeze
12
- UNSAFE_ATTRIBUTE_NAME_CHARS = %r([<>&"'/=\s\x00])
6
+ REF_ATTRIBUTES = Set.new(%w[href src action formaction lowsrc dynsrc background ping]).freeze
13
7
 
14
8
  ERBCompiler = ERB::Compiler.new("<>").tap do |compiler|
15
9
  compiler.pre_cmd = [""]
@@ -22,11 +16,6 @@ class Phlex::SGML
22
16
  end
23
17
  end
24
18
 
25
- autoload :Elements, "phlex/sgml/elements"
26
- autoload :SafeObject, "phlex/sgml/safe_object"
27
- autoload :SafeValue, "phlex/sgml/safe_value"
28
- autoload :State, "phlex/sgml/state"
29
-
30
19
  include Phlex::Helpers
31
20
 
32
21
  class << self
@@ -529,9 +518,7 @@ class Phlex::SGML
529
518
  if value != true && REF_ATTRIBUTES.include?(normalized_name)
530
519
  case value
531
520
  when String
532
- decoded_value = decode_html_character_references(value)
533
-
534
- if decoded_value.downcase.delete("^a-z:").start_with?("javascript:")
521
+ if value.downcase.delete("^a-z:").start_with?("javascript:")
535
522
  # We just ignore these because they were likely not specified by the developer.
536
523
  next
537
524
  end
@@ -549,7 +536,7 @@ class Phlex::SGML
549
536
  end
550
537
  end
551
538
 
552
- if name.match?(UNSAFE_ATTRIBUTE_NAME_CHARS)
539
+ if name.match?(/[<>&"']/)
553
540
  raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
554
541
  end
555
542
 
@@ -574,14 +561,20 @@ class Phlex::SGML
574
561
  attributes.each do |k, v|
575
562
  next unless v
576
563
 
577
- name = case k
578
- when String then k
579
- when Symbol then k.name.tr("_", "-")
580
- else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols")
581
- end
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
582
574
 
583
- if name.match?(UNSAFE_ATTRIBUTE_NAME_CHARS)
584
- raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
575
+ if name.match?(/[<>&"']/)
576
+ raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
577
+ end
585
578
  end
586
579
 
587
580
  case v
@@ -605,6 +598,10 @@ class Phlex::SGML
605
598
  raise Phlex::ArgumentError.new("Invalid attribute value #{v.inspect}.")
606
599
  end
607
600
 
601
+ if root_key
602
+ base_name = original_base_name
603
+ end
604
+
608
605
  buffer
609
606
  end
610
607
  end
@@ -656,27 +653,6 @@ class Phlex::SGML
656
653
  buffer.gsub('"', "&quot;")
657
654
  end
658
655
 
659
- private def decode_html_character_references(value)
660
- value
661
- .gsub(/&#x([0-9a-f]+);?/i) {
662
- begin
663
- [$1.to_i(16)].pack("U*")
664
- rescue
665
- ""
666
- end
667
- }
668
- .gsub(/&#(\d+);?/) {
669
- begin
670
- [$1.to_i].pack("U*")
671
- rescue
672
- ""
673
- end
674
- }
675
- .gsub(/&([a-z][a-z0-9]+);?/i) {
676
- NAMED_CHARACTER_REFERENCES[$1.downcase] || ""
677
- }
678
- end
679
-
680
656
  # Result is **unsafe**, so it should be escaped!
681
657
  def __styles__(styles)
682
658
  case styles
data/lib/phlex/svg.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Phlex::SVG < Phlex::SGML
4
- autoload :StandardElements, "phlex/svg/standard_elements"
5
-
6
4
  include StandardElements
7
5
 
8
6
  # Returns the string "image/svg+xml"
@@ -43,7 +41,7 @@ class Phlex::SVG < Phlex::SGML
43
41
  raise Phlex::ArgumentError.new("Expected the tag name to be a Symbol.")
44
42
  end
45
43
 
46
- if (tag = StandardElements.__registered_elements__[name]) || ((tag = name.name.tr("_", "-")).include?("-") && tag.match?(/\A[a-z0-9-]+\z/))
44
+ if (tag = StandardElements.__registered_elements__[name]) || (tag = name.name.tr("_", "-")).include?("-")
47
45
  if attributes.length > 0 # with attributes
48
46
  if block_given # with content block
49
47
  buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
data/lib/phlex/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- VERSION = "2.1.3"
4
+ VERSION = "2.2.0"
5
5
  end
data/lib/phlex.rb CHANGED
@@ -2,25 +2,22 @@
2
2
 
3
3
  require "erb"
4
4
  require "set"
5
+ require "zeitwerk"
5
6
 
6
7
  module Phlex
7
- autoload :VERSION, "phlex/version"
8
-
9
- autoload :Kit, "phlex/kit"
10
- autoload :FIFO, "phlex/fifo"
11
- autoload :Helpers, "phlex/helpers"
12
- autoload :FIFOCacheStore, "phlex/fifo_cache_store"
13
-
14
- autoload :CSV, "phlex/csv"
15
- autoload :SVG, "phlex/svg"
16
- autoload :HTML, "phlex/html"
17
- autoload :SGML, "phlex/sgml"
18
-
19
- autoload :Error, "phlex/error"
20
- autoload :NameError, "phlex/errors/name_error"
21
- autoload :RuntimeError, "phlex/errors/runtime_error"
22
- autoload :ArgumentError, "phlex/errors/argument_error"
23
- 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
24
21
 
25
22
  Escape = ERB::Escape
26
23
 
@@ -35,17 +32,6 @@ module Phlex
35
32
  end
36
33
  end
37
34
 
38
- def self.eager_load
39
- queue = [self]
40
-
41
- while (mod = queue.shift)
42
- mod.constants.each do |const_name|
43
- const = mod.const_get(const_name)
44
- queue << const if Module === const
45
- end
46
- end
47
- end
48
-
49
35
  # Generate an HTML string using Phlex’ HTML DSL
50
36
  def self.html(&block)
51
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.1.3
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: 2026-02-06 00:00:00.000000000 Z
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