phlex 0.3.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of phlex might be problematic. Click here for more details.

Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +8 -0
  3. data/.rubocop.yml +21 -5
  4. data/Gemfile +26 -12
  5. data/Procfile.dev +3 -0
  6. data/README.md +1 -1
  7. data/Rakefile +3 -5
  8. data/SECURITY.md +1 -1
  9. data/bench.rb +7 -0
  10. data/config/sus.rb +15 -0
  11. data/docs/assets/application.css +6 -0
  12. data/docs/build.rb +17 -10
  13. data/docs/components/callout.rb +1 -1
  14. data/docs/components/code_block.rb +2 -2
  15. data/docs/components/code_span.rb +9 -0
  16. data/docs/components/example.rb +5 -5
  17. data/docs/components/heading.rb +2 -2
  18. data/docs/components/layout.rb +62 -17
  19. data/docs/components/markdown.rb +14 -15
  20. data/docs/components/nav/item.rb +33 -0
  21. data/docs/components/nav.rb +6 -0
  22. data/docs/components/tabs/tab.rb +4 -2
  23. data/docs/components/tabs.rb +1 -1
  24. data/docs/components/title.rb +2 -2
  25. data/docs/page_builder.rb +3 -0
  26. data/docs/pages/application_page.rb +1 -1
  27. data/docs/pages/helpers.rb +97 -0
  28. data/docs/pages/index.rb +6 -17
  29. data/docs/pages/library/collections.rb +83 -0
  30. data/docs/pages/rails/getting_started.rb +53 -0
  31. data/docs/pages/rails/helpers.rb +55 -0
  32. data/docs/pages/rails/layouts.rb +61 -0
  33. data/docs/pages/rails/migrating.rb +37 -0
  34. data/docs/pages/rails/rendering_views.rb +35 -0
  35. data/docs/pages/templates.rb +53 -151
  36. data/docs/pages/testing/capybara.rb +48 -0
  37. data/docs/pages/testing/getting_started.rb +44 -0
  38. data/docs/pages/testing/nokogiri.rb +83 -0
  39. data/docs/pages/testing/rails.rb +17 -0
  40. data/docs/pages/translations.rb +81 -0
  41. data/docs/pages/views.rb +87 -78
  42. data/fixtures/compiler_test_helpers.rb +19 -0
  43. data/fixtures/content.rb +60 -0
  44. data/fixtures/dummy/app/components/comment_component.html.erb +14 -0
  45. data/fixtures/dummy/app/components/comment_component.rb +8 -0
  46. data/fixtures/dummy/app/components/reaction_component.html.erb +3 -0
  47. data/fixtures/dummy/app/components/reaction_component.rb +7 -0
  48. data/fixtures/dummy/app/controllers/comments_controller.rb +4 -0
  49. data/fixtures/dummy/app/views/application_view.rb +8 -0
  50. data/fixtures/dummy/app/views/articles/form.rb +3 -1
  51. data/fixtures/dummy/app/views/card.rb +4 -2
  52. data/fixtures/dummy/app/views/comments/comment.rb +25 -0
  53. data/fixtures/dummy/app/views/comments/index.html.erb +3 -0
  54. data/fixtures/dummy/app/views/comments/reaction.rb +17 -0
  55. data/fixtures/dummy/app/views/comments/show.html.erb +3 -0
  56. data/fixtures/dummy/app/views/heading.rb +1 -1
  57. data/fixtures/layout.rb +5 -5
  58. data/fixtures/page.rb +18 -24
  59. data/fixtures/{test_helper.rb → rails_helper.rb} +3 -7
  60. data/fixtures/standard_element.rb +87 -0
  61. data/fixtures/view_helper.rb +1 -1
  62. data/fixtures/void_element.rb +31 -0
  63. data/lib/generators/phlex/collection/USAGE +8 -0
  64. data/lib/generators/phlex/collection/collection_generator.rb +13 -0
  65. data/lib/generators/phlex/collection/templates/collection.rb.erb +16 -0
  66. data/lib/generators/phlex/controller/USAGE +10 -0
  67. data/lib/generators/phlex/controller/controller_generator.rb +54 -0
  68. data/lib/generators/phlex/controller/templates/controller.rb.erb +10 -0
  69. data/lib/generators/phlex/controller/templates/view.rb.erb +14 -0
  70. data/lib/generators/phlex/layout/USAGE +8 -0
  71. data/lib/generators/phlex/layout/layout_generator.rb +13 -0
  72. data/lib/generators/phlex/layout/templates/layout.rb.erb +31 -0
  73. data/lib/generators/phlex/page/USAGE +8 -0
  74. data/lib/generators/phlex/page/page_generator.rb +13 -0
  75. data/lib/generators/phlex/page/templates/page.rb.erb +13 -0
  76. data/lib/generators/phlex/table/USAGE +8 -0
  77. data/lib/generators/phlex/table/table_generator.rb +14 -0
  78. data/lib/generators/phlex/table/templates/table.rb.erb +11 -0
  79. data/lib/generators/phlex/view/templates/view.rb.erb +7 -1
  80. data/lib/generators/phlex/view/view_generator.rb +9 -1
  81. data/lib/install/phlex.rb +10 -1
  82. data/lib/phlex/block.rb +2 -4
  83. data/lib/phlex/buffered.rb +6 -8
  84. data/lib/phlex/callable.rb +9 -0
  85. data/lib/phlex/collection.rb +33 -0
  86. data/lib/phlex/compiler/elements.rb +49 -0
  87. data/lib/phlex/compiler/generators/content.rb +103 -0
  88. data/lib/phlex/compiler/generators/element.rb +61 -0
  89. data/lib/phlex/compiler/nodes/base.rb +19 -0
  90. data/lib/phlex/compiler/nodes/call.rb +9 -0
  91. data/lib/phlex/compiler/nodes/command.rb +13 -0
  92. data/lib/phlex/compiler/nodes/fcall.rb +18 -0
  93. data/lib/phlex/compiler/nodes/method_add_block.rb +33 -0
  94. data/lib/phlex/compiler/nodes/vcall.rb +9 -0
  95. data/lib/phlex/compiler/optimizer.rb +66 -0
  96. data/lib/phlex/compiler/visitors/base.rb +15 -0
  97. data/lib/phlex/compiler/visitors/file.rb +23 -11
  98. data/lib/phlex/compiler/visitors/stable_scope.rb +28 -0
  99. data/lib/phlex/compiler/visitors/statements.rb +36 -0
  100. data/lib/phlex/compiler/visitors/view.rb +19 -0
  101. data/lib/phlex/compiler/visitors/view_method.rb +59 -0
  102. data/lib/phlex/compiler.rb +23 -3
  103. data/lib/phlex/elements.rb +57 -0
  104. data/lib/phlex/engine.rb +0 -3
  105. data/lib/phlex/helpers.rb +59 -0
  106. data/lib/phlex/html/callbacks.rb +11 -0
  107. data/lib/phlex/html.rb +209 -54
  108. data/lib/phlex/markdown.rb +76 -0
  109. data/lib/phlex/rails/form.rb +67 -0
  110. data/lib/phlex/rails/helpers.rb +118 -0
  111. data/lib/phlex/rails/layout.rb +15 -0
  112. data/lib/phlex/rails.rb +10 -0
  113. data/lib/phlex/renderable.rb +9 -3
  114. data/lib/phlex/table.rb +104 -0
  115. data/lib/phlex/testing/capybara.rb +25 -0
  116. data/lib/phlex/testing/nokogiri.rb +24 -0
  117. data/lib/phlex/testing/rails.rb +19 -0
  118. data/lib/phlex/testing/view_helper.rb +15 -0
  119. data/lib/phlex/translation.rb +23 -0
  120. data/lib/phlex/turbo/frame.rb +21 -0
  121. data/lib/phlex/turbo/stream.rb +18 -0
  122. data/lib/phlex/version.rb +1 -1
  123. data/lib/phlex.rb +22 -24
  124. metadata +112 -15
  125. data/.rspec +0 -1
  126. data/fixtures/compilation/vcall.rb +0 -38
  127. data/lib/phlex/compiler/generators/standard_element.rb +0 -30
  128. data/lib/phlex/compiler/generators/void_element.rb +0 -29
  129. data/lib/phlex/compiler/optimizers/base_optimizer.rb +0 -34
  130. data/lib/phlex/compiler/optimizers/vcall.rb +0 -29
  131. data/lib/phlex/compiler/visitors/base_visitor.rb +0 -19
  132. data/lib/phlex/compiler/visitors/component.rb +0 -28
  133. data/lib/phlex/compiler/visitors/component_method.rb +0 -28
  134. data/lib/phlex/rails/tag_helpers.rb +0 -29
  135. data/lib/phlex/view.rb +0 -223
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Rails
5
+ module Helpers
6
+ module CSPMetaTag
7
+ def csp_meta_tag(**options)
8
+ if (output = @_view_context.csp_meta_tag(**options))
9
+ @_target << output
10
+ end
11
+ end
12
+ end
13
+
14
+ module CSRFMetaTags
15
+ def csrf_meta_tags
16
+ if (output = @_view_context.csrf_meta_tags)
17
+ @_target << output
18
+ end
19
+ end
20
+ end
21
+
22
+ module ActionCableMetaTag
23
+ def action_cable_meta_tag
24
+ if (output = @_view_context.action_cable_meta_tag)
25
+ @_target << output
26
+ end
27
+ end
28
+ end
29
+
30
+ module FormWith
31
+ class BufferedFormWith < Phlex::Buffered
32
+ alias_method :check_box, :__output_method__
33
+ alias_method :collection_check_boxes, :__output_method__
34
+ alias_method :collection_radio_buttons, :__output_method__
35
+ alias_method :collection_select, :__output_method__
36
+ alias_method :color_field, :__output_method__
37
+ alias_method :date_field, :__output_method__
38
+ alias_method :date_select, :__output_method__
39
+ alias_method :datetime_field, :__output_method__
40
+ alias_method :datetime_local_field, :__output_method__
41
+ alias_method :datetime_select, :__output_method__
42
+ alias_method :email_field, :__output_method__
43
+ alias_method :file_field, :__output_method__
44
+ alias_method :grouped_collection_select, :__output_method__
45
+ alias_method :hidden_field, :__output_method__
46
+ alias_method :label, :__output_method__
47
+ alias_method :month_field, :__output_method__
48
+ alias_method :number_field, :__output_method__
49
+ alias_method :password_field, :__output_method__
50
+ alias_method :phone_field, :__output_method__
51
+ alias_method :radio_button, :__output_method__
52
+ alias_method :range_field, :__output_method__
53
+ alias_method :search_field, :__output_method__
54
+ alias_method :select, :__output_method__
55
+ alias_method :submit, :__output_method__
56
+ alias_method :telephone_field, :__output_method__
57
+ alias_method :text_area, :__output_method__
58
+ alias_method :text_field, :__output_method__
59
+ alias_method :time_field, :__output_method__
60
+ alias_method :time_select, :__output_method__
61
+ alias_method :time_zone_select, :__output_method__
62
+ alias_method :url_field, :__output_method__
63
+ alias_method :week_field, :__output_method__
64
+ alias_method :weekday_select, :__output_method__
65
+ alias_method :button, :__output_method__
66
+ end
67
+
68
+ def form_with(*args, **kwargs, &block)
69
+ @_target << @_view_context.form_with(*args, **kwargs) { |form|
70
+ capture do
71
+ yield(
72
+ BufferedFormWith.new(form, buffer: @_target)
73
+ )
74
+ end
75
+ }
76
+ end
77
+ end
78
+
79
+ module StylesheetLinkTag
80
+ def stylesheet_link_tag(*sources)
81
+ if (output = @_view_context.stylesheet_link_tag(*sources))
82
+ @_target << output
83
+ end
84
+ end
85
+ end
86
+
87
+ module FaviconLinkTag
88
+ def favicon_link_tag(*args)
89
+ if (output = @_view_context.favicon_link_tag(*args))
90
+ @_target << output
91
+ end
92
+ end
93
+ end
94
+
95
+ module PreloadLinkTag
96
+ def preload_link_tag(*args)
97
+ if (output = @_view_context.preload_link_tag(*args))
98
+ @_target << output
99
+ end
100
+ end
101
+ end
102
+
103
+ module JavaScriptIncludeTag
104
+ def javascript_include_tag(*sources)
105
+ if (output = @_view_context.javascript_include_tag(*sources))
106
+ @_target << output
107
+ end
108
+ end
109
+ end
110
+
111
+ module ContentFor
112
+ def content_for(slot, &block)
113
+ @_view_context.content_for(slot, capture(&block))
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Rails
5
+ module Layout
6
+ include Helpers::CSPMetaTag
7
+ include Helpers::CSRFMetaTags
8
+ include Helpers::FaviconLinkTag
9
+ include Helpers::PreloadLinkTag
10
+ include Helpers::StylesheetLinkTag
11
+ include Helpers::ActionCableMetaTag
12
+ include Helpers::JavaScriptIncludeTag
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "phlex"
4
+
5
+ module Phlex::Rails
6
+ Loader = Zeitwerk::Loader.new.tap do |loader|
7
+ loader.push_dir("#{__dir__}/rails", namespace: Phlex::Rails)
8
+ loader.setup
9
+ end
10
+ end
@@ -3,13 +3,13 @@
3
3
  module Phlex
4
4
  module Renderable
5
5
  def render(renderable, *args, **kwargs, &block)
6
- if renderable.is_a?(View)
6
+ if renderable.is_a?(HTML)
7
7
  if block_given? && !block.binding.receiver.is_a?(Phlex::Block)
8
8
  block = Phlex::Block.new(self, &block)
9
9
  end
10
10
 
11
11
  renderable.call(@_target, view_context: @_view_context, parent: self, &block)
12
- elsif renderable.is_a?(Class) && renderable < View
12
+ elsif renderable.is_a?(Class) && renderable < Phlex::HTML
13
13
  raise ArgumentError, "You tried to render the Phlex view class: #{renderable.name} but you probably meant to render an instance of that class instead."
14
14
  else
15
15
  @_target << @_view_context.render(renderable, *args, **kwargs, &block)
@@ -26,7 +26,13 @@ module Phlex
26
26
  output = yield(*args, **kwargs)
27
27
  unchanged = (original_length == @_target.length)
28
28
 
29
- text(output) if unchanged && output.is_a?(String)
29
+ if unchanged
30
+ if defined?(ActiveSupport::SafeBuffer) && output.is_a?(ActiveSupport::SafeBuffer)
31
+ unsafe_raw(output)
32
+ else
33
+ text(output)
34
+ end
35
+ end
30
36
  end
31
37
  end.html_safe
32
38
  else
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Table
5
+ include Collection
6
+
7
+ module ClassMethods
8
+ attr_accessor :header
9
+
10
+ def property(header = nil, **attributes, &body)
11
+ if header.is_a?(String)
12
+ header_text = header
13
+ header = -> { head_header(scope: "col") { header_text } }
14
+ end
15
+
16
+ properties << {
17
+ header: header,
18
+ body: body,
19
+ attributes: attributes,
20
+ }
21
+ end
22
+
23
+ def properties
24
+ @properties ||= []
25
+ end
26
+ end
27
+
28
+ def self.included(child)
29
+ child.extend ClassMethods
30
+
31
+ child.alias_method :head, :thead
32
+ child.alias_method :body, :tbody
33
+ child.alias_method :foot, :tfoot
34
+
35
+ child.alias_method :row, :tr
36
+
37
+ child.alias_method :header, :th
38
+ child.alias_method :cell, :td
39
+
40
+ child.alias_method :head_row, :row
41
+ child.alias_method :body_row, :row
42
+ child.alias_method :foot_row, :row
43
+
44
+ child.alias_method :head_header, :header
45
+ child.alias_method :foot_header, :header
46
+
47
+ child.alias_method :head_cell, :cell
48
+ child.alias_method :body_cell, :cell
49
+ child.alias_method :foot_cell, :cell
50
+ end
51
+
52
+ private
53
+
54
+ def properties
55
+ self.class.properties
56
+ end
57
+
58
+ def collection_template(&block)
59
+ table do
60
+ head_template
61
+ body_template(&block)
62
+ foot_template
63
+ end
64
+ end
65
+
66
+ def item_template
67
+ row_template
68
+ end
69
+
70
+ def head_template
71
+ if self.class.properties.any? { |p| p[:header] }
72
+ head do
73
+ head_row do
74
+ self.class.properties.each do |property|
75
+ case property[:header]
76
+ when Proc
77
+ instance_exec(&property[:header])
78
+ when Symbol
79
+ send(property[:header])
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def body_template
88
+ body { yield_items }
89
+ end
90
+
91
+ def foot_template
92
+ end
93
+
94
+ def row_template
95
+ body_row do
96
+ self.class.properties.each do |property|
97
+ body_cell(**property[:attributes]) do
98
+ instance_exec(@item, &property[:body])
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "capybara"
4
+ require_relative "view_helper"
5
+
6
+ module Phlex::Testing
7
+ module Capybara
8
+ module ViewHelper
9
+ include Phlex::Testing::ViewHelper
10
+
11
+ def self.included(klass)
12
+ if defined?(Minitest::Test) && klass < Minitest::Test
13
+ require "capybara/minitest"
14
+ include ::Capybara::Minitest::Assertions
15
+ end
16
+ end
17
+
18
+ attr_accessor :page
19
+
20
+ def render(view, &block)
21
+ @page = ::Capybara::Node::Simple.new(super)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require_relative "view_helper"
5
+
6
+ module Phlex::Testing
7
+ module Nokogiri
8
+ module DocumentHelper
9
+ include Phlex::Testing::ViewHelper
10
+
11
+ def render(view, &block)
12
+ ::Nokogiri::HTML5(super)
13
+ end
14
+ end
15
+
16
+ module FragmentHelper
17
+ include Phlex::Testing::ViewHelper
18
+
19
+ def render(view, &block)
20
+ ::Nokogiri::HTML5.fragment(super)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "view_helper"
4
+
5
+ module Phlex::Testing
6
+ module Rails
7
+ module ViewHelper
8
+ include Phlex::Testing::ViewHelper
9
+
10
+ def view_context
11
+ controller.view_context
12
+ end
13
+
14
+ def controller
15
+ @controller ||= ActionView::TestCase::TestController.new
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Testing
5
+ module ViewHelper
6
+ def render(view, &block)
7
+ view.call(view_context: view_context, &block)
8
+ end
9
+
10
+ def view_context
11
+ nil
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Translation
5
+ def self.included(view)
6
+ view.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ attr_writer :translation_path
11
+
12
+ def translation_path
13
+ @translation_path ||= name&.split("::")&.join(".")&.downcase.to_s
14
+ end
15
+ end
16
+
17
+ def translate(key, **options)
18
+ key = "#{self.class.translation_path}#{key}" if key.start_with?(".")
19
+
20
+ ::I18n.translate(key, **options)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Turbo
5
+ class Frame < Phlex::HTML
6
+ register_element :turbo_frame
7
+
8
+ def initialize(src:, loading:, disabled:, target:, autoscroll:)
9
+ @src = src
10
+ @loading = loading
11
+ @disabled = disabled
12
+ @target = target
13
+ @autoscroll = autoscroll
14
+ end
15
+
16
+ def template(&content)
17
+ turbo_frame(src: @src, loading: @loading, disabled: @disabled, target: @target, autoscroll: @autoscroll, &content)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Turbo
5
+ class Stream < Phlex::HTML
6
+ register_element :turbo_stream
7
+
8
+ def initialize(action:, target:)
9
+ @action = action
10
+ @target = target
11
+ end
12
+
13
+ def template(&content)
14
+ turbo_stream(action: @action, target: @target, &content)
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/phlex/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- VERSION = "0.3.2"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/phlex.rb CHANGED
@@ -1,34 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "cgi"
3
+ require "hescape"
4
4
  require "zeitwerk"
5
5
  require "syntax_tree"
6
6
 
7
- loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
8
- loader.ignore("#{__dir__}/generators")
9
- loader.ignore("#{__dir__}/install")
10
- loader.inflector.inflect("html" => "HTML")
11
- loader.inflector.inflect("vcall" => "VCall")
12
- loader.inflector.inflect("fcall" => "FCall")
13
- loader.setup
14
-
15
7
  module Phlex
8
+ Loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false).tap do |loader|
9
+ loader.ignore("#{__dir__}/generators")
10
+ loader.ignore("#{__dir__}/install")
11
+
12
+ loader.ignore("#{__dir__}/phlex/testing")
13
+
14
+ loader.ignore("#{__dir__}/phlex/rails.rb")
15
+ loader.ignore("#{__dir__}/phlex/rails")
16
+
17
+ loader.inflector.inflect("html" => "HTML")
18
+ loader.inflector.inflect("vcall" => "VCall")
19
+ loader.inflector.inflect("fcall" => "FCall")
20
+ loader.setup
21
+ end
22
+
16
23
  Error = Module.new
17
24
  ArgumentError = Class.new(ArgumentError) { include Error }
18
25
  NameError = Class.new(NameError) { include Error }
19
26
 
27
+ def self.const_missing(name)
28
+ if name == :View
29
+ raise NameError, "👋 Phlex::View has been renamed (again 🙄) to Phlex::HTML."
30
+ end
31
+ end
32
+
20
33
  extend self
21
34
 
22
35
  ATTRIBUTE_CACHE = {}
23
36
 
24
- def const_missing(name)
25
- if name == :Component
26
- raise NameError, "👋 Phlex::Component is now Phlex::View"
27
- else
28
- super
29
- end
30
- end
31
-
32
37
  def configuration
33
38
  @configuration ||= Configuration.new
34
39
  end
@@ -37,10 +42,3 @@ module Phlex
37
42
  yield configuration
38
43
  end
39
44
  end
40
-
41
- begin
42
- require "rails"
43
- require "phlex/engine"
44
- rescue LoadError
45
- # Rails isn't in this env, don't load the engine.
46
- end