loading
Generated 2025-08-15T16:43:24+00:00

All Files ( 89.28% covered at 97.78 hits/line )

36 files in total.
1436 relevant lines, 1282 lines covered and 154 lines missed. ( 89.28% )
3549 total branches, 1217 branches covered and 2332 branches missed. ( 34.29% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex.rb 80.56 % 74 36 29 7 23.25 50.00 % 6 3 3
lib/phlex/compiler.rb 96.30 % 57 27 26 1 6.96 50.00 % 2 1 1
lib/phlex/compiler/class_compiler.rb 84.00 % 47 25 21 4 7.12 60.00 % 10 6 4
lib/phlex/compiler/compactor.rb 88.00 % 74 25 22 3 152.48 80.00 % 10 8 2
lib/phlex/compiler/file_compiler.rb 88.89 % 52 27 24 3 5.89 50.00 % 2 1 1
lib/phlex/compiler/method_compiler.rb 88.72 % 579 195 173 22 20.67 76.47 % 85 65 20
lib/phlex/csv.rb 95.35 % 265 129 123 6 31.64 94.12 % 51 48 3
lib/phlex/error.rb 100.00 % 5 1 1 0 1.00 100.00 % 0 0 0
lib/phlex/errors/argument_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/double_render_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/name_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/runtime_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/fifo.rb 90.00 % 55 30 27 3 203.27 100.00 % 7 7 0
lib/phlex/fifo_cache_store.rb 69.57 % 49 23 16 7 31.43 40.00 % 10 4 6
lib/phlex/helpers.rb 95.83 % 51 24 23 1 6.25 94.12 % 17 16 1
lib/phlex/html.rb 93.88 % 100 49 46 3 97.37 89.29 % 28 25 3
lib/phlex/html/standard_elements.rb 100.00 % 633 105 105 0 1.00 100.00 % 0 0 0
lib/phlex/html/void_elements.rb 100.00 % 66 14 14 0 1.00 100.00 % 0 0 0
lib/phlex/kit.rb 85.00 % 83 40 34 6 2.50 66.67 % 15 10 5
lib/phlex/sgml.rb 83.69 % 498 233 195 38 218.40 70.71 % 99 70 29
lib/phlex/sgml/attributes.rb 97.06 % 280 136 132 4 44.30 94.87 % 117 111 6
lib/phlex/sgml/elements.rb 94.29 % 171 70 66 4 474.40 26.26 % 3016 792 2224
lib/phlex/sgml/safe_object.rb 100.00 % 7 1 1 0 1.00 100.00 % 0 0 0
lib/phlex/sgml/safe_value.rb 100.00 % 11 5 5 0 3.40 100.00 % 0 0 0
lib/phlex/sgml/state.rb 100.00 % 123 74 74 0 302.00 95.45 % 22 21 1
lib/phlex/svg.rb 83.33 % 66 36 30 6 71.56 65.00 % 20 13 7
lib/phlex/svg/standard_elements.rb 100.00 % 421 66 66 0 1.00 100.00 % 0 0 0
quickdraw/compilation_equivalence_cases/attributes.rb 66.67 % 22 3 2 1 0.67 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/basic.rb 40.00 % 9 5 2 3 0.40 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/comment.rb 50.00 % 8 4 2 2 0.50 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/heredoc.rb 57.14 % 16 7 4 3 0.57 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/interpolated_string.rb 28.57 % 11 7 2 5 0.29 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/override_elements.rb 33.33 % 16 9 3 6 0.33 50.00 % 8 4 4
quickdraw/compilation_equivalence_cases/plain.rb 33.33 % 10 6 2 4 0.33 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/raw.rb 40.00 % 9 5 2 3 0.40 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/whitespace.rb 18.18 % 16 11 2 9 0.18 50.00 % 2 1 1

SGML ( 91.14% covered at 216.74 hits/line )

6 files in total.
519 relevant lines, 473 lines covered and 46 lines missed. ( 91.14% )
3254 total branches, 994 branches covered and 2260 branches missed. ( 30.55% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex/sgml.rb 83.69 % 498 233 195 38 218.40 70.71 % 99 70 29
lib/phlex/sgml/attributes.rb 97.06 % 280 136 132 4 44.30 94.87 % 117 111 6
lib/phlex/sgml/elements.rb 94.29 % 171 70 66 4 474.40 26.26 % 3016 792 2224
lib/phlex/sgml/safe_object.rb 100.00 % 7 1 1 0 1.00 100.00 % 0 0 0
lib/phlex/sgml/safe_value.rb 100.00 % 11 5 5 0 3.40 100.00 % 0 0 0
lib/phlex/sgml/state.rb 100.00 % 123 74 74 0 302.00 95.45 % 22 21 1

HTML ( 98.21% covered at 29.11 hits/line )

3 files in total.
168 relevant lines, 165 lines covered and 3 lines missed. ( 98.21% )
28 total branches, 25 branches covered and 3 branches missed. ( 89.29% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex/html.rb 93.88 % 100 49 46 3 97.37 89.29 % 28 25 3
lib/phlex/html/standard_elements.rb 100.00 % 633 105 105 0 1.00 100.00 % 0 0 0
lib/phlex/html/void_elements.rb 100.00 % 66 14 14 0 1.00 100.00 % 0 0 0

SVG ( 94.12% covered at 25.9 hits/line )

2 files in total.
102 relevant lines, 96 lines covered and 6 lines missed. ( 94.12% )
20 total branches, 13 branches covered and 7 branches missed. ( 65.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex/svg.rb 83.33 % 66 36 30 6 71.56 65.00 % 20 13 7
lib/phlex/svg/standard_elements.rb 100.00 % 421 66 66 0 1.00 100.00 % 0 0 0

CSV ( 95.35% covered at 31.64 hits/line )

1 files in total.
129 relevant lines, 123 lines covered and 6 lines missed. ( 95.35% )
51 total branches, 48 branches covered and 3 branches missed. ( 94.12% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex/csv.rb 95.35 % 265 129 123 6 31.64 94.12 % 51 48 3

Ungrouped ( 82.05% covered at 31.48 hits/line )

24 files in total.
518 relevant lines, 425 lines covered and 93 lines missed. ( 82.05% )
196 total branches, 137 branches covered and 59 branches missed. ( 69.9% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex.rb 80.56 % 74 36 29 7 23.25 50.00 % 6 3 3
lib/phlex/compiler.rb 96.30 % 57 27 26 1 6.96 50.00 % 2 1 1
lib/phlex/compiler/class_compiler.rb 84.00 % 47 25 21 4 7.12 60.00 % 10 6 4
lib/phlex/compiler/compactor.rb 88.00 % 74 25 22 3 152.48 80.00 % 10 8 2
lib/phlex/compiler/file_compiler.rb 88.89 % 52 27 24 3 5.89 50.00 % 2 1 1
lib/phlex/compiler/method_compiler.rb 88.72 % 579 195 173 22 20.67 76.47 % 85 65 20
lib/phlex/error.rb 100.00 % 5 1 1 0 1.00 100.00 % 0 0 0
lib/phlex/errors/argument_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/double_render_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/name_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/runtime_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/fifo.rb 90.00 % 55 30 27 3 203.27 100.00 % 7 7 0
lib/phlex/fifo_cache_store.rb 69.57 % 49 23 16 7 31.43 40.00 % 10 4 6
lib/phlex/helpers.rb 95.83 % 51 24 23 1 6.25 94.12 % 17 16 1
lib/phlex/kit.rb 85.00 % 83 40 34 6 2.50 66.67 % 15 10 5
quickdraw/compilation_equivalence_cases/attributes.rb 66.67 % 22 3 2 1 0.67 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/basic.rb 40.00 % 9 5 2 3 0.40 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/comment.rb 50.00 % 8 4 2 2 0.50 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/heredoc.rb 57.14 % 16 7 4 3 0.57 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/interpolated_string.rb 28.57 % 11 7 2 5 0.29 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/override_elements.rb 33.33 % 16 9 3 6 0.33 50.00 % 8 4 4
quickdraw/compilation_equivalence_cases/plain.rb 33.33 % 10 6 2 4 0.33 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/raw.rb 40.00 % 9 5 2 3 0.40 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/whitespace.rb 18.18 % 16 11 2 9 0.18 50.00 % 2 1 1

lib/phlex.rb

80.56% lines covered

50.0% branches covered

36 relevant lines. 29 lines covered and 7 lines missed.
6 total branches, 3 branches covered and 3 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "erb"
  3. 1 require "set"
  4. 1 require "zeitwerk"
  5. 1 module Phlex
  6. 1 Loader = Zeitwerk::Loader.for_gem.tap do |loader|
  7. 1 loader.ignore("#{__dir__}/ruby_lsp")
  8. 1 loader.inflector.inflect(
  9. "csv" => "CSV",
  10. "fifo" => "FIFO",
  11. "fifo_cache_store" => "FIFOCacheStore",
  12. "html" => "HTML",
  13. "sgml" => "SGML",
  14. "svg" => "SVG",
  15. )
  16. 1 loader.collapse("#{__dir__}/phlex/errors")
  17. 1 loader.setup
  18. end
  19. 1 Escape = ERB::Escape
  20. 1 DEPLOYED_AT = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
  21. 1 CACHED_FILES = Set.new
  22. 1 ATTRIBUTE_CACHE = FIFO.new
  23. 1 UNBOUND_INSTANCE_METHOD_METHOD = Module.instance_method(:instance_method)
  24. 1 def self.__expand_attribute_cache__(file_path)
  25. 703 else: 663 then: 40 unless CACHED_FILES.include?(file_path)
  26. 40 CACHED_FILES << file_path
  27. 40 Phlex::ATTRIBUTE_CACHE.expand(File.size(file_path))
  28. end
  29. end
  30. # Generate an HTML string using Phlex’ HTML DSL
  31. 1 def self.html(&block)
  32. 2 HTML.call do |component|
  33. 2 receiver = block.binding.receiver
  34. 2 receiver.instance_variables.each do |ivar|
  35. 9 then: 0 else: 9 next if component.instance_variable_defined?(ivar)
  36. 9 value = receiver.instance_variable_get(ivar)
  37. 9 component.instance_variable_set(ivar, value)
  38. end
  39. 2 component.instance_exec(receiver, &block)
  40. end
  41. end
  42. # Generate an SVG string using Phlex’ SVG DSL
  43. 1 def self.svg(&block)
  44. SVG.call do |component|
  45. receiver = block.binding.receiver
  46. receiver.instance_variables.each do |ivar|
  47. then: 0 else: 0 next if component.instance_variable_defined?(ivar)
  48. value = receiver.instance_variable_get(ivar)
  49. component.instance_variable_set(ivar, value)
  50. end
  51. component.instance_exec(receiver, &block)
  52. end
  53. end
  54. end
  55. 1 def 💪
  56. 1 Phlex
  57. end

lib/phlex/compiler.rb

96.3% lines covered

50.0% branches covered

27 relevant lines. 26 lines covered and 1 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "prism"
  3. 1 require "refract"
  4. 1 module Phlex::Compiler
  5. 1 MAP = {}
  6. 1 Error = Class.new(StandardError)
  7. 1 Concat = Data.define(:node) do
  8. 1 def start_line = nil
  9. 1 def accept(visitor) = self
  10. end
  11. 1 def self.compile(component)
  12. 11 path, line = Object.const_source_location(component.name)
  13. 11 compile_file(path)
  14. end
  15. 1 def self.compile_file(path)
  16. 11 else: 11 then: 0 unless File.exist?(path)
  17. raise ArgumentError, "Can’t compile #{path} because it doesn’t exist."
  18. end
  19. 11 require(path)
  20. 11 source = File.read(path)
  21. 11 tree = Prism.parse(source).value
  22. 11 refract = Refract::Converter.new.visit(tree)
  23. 11 last_line = source.count("\n")
  24. 11 starting_line = last_line + 1
  25. 11 results = FileCompiler.new(path).compile(refract)
  26. 11 result = Refract::StatementsNode.new(
  27. body: results.map do |result|
  28. 11 result.namespace.reverse_each.reduce(
  29. result.compiled_snippets
  30. ) do |body, scope|
  31. 13 scope.copy(
  32. body: Refract::StatementsNode.new(
  33. body: [body]
  34. )
  35. )
  36. end
  37. end
  38. )
  39. 11 formatting_result = Refract::Formatter.new(starting_line:).format_node(result)
  40. 11 MAP[path] = formatting_result.source_map
  41. 11 eval("# frozen_string_literal: true\n#{formatting_result.source}", TOPLEVEL_BINDING, path, starting_line - 1)
  42. end
  43. end

lib/phlex/compiler/class_compiler.rb

84.0% lines covered

60.0% branches covered

25 relevant lines. 21 lines covered and 4 lines missed.
10 total branches, 6 branches covered and 4 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::Compiler::ClassCompiler < Refract::Visitor
  3. 1 def initialize(component, path)
  4. 11 super()
  5. 11 @component = component
  6. 11 @path = path
  7. 11 @compiled_snippets = []
  8. end
  9. 1 def compile(node)
  10. 11 visit(node.body)
  11. 11 @compiled_snippets.compact.freeze
  12. end
  13. 1 visit Refract::DefNode do |node|
  14. 14 then: 1 else: 13 return if node.name == :initialize
  15. 13 then: 0 else: 13 return if node.receiver
  16. method = begin
  17. 13 Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name)
  18. rescue NameError
  19. nil
  20. end
  21. 13 else: 13 then: 0 return unless method
  22. 13 path, lineno = method.source_location
  23. 13 else: 13 then: 0 return unless @path == path
  24. 13 else: 13 then: 0 return unless node.start_line == lineno
  25. 13 @compiled_snippets << Phlex::Compiler::MethodCompiler.new(
  26. @component
  27. ).compile(node)
  28. end
  29. 1 visit Refract::ClassNode do |node|
  30. nil
  31. end
  32. 1 visit Refract::ModuleNode do |node|
  33. nil
  34. end
  35. 1 visit Refract::BlockNode do |node|
  36. nil
  37. end
  38. end

lib/phlex/compiler/compactor.rb

88.0% lines covered

80.0% branches covered

25 relevant lines. 22 lines covered and 3 lines missed.
10 total branches, 8 branches covered and 2 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::Compiler::Compactor < Refract::MutationVisitor
  3. 1 visit Refract::StatementsNode do |node|
  4. 63 queue = []
  5. 63 results = []
  6. 63 current_buffer = nil
  7. 63 nil_context = false
  8. 160 node.body.reverse_each { |n| queue << n }
  9. 729 body: 603 while (child_node = queue.pop)
  10. 603 case child_node
  11. when: 288 when Refract::StatementsNode
  12. 794 child_node.body.reverse_each { |n| queue << n }
  13. when: 193 when Phlex::Compiler::Concat
  14. 193 then: 173 if current_buffer
  15. 173 current_buffer << child_node.node
  16. 173 else: 173 then: 0 unless nil_context
  17. results << Refract::NilNode.new
  18. nil_context = true
  19. end
  20. else: 20 else
  21. 20 current_buffer = [child_node.node]
  22. 20 results << Refract::ParenthesesNode.new(
  23. body: Refract::StatementsNode.new(
  24. body: [
  25. Refract::IfNode.new(
  26. inline: false,
  27. predicate: Refract::LocalVariableReadNode.new(
  28. name: :__phlex_should_render__
  29. ),
  30. statements: Refract::StatementsNode.new(
  31. body: [
  32. Refract::CallNode.new(
  33. receiver: Refract::CallNode.new(
  34. name: :__phlex_buffer__,
  35. ),
  36. name: :<<,
  37. arguments: Refract::ArgumentsNode.new(
  38. arguments: [
  39. Refract::InterpolatedStringNode.new(
  40. parts: current_buffer
  41. ),
  42. ]
  43. )
  44. ),
  45. ]
  46. )
  47. ),
  48. Refract::NilNode.new,
  49. ]
  50. )
  51. )
  52. 20 nil_context = true
  53. end
  54. else: 122 else
  55. 122 resolved = visit(child_node)
  56. 122 case resolved
  57. when: 0 when Refract::StatementsNode
  58. resolved.body.reverse_each { |n| queue << n }
  59. else: 122 else
  60. 122 current_buffer = nil
  61. 122 results << resolved
  62. 122 nil_context = false
  63. end
  64. end
  65. end
  66. 63 node.copy(
  67. body: results
  68. )
  69. end
  70. end

lib/phlex/compiler/file_compiler.rb

88.89% lines covered

50.0% branches covered

27 relevant lines. 24 lines covered and 3 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::Compiler::FileCompiler < Refract::Visitor
  3. 1 Result = Data.define(:namespace, :compiled_snippets)
  4. 1 def initialize(path)
  5. 11 super()
  6. 11 @path = path
  7. 11 @current_namespace = []
  8. 11 @results = []
  9. end
  10. 1 def compile(node)
  11. 11 visit(node)
  12. 11 @results.freeze
  13. end
  14. 1 visit Refract::ModuleNode do |node|
  15. 2 @current_namespace.push(node)
  16. 2 super(node)
  17. 2 @current_namespace.pop
  18. end
  19. 1 visit Refract::ClassNode do |node|
  20. 11 @current_namespace.push(node)
  21. 11 namespace = @current_namespace.map do |node|
  22. 13 Refract::Formatter.new.format_node(node.constant_path).source
  23. end.join("::")
  24. 11 const = eval(namespace, TOPLEVEL_BINDING)
  25. 11 then: 11 if Class === const && Phlex::SGML > const
  26. 11 @results << Result.new(
  27. namespace: @current_namespace.dup.freeze,
  28. compiled_snippets: Phlex::Compiler::ClassCompiler.new(const, @path).compile(node)
  29. )
  30. else: 0 else
  31. super(node)
  32. end
  33. 11 @current_namespace.pop
  34. end
  35. 1 visit Refract::DefNode do |node|
  36. nil
  37. end
  38. 1 visit Refract::BlockNode do |node|
  39. nil
  40. end
  41. end

lib/phlex/compiler/method_compiler.rb

88.72% lines covered

76.47% branches covered

195 relevant lines. 173 lines covered and 22 lines missed.
85 total branches, 65 branches covered and 20 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "prism"
  3. 1 module Phlex::Compiler
  4. 1 class MethodCompiler < Refract::MutationVisitor
  5. 1 def initialize(component)
  6. 14 super()
  7. 14 @component = component
  8. 14 @preamble = []
  9. 14 @optimized = false
  10. end
  11. 1 def compile(node)
  12. 14 tree = visit(node)
  13. 13 then: 12 if @optimized
  14. 12 Compactor.new.visit(
  15. tree
  16. )
  17. else: 1 else
  18. 1 nil
  19. end
  20. end
  21. 1 visit Refract::ClassNode do |node|
  22. node
  23. end
  24. 1 visit Refract::ModuleNode do |node|
  25. node
  26. end
  27. 1 visit Refract::DefNode do |node|
  28. 14 then: 14 if @stack.size == 1
  29. 14 node.copy(
  30. body: Refract::BeginNode.new(
  31. statements: Refract::StatementsNode.new(
  32. body: [
  33. Refract::StatementsNode.new(
  34. body: @preamble
  35. ),
  36. Refract::NilNode.new,
  37. visit(node.body),
  38. ]
  39. ),
  40. rescue_clause: Refract::RescueNode.new(
  41. exceptions: [],
  42. reference: Refract::LocalVariableTargetNode.new(
  43. name: :__phlex_exception__
  44. ),
  45. statements: Refract::StatementsNode.new(
  46. body: [
  47. Refract::CallNode.new(
  48. receiver: Refract::ConstantReadNode.new(
  49. name: :Kernel
  50. ),
  51. name: :raise,
  52. arguments: Refract::ArgumentsNode.new(
  53. arguments: [
  54. Refract::CallNode.new(
  55. name: :__map_exception__,
  56. arguments: Refract::ArgumentsNode.new(
  57. arguments: [
  58. Refract::LocalVariableReadNode.new(
  59. name: :__phlex_exception__
  60. ),
  61. ]
  62. )
  63. ),
  64. ]
  65. )
  66. ),
  67. ]
  68. ),
  69. subsequent: nil
  70. ),
  71. else_clause: nil,
  72. ensure_clause: nil
  73. )
  74. )
  75. else: 0 else
  76. node
  77. end
  78. end
  79. 1 visit Refract::CallNode do |node|
  80. 63 then: 62 else: 1 if nil == node.receiver
  81. 62 then: 41 if (tag = standard_element?(node))
  82. 41 else: 21 return compile_standard_element(node, tag)
  83. 21 then: 6 elsif (tag = void_element?(node))
  84. 6 else: 15 return compile_void_element(node, tag)
  85. 15 then: 2 elsif whitespace_helper?(node)
  86. 2 else: 13 return compile_whitespace_helper(node)
  87. 13 then: 0 elsif doctype_helper?(node)
  88. else: 13 return compile_doctype_helper(node)
  89. 13 then: 6 elsif plain_helper?(node)
  90. 6 else: 7 return compile_plain_helper(node)
  91. 7 then: 0 elsif fragment_helper?(node)
  92. else: 7 return compile_fragment_helper(node)
  93. 7 then: 2 elsif comment_helper?(node)
  94. 2 else: 5 return compile_comment_helper(node)
  95. 5 then: 1 else: 4 elsif raw_helper?(node)
  96. 1 return compile_raw_helper(node)
  97. end
  98. end
  99. 5 super(node)
  100. end
  101. 1 visit Refract::BlockNode do |node|
  102. 3 then: 3 if node.body
  103. 3 node.copy(
  104. body: compile_block_body_node(
  105. node.body
  106. )
  107. )
  108. else: 0 else
  109. node
  110. end
  111. end
  112. 1 def compile_standard_element(node, tag)
  113. 41 node => Refract::CallNode
  114. 40 Refract::StatementsNode.new(
  115. body: [
  116. raw("<#{tag}"),
  117. *(
  118. 40 then: 16 else: 24 if node.arguments
  119. 16 compile_phlex_attributes(node.arguments)
  120. end
  121. ),
  122. raw(">"),
  123. *(
  124. 40 then: 38 else: 2 if node.block
  125. 38 compile_phlex_block(node.block)
  126. end
  127. ),
  128. raw("</#{tag}>"),
  129. ]
  130. )
  131. end
  132. 1 def compile_void_element(node, tag)
  133. 6 node => Refract::CallNode
  134. 6 Refract::StatementsNode.new(
  135. body: [
  136. raw("<#{tag}"),
  137. *(
  138. 6 then: 3 else: 3 if node.arguments
  139. 3 compile_phlex_attributes(node.arguments)
  140. end
  141. ),
  142. raw(">"),
  143. ]
  144. )
  145. end
  146. 1 def compile_phlex_attributes(node)
  147. 19 arguments = node.arguments
  148. 19 then: 19 else: 0 if arguments.size == 1 && Refract::KeywordHashNode === (first_argument = arguments[0])
  149. 19 attributes = first_argument.elements
  150. 19 literal_attributes = attributes.all? do |attribute|
  151. 35 Refract::AssocNode === attribute && static_attribute_value_literal?(attribute)
  152. end
  153. 19 then: 18 else: 1 if literal_attributes
  154. 18 return raw(
  155. Phlex::SGML::Attributes.generate_attributes(
  156. eval(
  157. "{#{Refract::Formatter.new.format_node(node).source}}",
  158. TOPLEVEL_BINDING
  159. )
  160. )
  161. )
  162. end
  163. 1 Refract::CallNode.new(
  164. name: :__render_attributes__,
  165. arguments: Refract::ArgumentsNode.new(
  166. arguments: [
  167. node,
  168. ]
  169. )
  170. )
  171. end
  172. end
  173. 1 def compile_phlex_block(node)
  174. 41 else: 1 case node
  175. when: 40 when Refract::BlockNode
  176. 40 then: 17 if output_block?(node)
  177. 17 else: 23 return visit(node.body)
  178. 23 then: 21 else: 2 elsif static_content_block?(node)
  179. 21 content = node.body.body.first
  180. 21 case content
  181. when: 16 when Refract::StringNode, Refract::SymbolNode
  182. 16 return plain(content.unescaped)
  183. when: 5 when Refract::InterpolatedStringNode
  184. 5 return compile_interpolated_string_node(content)
  185. when: 0 when Refract::NilNode
  186. return nil
  187. else: 0 else
  188. raise
  189. end
  190. end
  191. end
  192. 3 Refract::CallNode.new(
  193. name: :__yield_content__,
  194. block: node
  195. )
  196. end
  197. 1 def compile_block_body_node(node)
  198. 3 node => Refract::StatementsNode | Refract::BeginNode
  199. 3 Refract::StatementsNode.new(
  200. body: [
  201. Refract::IfNode.new(
  202. inline: false,
  203. predicate: Refract::CallNode.new(
  204. receiver: Refract::SelfNode.new,
  205. name: :==,
  206. arguments: Refract::ArgumentsNode.new(
  207. arguments: [
  208. Refract::LocalVariableReadNode.new(
  209. name: self_local
  210. ),
  211. ]
  212. )
  213. ),
  214. statements: Refract::StatementsNode.new(
  215. 4 body: node.body.map { |n| visit(n) }
  216. ),
  217. subsequent: Refract::ElseNode.new(
  218. statements: node
  219. )
  220. ),
  221. ]
  222. )
  223. end
  224. 1 def compile_interpolated_string_node(node)
  225. 5 node => Refract::InterpolatedStringNode
  226. 5 Refract::StatementsNode.new(
  227. body: node.parts.map do |part|
  228. 15 case part
  229. when: 10 when Refract::StringNode
  230. 10 plain(part.unescaped)
  231. when: 1 when Refract::EmbeddedVariableNode
  232. 1 interpolate(part.variable)
  233. when: 4 when Refract::EmbeddedStatementsNode
  234. 4 interpolate(part.statements)
  235. else: 0 else
  236. raise Phlex::Compiler::Error, "Unexpected node type in InterpolatedStringNode: #{part.class}"
  237. end
  238. end
  239. )
  240. end
  241. 1 def compile_whitespace_helper(node)
  242. 2 node => Refract::CallNode
  243. 2 then: 1 if node.block
  244. 1 Refract::StatementsNode.new(
  245. body: [
  246. raw(" "),
  247. compile_phlex_block(node.block),
  248. raw(" "),
  249. ]
  250. )
  251. else: 1 else
  252. 1 raw(" ")
  253. end
  254. end
  255. 1 def compile_doctype_helper(node)
  256. node => Refract::CallNode
  257. raw("<!doctype html>")
  258. end
  259. 1 def compile_plain_helper(node)
  260. 6 node => Refract::CallNode
  261. 6 then: 5 if node.arguments.arguments in [Refract::StringNode]
  262. 5 raw(node.arguments.arguments.first.unescaped)
  263. else: 1 else
  264. 1 node
  265. end
  266. end
  267. 1 def compile_fragment_helper(node)
  268. node => Refract::CallNode
  269. node.copy(
  270. block: compile_fragment_helper_block(node.block)
  271. )
  272. end
  273. 1 def compile_fragment_helper_block(node)
  274. node => Refract::BlockNode
  275. node.copy(
  276. body: Refract::StatementsNode.new(
  277. body: [
  278. Refract::LocalVariableWriteNode.new(
  279. name: :__phlex_original_should_render__,
  280. value: Refract::LocalVariableReadNode.new(
  281. name: should_render_local
  282. )
  283. ),
  284. Refract::LocalVariableWriteNode.new(
  285. name: should_render_local,
  286. value: Refract::CallNode.new(
  287. receiver: Refract::LocalVariableReadNode.new(
  288. name: state_local
  289. ),
  290. name: :should_render?
  291. )
  292. ),
  293. visit(node.body),
  294. Refract::LocalVariableWriteNode.new(
  295. name: should_render_local,
  296. value: Refract::LocalVariableReadNode.new(
  297. name: :__phlex_original_should_render__
  298. )
  299. ),
  300. ]
  301. )
  302. )
  303. end
  304. 1 def compile_comment_helper(node)
  305. 2 node => Refract::CallNode
  306. 2 Refract::StatementsNode.new(
  307. body: [
  308. raw("<!-- "),
  309. compile_phlex_block(node.block),
  310. raw(" -->"),
  311. ]
  312. )
  313. end
  314. 1 def compile_raw_helper(node)
  315. 1 node => Refract::CallNode
  316. 1 node
  317. end
  318. 1 private def plain(value)
  319. 26 value => String
  320. 26 raw(Phlex::Escape.html_escape(value))
  321. end
  322. 1 private def raw(value)
  323. 188 value => String
  324. 188 buffer(
  325. Refract::StringNode.new(
  326. unescaped: value
  327. )
  328. )
  329. end
  330. 1 private def interpolate(statements)
  331. 5 buffer(
  332. Refract::EmbeddedStatementsNode.new(
  333. statements: Refract::StatementsNode.new(
  334. body: [
  335. Refract::CallNode.new(
  336. receiver: Refract::ConstantPathNode.new(
  337. parent: Refract::ConstantPathNode.new(
  338. name: "Phlex"
  339. ),
  340. name: "Escape"
  341. ),
  342. name: :html_escape,
  343. arguments: Refract::ArgumentsNode.new(
  344. arguments: [
  345. Refract::CallNode.new(
  346. receiver: Refract::ParenthesesNode.new(
  347. body: statements
  348. ),
  349. name: :to_s
  350. ),
  351. ]
  352. )
  353. ),
  354. ]
  355. )
  356. )
  357. )
  358. end
  359. 1 private def buffer(node)
  360. 193 @optimized = true
  361. 193 node => Refract::StringNode | Refract::EmbeddedStatementsNode
  362. 193 should_render_local
  363. 193 buffer_local
  364. 193 Refract::StatementsNode.new(
  365. body: [
  366. Concat.new(
  367. node
  368. ),
  369. ]
  370. )
  371. end
  372. 1 private def output_block?(node)
  373. 40 then: 40 else: 0 then: 40 else: 0 node.body&.body&.any? do |child|
  374. 40 Refract::CallNode === child && (
  375. 18 standard_element?(child) ||
  376. void_element?(child) ||
  377. plain_helper?(child) ||
  378. whitespace_helper?(child) ||
  379. raw_helper?(child)
  380. )
  381. end
  382. end
  383. 1 private def static_content_block?(node)
  384. 23 then: 23 else: 0 then: 23 else: 0 else: 23 then: 0 return false unless node.body&.body&.length == 1
  385. 23 node.body.body.first in Refract::StringNode | Refract::InterpolatedStringNode | Refract::SymbolNode | Refract::NilNode
  386. end
  387. 1 private def standard_element?(node)
  388. 80 if (tag = Phlex::HTML::StandardElements.__registered_elements__[node.name]) &&
  389. 57 (Phlex::HTML::StandardElements == Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name).owner)
  390. then: 57
  391. 57 tag
  392. else: 23 else
  393. 23 false
  394. end
  395. end
  396. 1 private def void_element?(node)
  397. 23 if (tag = Phlex::HTML::VoidElements.__registered_elements__[node.name]) &&
  398. 9 (Phlex::HTML::VoidElements == Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name).owner)
  399. then: 6
  400. 6 tag
  401. else: 17 else
  402. 17 false
  403. end
  404. end
  405. 1 private def static_attribute_value_literal?(value)
  406. 70 case value
  407. when: 34 when Refract::SymbolNode, Refract::StringNode, Refract::IntegerNode, Refract::FloatNode, Refract::TrueNode, Refract::FalseNode, Refract::NilNode
  408. 34 true
  409. when: 1 when Refract::ArrayNode
  410. 2 value.elements.all? { |n| static_token_value_literal?(n) }
  411. when: 0 when Refract::HashNode
  412. value.elements.all? { |n| static_attribute_value_literal?(n) }
  413. when: 35 when Refract::AssocNode
  414. 35 (Refract::StringNode === value.key || Refract::SymbolNode === value.key) && static_attribute_value_literal?(value.value)
  415. when: 0 when Refract::CallNode
  416. then: 0 if value in { receiver: Prism::ConstantReadNode[name: :Set]| Prism::ConstantPathNode[name: :Set, parent: nil], name: :[] }
  417. value.arguments.arguments.all? { |n| static_token_value_literal?(n) }
  418. else: 0 else
  419. false
  420. end
  421. else: 0 else
  422. false
  423. end
  424. end
  425. 1 private def static_token_value_literal?(value)
  426. 1 case value
  427. when: 0 when Prism::SymbolNode, Prism::StringNode, Prism::IntegerNode, Prism::FloatNode, Prism::NilNode
  428. true
  429. when: 0 when Prism::ArrayNode
  430. value.elements.all? { |n| static_token_value_literal?(n) }
  431. else: 1 else
  432. 1 false
  433. end
  434. end
  435. 1 private def whitespace_helper?(node)
  436. 16 node.name == :whitespace && own_method_without_scope?(node)
  437. end
  438. 1 private def doctype_helper?(node)
  439. 13 node.name == :doctype && own_method_without_scope?(node)
  440. end
  441. 1 private def plain_helper?(node)
  442. 15 node.name == :plain && own_method_without_scope?(node)
  443. end
  444. 1 private def fragment_helper?(node)
  445. 7 node.name == :fragment && own_method_without_scope?(node)
  446. end
  447. 1 private def comment_helper?(node)
  448. 7 node.name == :comment && own_method_without_scope?(node)
  449. end
  450. 1 private def raw_helper?(node)
  451. 6 node.name == :raw && own_method_without_scope?(node)
  452. end
  453. 1 ALLOWED_OWNERS = Set[Phlex::SGML, Phlex::HTML, Phlex::SVG]
  454. 1 private def own_method_without_scope?(node)
  455. 12 ALLOWED_OWNERS.include?(Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name).owner)
  456. end
  457. 1 private def state_local
  458. 24 :__phlex_state__.tap do |local|
  459. 24 else: 12 then: 12 unless @state_local_set
  460. 12 @preamble << Refract::LocalVariableWriteNode.new(
  461. name: local,
  462. value: Refract::InstanceVariableReadNode.new(
  463. name: :@_state
  464. )
  465. )
  466. 12 @state_local_set = true
  467. end
  468. end
  469. end
  470. 1 private def buffer_local
  471. 193 :__phlex_buffer__.tap do |local|
  472. 193 else: 181 then: 12 unless @buffer_local_set
  473. 12 @preamble << Refract::LocalVariableWriteNode.new(
  474. name: local,
  475. value: Refract::CallNode.new(
  476. receiver: Refract::LocalVariableReadNode.new(
  477. name: state_local
  478. ),
  479. name: :buffer,
  480. )
  481. )
  482. 12 @buffer_local_set = true
  483. end
  484. end
  485. end
  486. 1 private def self_local
  487. 3 :__phlex_self__.tap do |local|
  488. 3 else: 1 then: 2 unless @self_local_set
  489. 2 @preamble << Refract::LocalVariableWriteNode.new(
  490. name: local,
  491. value: Refract::SelfNode.new
  492. )
  493. 2 @self_local_set = true
  494. end
  495. end
  496. end
  497. 1 private def should_render_local
  498. 193 :__phlex_should_render__.tap do |local|
  499. 193 else: 181 then: 12 unless @should_render_local_set
  500. 12 @preamble << Refract::LocalVariableWriteNode.new(
  501. name: :__phlex_should_render__,
  502. value: Refract::CallNode.new(
  503. receiver: Refract::LocalVariableReadNode.new(
  504. name: state_local
  505. ),
  506. name: :should_render?
  507. )
  508. )
  509. 12 @should_render_local_set = true
  510. end
  511. end
  512. end
  513. end
  514. end

lib/phlex/csv.rb

95.35% lines covered

94.12% branches covered

129 relevant lines. 123 lines covered and 6 lines missed.
51 total branches, 48 branches covered and 3 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::CSV
  3. 1 FORMULA_PREFIXES_MAP = Array.new(128).tap do |map|
  4. 1 "=+-@\t\r".each_byte do |byte|
  5. 6 map[byte] = true
  6. end
  7. end.freeze
  8. 1 UNDEFINED = Object.new
  9. 1 def initialize(collection)
  10. 14 @collection = collection
  11. 14 @_row_buffer = []
  12. 14 @_headers = []
  13. 14 @_row_appender = nil
  14. end
  15. 1 attr_reader :collection
  16. 1 def call(buffer = +"", context: nil, delimiter: self.delimiter)
  17. 12 ensure_escape_csv_injection_configured!
  18. 11 strip_whitespace = trim_whitespace?
  19. 11 escape_csv_injection = escape_csv_injection?
  20. 11 row_buffer = @_row_buffer
  21. 11 headers = @_headers
  22. 11 has_yielder = respond_to?(:yielder, true)
  23. 11 first_row = true
  24. 11 render_headers = render_headers?
  25. 11 then: 1 else: 10 if delimiter.length != 1
  26. 1 raise Phlex::ArgumentError.new("Delimiter must be a single character")
  27. end
  28. 10 then: 4 if strip_whitespace
  29. 4 escape_regex = /[\n"#{delimiter}]/
  30. else: 6 else
  31. 6 escape_regex = /^\s|\s$|[\n"#{delimiter}]/
  32. end
  33. 10 then: 1 else: 9 if has_yielder
  34. 1 warn <<~MESSAGE
  35. Custom yielders are deprecated in Phlex::CSV.
  36. Please replace your yielder with an `around_row` method.
  37. You should be able to just rename your yielder method
  38. and change `yield` to `super`.
  39. MESSAGE
  40. end
  41. 10 row_appender = -> {
  42. 84 row = row_buffer
  43. 84 then: 10 else: 74 if first_row
  44. 10 first_row = false
  45. 10 i = 0
  46. 10 number_of_columns = row.length
  47. 10 first_col = true
  48. 10 body: 20 while i < number_of_columns
  49. 20 header, = row[i]
  50. 20 headers[i] = header
  51. 20 then: 18 else: 2 if render_headers
  52. 18 then: 9 if first_col
  53. 9 first_col = false
  54. else: 9 else
  55. 9 buffer << delimiter
  56. end
  57. 18 __escape__(buffer, header, escape_csv_injection:, strip_whitespace:, escape_regex:)
  58. end
  59. 20 i += 1
  60. end
  61. 10 then: 9 else: 1 buffer << "\n" if render_headers
  62. end
  63. 84 i = 0
  64. 84 number_of_columns = row.length
  65. 84 first_col = true
  66. 84 body: 168 while i < number_of_columns
  67. 168 header, value = row[i]
  68. 168 else: 168 then: 0 unless headers[i] == header
  69. raise Phlex::RuntimeError.new("Header mismatch at index #{i}: expected #{headers[i]}, got #{header}.")
  70. end
  71. 168 then: 84 if first_col
  72. 84 first_col = false
  73. else: 84 else
  74. 84 buffer << delimiter
  75. end
  76. 168 __escape__(buffer, value, escape_csv_injection:, strip_whitespace:, escape_regex:)
  77. 168 i += 1
  78. end
  79. 84 buffer << "\n"
  80. 84 row_buffer.clear
  81. }
  82. 10 then: 1 if has_yielder
  83. 1 each_item do |record|
  84. 7 yielder(record) do |*a, **k|
  85. 14 row_template(*a, **k)
  86. 14 row_appender.call
  87. end
  88. end
  89. else: 9 else
  90. 9 @row_appender = row_appender
  91. 9 each_item do |record|
  92. 63 around_row(record)
  93. end
  94. end
  95. 10 buffer
  96. end
  97. 1 def around_row(...)
  98. 70 row_template(...)
  99. 70 @row_appender.call
  100. end
  101. 1 def filename
  102. 1 nil
  103. end
  104. 1 def content_type
  105. 1 "text/csv"
  106. end
  107. 1 def delimiter
  108. 9 ","
  109. end
  110. 1 private def column(header = nil, value)
  111. 168 @_row_buffer << [header, value]
  112. end
  113. 1 private def each_item(&)
  114. 10 collection.each(&)
  115. end
  116. # Override and set to `false` to disable rendering headers.
  117. 1 private def render_headers?
  118. 10 true
  119. end
  120. # Override and set to `true` to strip leading and trailing whitespace from values.
  121. 1 private def trim_whitespace?
  122. 3 false
  123. end
  124. # Override and set to `false` to disable CSV injection escapes or `true` to enable.
  125. 1 private def escape_csv_injection?
  126. 1 UNDEFINED
  127. end
  128. 1 private def __escape__(buffer, value, escape_csv_injection:, strip_whitespace:, escape_regex:)
  129. 186 value = case value
  130. when: 126 when String
  131. 126 value
  132. when: 12 when Symbol
  133. 12 value.name
  134. else: 48 else
  135. 48 value.to_s
  136. end
  137. 186 then: 64 if strip_whitespace
  138. 64 value = value.strip
  139. 64 then: 48 if escape_csv_injection
  140. 48 then: 12 if value.empty?
  141. 12 else: 36 buffer << value
  142. 36 then: 6 elsif FORMULA_PREFIXES_MAP[value.getbyte(0)]
  143. 6 value.gsub!('"', '""')
  144. 6 else: 30 buffer << '"\'' << value << '"'
  145. 30 then: 6 elsif value.match?(escape_regex)
  146. 6 value.gsub!('"', '""')
  147. 6 buffer << '"' << value << '"'
  148. else: 24 else
  149. 24 buffer << value
  150. end
  151. else: 16 else # not escaping CSV injection
  152. 16 buffer << value
  153. end
  154. else: 122 else # not stripping whitespace
  155. 122 then: 92 if escape_csv_injection
  156. 92 first_byte = value.getbyte(0)
  157. 92 then: 24 if value.empty?
  158. 24 else: 68 buffer << '""'
  159. 68 then: 12 elsif FORMULA_PREFIXES_MAP[first_byte]
  160. 12 else: 56 buffer << '"\'' << value.gsub('"', '""') << '"'
  161. 56 then: 18 elsif value.match?(escape_regex)
  162. 18 buffer << '"' << value.gsub('"', '""') << '"'
  163. else: 38 else
  164. 38 buffer << value
  165. end
  166. else: 30 else # not escaping CSV injection
  167. 30 then: 8 if value.empty?
  168. 8 else: 22 buffer << '""'
  169. 22 then: 6 elsif value.match?(escape_regex)
  170. 6 buffer << '"' << value.gsub('"', '""') << '"'
  171. else: 16 else
  172. 16 buffer << value
  173. end
  174. end
  175. end
  176. end
  177. # Handle legacy `view_template` method
  178. 1 private def respond_to_missing?(method_name, include_private)
  179. 10 (method_name == :row_template && respond_to?(:view_template)) || super
  180. end
  181. # Handle legacy `view_template` method
  182. 1 private def method_missing(method_name, ...)
  183. then: 0 if method_name == :row_template && respond_to?(:view_template)
  184. warn "Deprecated: Use `row_template` instead of `view_template` in Phlex CSVs."
  185. self.class.alias_method :row_template, :view_template
  186. view_template(...)
  187. else: 0 else
  188. super
  189. end
  190. end
  191. 1 private def ensure_escape_csv_injection_configured!
  192. 12 then: 1 else: 11 if escape_csv_injection? == UNDEFINED
  193. 1 raise <<~MESSAGE
  194. You need to define `escape_csv_injection?` in #{self.class.name}.
  195. CSV injection is a security vulnerability where malicious spreadsheet
  196. formulae are used to execute code or exfiltrate data when a CSV is opened
  197. in a spreadsheet program such as Microsoft Excel or Google Sheets.
  198. For more information, see https://owasp.org/www-community/attacks/CSV_Injection
  199. If you’re sure this CSV will never be opened in a spreadsheet program,
  200. you can *disable* CSV injection escapes:
  201. def escape_csv_injection? = false
  202. This is useful when using CSVs for byte-for-byte data exchange between secure systems.
  203. Alternatively, you can *enable* CSV injection escapes at the cost of data integrity:
  204. def escape_csv_injection? = true
  205. Enabling the CSV injection escapes will prefix with a single quote `'` any
  206. values that start with: `=`, `+`, `-`, `@`, `\\t`, `\\r`
  207. Unfortunately, there is no one-size-fits-all solution to CSV injection.
  208. You need to decide based on your specific use case.
  209. MESSAGE
  210. end
  211. end
  212. end

lib/phlex/error.rb

100.0% lines covered

100.0% branches covered

1 relevant lines. 1 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # @api private
  3. 1 module Phlex::Error
  4. end

lib/phlex/errors/argument_error.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::ArgumentError < ArgumentError
  3. 1 include Phlex::Error
  4. end

lib/phlex/errors/double_render_error.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::DoubleRenderError < RuntimeError
  3. 1 include Phlex::Error
  4. end

lib/phlex/errors/name_error.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::NameError < NameError
  3. 1 include Phlex::Error
  4. end

lib/phlex/errors/runtime_error.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::RuntimeError < RuntimeError
  3. 1 include Phlex::Error
  4. end

lib/phlex/fifo.rb

90.0% lines covered

100.0% branches covered

30 relevant lines. 27 lines covered and 3 lines missed.
7 total branches, 7 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # @api private
  3. 1 class Phlex::FIFO
  4. 1 def initialize(max_bytesize: 2_000, max_value_bytesize: 2_000)
  5. 15 @store = {}
  6. 15 @max_bytesize = max_bytesize
  7. 15 @max_value_bytesize = max_value_bytesize
  8. 15 @bytesize = 0
  9. 15 @mutex = Monitor.new
  10. end
  11. 1 attr_reader :bytesize, :max_bytesize
  12. 1 def expand(bytes)
  13. 40 @mutex.synchronize do
  14. 40 @max_bytesize += bytes
  15. end
  16. end
  17. 1 def [](key)
  18. 1822 k, v = @store[key.hash]
  19. 1822 then: 1595 else: 227 v if k.eql?(key)
  20. end
  21. 1 def []=(key, value)
  22. 301 then: 1 else: 300 return if value.bytesize > @max_value_bytesize
  23. 300 digest = key.hash
  24. 300 @mutex.synchronize do
  25. # Check the key definitely doesn't exist now we have the lock
  26. 300 then: 2 else: 298 return if @store[digest]
  27. 298 @store[digest] = [key, value].freeze
  28. 298 @bytesize += value.bytesize
  29. 298 body: 97 while @bytesize > @max_bytesize
  30. 97 _k, v = @store.shift
  31. 97 @bytesize -= v[1].bytesize
  32. end
  33. end
  34. end
  35. 1 def size
  36. 2 @store.size
  37. end
  38. 1 def clear
  39. @mutex.synchronize do
  40. @store.clear
  41. @bytesize = 0
  42. end
  43. end
  44. end

lib/phlex/fifo_cache_store.rb

69.57% lines covered

40.0% branches covered

23 relevant lines. 16 lines covered and 7 lines missed.
10 total branches, 4 branches covered and 6 branches missed.
    
  1. # frozen_string_literal: true
  2. # An extremely fast in-memory cache store that evicts keys on a first-in-first-out basis.
  3. 1 class Phlex::FIFOCacheStore
  4. 1 def initialize(max_bytesize: 2 ** 20)
  5. 11 @fifo = Phlex::FIFO.new(
  6. max_bytesize:,
  7. max_value_bytesize: max_bytesize
  8. )
  9. end
  10. 1 def fetch(key)
  11. 29 fifo = @fifo
  12. 29 key = map_key(key)
  13. 29 then: 7 if (result = fifo[key])
  14. 7 JSON.parse(result)
  15. else: 22 else
  16. 22 result = yield
  17. 22 fifo[key] = JSON.fast_generate(result)
  18. 22 result
  19. end
  20. end
  21. 1 def clear
  22. @fifo.clear
  23. end
  24. 1 private def map_key(value)
  25. 192 case value
  26. when: 50 when Array
  27. 213 value.map { |it| map_key(it) }
  28. when: 0 when Hash
  29. value.to_h { |k, v| [map_key(k), map_key(v)].freeze }
  30. when: 142 when String, Symbol, Integer, Float, Time, true, false, nil
  31. 142 value
  32. else: 0 else
  33. then: 0 if value.respond_to?(:cache_key_with_version)
  34. else: 0 map_key(value.cache_key_with_version)
  35. then: 0 elsif value.respond_to?(:cache_key)
  36. map_key(value.cache_key)
  37. else: 0 else
  38. raise ArgumentError.new("Invalid cache key: #{value.class}")
  39. end
  40. end
  41. end
  42. end

lib/phlex/helpers.rb

95.83% lines covered

94.12% branches covered

24 relevant lines. 23 lines covered and 1 lines missed.
17 total branches, 16 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. # @api private
  3. 1 module Phlex::Helpers
  4. 1 private def mix(*args)
  5. 16 args.each_with_object({}) do |object, result|
  6. 32 result.merge!(object) do |_key, old, new|
  7. 15 case [old, new].freeze
  8. in: 2 in [Array, Array] | [Set, Set]
  9. 2 old + new
  10. in: 1 in [Array, Set]
  11. 1 old + new.to_a
  12. in: 1 in [Array, String]
  13. 1 old + [new]
  14. in: 1 in [Hash, Hash]
  15. 1 mix(old, new)
  16. in: 1 in [Set, Array]
  17. 1 old.to_a + new
  18. in: 1 in [Set, String]
  19. 1 old.to_a + [new]
  20. in: 1 in [String, Array]
  21. 1 [old] + new
  22. in: 1 in [String, Set]
  23. 1 [old] + new.to_a
  24. in: 2 in [String, String]
  25. 2 "#{old} #{new}"
  26. in: 1 in [_, Hash]
  27. 1 { _: old, **new }
  28. in: 0 in [Hash, _]
  29. { **old, _: new }
  30. in: 2 in [_, nil]
  31. 2 old
  32. else: 1 else
  33. 1 new
  34. end
  35. end
  36. 32 result.transform_keys! do |key|
  37. 33 then: 1 else: 32 key.end_with?("!") ? key.name.chop.to_sym : key
  38. end
  39. end
  40. end
  41. 1 private def grab(**bindings)
  42. 2 then: 1 if bindings.size > 1
  43. 1 bindings.values
  44. else: 1 else
  45. 1 bindings.values.first
  46. end
  47. end
  48. end

lib/phlex/html.rb

93.88% lines covered

89.29% branches covered

49 relevant lines. 46 lines covered and 3 lines missed.
28 total branches, 25 branches covered and 3 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::HTML < Phlex::SGML
  3. 1 extend Phlex::SGML::Elements
  4. 1 include VoidElements, StandardElements
  5. # Output an HTML doctype.
  6. 1 def doctype
  7. 5 state = @_state
  8. 5 else: 1 then: 4 return unless state.should_render?
  9. 1 state.buffer << "<!doctype html>"
  10. 1 nil
  11. end
  12. # Outputs an `<svg>` tag.
  13. #
  14. # [MDN Docs](https://developer.mozilla.org/docs/Web/SVG/Element/svg)
  15. # [Spec](https://html.spec.whatwg.org/#the-svg-element)
  16. 1 def svg(*, **, &)
  17. 6 then: 5 if block_given?
  18. 10 super { render Phlex::SVG.new(&) }
  19. else: 1 else
  20. 1 super
  21. end
  22. end
  23. # Override to provide a filename for the HTML file
  24. 1 def filename
  25. nil
  26. end
  27. # Returns the string "text/html"
  28. 1 def content_type
  29. 1 "text/html"
  30. end
  31. # Output an HTML tag dynamically, e.g:
  32. #
  33. # ```ruby
  34. # @tag_name = :h1
  35. # tag(@tag_name, class: "title")
  36. # ```
  37. 1 def tag(name, **attributes, &)
  38. 452 state = @_state
  39. 452 block_given = block_given?
  40. 452 buffer = state.buffer
  41. 452 else: 452 then: 0 unless state.should_render?
  42. then: 0 else: 0 yield(self) if block_given
  43. return nil
  44. end
  45. 452 else: 451 then: 1 unless Symbol === name
  46. 1 raise Phlex::ArgumentError.new("Expected the tag name to be a Symbol.")
  47. end
  48. 451 then: 414 if (tag = StandardElements.__registered_elements__[name]) || (tag = name.name.tr("_", "-")).include?("-")
  49. 414 then: 206 if attributes.length > 0 # with attributes
  50. 206 then: 103 if block_given # with content block
  51. 103 buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
  52. 103 then: 1 if tag == "svg"
  53. 1 render Phlex::SVG.new(&)
  54. else: 102 else
  55. 102 __yield_content__(&)
  56. end
  57. 103 buffer << "</#{tag}>"
  58. else: 103 else # without content
  59. 103 buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << "></#{tag}>"
  60. end
  61. else: 208 else # without attributes
  62. 208 then: 104 if block_given # with content block
  63. 104 buffer << ("<#{tag}>")
  64. 104 then: 2 if tag == "svg"
  65. 2 render Phlex::SVG.new(&)
  66. else: 102 else
  67. 102 __yield_content__(&)
  68. end
  69. 104 buffer << "</#{tag}>"
  70. else: 104 else # without content
  71. 104 buffer << "<#{tag}></#{tag}>"
  72. end
  73. else: 37 end
  74. 37 then: 36 elsif (tag = VoidElements.__registered_elements__[name])
  75. 36 then: 12 else: 24 if block_given
  76. 12 raise Phlex::ArgumentError.new("Void elements cannot have content blocks.")
  77. end
  78. 24 then: 12 if attributes.length > 0 # with attributes
  79. 12 buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
  80. else: 12 else # without attributes
  81. 12 buffer << "<#{tag}>"
  82. end
  83. 24 nil
  84. else: 1 else
  85. 1 raise Phlex::ArgumentError.new("Invalid HTML tag: #{name}")
  86. end
  87. end
  88. end

lib/phlex/html/standard_elements.rb

100.0% lines covered

100.0% branches covered

105 relevant lines. 105 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # Standard HTML elements accept content and always have a closing tag.
  3. 1 module Phlex::HTML::StandardElements
  4. 1 extend Phlex::SGML::Elements
  5. # Outputs an `<a>` tag.
  6. #
  7. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/a)
  8. # [Spec](https://html.spec.whatwg.org/#the-a-element)
  9. 1 register_element def a(**attributes, &content) = nil
  10. # Outputs an `<abbr>` tag.
  11. #
  12. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/abbr)
  13. # [Spec](https://html.spec.whatwg.org/#the-abbr-element)
  14. 1 register_element def abbr(**attributes, &content) = nil
  15. # Outputs an `<address>` tag.
  16. #
  17. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/address)
  18. # [Spec](https://html.spec.whatwg.org/#the-address-element)
  19. 1 register_element def address(**attributes, &content) = nil
  20. # Outputs an `<article>` tag.
  21. #
  22. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/article)
  23. # [Spec](https://html.spec.whatwg.org/#the-article-element)
  24. 1 register_element def article(**attributes, &content) = nil
  25. # Outputs an `<aside>` tag.
  26. #
  27. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/aside)
  28. # [Spec](https://html.spec.whatwg.org/#the-aside-element)
  29. 1 register_element def aside(**attributes, &content) = nil
  30. # Outputs an `<audio>` tag.
  31. #
  32. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/audio)
  33. # [Spec](https://html.spec.whatwg.org/#the-audio-element)
  34. # [Can I Use?](https://caniuse.com/audio)
  35. 1 register_element def audio(**attributes, &content) = nil
  36. # Outputs a `<b>` tag.
  37. #
  38. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/b)
  39. # [Spec](https://html.spec.whatwg.org/#the-b-element)
  40. 1 register_element def b(**attributes, &content) = nil
  41. # Outputs a `<bdi>` tag.
  42. #
  43. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/bdi)
  44. # [Spec](https://html.spec.whatwg.org/#the-bdi-element)
  45. 1 register_element def bdi(**attributes, &content) = nil
  46. # Outputs a `<bdo>` tag.
  47. #
  48. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/bdo)
  49. # [Spec](https://html.spec.whatwg.org/#the-bdo-element)
  50. 1 register_element def bdo(**attributes, &content) = nil
  51. # Outputs a `<blockquote>` tag.
  52. #
  53. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/blockquote)
  54. # [Spec](https://html.spec.whatwg.org/#the-blockquote-element)
  55. 1 register_element def blockquote(**attributes, &content) = nil
  56. # Outputs a `<body>` tag.
  57. #
  58. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/body)
  59. # [Spec](https://html.spec.whatwg.org/#the-body-element)
  60. 1 register_element def body(**attributes, &content) = nil
  61. # Outputs a `<button>` tag.
  62. # The `<button>` element is an interactive element activated by a user with a mouse, keyboard, finger, voice command, or other assistive technology to perform an action, such as submitting a form or opening a dialog.
  63. #
  64. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/button)
  65. # [Spec](https://html.spec.whatwg.org/#the-button-element)
  66. 1 register_element def button(**attributes, &content) = nil
  67. # Outputs a `<canvas>` tag.
  68. #
  69. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/canvas)
  70. # [Spec](https://html.spec.whatwg.org/#the-canvas-element)
  71. 1 register_element def canvas(**attributes, &content) = nil
  72. # Outputs a `<caption>` tag.
  73. #
  74. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/caption)
  75. # [Spec](https://html.spec.whatwg.org/#the-caption-element)
  76. 1 register_element def caption(**attributes, &content) = nil
  77. # Outputs a `<cite>` tag.
  78. #
  79. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/cite)
  80. # [Spec](https://html.spec.whatwg.org/#the-cite-element)
  81. 1 register_element def cite(**attributes, &content) = nil
  82. # Outputs a `<code>` tag.
  83. #
  84. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/code)
  85. # [Spec](https://html.spec.whatwg.org/#the-code-element)
  86. 1 register_element def code(**attributes, &content) = nil
  87. # Outputs a `<colgroup>` tag.
  88. #
  89. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/colgroup)
  90. # [Spec](https://html.spec.whatwg.org/#the-colgroup-element)
  91. 1 register_element def colgroup(**attributes, &content) = nil
  92. # Outputs a `<data>` tag.
  93. #
  94. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/data)
  95. # [Spec](https://html.spec.whatwg.org/#the-data-element)
  96. 1 register_element def data(**attributes, &content) = nil
  97. # Outputs a `<datalist>` tag.
  98. #
  99. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/datalist)
  100. # [Spec](https://html.spec.whatwg.org/#the-datalist-element)
  101. 1 register_element def datalist(**attributes, &content) = nil
  102. # Outputs a `<dd>` tag.
  103. #
  104. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dd)
  105. # [Spec](https://html.spec.whatwg.org/#the-dd-element)
  106. 1 register_element def dd(**attributes, &content) = nil
  107. # Outputs a `<del>` tag.
  108. #
  109. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/del)
  110. # [Spec](https://html.spec.whatwg.org/#the-del-element)
  111. 1 register_element def del(**attributes, &content) = nil
  112. # Outputs a `<details>` tag.
  113. #
  114. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/details)
  115. # [Spec](https://html.spec.whatwg.org/#the-details-element)
  116. # [Can I Use?](https://caniuse.com/details)
  117. 1 register_element def details(**attributes, &content) = nil
  118. # Outputs a `<dfn>` tag.
  119. #
  120. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dfn)
  121. # [Spec](https://html.spec.whatwg.org/#the-dfn-element)
  122. 1 register_element def dfn(**attributes, &content) = nil
  123. # Outputs a `<dialog>` tag.
  124. #
  125. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dialog)
  126. # [Spec](https://html.spec.whatwg.org/#the-dialog-element)
  127. # [Can I use?](https://caniuse.com/dialog)
  128. 1 register_element def dialog(**attributes, &content) = nil
  129. # Outputs a `<div>` tag.
  130. #
  131. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/div)
  132. # [Spec](https://html.spec.whatwg.org/#the-div-element)
  133. 1 register_element def div(**attributes, &content) = nil
  134. # Outputs a `<dl>` tag.
  135. #
  136. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dl)
  137. # [Spec](https://html.spec.whatwg.org/#the-dl-element)
  138. 1 register_element def dl(**attributes, &content) = nil
  139. # Outputs a `<dt>` tag.
  140. #
  141. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dt)
  142. # [Spec](https://html.spec.whatwg.org/#the-dt-element)
  143. 1 register_element def dt(**attributes, &content) = nil
  144. # Outputs an `<em>` tag.
  145. #
  146. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/em)
  147. # [Spec](https://html.spec.whatwg.org/#the-em-element)
  148. 1 register_element def em(**attributes, &content) = nil
  149. # [EXPERIMENTAL] Outputs a `<fencedframe>` tag.
  150. #
  151. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/fencedframe)
  152. # [Can I Use?](https://caniuse.com/mdn-html_elements_fencedframe)
  153. 1 register_element def fencedframe(**attributes, &content) = nil
  154. # Outputs a `<fieldset>` tag.
  155. #
  156. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/fieldset)
  157. # [Spec](https://html.spec.whatwg.org/#the-fieldset-element)
  158. 1 register_element def fieldset(**attributes, &content) = nil
  159. # Outputs a `<figcaption>` tag.
  160. #
  161. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/figcaption)
  162. # [Spec](https://html.spec.whatwg.org/#the-figcaption-element)
  163. 1 register_element def figcaption(**attributes, &content) = nil
  164. # Outputs a `<figure>` tag.
  165. #
  166. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/figure)
  167. # [Spec](https://html.spec.whatwg.org/#the-figure-element)
  168. 1 register_element def figure(**attributes, &content) = nil
  169. # Outputs a `<footer>` tag.
  170. #
  171. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/footer)
  172. # [Spec](https://html.spec.whatwg.org/#the-footer-element)
  173. 1 register_element def footer(**attributes, &content) = nil
  174. # Outputs a `<form>` tag.
  175. #
  176. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/form)
  177. # [Spec](https://html.spec.whatwg.org/#the-form-element)
  178. 1 register_element def form(**attributes, &content) = nil
  179. # Outputs an `<h1>` tag.
  180. #
  181. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h1)
  182. # [Spec](https://html.spec.whatwg.org/#the-h1-element)
  183. 1 register_element def h1(**attributes, &content) = nil
  184. # Outputs an `<h2>` tag.
  185. #
  186. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h2)
  187. # [Spec](https://html.spec.whatwg.org/#the-h2-element)
  188. 1 register_element def h2(**attributes, &content) = nil
  189. # Outputs an `<h3>` tag.
  190. #
  191. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h3)
  192. # [Spec](https://html.spec.whatwg.org/#the-h3-element)
  193. 1 register_element def h3(**attributes, &content) = nil
  194. # Outputs an `<h4>` tag.
  195. #
  196. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h4)
  197. # [Spec](https://html.spec.whatwg.org/#the-h4-element)
  198. 1 register_element def h4(**attributes, &content) = nil
  199. # Outputs an `<h5>` tag.
  200. #
  201. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h5)
  202. # [Spec](https://html.spec.whatwg.org/#the-h5-element)
  203. 1 register_element def h5(**attributes, &content) = nil
  204. # Outputs an `<h6>` tag.
  205. #
  206. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h6)
  207. # [Spec](https://html.spec.whatwg.org/#the-h6-element)
  208. 1 register_element def h6(**attributes, &content) = nil
  209. # Outputs a `<head>` tag.
  210. #
  211. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/head)
  212. # [Spec](https://html.spec.whatwg.org/#the-head-element)
  213. 1 register_element def head(**attributes, &content) = nil
  214. # Outputs a `<header>` tag.
  215. #
  216. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/header)
  217. # [Spec](https://html.spec.whatwg.org/#the-header-element)
  218. 1 register_element def header(**attributes, &content) = nil
  219. # Outputs an `<hgroup>` tag.
  220. #
  221. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/hgroup)
  222. # [Spec](https://html.spec.whatwg.org/#the-hgroup-element)
  223. 1 register_element def hgroup(**attributes, &content) = nil
  224. # Outputs an `<html>` tag.
  225. #
  226. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/html)
  227. # [Spec](https://html.spec.whatwg.org/#the-html-element)
  228. 1 register_element def html(**attributes, &content) = nil
  229. # Outputs an `<i>` tag.
  230. #
  231. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/i)
  232. # [Spec](https://html.spec.whatwg.org/#the-i-element)
  233. 1 register_element def i(**attributes, &content) = nil
  234. # Outputs an `<iframe>` tag.
  235. #
  236. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/iframe)
  237. # [Spec](https://html.spec.whatwg.org/#the-iframe-element)
  238. 1 register_element def iframe(**attributes, &content) = nil
  239. # Outputs an `<ins>` tag.
  240. #
  241. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/ins)
  242. # [Spec](https://html.spec.whatwg.org/#the-ins-element)
  243. 1 register_element def ins(**attributes, &content) = nil
  244. # Outputs a `<kbd>` tag.
  245. #
  246. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/kbd)
  247. # [Spec](https://html.spec.whatwg.org/#the-kbd-element)
  248. 1 register_element def kbd(**attributes, &content) = nil
  249. # Outputs a `<label>` tag.
  250. #
  251. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/label)
  252. # [Spec](https://html.spec.whatwg.org/#the-label-element)
  253. 1 register_element def label(**attributes, &content) = nil
  254. # Outputs a `<legend>` tag.
  255. #
  256. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/legend)
  257. # [Spec](https://html.spec.whatwg.org/#the-legend-element)
  258. 1 register_element def legend(**attributes, &content) = nil
  259. # Outputs a `<li>` tag.
  260. #
  261. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/li)
  262. # [Spec](https://html.spec.whatwg.org/#the-li-element)
  263. 1 register_element def li(**attributes, &content) = nil
  264. # Outputs a `<main>` tag.
  265. #
  266. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/main)
  267. # [Spec](https://html.spec.whatwg.org/#the-main-element)
  268. 1 register_element def main(**attributes, &content) = nil
  269. # Outputs a `<map>` tag.
  270. #
  271. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/map)
  272. # [Spec](https://html.spec.whatwg.org/#the-map-element)
  273. 1 register_element def map(**attributes, &content) = nil
  274. # Outputs a `<mark>` tag.
  275. #
  276. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/mark)
  277. # [Spec](https://html.spec.whatwg.org/#the-mark-element)
  278. 1 register_element def mark(**attributes, &content) = nil
  279. # Outputs a `<menu>` tag.
  280. #
  281. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/menu)
  282. # [Spec](https://html.spec.whatwg.org/#the-menu-element)
  283. 1 register_element def menu(**attributes, &content) = nil
  284. # Outputs a `<meter>` tag.
  285. #
  286. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/meter)
  287. # [Spec](https://html.spec.whatwg.org/#the-meter-element)
  288. 1 register_element def meter(**attributes, &content) = nil
  289. # Outputs a `<nav>` tag.
  290. #
  291. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/nav)
  292. # [Spec](https://html.spec.whatwg.org/#the-nav-element)
  293. 1 register_element def nav(**attributes, &content) = nil
  294. # Outputs a `<noscript>` tag.
  295. #
  296. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/noscript)
  297. # [Spec](https://html.spec.whatwg.org/#the-noscript-element)
  298. 1 register_element def noscript(**attributes, &content) = nil
  299. # Outputs an `<object>` tag.
  300. #
  301. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/object)
  302. # [Spec](https://html.spec.whatwg.org/#the-object-element)
  303. 1 register_element def object(**attributes, &content) = nil
  304. # Outputs an `<ol>` tag.
  305. #
  306. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/ol)
  307. # [Spec](https://html.spec.whatwg.org/#the-ol-element)
  308. 1 register_element def ol(**attributes, &content) = nil
  309. # Outputs an `<optgroup>` tag.
  310. #
  311. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/optgroup)
  312. # [Spec](https://html.spec.whatwg.org/#the-optgroup-element)
  313. 1 register_element def optgroup(**attributes, &content) = nil
  314. # Outputs an `<option>` tag.
  315. #
  316. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/option)
  317. # [Spec](https://html.spec.whatwg.org/#the-option-element)
  318. 1 register_element def option(**attributes, &content) = nil
  319. # Outputs an `<output>` tag.
  320. #
  321. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/output)
  322. # [Spec](https://html.spec.whatwg.org/#the-output-element)
  323. 1 register_element def output(**attributes, &content) = nil
  324. # Outputs a `<p>` tag.
  325. #
  326. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/p)
  327. # [Spec](https://html.spec.whatwg.org/#the-p-element)
  328. 1 register_element def p(**attributes, &content) = nil
  329. # Outputs a `<picture>` tag.
  330. #
  331. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/picture)
  332. # [Spec](https://html.spec.whatwg.org/#the-picture-element)
  333. # [Can I Use?](https://caniuse.com/picture)
  334. 1 register_element def picture(**attributes, &content) = nil
  335. # Outputs a `<pre>` tag.
  336. #
  337. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/pre)
  338. # [Spec](https://html.spec.whatwg.org/#the-pre-element)
  339. 1 register_element def pre(**attributes, &content) = nil
  340. # Outputs a `<progress>` tag.
  341. #
  342. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/progress)
  343. # [Spec](https://html.spec.whatwg.org/#the-progress-element)
  344. 1 register_element def progress(**attributes, &content) = nil
  345. # Outputs a `<q>` tag.
  346. #
  347. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/q)
  348. # [Spec](https://html.spec.whatwg.org/#the-q-element)
  349. 1 register_element def q(**attributes, &content) = nil
  350. # Outputs an `<rp>` tag.
  351. #
  352. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/rp)
  353. # [Spec](https://html.spec.whatwg.org/#the-rp-element)
  354. 1 register_element def rp(**attributes, &content) = nil
  355. # Outputs an `<rt>` tag.
  356. #
  357. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/rt)
  358. # [Spec](https://html.spec.whatwg.org/#the-rt-element)
  359. 1 register_element def rt(**attributes, &content) = nil
  360. # Outputs a `<ruby>` tag. (The best tag ever!)
  361. #
  362. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/ruby)
  363. # [Spec](https://html.spec.whatwg.org/#the-ruby-element)
  364. 1 register_element def ruby(**attributes, &content) = nil
  365. # Outputs an `<s>` tag.
  366. #
  367. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/s)
  368. # [Spec](https://html.spec.whatwg.org/#the-s-element)
  369. 1 register_element def s(**attributes, &content) = nil
  370. # Outputs a `<samp>` tag.
  371. #
  372. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/samp)
  373. # [Spec](https://html.spec.whatwg.org/#the-samp-element)
  374. 1 register_element def samp(**attributes, &content) = nil
  375. # Outputs a `<script>` tag.
  376. #
  377. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/script)
  378. # [Spec](https://html.spec.whatwg.org/#the-script-element)
  379. 1 register_element def script(**attributes, &content) = nil
  380. # Outputs a `<search>` tag.
  381. #
  382. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/search)
  383. # [Spec](https://html.spec.whatwg.org/#the-search-element)
  384. # [Can I Use?](https://caniuse.com/mdn-html_elements_search)
  385. 1 register_element def search(**attributes, &content) = nil
  386. # Outputs a `<section>` tag.
  387. #
  388. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/section)
  389. # [Spec](https://html.spec.whatwg.org/#the-section-element)
  390. 1 register_element def section(**attributes, &content) = nil
  391. # Outputs a `<select>` tag.
  392. #
  393. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/select)
  394. # [Spec](https://html.spec.whatwg.org/#the-select-element)
  395. 1 register_element def select(**attributes, &content) = nil
  396. # [EXPERIMENTAL] Outputs a `<selectedcontent>` tag.
  397. #
  398. # [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/selectedcontent)
  399. # [Draft Spec](https://github.com/whatwg/html/pull/10633)
  400. # [Can I Use?](https://caniuse.com/mdn-html_elements_selectedcontent)
  401. 1 register_element def selectedcontent(**attributes, &content) = nil
  402. # Outputs a `<slot>` tag.
  403. #
  404. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/slot)
  405. # [Spec](https://html.spec.whatwg.org/#the-slot-element)
  406. 1 register_element def slot(**attributes, &content) = nil
  407. # Outputs a `<small>` tag.
  408. #
  409. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/small)
  410. # [Spec](https://html.spec.whatwg.org/#the-small-element)
  411. 1 register_element def small(**attributes, &content) = nil
  412. # Outputs a `<span>` tag.
  413. #
  414. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/span)
  415. # [Spec](https://html.spec.whatwg.org/#the-span-element)
  416. 1 register_element def span(**attributes, &content) = nil
  417. # Outputs a `<strong>` tag.
  418. #
  419. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/strong)
  420. # [Spec](https://html.spec.whatwg.org/#the-strong-element)
  421. 1 register_element def strong(**attributes, &content) = nil
  422. # Outputs a `<style>` tag.
  423. #
  424. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/style)
  425. # [Spec](https://html.spec.whatwg.org/#the-style-element)
  426. 1 register_element def style(**attributes, &content) = nil
  427. # Outputs a `<sub>` tag.
  428. #
  429. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/sub)
  430. # [Spec](https://html.spec.whatwg.org/#the-sub-element)
  431. 1 register_element def sub(**attributes, &content) = nil
  432. # Outputs a `<summary>` tag.
  433. #
  434. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/summary)
  435. # [Spec](https://html.spec.whatwg.org/#the-summary-element)
  436. # [Can I Use?](https://caniuse.com/details)
  437. 1 register_element def summary(**attributes, &content) = nil
  438. # Outputs a `<sup>` tag.
  439. #
  440. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/sup)
  441. # [Spec](https://html.spec.whatwg.org/#the-sup-element)
  442. 1 register_element def sup(**attributes, &content) = nil
  443. # Outputs an `<svg>` tag.
  444. #
  445. # [MDN Docs](https://developer.mozilla.org/docs/Web/SVG/Element/svg)
  446. # [Spec](https://html.spec.whatwg.org/#the-svg-element)
  447. 1 register_element def svg(**attributes, &content) = nil
  448. # Outputs a `<table>` tag.
  449. #
  450. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/table)
  451. # [Spec](https://html.spec.whatwg.org/#the-table-element)
  452. 1 register_element def table(**attributes, &content) = nil
  453. # Outputs a `<tbody>` tag.
  454. #
  455. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/tbody)
  456. # [Spec](https://html.spec.whatwg.org/#the-tbody-element)
  457. 1 register_element def tbody(**attributes, &content) = nil
  458. # Outputs a `<td>` tag.
  459. #
  460. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/td)
  461. # [Spec](https://html.spec.whatwg.org/#the-td-element)
  462. 1 register_element def td(**attributes, &content) = nil
  463. # Outputs a `<template>` tag.
  464. #
  465. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/template)
  466. # [Spec](https://html.spec.whatwg.org/#the-template-element)
  467. 1 register_element def template(**attributes, &content) = nil
  468. # Outputs a `<textarea>` tag.
  469. #
  470. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/textarea)
  471. # [Spec](https://html.spec.whatwg.org/#the-textarea-element)
  472. 1 register_element def textarea(**attributes, &content) = nil
  473. # Outputs a `<tfoot>` tag.
  474. #
  475. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/tfoot)
  476. # [Spec](https://html.spec.whatwg.org/#the-tfoot-element)
  477. 1 register_element def tfoot(**attributes, &content) = nil
  478. # Outputs a `<th>` tag.
  479. #
  480. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/th)
  481. # [Spec](https://html.spec.whatwg.org/#the-th-element)
  482. 1 register_element def th(**attributes, &content) = nil
  483. # Outputs a `<thead>` tag.
  484. #
  485. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/thead)
  486. # [Spec](https://html.spec.whatwg.org/#the-thead-element)
  487. 1 register_element def thead(**attributes, &content) = nil
  488. # Outputs a `<time>` tag.
  489. #
  490. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/time)
  491. # [Spec](https://html.spec.whatwg.org/#the-time-element)
  492. 1 register_element def time(**attributes, &content) = nil
  493. # Outputs a `<title>` tag.
  494. #
  495. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/title)
  496. # [Spec](https://html.spec.whatwg.org/#the-title-element)
  497. 1 register_element def title(**attributes, &content) = nil
  498. # Outputs a `<tr>` tag.
  499. #
  500. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/tr)
  501. # [Spec](https://html.spec.whatwg.org/#the-tr-element)
  502. 1 register_element def tr(**attributes, &content) = nil
  503. # Outputs a `<u>` tag.
  504. #
  505. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/u)
  506. # [Spec](https://html.spec.whatwg.org/#the-u-element)
  507. 1 register_element def u(**attributes, &content) = nil
  508. # Outputs a `<ul>` tag.
  509. #
  510. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/ul)
  511. # [Spec](https://html.spec.whatwg.org/#the-ul-element)
  512. 1 register_element def ul(**attributes, &content) = nil
  513. # Outputs a `<var>` tag.
  514. #
  515. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/var)
  516. # [Spec](https://html.spec.whatwg.org/#the-var-element)
  517. 1 register_element def var(**attributes, &content) = nil
  518. # Outputs a `<video>` tag.
  519. #
  520. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/video)
  521. # [Spec](https://html.spec.whatwg.org/#the-video-element)
  522. # [Can I Use?](https://caniuse.com/video)
  523. 1 register_element def video(**attributes, &content) = nil
  524. # Outputs a `<wbr>` tag.
  525. #
  526. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/wbr)
  527. # [Spec](https://html.spec.whatwg.org/#the-wbr-element)
  528. 1 register_element def wbr(**attributes, &content) = nil
  529. end

lib/phlex/html/void_elements.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # Void HTML elements don't accept content and never have a closing tag.
  3. 1 module Phlex::HTML::VoidElements
  4. 1 extend Phlex::SGML::Elements
  5. # Outputs an `<area>` tag.
  6. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/area)
  7. # [Spec](https://html.spec.whatwg.org/#the-area-element)
  8. 1 __register_void_element__ def area(**attributes) = nil
  9. # Outputs a `<base>` tag.
  10. # [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
  11. # [Spec](https://html.spec.whatwg.org/#the-base-element)
  12. 1 __register_void_element__ def base(**attributes) = nil
  13. # Outputs a `<br>` tag.
  14. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/br)
  15. # [Spec](https://html.spec.whatwg.org/#the-br-element)
  16. 1 __register_void_element__ def br(**attributes) = nil
  17. # Outputs a `<col>` tag.
  18. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/col)
  19. # [Spec](https://html.spec.whatwg.org/#the-col-element)
  20. 1 __register_void_element__ def col(**attributes) = nil
  21. # Outputs an `<embed>` tag.
  22. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/embed)
  23. # [Spec](https://html.spec.whatwg.org/#the-embed-element)
  24. 1 __register_void_element__ def embed(**attributes) = nil
  25. # Outputs an `<hr>` tag.
  26. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/hr)
  27. # [Spec](https://html.spec.whatwg.org/#the-hr-element)
  28. 1 __register_void_element__ def hr(**attributes) = nil
  29. # Outputs an `<img>` tag.
  30. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/img)
  31. # [Spec](https://html.spec.whatwg.org/#the-img-element)
  32. 1 __register_void_element__ def img(**attributes) = nil
  33. # Outputs an `<input>` tag.
  34. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/input)
  35. # [Spec](https://html.spec.whatwg.org/#the-input-element)
  36. 1 __register_void_element__ def input(**attributes) = nil
  37. # Outputs a `<link>` tag.
  38. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/link)
  39. # [Spec](https://html.spec.whatwg.org/#the-link-element)
  40. 1 __register_void_element__ def link(**attributes) = nil
  41. # Outputs a `<meta>` tag.
  42. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/meta)
  43. # [Spec](https://html.spec.whatwg.org/#the-meta-element)
  44. 1 __register_void_element__ def meta(**attributes) = nil
  45. # Outputs a `<source>` tag.
  46. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/source)
  47. # [Spec](https://html.spec.whatwg.org/#the-source-element)
  48. 1 __register_void_element__ def source(**attributes) = nil
  49. # Outputs a `<track>` tag.
  50. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/track)
  51. # [Spec](https://html.spec.whatwg.org/#the-track-element)
  52. 1 __register_void_element__ def track(**attributes) = nil
  53. end

lib/phlex/kit.rb

85.0% lines covered

66.67% branches covered

40 relevant lines. 34 lines covered and 6 lines missed.
15 total branches, 10 branches covered and 5 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Phlex::Kit
  3. 1 module LazyLoader
  4. 1 def method_missing(name, ...)
  5. 1 mod = self.class
  6. 1 then: 1 if name[0] == name[0].upcase && mod.constants.include?(name) && mod.const_get(name) && methods.include?(name)
  7. 1 public_send(name, ...)
  8. else: 0 else
  9. super
  10. end
  11. end
  12. 1 def respond_to_missing?(name, include_private = false)
  13. mod = self.class
  14. (name[0] == name[0].upcase && mod.constants.include?(name) && mod.const_get(name) && methods.include?(name)) || super
  15. end
  16. end
  17. 1 def self.extended(mod)
  18. 3 case mod
  19. when: 0 when Class
  20. raise Phlex::ArgumentError.new(<<~MESSAGE)
  21. `Phlex::Kit` was extended into #{mod.name}.
  22. You should only extend modules with `Phlex::Kit` as it is not compatible with classes.
  23. MESSAGE
  24. else: 3 else
  25. 3 mod.include(LazyLoader)
  26. end
  27. end
  28. 1 def method_missing(name, ...)
  29. 1 then: 1 if name[0] == name[0].upcase && constants.include?(name) && const_get(name) && methods.include?(name)
  30. 1 public_send(name, ...)
  31. else: 0 else
  32. super
  33. end
  34. end
  35. 1 def respond_to_missing?(name, include_private = false)
  36. (name[0] == name[0].upcase && constants.include?(name) && const_get(name) && methods.include?(name)) || super
  37. end
  38. 1 def const_added(name)
  39. 8 then: 2 else: 6 return if autoload?(name)
  40. 6 me = self
  41. 6 constant = const_get(name)
  42. 6 else: 0 case constant
  43. when: 5 when Class
  44. 5 then: 5 else: 0 if constant < Phlex::SGML
  45. 5 constant.include(me)
  46. 5 constant = nil
  47. 5 define_method(name) do |*args, **kwargs, &block|
  48. 5 constant = me.const_get(name)
  49. 5 render(constant.new(*args, **kwargs), &block)
  50. end
  51. 5 define_singleton_method(name) do |*args, **kwargs, &block|
  52. 3 component, fiber_id = Thread.current[:__phlex_component__]
  53. 3 then: 2 if (component && fiber_id == Fiber.current.object_id)
  54. 2 component.instance_exec do
  55. 2 constant = me.const_get(name)
  56. 2 render(constant.new(*args, **kwargs), &block)
  57. end
  58. else: 1 else
  59. 1 raise "You can't call `#{name}' outside of a Phlex rendering context."
  60. end
  61. end
  62. end
  63. when: 1 when Module
  64. 1 constant.extend(Phlex::Kit)
  65. end
  66. 6 super
  67. end
  68. end

lib/phlex/sgml.rb

83.69% lines covered

70.71% branches covered

233 relevant lines. 195 lines covered and 38 lines missed.
99 total branches, 70 branches covered and 29 branches missed.
    
  1. # frozen_string_literal: true
  2. # **Standard Generalized Markup Language** for behaviour common to {HTML} and {SVG}.
  3. 1 class Phlex::SGML
  4. 1 ERBCompiler = ERB::Compiler.new("<>").tap do |compiler|
  5. 1 compiler.pre_cmd = [""]
  6. 1 compiler.put_cmd = "@_state.buffer.<<"
  7. 1 compiler.insert_cmd = "__implicit_output__"
  8. 1 compiler.post_cmd = ["nil"]
  9. 1 def compiler.add_insert_cmd(out, content)
  10. 4 out.push("#{@insert_cmd}((#{content}))")
  11. end
  12. end
  13. 1 include Phlex::Helpers
  14. 1 class << self
  15. # Render the view to a String. Arguments are delegated to {.new}.
  16. 1 def call(...)
  17. 1360 new(...).call
  18. end
  19. # Create a new instance of the component.
  20. # @note The block will not be delegated to {#initialize}. Instead, it will be sent to {#view_template} when rendering.
  21. 1 def new(*a, **k, &block)
  22. 1620 then: 360 if block
  23. 360 object = super(*a, **k, &nil)
  24. 720 object.instance_exec { @_content_block = block }
  25. 360 object
  26. else: 1260 else
  27. 1260 super
  28. end
  29. end
  30. 1 def erb(method_name, erb = nil, locals: nil, &block)
  31. 2 loc = caller_locations(1, 1)[0]
  32. 2 path = loc.path.delete_suffix(".rb")
  33. 2 file = loc.path
  34. 2 line = loc.lineno - 1
  35. 2 else: 2 then: 0 unless erb
  36. method_path = "#{path}/#{method_name}.html.erb"
  37. sidecar_path = "#{path}.html.erb"
  38. then: 0 if File.exist?(method_path)
  39. erb = File.read(method_path)
  40. file = method_path
  41. else: 0 line = 1
  42. then: 0 elsif method_name == :view_template && File.exist?(sidecar_path)
  43. erb = File.read(sidecar_path)
  44. file = sidecar_path
  45. line = 1
  46. else: 0 else
  47. raise Phlex::RuntimeError.new(<<~MESSAGE)
  48. No ERB template found for #{method_name}
  49. MESSAGE
  50. end
  51. end
  52. 2 code, _enc = ERBCompiler.compile(erb)
  53. 2 class_eval(<<~RUBY, file, line)
  54. def #{method_name} #{locals}
  55. #{code}
  56. end
  57. RUBY
  58. end
  59. end
  60. 1 def view_template
  61. 194 then: 191 if block_given?
  62. 191 yield
  63. else: 3 else
  64. 3 plain "Phlex Warning: Your `#{self.class.name}` class doesn't define a `view_template` method. If you are upgrading to Phlex 2.x make sure to rename your `template` method to `view_template`. See: https://beta.phlex.fun/guides/v2-upgrade.html"
  65. end
  66. end
  67. 1 def to_proc
  68. 2 proc { |c| c.render(self) }
  69. end
  70. 1 def call(buffer = +"", context: {}, fragments: nil, &)
  71. 1597 state = Phlex::SGML::State.new(
  72. user_context: context,
  73. output_buffer: buffer,
  74. then: 14 else: 1583 fragments: fragments&.to_set,
  75. )
  76. 1597 internal_call(parent: nil, state:, &)
  77. 1550 state.output_buffer << state.buffer
  78. end
  79. 1 def internal_call(parent: nil, state: nil, &block)
  80. 1618 then: 1 else: 1617 if @_state
  81. 1 raise Phlex::DoubleRenderError.new(
  82. "You can't render a #{self.class.name} more than once."
  83. )
  84. end
  85. 1617 @_state = state
  86. 1617 else: 1615 then: 2 return "" unless render?
  87. 1615 block ||= @_content_block
  88. 1615 Thread.current[:__phlex_component__] = [self, Fiber.current.object_id].freeze
  89. 1615 state.around_render(self) do
  90. 1615 before_template(&block)
  91. 1615 around_template do
  92. 1615 then: 549 if block
  93. 549 view_template do |*args|
  94. 537 then: 344 if args.length > 0
  95. 344 __yield_content_with_args__(*args, &block)
  96. else: 193 else
  97. 193 __yield_content__(&block)
  98. end
  99. end
  100. else: 1066 else
  101. 1066 view_template
  102. end
  103. end
  104. 1563 after_template(&block)
  105. end
  106. ensure
  107. 1618 Thread.current[:__phlex_component__] = [parent, Fiber.current.object_id].freeze
  108. end
  109. 1 def context
  110. 6 then: 5 if rendering?
  111. 5 @_state.user_context
  112. else: 1 else
  113. 1 raise Phlex::ArgumentError.new(<<~MESSAGE)
  114. You can’t access the context before the component has started rendering.
  115. MESSAGE
  116. end
  117. end
  118. # Returns `false` before rendering and `true` once the component has started rendering.
  119. # It will not reset back to false after rendering.
  120. 1 def rendering?
  121. 6 !!@_state
  122. end
  123. # Output plain text.
  124. 1 def plain(content)
  125. 22 else: 21 then: 1 unless __text__(content)
  126. 1 raise Phlex::ArgumentError.new("You've passed an object to plain that is not handled by format_object. See https://rubydoc.info/gems/phlex/Phlex/SGML#format_object-instance_method for more information")
  127. end
  128. 21 nil
  129. end
  130. # Output a single space character. If a block is given, a space will be output before and after the block.
  131. 1 def whitespace(&)
  132. 8 state = @_state
  133. 8 else: 4 then: 4 return unless state.should_render?
  134. 4 buffer = state.buffer
  135. 4 buffer << " "
  136. 4 then: 2 else: 2 if block_given?
  137. 2 __yield_content__(&)
  138. 2 buffer << " "
  139. end
  140. 4 nil
  141. end
  142. # Wrap the output in an HTML comment.
  143. #
  144. # [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Comments)
  145. 1 def comment(&)
  146. 12 state = @_state
  147. 12 else: 4 then: 8 return unless state.should_render?
  148. 4 buffer = state.buffer
  149. 4 buffer << "<!-- "
  150. 4 __yield_content__(&)
  151. 4 buffer << " -->"
  152. 4 nil
  153. end
  154. # Output the given safe object as-is. You may need to use `safe` to mark a string as a safe object.
  155. 1 def raw(content)
  156. 6 case content
  157. when: 3 when Phlex::SGML::SafeObject
  158. 3 state = @_state
  159. 3 else: 3 then: 0 return unless state.should_render?
  160. 3 when: 2 state.buffer << content.to_s
  161. when nil, "" # do nothing
  162. else: 1 else
  163. 1 raise Phlex::ArgumentError.new("You passed an unsafe object to `raw`.")
  164. end
  165. 5 nil
  166. end
  167. # Capture the output of the block and returns it as a string.
  168. 1 def capture(*args, &block)
  169. 5 else: 5 then: 0 return "" unless block
  170. 5 then: 1 if args.length > 0
  171. 2 @_state.capture { __yield_content_with_args__(*args, &block) }
  172. else: 4 else
  173. 8 @_state.capture { __yield_content__(&block) }
  174. end
  175. end
  176. # Define a named fragment that can be selectively rendered.
  177. 1 def fragment(name)
  178. 39 state = @_state
  179. 39 state.begin_fragment(name)
  180. 39 yield
  181. 39 state.end_fragment(name)
  182. 33 nil
  183. end
  184. # Mark the given string as safe for HTML output.
  185. 1 def safe(value)
  186. 8 case value
  187. when: 7 when String
  188. 7 Phlex::SGML::SafeValue.new(value)
  189. else: 1 else
  190. 1 raise Phlex::ArgumentError.new("Expected a String.")
  191. end
  192. end
  193. 1 alias_method :🦺, :safe
  194. # Flush the current state to the output buffer.
  195. 1 def flush
  196. 6 @_state.flush
  197. end
  198. 1 def render(renderable = nil, &)
  199. 23 case renderable
  200. when: 21 when Phlex::SGML
  201. 21 renderable.internal_call(state: @_state, parent: self, &)
  202. when: 1 when Class
  203. 1 then: 1 else: 0 if renderable < Phlex::SGML
  204. 1 render(renderable.new, &)
  205. end
  206. when: 0 when Enumerable
  207. renderable.each { |r| render(r, &) }
  208. when: 0 when Proc, Method
  209. then: 0 if renderable.arity == 0
  210. __yield_content_with_no_yield_args__(&renderable)
  211. else: 0 else
  212. __yield_content__(&renderable)
  213. end
  214. when: 0 when String
  215. plain(renderable)
  216. when: 1 when nil
  217. 1 then: 1 else: 0 __yield_content__(&) if block_given?
  218. else: 0 else
  219. raise Phlex::ArgumentError.new("You can't render a #{renderable.inspect}.")
  220. end
  221. 23 nil
  222. end
  223. # Cache a block of content.
  224. #
  225. # ```ruby
  226. # @products.each do |product|
  227. # cache product do
  228. # h1 { product.name }
  229. # end
  230. # end
  231. # ```
  232. 1 def cache(*cache_key, **, &content)
  233. 25 location = caller_locations(1, 1)[0]
  234. full_key = [
  235. 25 app_version_key, # invalidates the key when deploying new code in case of changes
  236. self.class.name, # prevents collisions between classes
  237. 25 then: 0 else: 25 (self.class.object_id if enable_cache_reloading?), # enables reloading
  238. location.base_label, # prevents collisions between different methods
  239. location.lineno, # prevents collisions between different lines
  240. cache_key, # allows for custom cache keys
  241. ].freeze
  242. 25 low_level_cache(full_key, **, &content)
  243. 25 nil
  244. end
  245. # Cache a block of content where you control the entire cache key.
  246. # If you really know what you’re doing and want to take full control
  247. # and responsibility for the cache key, use this method.
  248. #
  249. # ```ruby
  250. # low_level_cache([Commonmarker::VERSION, Digest::MD5.hexdigest(@content)]) do
  251. # markdown(@content)
  252. # end
  253. # ```
  254. #
  255. # Note: To allow you more control, this method does not take a splat of cache keys.
  256. # If you need to pass multiple cache keys, you should pass an array.
  257. 1 def low_level_cache(cache_key, **options, &content)
  258. 25 state = @_state
  259. 44 cached_buffer, fragment_map = cache_store.fetch(cache_key, **options) { state.caching(&content) }
  260. 25 then: 18 if state.should_render?
  261. 18 fragment_map.each do |fragment_name, (offset, length, nested_fragments)|
  262. 38 state.record_fragment(fragment_name, offset, length, nested_fragments)
  263. end
  264. 18 state.buffer << cached_buffer
  265. else: 7 else
  266. 7 fragment_map.each do |fragment_name, (offset, length, nested_fragments)|
  267. 21 then: 7 else: 14 if state.fragments.include?(fragment_name)
  268. 7 state.fragments.delete(fragment_name)
  269. 7 state.fragments.subtract(nested_fragments)
  270. 7 state.buffer << cached_buffer.byteslice(offset, length)
  271. end
  272. end
  273. end
  274. 25 nil
  275. end
  276. 1 def json_escape(string)
  277. ERB::Util.json_escape(string)
  278. end
  279. # Override this method to use a different deployment key.
  280. 1 private def app_version_key
  281. 25 Phlex::DEPLOYED_AT
  282. end
  283. # Override this method to use a different cache store.
  284. 1 private def cache_store
  285. raise "Cache store not implemented."
  286. end
  287. 1 private def enable_cache_reloading?
  288. 25 false
  289. end
  290. 1 private def vanish(...)
  291. capture(...)
  292. nil
  293. end
  294. 1 private def render?
  295. 1613 true
  296. end
  297. 1 private def format_object(object)
  298. 29 else: 3 case object
  299. when: 26 when Float, Integer
  300. 26 object.to_s
  301. end
  302. end
  303. 1 private def around_template
  304. 1615 yield
  305. 1563 nil
  306. end
  307. 1 private def before_template
  308. 1614 nil
  309. end
  310. 1 private def after_template
  311. 1562 nil
  312. end
  313. 1 private def __yield_content__
  314. 539 else: 539 then: 0 return unless block_given?
  315. 539 buffer = @_state.buffer
  316. 539 original_length = buffer.bytesize
  317. 539 content = yield(self)
  318. 507 then: 10 else: 497 __implicit_output__(content) if original_length == buffer.bytesize
  319. 507 nil
  320. end
  321. 1 private def __yield_content_with_no_yield_args__
  322. else: 0 then: 0 return unless block_given?
  323. buffer = @_state.buffer
  324. original_length = buffer.bytesize
  325. content = yield # <-- doesn’t yield self 😉
  326. then: 0 else: 0 __implicit_output__(content) if original_length == buffer.bytesize
  327. nil
  328. end
  329. 1 private def __yield_content_with_args__(*a)
  330. 345 else: 345 then: 0 return unless block_given?
  331. 345 buffer = @_state.buffer
  332. 345 original_length = buffer.bytesize
  333. 345 content = yield(*a)
  334. 345 then: 338 else: 7 __implicit_output__(content) if original_length == buffer.bytesize
  335. 345 nil
  336. end
  337. 1 private def __implicit_output__(content)
  338. 352 state = @_state
  339. 352 else: 349 then: 3 return true unless state.should_render?
  340. 349 case content
  341. when: 1 when Phlex::SGML::SafeObject
  342. 1 state.buffer << content.to_s
  343. when: 346 when String
  344. 346 state.buffer << Phlex::Escape.html_escape(content)
  345. when: 0 when Symbol
  346. state.buffer << Phlex::Escape.html_escape(content.name)
  347. when: 2 when nil
  348. nil
  349. else: 0 else
  350. then: 0 if (formatted_object = format_object(content))
  351. state.buffer << Phlex::Escape.html_escape(formatted_object)
  352. else: 0 else
  353. return false
  354. end
  355. end
  356. 349 true
  357. end
  358. # same as __implicit_output__ but escapes even `safe` objects
  359. 1 private def __text__(content)
  360. 22 state = @_state
  361. 22 else: 21 then: 1 return true unless state.should_render?
  362. 21 case content
  363. when: 16 when String
  364. 16 state.buffer << Phlex::Escape.html_escape(content)
  365. when: 1 when Symbol
  366. 1 state.buffer << Phlex::Escape.html_escape(content.name)
  367. when: 1 when nil
  368. nil
  369. else: 3 else
  370. 3 then: 2 if (formatted_object = format_object(content))
  371. 2 state.buffer << Phlex::Escape.html_escape(formatted_object)
  372. else: 1 else
  373. 1 return false
  374. end
  375. end
  376. 20 true
  377. end
  378. 1 private def __render_attributes__(attributes)
  379. 1 state = @_state
  380. 1 else: 1 then: 0 return unless state.should_render?
  381. 1 state.buffer << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
  382. end
  383. 1 private_class_method def self.method_added(method_name)
  384. 740 then: 704 else: 36 if method_name == :view_template
  385. 704 location = instance_method(method_name).source_location[0]
  386. 704 then: 703 else: 1 if location[0] in "/" | "."
  387. 703 Phlex.__expand_attribute_cache__(location)
  388. end
  389. end
  390. 740 super
  391. end
  392. 1 def self.__compile__(method_name)
  393. path, line = instance_method(method_name).source_location
  394. Phlex::Compiler::Method.new(self, path, line, method_name).compile
  395. end
  396. 1 def __map_exception__(exception)
  397. exception.set_backtrace(
  398. exception.backtrace_locations.map do |loc|
  399. then: 0 if ((map = Phlex::Compiler::MAP[loc.path]) && (line = map[loc.lineno]))
  400. "[Phlex] #{loc.path}:#{line}:#{loc.label}"
  401. else: 0 else
  402. "#{loc.path}:#{loc.lineno}:#{loc.label}"
  403. end
  404. end
  405. )
  406. exception
  407. end
  408. end

lib/phlex/sgml/attributes.rb

97.06% lines covered

94.87% branches covered

136 relevant lines. 132 lines covered and 4 lines missed.
117 total branches, 111 branches covered and 6 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Phlex::SGML::Attributes
  3. 1 extend self
  4. 1 UNSAFE_ATTRIBUTES = Set.new(%w[srcdoc sandbox http-equiv]).freeze
  5. 1 REF_ATTRIBUTES = Set.new(%w[href src action formaction lowsrc dynsrc background ping]).freeze
  6. 1 def generate_attributes(attributes, buffer = +"")
  7. 223 attributes.each do |k, v|
  8. 283 else: 271 then: 12 next unless v
  9. 271 when: 18 name = case k
  10. 18 when: 252 when String then k
  11. 252 else: 1 when Symbol then k.name.tr("_", "-")
  12. 1 else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols.")
  13. end
  14. 270 value = case v
  15. when: 17 when true
  16. 17 true
  17. when: 114 when String
  18. 114 v.gsub('"', "&quot;")
  19. when: 12 when Symbol
  20. 12 v.name.tr("_", "-").gsub('"', "&quot;")
  21. when: 11 when Integer, Float
  22. 11 v.to_s
  23. when: 1 when Date
  24. 1 v.iso8601
  25. when: 1 when Time
  26. 1 then: 1 else: 0 v.respond_to?(:iso8601) ? v.iso8601 : v.strftime("%Y-%m-%dT%H:%M:%S%:z")
  27. when: 65 when Hash
  28. 65 case k
  29. when: 10 when :style
  30. 10 generate_styles(v).gsub('"', "&quot;")
  31. else: 55 else
  32. 55 generate_nested_attributes(v, "#{name}-", buffer)
  33. end
  34. when: 27 when Array
  35. 27 case k
  36. when: 8 when :style
  37. 8 generate_styles(v).gsub('"', "&quot;")
  38. else: 19 else
  39. 19 generate_nested_tokens(v)
  40. end
  41. when: 19 when Set
  42. 19 case k
  43. when: 2 when :style
  44. 2 generate_styles(v).gsub('"', "&quot;")
  45. else: 17 else
  46. 17 generate_nested_tokens(v.to_a)
  47. end
  48. when: 2 when Phlex::SGML::SafeObject
  49. 2 v.to_s.gsub('"', "&quot;")
  50. else: 1 else
  51. 1 raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.")
  52. end
  53. 252 lower_name = name.downcase
  54. 252 else: 2 then: 250 unless Phlex::SGML::SafeObject === v
  55. 250 normalized_name = lower_name.delete("^a-z-")
  56. 250 then: 19 else: 231 if value != true && REF_ATTRIBUTES.include?(normalized_name)
  57. 19 case value
  58. when: 18 when String
  59. 18 else: 12 if value.downcase.delete("^a-z:").start_with?("javascript:")
  60. then: 6 # We just ignore these because they were likely not specified by the developer.
  61. 6 next
  62. end
  63. else: 1 else
  64. 1 raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.")
  65. end
  66. end
  67. 243 then: 1 else: 242 if normalized_name.bytesize > 2 && normalized_name.start_with?("on") && !normalized_name.include?("-")
  68. 1 raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
  69. end
  70. 242 then: 0 else: 242 if UNSAFE_ATTRIBUTES.include?(normalized_name)
  71. raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
  72. end
  73. end
  74. 244 then: 5 else: 239 if name.match?(/[<>&"']/)
  75. 5 raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
  76. end
  77. 239 then: 3 else: 236 if lower_name.to_sym == :id && k != :id
  78. 3 raise Phlex::ArgumentError.new(":id attribute should only be passed as a lowercase symbol.")
  79. end
  80. 236 else: 53 case value
  81. when: 11 when true
  82. 11 buffer << " " << name
  83. when: 172 when String
  84. 172 buffer << " " << name << '="' << value << '"'
  85. end
  86. end
  87. 194 buffer
  88. end
  89. # Provides the nested-attributes case for serializing out attributes.
  90. # This allows us to skip many of the checks the `__attributes__` method must perform.
  91. 1 def generate_nested_attributes(attributes, base_name, buffer = +"")
  92. 57 attributes.each do |k, v|
  93. 58 else: 55 then: 3 next unless v
  94. 55 then: 2 if (root_key = (:_ == k))
  95. 2 name = ""
  96. 2 original_base_name = base_name
  97. 2 base_name = base_name.delete_suffix("-")
  98. else: 53 else
  99. 53 when: 7 name = case k
  100. 7 when: 45 when String then k
  101. 45 else: 1 when Symbol then k.name.tr("_", "-")
  102. 1 else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols")
  103. end
  104. 52 then: 10 else: 42 if name.match?(/[<>&"']/)
  105. 10 raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
  106. end
  107. end
  108. 44 case v
  109. when: 3 when true
  110. 3 buffer << " " << base_name << name
  111. when: 15 when String
  112. 15 buffer << " " << base_name << name << '="' << v.gsub('"', "&quot;") << '"'
  113. when: 6 when Symbol
  114. 6 buffer << " " << base_name << name << '="' << v.name.tr("_", "-").gsub('"', "&quot;") << '"'
  115. when: 8 when Integer, Float
  116. 8 buffer << " " << base_name << name << '="' << v.to_s << '"'
  117. when: 2 when Hash
  118. 2 generate_nested_attributes(v, "#{base_name}#{name}-", buffer)
  119. when: 3 when Array
  120. 3 then: 1 else: 2 if (value = generate_nested_tokens(v))
  121. 1 buffer << " " << base_name << name << '="' << value << '"'
  122. end
  123. when: 5 when Set
  124. 5 then: 1 else: 4 if (value = generate_nested_tokens(v.to_a))
  125. 1 buffer << " " << base_name << name << '="' << value << '"'
  126. end
  127. when: 1 when Phlex::SGML::SafeObject
  128. 1 buffer << " " << base_name << name << '="' << v.to_s.gsub('"', "&quot;") << '"'
  129. else: 1 else
  130. 1 raise Phlex::ArgumentError.new("Invalid attribute value #{v.inspect}.")
  131. end
  132. 43 then: 2 else: 41 if root_key
  133. 2 base_name = original_base_name
  134. end
  135. 43 buffer
  136. end
  137. end
  138. 1 def generate_nested_tokens(tokens, sep = " ", gsub_from = nil, gsub_to = "")
  139. 71 buffer = +""
  140. 71 i, length = 0, tokens.length
  141. 71 body: 126 while i < length
  142. 126 token = tokens[i]
  143. 126 case token
  144. when: 44 when String
  145. 44 then: 23 else: 21 token = token.gsub(gsub_from, gsub_to) if gsub_from
  146. 44 then: 22 if i > 0
  147. 22 buffer << sep << token
  148. else: 22 else
  149. 22 buffer << token
  150. end
  151. when: 18 when Symbol
  152. 18 then: 12 if i > 0
  153. 12 buffer << sep << token.name.tr("_", "-")
  154. else: 6 else
  155. 6 buffer << token.name.tr("_", "-")
  156. end
  157. when: 24 when Integer, Float, Phlex::SGML::SafeObject
  158. 24 then: 14 if i > 0
  159. 14 buffer << sep << token.to_s
  160. else: 10 else
  161. 10 buffer << token.to_s
  162. end
  163. when: 12 when Array
  164. 12 then: 5 else: 7 if token.length > 0 && (value = generate_nested_tokens(token, sep, gsub_from, gsub_to))
  165. 5 then: 4 if i > 0
  166. 4 buffer << sep << value
  167. else: 1 else
  168. 1 buffer << value
  169. end
  170. end
  171. when: 6 when Set
  172. 6 then: 0 else: 6 if token.length > 0 && (value = generate_nested_tokens(token.to_a, sep, gsub_from, gsub_to))
  173. then: 0 if i > 0
  174. buffer << sep << value
  175. else: 0 else
  176. buffer << value
  177. end
  178. when: 20 end
  179. when nil
  180. # Do nothing
  181. else: 2 else
  182. 2 raise Phlex::ArgumentError.new("Invalid token type: #{token.class}.")
  183. end
  184. 124 i += 1
  185. end
  186. 69 then: 28 else: 41 return if buffer.empty?
  187. 41 buffer.gsub('"', "&quot;")
  188. end
  189. # The result is unsafe so should be escaped.
  190. 1 def generate_styles(styles)
  191. 22 else: 0 case styles
  192. when: 10 when Array, Set
  193. 10 styles.filter_map do |s|
  194. 14 case s
  195. when: 5 when String
  196. 5 then: 1 if s == "" || s.end_with?(";")
  197. 1 s
  198. else: 4 else
  199. 4 "#{s};"
  200. end
  201. when: 2 when Phlex::SGML::SafeObject
  202. 2 value = s.to_s
  203. 2 then: 1 else: 1 value.end_with?(";") ? value : "#{value};"
  204. when: 2 when Hash
  205. 2 next generate_styles(s)
  206. when: 4 when nil
  207. 4 next nil
  208. else: 1 else
  209. 1 raise Phlex::ArgumentError.new("Invalid style: #{s.inspect}.")
  210. end
  211. end.join(" ")
  212. when: 12 when Hash
  213. 12 buffer = +""
  214. 12 i = 0
  215. 12 styles.each do |k, v|
  216. 14 prop = case k
  217. when: 1 when String
  218. 1 k
  219. when: 12 when Symbol
  220. 12 k.name.tr("_", "-")
  221. else: 1 else
  222. 1 raise Phlex::ArgumentError.new("Style keys should be Strings or Symbols.")
  223. end
  224. 13 value = case v
  225. when: 5 when String
  226. 5 v
  227. when: 2 when Symbol
  228. 2 v.name.tr("_", "-")
  229. when: 4 when Integer, Float, Phlex::SGML::SafeObject
  230. 4 v.to_s
  231. when: 1 when nil
  232. 1 nil
  233. else: 1 else
  234. 1 raise Phlex::ArgumentError.new("Invalid style value: #{v.inspect}")
  235. end
  236. 12 then: 11 else: 1 if value
  237. 11 then: 9 if i == 0
  238. 9 buffer << prop << ": " << value << ";"
  239. else: 2 else
  240. 2 buffer << " " << prop << ": " << value << ";"
  241. end
  242. end
  243. 12 i += 1
  244. end
  245. 10 buffer
  246. end
  247. end
  248. end

lib/phlex/sgml/elements.rb

94.29% lines covered

26.26% branches covered

70 relevant lines. 66 lines covered and 4 lines missed.
3016 total branches, 792 branches covered and 2224 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Phlex::SGML::Elements
  3. 1 COMMA_SEPARATED_TOKENS = {
  4. img: <<~RUBY,
  5. if Array === (srcset_attribute = attributes[:srcset])
  6. attributes[:srcset] = Phlex::SGML::Attributes.generate_nested_tokens(srcset_attribute, ", ", ",", "%2C")
  7. end
  8. RUBY
  9. link: <<~RUBY,
  10. if Array === (media_attribute = attributes[:media])
  11. attributes[:media] = Phlex::SGML::Attributes.generate_nested_tokens(media_attribute, ", ", ",", "%2C")
  12. end
  13. if Array === (sizes_attribute = attributes[:sizes])
  14. attributes[:sizes] = Phlex::SGML::Attributes.generate_nested_tokens(sizes_attribute, ", ", ",", "%2C")
  15. end
  16. if Array === (imagesrcset_attribute = attributes[:imagesrcset])
  17. rel_attribute = attributes[:rel] || attributes["rel"]
  18. as_attribute = attributes[:as] || attributes["as"]
  19. if ("preload" == rel_attribute || :preload == rel_attribute) && ("image" == as_attribute || :image == as_attribute)
  20. attributes[:imagesrcset] = Phlex::SGML::Attributes.generate_nested_tokens(imagesrcset_attribute, ", ", ",", "%2C")
  21. end
  22. end
  23. RUBY
  24. input: <<~RUBY,
  25. if Array === (accept_attribute = attributes[:accept])
  26. type_attribute = attributes[:type] || attributes["type"]
  27. if "file" == type_attribute || :file == type_attribute
  28. attributes[:accept] = Phlex::SGML::Attributes.generate_nested_tokens(accept_attribute, ", ", ",", "%2C")
  29. end
  30. end
  31. RUBY
  32. }.freeze
  33. 1 def __registered_elements__
  34. 1032 @__registered_elements__ ||= {}
  35. end
  36. 1 def register_element(method_name, tag: method_name.name.tr("_", "-"))
  37. 167 class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
  38. # frozen_string_literal: true
  39. 1 def #{method_name}(**attributes)
  40. 2223 state = @_state
  41. 2223 buffer = state.buffer
  42. 2223 block_given = block_given?
  43. 2223 else: 15 then: 0 else: 4 then: 0 else: 4 then: 0 else: 8 then: 0 else: 4 then: 0 else: 8 then: 0 else: 8 then: 0 else: 4 then: 0 else: 5 then: 0 else: 16 then: 6 else: 4 then: 0 else: 11 then: 1 else: 7 then: 1 else: 17 then: 1 else: 4 then: 0 else: 4 then: 0 else: 8 then: 0 else: 4 then: 0 else: 10 then: 2 else: 8 then: 0 else: 8 then: 0 else: 11 then: 1 else: 11 then: 0 else: 8 then: 0 else: 4 then: 0 else: 8 then: 0 else: 4 then: 0 else: 12 then: 1 else: 12 then: 0 else: 9 then: 1 else: 5 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 8 then: 0 else: 7 then: 1 else: 4 then: 0 else: 8 then: 0 else: 12 then: 0 else: 9 then: 0 else: 52 then: 0 else: 8 then: 0 else: 5 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 13 then: 0 else: 4 then: 0 else: 4 then: 0 else: 7 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 4 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 6 then: 0 else: 3 then: 0 unless state.should_render?
  44. 20 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 7 else: 101 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 7 else: 1 then: 164 else: 9 then: 0 else: 0 then: 0 else: 0 then: 9 else: 0 then: 7 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 13 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 17 else: 5 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 8 else: 3 then: 4 else: 0 then: 6 else: 502 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 7 else: 7 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 6 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 then: 3 else: 9 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 yield(self) if block_given
  45. 15 return nil
  46. end
  47. 2203 then: 15 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 5 then: 7 then: 4 then: 4 then: 4 then: 4 then: 4 then: 8 then: 4 then: 4 then: 4 then: 7 then: 7 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 11 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 9 then: 4 then: 18 then: 10 then: 4 then: 4 then: 15 then: 10 then: 8 then: 4 then: 104 then: 4 then: 504 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 10 then: 3 then: 9 then: 3 then: 3 then: 7 then: 18 then: 6 then: 3 then: 3 then: 3 then: 6 then: 6 then: 3 then: 6 then: 6 then: 9 then: 6 then: 3 then: 9 then: 6 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 6 then: 3 then: 6 then: 3 then: 6 then: 3 then: 3 if attributes.length > 0 # with attributes
  48. 1384 then: 11 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 15 then: 5 then: 4 then: 4 then: 4 then: 8 then: 14 then: 12 then: 4 then: 4 then: 7 then: 16 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 11 then: 4 then: 4 then: 4 then: 12 then: 8 then: 4 then: 7 then: 4 then: 4 then: 4 then: 12 then: 8 then: 4 then: 4 then: 8 then: 14 then: 8 then: 508 then: 4 then: 8 then: 104 then: 4 then: 504 then: 12 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 10 then: 16 then: 3 then: 6 then: 3 then: 12 then: 3 then: 6 then: 9 then: 6 then: 3 then: 3 then: 9 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 6 then: 6 then: 3 then: 3 then: 3 then: 3 if block_given # with content block
  49. 1232 buffer << "<#{tag}"
  50. begin
  51. #{COMMA_SEPARATED_TOKENS[method_name]}
  52. 1232 buffer << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
  53. ensure
  54. 1232 buffer << ">"
  55. end
  56. begin
  57. 1232 original_length = buffer.bytesize
  58. 1232 content = yield(self)
  59. 1232 then: 6 else: 1 then: 3 else: 1 then: 3 else: 4 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 2 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 6 else: 2 then: 3 else: 1 then: 4 else: 12 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 11 else: 5 then: 13 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 4 then: 6 else: 2 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 7 else: 1 then: 6 else: 5 then: 3 else: 1 then: 3 else: 1 then: 11 else: 4 then: 3 else: 1 then: 7 else: 4 then: 3 else: 4 then: 9 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 15 else: 1 then: 3 else: 1 then: 3 else: 0 then: 3 else: 0 then: 3 else: 3 then: 3 else: 3 then: 6 else: 3 then: 3 else: 0 then: 9 else: 0 then: 3 else: 0 then: 6 else: 0 then: 6 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 if original_length == buffer.bytesize
  60. 507 case content
  61. when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 6 when: 0 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 4 when: 4 when: 0 when: 104 when: 0 when: 0 when: 0 when: 0 when: 0 when: 3 when: 6 when: 0 when: 3 when: 0 when: 0 when: 0 when: 3 when: 3 when: 3 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 0 when ::Phlex::SGML::SafeObject
  62. buffer << content.to_s
  63. when: 6 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 6 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 506 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 6 when: 3 when: 3 when: 7 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 6 when: 3 when: 3 when: 6 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 9 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when String
  64. 506 buffer << ::Phlex::Escape.html_escape(content)
  65. when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when Symbol
  66. buffer << ::Phlex::Escape.html_escape(content.name)
  67. when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when nil
  68. nil
  69. else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else
  70. 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 13 else: 9 then: 0 else: 0 then: 0 else: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 1 then: 4 else: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 6 else: 0 then: 1 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 7 else: 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 else: 0 then: 3 else: 0 then: 0 else: 3 then: 0 else: 3 else: 0 then: 9 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 9 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 if (formatted_object = format_object(content))
  71. buffer << ::Phlex::Escape.html_escape(formatted_object)
  72. end
  73. end
  74. end
  75. ensure
  76. 1232 buffer << "</#{tag}>"
  77. end
  78. else: 8 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 17 else: 0 else: 0 else: 0 else: 0 else: 0 else: 5 else: 3 else: 0 else: 0 else: 1 else: 140 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 6 else: 0 else: 1 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else # without content
  79. 152 buffer << "<#{tag}"
  80. begin
  81. #{COMMA_SEPARATED_TOKENS[method_name]}
  82. 152 buffer << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
  83. ensure
  84. 152 buffer << "></#{tag}>"
  85. end
  86. end
  87. else: 0 else: 0 else: 0 else: 4 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 1 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 133 else: 2 else: 1 else: 6 else: 0 else: 2 else: 0 else: 0 else: 2 else: 7 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 8 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 2 else: 100 else: 0 else: 10 else: 0 else: 0 else: 3 else: 0 else: 3 else: 0 else: 0 else: 0 else: 6 else: 6 else: 0 else: 0 else: 0 else: 0 else: 6 else: 3 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else # without attributes
  88. 819 then: 0 then: 0 then: 0 then: 4 then: 3 then: 0 then: 0 then: 4 then: 0 then: 0 then: 0 then: 0 then: 4 then: 0 then: 0 then: 0 then: 3 then: 1 then: 3 then: 0 then: 0 then: 0 then: 0 then: 0 then: 140 then: 1 then: 3 then: 0 then: 0 then: 2 then: 0 then: 0 then: 2 then: 7 then: 0 then: 0 then: 0 then: 0 then: 0 then: 24 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 4 then: 18 then: 7 then: 3 then: 0 then: 500 then: 3 then: 0 then: 0 then: 0 then: 0 then: 1 then: 0 then: 0 then: 0 then: 0 then: 0 then: 4 then: 0 then: 0 then: 0 then: 2 then: 100 then: 0 then: 10 then: 0 then: 12 then: 6 then: 3 then: 3 then: 3 then: 0 then: 0 then: 3 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 6 then: 0 then: 0 then: 0 then: 3 then: 3 then: 0 then: 0 then: 3 then: 3 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 3 then: 0 then: 0 then: 0 then: 1 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 if block_given # with content block
  89. 812 buffer << "<#{tag}>"
  90. begin
  91. 812 original_length = buffer.bytesize
  92. 812 content = yield(self)
  93. 813 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 4 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 2 else: 1 then: 0 else: 0 then: 1 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 19 else: 3 then: 2 else: 0 then: 1 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 2 then: 0 else: 0 then: 0 else: 0 then: 7 else: 26 then: 11 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 24 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 8 else: 509 then: 0 else: 0 then: 0 else: 3 then: 0 else: 12 then: 0 else: 0 then: 500 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 2 else: 0 then: 0 else: 100 then: 0 else: 0 then: 1 else: 9 then: 0 else: 0 then: 3 else: 0 then: 3 else: 3 then: 0 else: 0 then: 6 else: 0 then: 3 else: 6 then: 0 else: 0 then: 6 else: 0 then: 0 else: 0 then: 0 else: 0 then: 6 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 if original_length == buffer.bytesize
  94. 681 case content
  95. when: 9 when: 0 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 0 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 157 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 8 when: 0 when: 4 when: 0 when: 0 when: 4 when: 108 when: 0 when: 0 when: 4 when: 504 when: 8 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 12 when: 6 when: 6 when: 0 when: 0 when: 0 when: 0 when: 6 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 6 when: 0 when: 3 when: 3 when: 3 when: 0 when: 3 when: 6 when: 0 when: 0 when: 0 when: 3 when: 3 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when ::Phlex::SGML::SafeObject
  96. 1 buffer << content.to_s
  97. when: 0 when: 0 when: 0 when: 0 when: 0 when: 1 when: 0 when: 0 when: 0 when: 132 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 9 when: 0 when: 0 when: 0 when: 2 when: 0 when: 1 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 7 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 2 when: 0 when: 0 when: 5 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 2 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when String
  98. 653 buffer << ::Phlex::Escape.html_escape(content)
  99. when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when Symbol
  100. buffer << ::Phlex::Escape.html_escape(content.name)
  101. when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 2 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when nil
  102. nil
  103. else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 24 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else
  104. 25 then: 0 else: 0 then: 5 else: 0 then: 0 else: 0 then: 0 else: 0 then: 156 else: 8 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 135 else: 2 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 24 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 3 else: 1 then: 0 else: 0 then: 0 else: 0 then: 4 else: 2 then: 8 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 1 then: 0 else: 0 then: 12 else: 0 then: 0 else: 0 then: 6 else: 0 then: 3 else: 0 then: 6 else: 0 then: 0 else: 0 then: 0 else: 3 then: 15 else: 0 then: 0 else: 0 then: 0 else: 0 then: 9 else: 3 then: 0 else: 0 then: 6 else: 6 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 9 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 if (formatted_object = format_object(content))
  105. 24 buffer << ::Phlex::Escape.html_escape(formatted_object)
  106. end
  107. end
  108. end
  109. ensure
  110. 812 buffer << "</#{tag}>"
  111. end
  112. else: 0 else: 0 else: 0 else: 0 else: 5 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 5 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 4 else: 0 else: 1 else: 0 else: 3 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 3 else: 0 else: 1 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else # without content
  113. 7 buffer << "<#{tag}></#{tag}>"
  114. end
  115. end
  116. 6 then: 4 else: 166 #{'flush' if tag == 'head'}
  117. 2173 nil
  118. end
  119. RUBY
  120. 167 __registered_elements__[method_name] = tag
  121. 167 method_name
  122. end
  123. 1 def __register_void_element__(method_name, tag: method_name.name.tr("_", "-"))
  124. 12 class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
  125. # frozen_string_literal: true
  126. 1 def #{method_name}(**attributes)
  127. 68 state = @_state
  128. 68 else: 21 then: 4 else: 17 then: 0 else: 10 then: 0 else: 16 then: 0 return unless state.should_render?
  129. 64 buffer = state.buffer
  130. 64 then: 10 then: 4 then: 3 then: 3 then: 11 then: 8 then: 13 then: 3 if attributes.length > 0 # with attributes
  131. 61 buffer << "<#{tag}"
  132. begin
  133. 32 then: 3 else: 8 then: 7 else: 14 #{COMMA_SEPARATED_TOKENS[method_name]}
  134. 39 buffer << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
  135. ensure
  136. 33 buffer << ">"
  137. 28 then: 2 else: 11 end
  138. 2 else: 0 else: 3 else: 0 else: 0 else: 0 else # without attributes
  139. 14 buffer << "<#{tag}>"
  140. end
  141. 21 else: 0 then: 8 else: 9
  142. 36 nil
  143. 12 end
  144. RUBY
  145. 15 else: 6 then: 7 else: 0
  146. 8 __registered_elements__[method_name] = tag
  147. 12 method_name
  148. 8 end
  149. 13 end

lib/phlex/sgml/safe_object.rb

100.0% lines covered

100.0% branches covered

1 relevant lines. 1 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # @api private
  3. 1 module Phlex::SGML::SafeObject
  4. # This is included in objects that are safe to render in an SGML context.
  5. # They must implement a `to_s` method that returns a string.
  6. end

lib/phlex/sgml/safe_value.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::SGML::SafeValue
  3. 1 include Phlex::SGML::SafeObject
  4. 1 def initialize(to_s)
  5. 13 @to_s = to_s
  6. end
  7. 1 attr_reader :to_s
  8. end

lib/phlex/sgml/state.rb

100.0% lines covered

95.45% branches covered

74 relevant lines. 74 lines covered and 0 lines missed.
22 total branches, 21 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::SGML::State
  3. 1 def initialize(user_context: {}, output_buffer:, fragments:)
  4. 1597 @buffer = +""
  5. 1597 @capturing = false
  6. 1597 @user_context = user_context
  7. 1597 @fragments = fragments
  8. 1597 @fragment_depth = 0
  9. 1597 @cache_stack = []
  10. 1597 @halt_signal = nil
  11. 1597 @output_buffer = output_buffer
  12. end
  13. 1 attr_accessor :capturing, :user_context
  14. 1 attr_reader :fragments, :fragment_depth, :output_buffer, :buffer
  15. 1 def around_render(component)
  16. 1615 stack = @stack
  17. 1615 then: 1601 if !@fragments || @halt_signal
  18. 1601 yield
  19. else: 14 else
  20. 14 catch do |signal|
  21. 14 @halt_signal = signal
  22. 14 yield
  23. end
  24. end
  25. end
  26. 1 def should_render?
  27. 3443 !@fragments || @fragment_depth > 0
  28. end
  29. 1 def begin_fragment(id)
  30. 39 then: 10 else: 29 then: 7 else: 32 @fragment_depth += 1 if @fragments&.include?(id)
  31. 39 then: 26 else: 13 if caching?
  32. 26 current_byte_offset = 0 # Start tracking the byte offset of this fragment from the start of the cache buffer
  33. 26 @cache_stack.reverse_each do |(cache_buffer, fragment_map)| # We'll iterate deepest to shallowest
  34. 42 current_byte_offset += cache_buffer.bytesize # Add the length of the cache buffer to the current byte offset
  35. 42 fragment_map[id] = [current_byte_offset, nil, []] # Record the byte offset, length, and store a list of the nested fragments
  36. 42 fragment_map.each do |name, (_offset, length, nested_fragments)| # Iterate over the other fragments
  37. 74 then: 42 else: 32 next if name == id || length # Skip if it's the current fragment, or if the fragment has already ended
  38. 32 nested_fragments << id # Add the current fragment to the list of nested fragments
  39. end
  40. end
  41. end
  42. end
  43. 1 def end_fragment(id)
  44. 39 then: 26 else: 13 if caching?
  45. 26 byte_length = nil
  46. 26 @cache_stack.reverse_each do |(cache_buffer, fragment_map)| # We'll iterate deepest to shallowest
  47. 42 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
  48. 42 fragment_map[id][1] = byte_length # All cache contexts will use the same by
  49. end
  50. end
  51. 39 then: 10 else: 29 else: 7 then: 32 return unless @fragments&.include?(id)
  52. 7 @fragments.delete(id)
  53. 7 @fragment_depth -= 1
  54. 7 then: 6 else: 1 throw @halt_signal if @fragments.length == 0
  55. end
  56. 1 def record_fragment(id, offset, length, nested_fragments)
  57. 38 else: 20 then: 18 return unless caching?
  58. 20 @cache_stack.reverse_each do |(cache_buffer, fragment_map)|
  59. 20 offset += cache_buffer.bytesize
  60. 20 fragment_map[id] = [offset, length, nested_fragments]
  61. end
  62. end
  63. 1 def caching(&)
  64. 19 result = nil
  65. 19 capture do
  66. 19 @cache_stack.push([buffer, {}].freeze)
  67. 19 yield
  68. 19 result = @cache_stack.pop
  69. end
  70. 19 result
  71. end
  72. 1 def caching?
  73. 116 @cache_stack.length > 0
  74. end
  75. 1 def capture
  76. 24 new_buffer = +""
  77. 24 original_buffer = @buffer
  78. 24 original_capturing = @capturing
  79. 24 original_fragments = @fragments
  80. begin
  81. 24 @buffer = new_buffer
  82. 24 @capturing = true
  83. 24 @fragments = nil
  84. 24 yield
  85. ensure
  86. 24 @buffer = original_buffer
  87. 24 @capturing = original_capturing
  88. 24 @fragments = original_fragments
  89. end
  90. 24 new_buffer
  91. end
  92. 1 def flush
  93. 6 then: 0 else: 6 return if capturing
  94. 6 buffer = @buffer
  95. 6 @output_buffer << buffer.dup
  96. 6 buffer.clear
  97. 6 nil
  98. end
  99. end

lib/phlex/svg.rb

83.33% lines covered

65.0% branches covered

36 relevant lines. 30 lines covered and 6 lines missed.
20 total branches, 13 branches covered and 7 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::SVG < Phlex::SGML
  3. 1 include StandardElements
  4. # Returns the string "image/svg+xml"
  5. 1 def content_type
  6. 1 "image/svg+xml"
  7. end
  8. # Override to provide a filename for the SVG file
  9. 1 def filename
  10. nil
  11. end
  12. 1 def cdata(content = nil, &block)
  13. 2 state = @_state
  14. 2 else: 2 then: 0 return unless state.should_render?
  15. 2 then: 1 if !block && String === content
  16. 1 else: 1 state.buffer << "<![CDATA[" << content.gsub("]]>", "]]>]]<![CDATA[") << "]]>"
  17. 1 then: 1 elsif block && nil == content
  18. 1 state.buffer << "<![CDATA[" << capture(&block).gsub("]]>", "]]>]]<![CDATA[") << "]]>"
  19. else
  20. else: 0
  21. raise Phlex::ArgumentError.new("Expected a String or block.")
  22. end
  23. end
  24. 1 def tag(name, **attributes, &)
  25. 256 state = @_state
  26. 256 block_given = block_given?
  27. 256 buffer = state.buffer
  28. 256 else: 256 then: 0 unless state.should_render?
  29. then: 0 else: 0 yield(self) if block_given
  30. return nil
  31. end
  32. 256 else: 256 then: 0 unless Symbol === name
  33. raise Phlex::ArgumentError.new("Expected the tag name to be a Symbol.")
  34. end
  35. 256 then: 256 if (tag = StandardElements.__registered_elements__[name]) || (tag = name.name.tr("_", "-")).include?("-")
  36. 256 then: 128 if attributes.length > 0 # with attributes
  37. 128 then: 64 if block_given # with content block
  38. 64 buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
  39. 64 __yield_content__(&)
  40. 64 buffer << "</#{tag}>"
  41. else: 64 else # without content
  42. 64 buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << "></#{tag}>"
  43. end
  44. else: 128 else # without attributes
  45. 128 then: 64 if block_given # with content block
  46. 64 buffer << ("<#{tag}>")
  47. 64 __yield_content__(&)
  48. 64 buffer << "</#{tag}>"
  49. else: 64 else # without content
  50. 64 buffer << "<#{tag}></#{tag}>"
  51. end
  52. end
  53. else: 0 else
  54. raise Phlex::ArgumentError.new("Invalid SVG tag: #{name}")
  55. end
  56. end
  57. end

lib/phlex/svg/standard_elements.rb

100.0% lines covered

100.0% branches covered

66 relevant lines. 66 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Phlex::SVG::StandardElements
  3. 1 extend Phlex::SGML::Elements
  4. # Outputs an `<a>` tag.
  5. # See https://developer.mozilla.org/docs/Web/SVG/Element/a
  6. 1 register_element def a(
  7. download: nil,
  8. href: nil,
  9. hreflang: nil,
  10. ping: nil,
  11. referrerpolicy: nil,
  12. rel: nil,
  13. target: nil,
  14. type: nil,
  15. **attributes,
  16. &content
  17. ) = nil
  18. # Outputs an `<animate>` tag.
  19. # See https://developer.mozilla.org/docs/Web/SVG/Element/animate
  20. 1 register_element def animate(
  21. attributeName: nil,
  22. values: nil,
  23. dur: nil,
  24. repeatCount: nil,
  25. **attributes,
  26. &content
  27. ) = nil
  28. # Outputs an `<animateMotion>` tag.
  29. # See https://developer.mozilla.org/docs/Web/SVG/Element/animateMotion
  30. 1 register_element def animateMotion(
  31. keyPoints: nil,
  32. path: nil,
  33. rotate: nil,
  34. begin: nil,
  35. dur: nil,
  36. end: nil,
  37. repeatCount: nil,
  38. repeatDur: nil,
  39. fill: nil,
  40. calcMode: nil,
  41. values: nil,
  42. keyTimes: nil,
  43. keySplines: nil,
  44. from: nil,
  45. to: nil,
  46. by: nil,
  47. attributeName: nil,
  48. additive: nil,
  49. accumulate: nil,
  50. **attributes,
  51. &content
  52. ) = nil
  53. # Outputs an `<animateTransform>` tag.
  54. # See https://developer.mozilla.org/docs/Web/SVG/Element/animateTransform
  55. 1 register_element def animateTransform(
  56. **attributes,
  57. &content
  58. ) = nil
  59. # Outputs a `<circle>` tag.
  60. # See https://developer.mozilla.org/docs/Web/SVG/Element/circle
  61. 1 register_element def circle(
  62. **attributes,
  63. &content
  64. ) = nil
  65. # Outputs a `<clipPath>` tag.
  66. # See https://developer.mozilla.org/docs/Web/SVG/Element/clipPath
  67. 1 register_element def clipPath(
  68. **attributes,
  69. &content
  70. ) = nil
  71. # Outputs a `<defs>` tag.
  72. # See https://developer.mozilla.org/docs/Web/SVG/Element/defs
  73. 1 register_element def defs(
  74. **attributes,
  75. &content
  76. ) = nil
  77. # Outputs a `<desc>` tag.
  78. # See https://developer.mozilla.org/docs/Web/SVG/Element/desc
  79. 1 register_element def desc(
  80. **attributes,
  81. &content
  82. ) = nil
  83. # Outputs a `<discard>` tag.
  84. # See https://developer.mozilla.org/docs/Web/SVG/Element/discard
  85. 1 register_element def discard(
  86. **attributes,
  87. &content
  88. ) = nil
  89. # Outputs an `<ellipse>` tag.
  90. # See https://developer.mozilla.org/docs/Web/SVG/Element/ellipse
  91. 1 register_element def ellipse(
  92. **attributes,
  93. &content
  94. ) = nil
  95. # Outputs an `<feBlend>` tag.
  96. # See https://developer.mozilla.org/docs/Web/SVG/Element/feBlend
  97. 1 register_element def feBlend(
  98. **attributes,
  99. &content
  100. ) = nil
  101. # Outputs an `<feColorMatrix>` tag.
  102. # See https://developer.mozilla.org/docs/Web/SVG/Element/feColorMatrix
  103. 1 register_element def feColorMatrix(
  104. **attributes,
  105. &content
  106. ) = nil
  107. # Outputs an `<feComponentTransfer>` tag.
  108. # See https://developer.mozilla.org/docs/Web/SVG/Element/feComponentTransfer
  109. 1 register_element def feComponentTransfer(
  110. **attributes,
  111. &content
  112. ) = nil
  113. # Outputs an `<feComposite>` tag.
  114. # See https://developer.mozilla.org/docs/Web/SVG/Element/feComposite
  115. 1 register_element def feComposite(
  116. **attributes,
  117. &content
  118. ) = nil
  119. # Outputs an `<feConvolveMatrix>` tag.
  120. # See https://developer.mozilla.org/docs/Web/SVG/Element/feConvolveMatrix
  121. 1 register_element def feConvolveMatrix(
  122. **attributes,
  123. &content
  124. ) = nil
  125. # Outputs an `<feDiffuseLighting>` tag.
  126. # See https://developer.mozilla.org/docs/Web/SVG/Element/feDiffuseLighting
  127. 1 register_element def feDiffuseLighting(
  128. **attributes,
  129. &content
  130. ) = nil
  131. # Outputs an `<feDisplacementMap>` tag.
  132. # See https://developer.mozilla.org/docs/Web/SVG/Element/feDisplacementMap
  133. 1 register_element def feDisplacementMap(
  134. **attributes,
  135. &content
  136. ) = nil
  137. # Outputs an `<feDistantLight>` tag.
  138. # See https://developer.mozilla.org/docs/Web/SVG/Element/feDistantLight
  139. 1 register_element def feDistantLight(
  140. **attributes,
  141. &content
  142. ) = nil
  143. # Outputs an `<feDropShadow>` tag.
  144. # See https://developer.mozilla.org/docs/Web/SVG/Element/feDropShadow
  145. 1 register_element def feDropShadow(
  146. **attributes,
  147. &content
  148. ) = nil
  149. # Outputs an `<feFlood>` tag.
  150. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFlood
  151. 1 register_element def feFlood(
  152. **attributes,
  153. &content
  154. ) = nil
  155. # Outputs an `<feFuncA>` tag.
  156. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFuncA
  157. 1 register_element def feFuncA(
  158. **attributes,
  159. &content
  160. ) = nil
  161. # Outputs an `<feFuncB>` tag.
  162. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFuncB
  163. 1 register_element def feFuncB(
  164. **attributes,
  165. &content
  166. ) = nil
  167. # Outputs an `<feFuncG>` tag.
  168. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFuncG
  169. 1 register_element def feFuncG(
  170. **attributes,
  171. &content
  172. ) = nil
  173. # Outputs an `<feFuncR>` tag.
  174. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFuncR
  175. 1 register_element def feFuncR(
  176. **attributes,
  177. &content
  178. ) = nil
  179. # Outputs an `<feGaussianBlur>` tag.
  180. # See https://developer.mozilla.org/docs/Web/SVG/Element/feGaussianBlur
  181. 1 register_element def feGaussianBlur(
  182. **attributes,
  183. &content
  184. ) = nil
  185. # Outputs an `<feImage>` tag.
  186. # See https://developer.mozilla.org/docs/Web/SVG/Element/feImage
  187. 1 register_element def feImage(
  188. **attributes,
  189. &content
  190. ) = nil
  191. # Outputs an `<feMerge>` tag.
  192. # See https://developer.mozilla.org/docs/Web/SVG/Element/feMerge
  193. 1 register_element def feMerge(
  194. **attributes,
  195. &content
  196. ) = nil
  197. # Outputs an `<feMergeNode>` tag.
  198. # See https://developer.mozilla.org/docs/Web/SVG/Element/feMergeNode
  199. 1 register_element def feMergeNode(
  200. **attributes,
  201. &content
  202. ) = nil
  203. # Outputs an `<feMorphology>` tag.
  204. # See https://developer.mozilla.org/docs/Web/SVG/Element/feMorphology
  205. 1 register_element def feMorphology(
  206. **attributes,
  207. &content
  208. ) = nil
  209. # Outputs an `<feOffset>` tag.
  210. # See https://developer.mozilla.org/docs/Web/SVG/Element/feOffset
  211. 1 register_element def feOffset(
  212. **attributes,
  213. &content
  214. ) = nil
  215. # Outputs an `<fePointLight>` tag.
  216. # See https://developer.mozilla.org/docs/Web/SVG/Element/fePointLight
  217. 1 register_element def fePointLight(
  218. **attributes,
  219. &content
  220. ) = nil
  221. # Outputs an `<feSpecularLighting>` tag.
  222. # See https://developer.mozilla.org/docs/Web/SVG/Element/feSpecularLighting
  223. 1 register_element def feSpecularLighting(
  224. **attributes,
  225. &content
  226. ) = nil
  227. # Outputs an `<feSpotLight>` tag.
  228. # See https://developer.mozilla.org/docs/Web/SVG/Element/feSpotLight
  229. 1 register_element def feSpotLight(
  230. **attributes,
  231. &content
  232. ) = nil
  233. # Outputs an `<feTile>` tag.
  234. # See https://developer.mozilla.org/docs/Web/SVG/Element/feTile
  235. 1 register_element def feTile(
  236. **attributes,
  237. &content
  238. ) = nil
  239. # Outputs an `<feTurbulence>` tag.
  240. # See https://developer.mozilla.org/docs/Web/SVG/Element/feTurbulence
  241. 1 register_element def feTurbulence(
  242. **attributes,
  243. &content
  244. ) = nil
  245. # Outputs a `<filter>` tag.
  246. # See https://developer.mozilla.org/docs/Web/SVG/Element/filter
  247. 1 register_element def filter(
  248. **attributes,
  249. &content
  250. ) = nil
  251. # Outputs a `<foreignObject>` tag.
  252. # See https://developer.mozilla.org/docs/Web/SVG/Element/foreignObject
  253. 1 register_element def foreignObject(
  254. **attributes,
  255. &content
  256. ) = nil
  257. # Outputs a `<g>` tag.
  258. # See https://developer.mozilla.org/docs/Web/SVG/Element/g
  259. 1 register_element def g(
  260. **attributes,
  261. &content
  262. ) = nil
  263. # Outputs an `<image>` tag.
  264. # See https://developer.mozilla.org/docs/Web/SVG/Element/image
  265. 1 register_element def image(
  266. **attributes,
  267. &content
  268. ) = nil
  269. # Outputs a `<line>` tag.
  270. # See https://developer.mozilla.org/docs/Web/SVG/Element/line
  271. 1 register_element def line(
  272. **attributes,
  273. &content
  274. ) = nil
  275. # Outputs a `<linearGradient>` tag.
  276. # See https://developer.mozilla.org/docs/Web/SVG/Element/linearGradient
  277. 1 register_element def linearGradient(
  278. **attributes,
  279. &content
  280. ) = nil
  281. # Outputs a `<marker>` tag.
  282. # See https://developer.mozilla.org/docs/Web/SVG/Element/marker
  283. 1 register_element def marker(
  284. **attributes,
  285. &content
  286. ) = nil
  287. # Outputs a `<mask>` tag.
  288. # See https://developer.mozilla.org/docs/Web/SVG/Element/mask
  289. 1 register_element def mask(
  290. **attributes,
  291. &content
  292. ) = nil
  293. # Outputs a `<metadata>` tag.
  294. # See https://developer.mozilla.org/docs/Web/SVG/Element/metadata
  295. 1 register_element def metadata(
  296. **attributes,
  297. &content
  298. ) = nil
  299. # Outputs an `<mpath>` tag.
  300. # See https://developer.mozilla.org/docs/Web/SVG/Element/mpath
  301. 1 register_element def mpath(
  302. **attributes,
  303. &content
  304. ) = nil
  305. # Outputs a `<path>` tag.
  306. # See https://developer.mozilla.org/docs/Web/SVG/Element/path
  307. 1 register_element def path(
  308. **attributes,
  309. &content
  310. ) = nil
  311. # Outputs a `<pattern>` tag.
  312. # See https://developer.mozilla.org/docs/Web/SVG/Element/pattern
  313. 1 register_element def pattern(
  314. **attributes,
  315. &content
  316. ) = nil
  317. # Outputs a `<polygon>` tag.
  318. # See https://developer.mozilla.org/docs/Web/SVG/Element/polygon
  319. 1 register_element def polygon(
  320. **attributes,
  321. &content
  322. ) = nil
  323. # Outputs a `<polyline>` tag.
  324. # See https://developer.mozilla.org/docs/Web/SVG/Element/polyline
  325. 1 register_element def polyline(
  326. **attributes,
  327. &content
  328. ) = nil
  329. # Outputs a `<radialGradient>` tag.
  330. # See https://developer.mozilla.org/docs/Web/SVG/Element/radialGradient
  331. 1 register_element def radialGradient(
  332. **attributes,
  333. &content
  334. ) = nil
  335. # Outputs a `<rect>` tag.
  336. # See https://developer.mozilla.org/docs/Web/SVG/Element/rect
  337. 1 register_element def rect(
  338. **attributes,
  339. &content
  340. ) = nil
  341. # Outputs a `<script>` tag.
  342. # See https://developer.mozilla.org/docs/Web/SVG/Element/script
  343. 1 register_element def script(
  344. **attributes,
  345. &content
  346. ) = nil
  347. # Outputs a `<set>` tag.
  348. # See https://developer.mozilla.org/docs/Web/SVG/Element/set
  349. 1 register_element def set(
  350. **attributes,
  351. &content
  352. ) = nil
  353. # Outputs a `<stop>` tag.
  354. # See https://developer.mozilla.org/docs/Web/SVG/Element/stop
  355. 1 register_element def stop(
  356. **attributes,
  357. &content
  358. ) = nil
  359. # Outputs a `<style>` tag.
  360. # See https://developer.mozilla.org/docs/Web/SVG/Element/style
  361. 1 register_element def style(
  362. **attributes,
  363. &content
  364. ) = nil
  365. # Outputs an `<svg>` tag.
  366. # See https://developer.mozilla.org/docs/Web/SVG/Element/svg
  367. 1 register_element def svg(
  368. **attributes,
  369. &content
  370. ) = nil
  371. # Outputs a `<switch>` tag.
  372. # See https://developer.mozilla.org/docs/Web/SVG/Element/switch
  373. 1 register_element def switch(
  374. **attributes,
  375. &content
  376. ) = nil
  377. # Outputs a `<symbol>` tag.
  378. # See https://developer.mozilla.org/docs/Web/SVG/Element/symbol
  379. 1 register_element def symbol(
  380. **attributes,
  381. &content
  382. ) = nil
  383. # Outputs a `<text>` tag.
  384. # See https://developer.mozilla.org/docs/Web/SVG/Element/text
  385. 1 register_element def text(
  386. **attributes,
  387. &content
  388. ) = nil
  389. # Outputs a `<textPath>` tag.
  390. # See https://developer.mozilla.org/docs/Web/SVG/Element/textPath
  391. 1 register_element def textPath(
  392. **attributes,
  393. &content
  394. ) = nil
  395. # Outputs a `<title>` tag.
  396. # See https://developer.mozilla.org/docs/Web/SVG/Element/title
  397. 1 register_element def title(
  398. **attributes,
  399. &content
  400. ) = nil
  401. # Outputs a `<tspan>` tag.
  402. # See https://developer.mozilla.org/docs/Web/SVG/Element/tspan
  403. 1 register_element def tspan(
  404. **attributes,
  405. &content
  406. ) = nil
  407. # Outputs a `<use>` tag.
  408. # See https://developer.mozilla.org/docs/Web/SVG/Element/use
  409. 1 register_element def use(
  410. **attributes,
  411. &content
  412. ) = nil
  413. # Outputs a `<view>` tag.
  414. # See https://developer.mozilla.org/docs/Web/SVG/Element/view
  415. 1 register_element def view(
  416. **attributes,
  417. &content
  418. ) = nil
  419. end

quickdraw/compilation_equivalence_cases/attributes.rb

66.67% lines covered

50.0% branches covered

3 relevant lines. 2 lines covered and 1 lines missed.
4 total branches, 2 branches covered and 2 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Attributes < Phlex::HTML
  3. 1 def view_template
  4. div(
  5. a: nil,
  6. b: 1,
  7. c: :two,
  8. d: "three",
  9. e: 4.5,
  10. f: false,
  11. g: true,
  12. h: [nil, 1, :two, "three", 4.5, [1]],
  13. i: Set[nil, 1, :two, "three", 4.5, [1]],
  14. j: {
  15. k: 1,
  16. "l" => 2
  17. },
  18. "m" => 3
  19. )
  20. end
  21. end

quickdraw/compilation_equivalence_cases/basic.rb

40.0% lines covered

50.0% branches covered

5 relevant lines. 2 lines covered and 3 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Basic < Phlex::HTML
  3. 1 def view_template
  4. h1 { "Hello" }
  5. br
  6. br(class: "my-class")
  7. end
  8. end

quickdraw/compilation_equivalence_cases/comment.rb

50.0% lines covered

50.0% branches covered

4 relevant lines. 2 lines covered and 2 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Comment < Phlex::HTML
  3. 1 def view_template
  4. comment { "hello world" }
  5. comment { "Begin rendering #{self.class.name}" }
  6. end
  7. end

quickdraw/compilation_equivalence_cases/heredoc.rb

57.14% lines covered

50.0% branches covered

7 relevant lines. 4 lines covered and 3 lines missed.
4 total branches, 2 branches covered and 2 branches missed.
    
  1. 1 class Heredoc < Phlex::HTML
  2. 1 def view_template
  3. lines = 3
  4. unknown {
  5. p { <<~FIRST }
  6. This is a heredoc.
  7. It has #{lines} lines of text.
  8. And it's all going in this <p> tag.
  9. FIRST
  10. }
  11. end
  12. 1 def unknown
  13. 1 yield
  14. end
  15. end

quickdraw/compilation_equivalence_cases/interpolated_string.rb

28.57% lines covered

50.0% branches covered

7 relevant lines. 2 lines covered and 5 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class InterpolatedString < Phlex::HTML
  3. 1 def view_template
  4. name = "Joel"
  5. @ivar = "Will"
  6. h1 { "Hello, #{name}!" }
  7. h2 { "Hello, #@ivar!" } # rubocop:disable Style/VariableInterpolation
  8. h3 { "Hello #{"#{name}"}" }
  9. end
  10. end

quickdraw/compilation_equivalence_cases/override_elements.rb

33.33% lines covered

50.0% branches covered

9 relevant lines. 3 lines covered and 6 lines missed.
8 total branches, 4 branches covered and 4 branches missed.
    
  1. 1 class OverrideElements < Phlex::HTML
  2. 1 def view_template
  3. input(type: "email")
  4. h1
  5. input(type: "email")
  6. div do
  7. input(type: "email")
  8. end
  9. end
  10. 1 def input(**)
  11. plain("not-an-input")
  12. # super(class: "form-control", **)
  13. end
  14. end

quickdraw/compilation_equivalence_cases/plain.rb

33.33% lines covered

50.0% branches covered

6 relevant lines. 2 lines covered and 4 lines missed.
4 total branches, 2 branches covered and 2 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Plain < Phlex::HTML
  3. 1 def view_template
  4. local_variable = "good"
  5. plain "Greetings "
  6. plain local_variable
  7. plain "sir!"
  8. end
  9. end

quickdraw/compilation_equivalence_cases/raw.rb

40.0% lines covered

50.0% branches covered

5 relevant lines. 2 lines covered and 3 lines missed.
4 total branches, 2 branches covered and 2 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Raw < Phlex::HTML
  3. 1 def view_template
  4. p { "output before" }
  5. raw(safe("<h1>raw output in the middle</h1>"))
  6. p { "output after" }
  7. end
  8. end

quickdraw/compilation_equivalence_cases/whitespace.rb

18.18% lines covered

50.0% branches covered

11 relevant lines. 2 lines covered and 9 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Whitespace < Phlex::HTML
  3. 1 def view_template
  4. h1 { "Hello" }
  5. whitespace
  6. h2 { "world" }
  7. br
  8. br
  9. p do
  10. plain "This sentence has"
  11. whitespace { em { "emphasis" } }
  12. plain "in it."
  13. end
  14. end
  15. end