phlex 0.2.2 → 0.3.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +5 -0
  3. data/.rubocop.yml +6 -0
  4. data/CONTRIBUTING.md +23 -0
  5. data/Rakefile +2 -2
  6. data/bench.rb +1 -1
  7. data/docs/assets/application.css +2 -0
  8. data/docs/build.rb +4 -4
  9. data/docs/components/callout.rb +5 -5
  10. data/docs/components/code_block.rb +18 -18
  11. data/docs/components/example.rb +23 -23
  12. data/docs/components/heading.rb +5 -5
  13. data/docs/components/layout.rb +42 -41
  14. data/docs/components/markdown.rb +19 -19
  15. data/docs/components/tabs/tab.rb +18 -18
  16. data/docs/components/tabs.rb +21 -21
  17. data/docs/components/title.rb +5 -5
  18. data/docs/page_builder.rb +32 -32
  19. data/docs/pages/application_page.rb +3 -3
  20. data/docs/pages/index.rb +23 -25
  21. data/docs/pages/rails_integration.rb +58 -0
  22. data/docs/pages/templates.rb +238 -238
  23. data/docs/pages/views.rb +175 -0
  24. data/fixtures/compilation/vcall.rb +38 -0
  25. data/fixtures/dummy/app/views/articles/form.rb +9 -9
  26. data/fixtures/dummy/app/views/card.rb +8 -8
  27. data/fixtures/dummy/app/views/heading.rb +5 -5
  28. data/fixtures/dummy/config/routes.rb +1 -1
  29. data/fixtures/dummy/db/schema.rb +2 -2
  30. data/fixtures/layout.rb +24 -24
  31. data/fixtures/page.rb +34 -34
  32. data/fixtures/test_helper.rb +2 -2
  33. data/fixtures/view_helper.rb +16 -0
  34. data/lib/generators/phlex/component/USAGE +1 -1
  35. data/lib/generators/phlex/component/component_generator.rb +8 -8
  36. data/lib/generators/phlex/component/templates/{component.rb.erb → view.rb.erb} +1 -1
  37. data/lib/install/phlex.rb +18 -0
  38. data/lib/overrides/symbol/name.rb +1 -1
  39. data/lib/phlex/block.rb +12 -12
  40. data/lib/phlex/buffered.rb +13 -13
  41. data/lib/phlex/compiler/formatter.rb +91 -0
  42. data/lib/phlex/compiler/generators/standard_element.rb +30 -0
  43. data/lib/phlex/compiler/generators/void_element.rb +29 -0
  44. data/lib/phlex/compiler/optimizers/base_optimizer.rb +34 -0
  45. data/lib/phlex/compiler/optimizers/vcall.rb +29 -0
  46. data/lib/phlex/compiler/visitors/base_visitor.rb +19 -0
  47. data/lib/phlex/compiler/visitors/component.rb +28 -0
  48. data/lib/phlex/compiler/visitors/component_method.rb +28 -0
  49. data/lib/phlex/compiler/visitors/file.rb +17 -0
  50. data/lib/phlex/compiler.rb +50 -0
  51. data/lib/phlex/configuration.rb +3 -3
  52. data/lib/phlex/engine.rb +11 -0
  53. data/lib/phlex/html.rb +128 -17
  54. data/lib/phlex/rails/tag_helpers.rb +23 -23
  55. data/lib/phlex/renderable.rb +32 -32
  56. data/lib/phlex/version.rb +1 -1
  57. data/lib/phlex/view.rb +223 -0
  58. data/lib/phlex.rb +27 -12
  59. data/lib/tasks/phlex_tasks.rake +10 -0
  60. metadata +39 -10
  61. data/CHANGELOG.md +0 -5
  62. data/docs/pages/components.rb +0 -175
  63. data/fixtures/component_helper.rb +0 -16
  64. data/lib/phlex/component.rb +0 -196
  65. data/lib/phlex/rails.rb +0 -8
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ module Generators
6
+ class StandardElement
7
+ def initialize(formatter, method_name:, arguments: nil)
8
+ @formatter = formatter
9
+ @method_name = method_name
10
+ end
11
+
12
+ def call
13
+ @formatter.append do |f|
14
+ f.text "<"
15
+ f.text tag
16
+ f.text ">"
17
+
18
+ f.text "</"
19
+ f.text tag
20
+ f.text ">"
21
+ end
22
+ end
23
+
24
+ def tag
25
+ HTML::STANDARD_ELEMENTS[@method_name]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ module Generators
6
+ class VoidElement
7
+ def initialize(formatter, method_name:, arguments: nil)
8
+ @formatter = formatter
9
+ @method_name = method_name
10
+ @arguments = arguments
11
+ end
12
+
13
+ def call
14
+ @formatter.append do |f|
15
+ f.text "<"
16
+ f.text tag
17
+ f.text " />"
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def tag
24
+ HTML::VOID_ELEMENTS[@method_name]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ module Optimizers
6
+ class BaseOptimizer
7
+ def initialize(node, compiler:)
8
+ @node = node
9
+ @compiler = compiler
10
+ end
11
+
12
+ def call
13
+ if standard_element?
14
+ @node.extend(self.class::StandardElement)
15
+ elsif void_element?
16
+ @node.extend(self.class::VoidElement)
17
+ else
18
+ false
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def standard_element?
25
+ HTML::STANDARD_ELEMENTS[name] && !@compiler.redefined?(name)
26
+ end
27
+
28
+ def void_element?
29
+ HTML::VOID_ELEMENTS[name] && !@compiler.redefined?(name)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ module Optimizers
6
+ class VCall < BaseOptimizer
7
+ module StandardElement
8
+ def format(formatter)
9
+ Generators::StandardElement.new(formatter,
10
+ method_name: value.value.to_sym).call
11
+ end
12
+ end
13
+
14
+ module VoidElement
15
+ def format(formatter)
16
+ Generators::VoidElement.new(formatter,
17
+ method_name: value.value.to_sym).call
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def name
24
+ @node.value.value.to_sym
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ module Visitors
6
+ class BaseVisitor < SyntaxTree::Visitor
7
+ def initialize(compiler = nil)
8
+ @compiler = compiler
9
+ end
10
+
11
+ private
12
+
13
+ def format(node)
14
+ Formatter.format("", node)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ module Visitors
6
+ class Component < BaseVisitor
7
+ visit_method def visit_def(node)
8
+ visitor = Visitors::ComponentMethod.new(@compiler)
9
+ visitor.visit_all(node.child_nodes)
10
+
11
+ if visitor.optimized_something?
12
+ @compiler.redefine(
13
+ format(node)
14
+ )
15
+ end
16
+ end
17
+
18
+ visit_method def visit_class(node)
19
+ nil
20
+ end
21
+
22
+ visit_method def visit_module(node)
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ module Visitors
6
+ class ComponentMethod < BaseVisitor
7
+ def optimized_something?
8
+ !!@optimized_something
9
+ end
10
+
11
+ visit_method def visit_vcall(node)
12
+ @optimized_something = Optimizers::VCall.new(node,
13
+ compiler: @compiler).call
14
+
15
+ super
16
+ end
17
+
18
+ visit_method def visit_class(node)
19
+ nil
20
+ end
21
+
22
+ visit_method def visit_module(node)
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ module Visitors
6
+ class File < BaseVisitor
7
+ visit_method def visit_class(node)
8
+ if node.location.start_line == @compiler.line
9
+ Visitors::Component.new(@compiler).visit_all(node.child_nodes)
10
+ else
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ def initialize(view)
6
+ @view = view
7
+ end
8
+
9
+ def inspect
10
+ "#{self.class.name} for #{@view.name} view class"
11
+ end
12
+
13
+ def call
14
+ Visitors::File.new(self).visit(tree)
15
+ end
16
+
17
+ def redefined?(method_name)
18
+ prototype = @view.allocate
19
+
20
+ @view.instance_method(method_name).bind(prototype) !=
21
+ Phlex::View.instance_method(method_name).bind(prototype)
22
+ end
23
+
24
+ def redefine(method)
25
+ @view.class_eval(method)
26
+ end
27
+
28
+ def line
29
+ location[1]
30
+ end
31
+
32
+ private
33
+
34
+ def tree
35
+ @tree ||= SyntaxTree.parse(source)
36
+ end
37
+
38
+ def source
39
+ SyntaxTree.read(file)
40
+ end
41
+
42
+ def file
43
+ location[0]
44
+ end
45
+
46
+ def location
47
+ ::Module.const_source_location(@view.name)
48
+ end
49
+ end
50
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- class Configuration
5
- # Config coming soon.
6
- end
4
+ class Configuration
5
+ # Config coming soon.
6
+ end
7
7
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/engine"
4
+
5
+ module Phlex
6
+ class Engine < ::Rails::Engine
7
+ initializer "phlex.tag_helpers" do
8
+ Phlex::View.include(Phlex::Rails::TagHelpers)
9
+ end
10
+ end
11
+ end
data/lib/phlex/html.rb CHANGED
@@ -1,21 +1,132 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
4
- using Overrides::Symbol::Name
4
+ using Overrides::Symbol::Name
5
5
  end
6
6
 
7
7
  module Phlex
8
- module HTML
9
- DOCTYPE = "<!DOCTYPE html>"
8
+ module HTML
9
+ DOCTYPE = "<!DOCTYPE html>"
10
10
 
11
- STANDARD_ELEMENTS = %i[a abbr address article aside b bdi bdo blockquote body button caption cite code colgroup data datalist dd del details dfn dialog div dl dt em fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header html i iframe ins kbd label legend li main map mark menuitem meter nav noscript object ol optgroup option output p path picture pre progress q rp rt ruby s samp script section select slot small span strong style sub summary sup svg table tbody td textarea tfoot th thead time title tr u ul video wbr].freeze
11
+ STANDARD_ELEMENTS = {
12
+ a: "a",
13
+ abbr: "abbr",
14
+ address: "address",
15
+ article: "article",
16
+ aside: "aside",
17
+ b: "b",
18
+ bdi: "bdi",
19
+ bdo: "bdo",
20
+ blockquote: "blockquote",
21
+ body: "body",
22
+ button: "button",
23
+ caption: "caption",
24
+ cite: "cite",
25
+ code: "code",
26
+ colgroup: "colgroup",
27
+ data: "data",
28
+ datalist: "datalist",
29
+ dd: "dd",
30
+ del: "del",
31
+ details: "details",
32
+ dfn: "dfn",
33
+ dialog: "dialog",
34
+ div: "div",
35
+ dl: "dl",
36
+ dt: "dt",
37
+ em: "em",
38
+ fieldset: "fieldset",
39
+ figcaption: "figcaption",
40
+ figure: "figure",
41
+ footer: "footer",
42
+ form: "form",
43
+ g: "g",
44
+ h1: "h1",
45
+ h2: "h2",
46
+ h3: "h3",
47
+ h4: "h4",
48
+ h5: "h5",
49
+ h6: "h6",
50
+ head: "head",
51
+ header: "header",
52
+ html: "html",
53
+ i: "i",
54
+ iframe: "iframe",
55
+ ins: "ins",
56
+ kbd: "kbd",
57
+ label: "label",
58
+ legend: "legend",
59
+ li: "li",
60
+ main: "main",
61
+ map: "map",
62
+ mark: "mark",
63
+ menuitem: "menuitem",
64
+ meter: "meter",
65
+ nav: "nav",
66
+ noscript: "noscript",
67
+ object: "object",
68
+ ol: "ol",
69
+ optgroup: "optgroup",
70
+ option: "option",
71
+ output: "output",
72
+ p: "p",
73
+ path: "path",
74
+ picture: "picture",
75
+ pre: "pre",
76
+ progress: "progress",
77
+ q: "q",
78
+ rp: "rp",
79
+ rt: "rt",
80
+ ruby: "ruby",
81
+ s: "s",
82
+ samp: "samp",
83
+ script: "script",
84
+ section: "section",
85
+ select: "select",
86
+ slot: "slot",
87
+ small: "small",
88
+ span: "span",
89
+ strong: "strong",
90
+ style: "style",
91
+ sub: "sub",
92
+ summary: "summary",
93
+ sup: "sup",
94
+ svg: "svg",
95
+ table: "table",
96
+ tbody: "tbody",
97
+ td: "td",
98
+ template_tag: "template",
99
+ textarea: "textarea",
100
+ tfoot: "tfoot",
101
+ th: "th",
102
+ thead: "thead",
103
+ time: "time",
104
+ title: "title",
105
+ tr: "tr",
106
+ u: "u",
107
+ ul: "ul",
108
+ video: "video",
109
+ wbr: "wbr",
110
+ }.freeze
12
111
 
13
- VOID_ELEMENTS = %i[area embed img input link meta param source track col].freeze
112
+ VOID_ELEMENTS = {
113
+ area: "area",
114
+ br: "br",
115
+ embed: "embed",
116
+ img: "img",
117
+ input: "input",
118
+ link: "link",
119
+ meta: "meta",
120
+ param: "param",
121
+ source: "source",
122
+ track: "track",
123
+ col: "col",
124
+ }.freeze
14
125
 
15
- EVENT_ATTRIBUTES = %w[onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmessage onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel].to_h { [_1, true] }.freeze
126
+ EVENT_ATTRIBUTES = %w[onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmessage onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel].to_h { [_1, true] }.freeze
16
127
 
17
- def register_element(element, tag: element.name.tr("_", "-"))
18
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
128
+ def register_element(element, tag: element.name.tr("_", "-"))
129
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
19
130
  # frozen_string_literal: true
20
131
 
21
132
  def #{element}(content = nil, **attributes, &block)
@@ -24,7 +135,7 @@ module Phlex
24
135
  @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << ">" << CGI.escape_html(content) << "</#{tag}>"
25
136
  elsif block_given?
26
137
  @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << ">"
27
- content(&block)
138
+ yield_content(&block)
28
139
  @_target << "</#{tag}>"
29
140
  else
30
141
  @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << "></#{tag}>"
@@ -41,7 +152,7 @@ module Phlex
41
152
  end
42
153
  elsif block_given?
43
154
  @_target << "<#{tag}>"
44
- content(&block)
155
+ yield_content(&block)
45
156
  @_target << "</#{tag}>"
46
157
  else
47
158
  @_target << "<#{tag}></#{tag}>"
@@ -50,11 +161,11 @@ module Phlex
50
161
 
51
162
  nil
52
163
  end
53
- RUBY
54
- end
164
+ RUBY
165
+ end
55
166
 
56
- def register_void_element(element, tag: element.name.tr("_", "-"))
57
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
167
+ def register_void_element(element, tag: element.name.tr("_", "-"))
168
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
58
169
  # frozen_string_literal: true
59
170
 
60
171
  def #{element}(**attributes)
@@ -66,7 +177,7 @@ module Phlex
66
177
 
67
178
  nil
68
179
  end
69
- RUBY
70
- end
71
- end
180
+ RUBY
181
+ end
182
+ end
72
183
  end
@@ -1,29 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- module Rails
5
- module TagHelpers
6
- def form_with(*args, **kwargs, &block)
7
- raw @_view_context.form_with(*args, **kwargs) { |form|
8
- capture do
9
- yield(
10
- Phlex::Buffered.new(form, buffer: @_target)
11
- )
12
- end
13
- }
14
- end
4
+ module Rails
5
+ module TagHelpers
6
+ def form_with(*args, **kwargs, &block)
7
+ raw @_view_context.form_with(*args, **kwargs) { |form|
8
+ capture do
9
+ yield(
10
+ Phlex::Buffered.new(form, buffer: @_target)
11
+ )
12
+ end
13
+ }
14
+ end
15
15
 
16
- def csp_meta_tag
17
- if (output = @_view_context.csp_meta_tag)
18
- @_target << output
19
- end
20
- end
16
+ def csp_meta_tag
17
+ if (output = @_view_context.csp_meta_tag)
18
+ @_target << output
19
+ end
20
+ end
21
21
 
22
- def csrf_meta_tags
23
- if (output = @_view_context.csrf_meta_tags)
24
- @_target << output
25
- end
26
- end
27
- end
28
- end
22
+ def csrf_meta_tags
23
+ if (output = @_view_context.csrf_meta_tags)
24
+ @_target << output
25
+ end
26
+ end
27
+ end
28
+ end
29
29
  end
@@ -1,41 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- module Renderable
5
- def render(renderable, *args, **kwargs, &block)
6
- if renderable.is_a?(Component)
7
- if block_given? && !block.binding.receiver.is_a?(Phlex::Block)
8
- block = Phlex::Block.new(self, &block)
9
- end
4
+ module Renderable
5
+ def render(renderable, *args, **kwargs, &block)
6
+ if renderable.is_a?(View)
7
+ if block_given? && !block.binding.receiver.is_a?(Phlex::Block)
8
+ block = Phlex::Block.new(self, &block)
9
+ end
10
10
 
11
- renderable.call(@_target, view_context: @_view_context, parent: self, &block)
12
- elsif renderable.is_a?(Class) && renderable < Component
13
- raise ArgumentError, "You tried to render the Phlex component class: #{renderable.name} but you probably meant to render an instance of that class instead."
14
- else
15
- @_target << @_view_context.render(renderable, *args, **kwargs, &block)
16
- end
11
+ renderable.call(@_target, view_context: @_view_context, parent: self, &block)
12
+ elsif renderable.is_a?(Class) && renderable < View
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
+ else
15
+ @_target << @_view_context.render(renderable, *args, **kwargs, &block)
16
+ end
17
17
 
18
- nil
19
- end
18
+ nil
19
+ end
20
20
 
21
- def render_in(view_context, &block)
22
- if block_given?
23
- call(view_context: view_context) do |*args, **kwargs|
24
- view_context.with_output_buffer(self) do
25
- original_length = @_target.length
26
- output = yield(*args, **kwargs)
27
- unchanged = (original_length == @_target.length)
21
+ def render_in(view_context, &block)
22
+ if block_given?
23
+ call(view_context: view_context) do |*args, **kwargs|
24
+ view_context.with_output_buffer(self) do
25
+ original_length = @_target.length
26
+ output = yield(*args, **kwargs)
27
+ unchanged = (original_length == @_target.length)
28
28
 
29
- text(output) if unchanged && output.is_a?(String)
30
- end
31
- end.html_safe
32
- else
33
- call(view_context: view_context).html_safe
34
- end
35
- end
29
+ text(output) if unchanged && output.is_a?(String)
30
+ end
31
+ end.html_safe
32
+ else
33
+ call(view_context: view_context).html_safe
34
+ end
35
+ end
36
36
 
37
- def format
38
- :html
39
- end
40
- end
37
+ def format
38
+ :html
39
+ end
40
+ end
41
41
  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.2.2"
4
+ VERSION = "0.3.0"
5
5
  end