phlex 0.2.2 → 0.3.1

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 (67) 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/view/USAGE +8 -0
  35. data/lib/generators/phlex/{component/templates/component.rb.erb → view/templates/view.rb.erb} +1 -1
  36. data/lib/generators/phlex/view/view_generator.rb +13 -0
  37. data/lib/install/phlex.rb +30 -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 +41 -12
  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/generators/phlex/component/USAGE +0 -8
  65. data/lib/generators/phlex/component/component_generator.rb +0 -13
  66. data/lib/phlex/component.rb +0 -196
  67. data/lib/phlex/rails.rb +0 -8
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pages
4
+ class Views < ApplicationPage
5
+ def template
6
+ render Layout.new(title: "Components in Phlex") do
7
+ render Markdown.new(<<~MD)
8
+ # Views
9
+
10
+ ## Yielding content
11
+
12
+ Your views can accept content as a block passed to the template method. You can capture the content block and pass it to the `content` method to yield it.
13
+ MD
14
+
15
+ render Example.new do |e|
16
+ e.tab "card.rb", <<~RUBY
17
+ class Card < Phlex::View
18
+ def template(&)
19
+ article(class: "drop-shadow rounded p-5") {
20
+ h1 "Amazing content!"
21
+ yield_content(&)
22
+ }
23
+ end
24
+ end
25
+ RUBY
26
+
27
+ e.execute "Card.new.call { 'Your content here.\n' }"
28
+ end
29
+
30
+ render Markdown.new(<<~MD)
31
+ ## Delegating content
32
+
33
+ Alternatively, you can pass the content down as an argument to another view or tag.
34
+ MD
35
+
36
+ render Example.new do |e|
37
+ e.tab "card.rb", <<~RUBY
38
+ class Card < Phlex::View
39
+ def template(&)
40
+ article(class: "drop-shadow rounded p-5", &)
41
+ end
42
+ end
43
+ RUBY
44
+
45
+ e.execute "Card.new.call { 'Your content here.' }"
46
+ end
47
+
48
+ render Markdown.new(<<~MD)
49
+ ## Nested views
50
+
51
+ Components can render other views and optionally pass them content as a block.
52
+ MD
53
+
54
+ render Example.new do |e|
55
+ e.tab "example.rb", <<~RUBY
56
+ class Example < Phlex::View
57
+ def template
58
+ render Card.new do
59
+ h1 "Hello"
60
+ end
61
+ end
62
+ end
63
+ RUBY
64
+
65
+ e.tab "card.rb", <<~RUBY
66
+ class Card < Phlex::View
67
+ def template(&)
68
+ article(class: "drop-shadow rounded p-5", &)
69
+ end
70
+ end
71
+ RUBY
72
+
73
+ e.execute "Example.new.call"
74
+ end
75
+
76
+ render Markdown.new(<<~MD)
77
+ If the block just wraps a string, the string is treated as _text content_.
78
+ MD
79
+
80
+ render Example.new do |e|
81
+ e.tab "example.rb", <<~RUBY
82
+ class Example < Phlex::View
83
+ def template
84
+ render(Card.new) { "Hi" }
85
+ end
86
+ end
87
+ RUBY
88
+
89
+ e.tab "card.rb", <<~RUBY
90
+ class Card < Phlex::View
91
+ def template(&)
92
+ article(class: "drop-shadow rounded p-5", &)
93
+ end
94
+ end
95
+ RUBY
96
+
97
+ e.execute "Example.new.call"
98
+ end
99
+
100
+ render Markdown.new(<<~MD)
101
+ ## Component attributes
102
+
103
+ Besides content, views can define attributes in an initializer, which can then be rendered in the template.
104
+ MD
105
+
106
+ render Example.new do |e|
107
+ e.tab "hello.rb", <<~RUBY
108
+ class Hello < Phlex::View
109
+ def initialize(name:)
110
+ @name = name
111
+ end
112
+
113
+ def template
114
+ h1 "Hello \#{@name}!"
115
+ end
116
+ end
117
+ RUBY
118
+
119
+ e.tab "example.rb", <<~RUBY
120
+ class Example < Phlex::View
121
+ def template
122
+ render Hello.new(name: "Joel")
123
+ end
124
+ end
125
+ RUBY
126
+
127
+ e.execute "Example.new.call"
128
+ end
129
+
130
+ render Markdown.new(<<~MD)
131
+ It’s usually a good idea to use instance variables directly rather than creating accessor methods for them. Otherwise it’s easy to run into naming conflicts. For example, your layout view might have the attribute `title`, to render into a `<title>` element in the document head. If you define `attr_accessor :title`, that would overwrite the `title` method for creating `<title>` elements.
132
+
133
+ ## Calculations with methods
134
+
135
+ Views are just Ruby classes, so you can perform calculations on view attributes by defining your own methods.
136
+ MD
137
+
138
+ render Example.new do |e|
139
+ e.tab "status.rb", <<~RUBY
140
+ class Status < Phlex::View
141
+ def initialize(status:)
142
+ @status = status
143
+ end
144
+
145
+ def template
146
+ span status_emoji
147
+ end
148
+
149
+ private
150
+
151
+ def status_emoji
152
+ case @status
153
+ when :success
154
+ "✅"
155
+ when :failure
156
+ "❌"
157
+ end
158
+ end
159
+ end
160
+ RUBY
161
+
162
+ e.tab "example.rb", <<~RUBY
163
+ class Example < Phlex::View
164
+ def template
165
+ render Status.new(status: :success)
166
+ end
167
+ end
168
+ RUBY
169
+
170
+ e.execute "Example.new.call"
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fixtures
4
+ module Compilation
5
+ module VCall
6
+ class WithStandardElement < Phlex::View
7
+ def template
8
+ div
9
+ end
10
+ end
11
+
12
+ class WithVoidElement < Phlex::View
13
+ def template
14
+ img
15
+ end
16
+ end
17
+
18
+ class WithAnotherMethodCall < Phlex::View
19
+ def template
20
+ article
21
+ some_other_method
22
+ article
23
+ end
24
+ end
25
+
26
+ class WithRedefinedTagMethod < Phlex::View
27
+ def template
28
+ title
29
+ article
30
+ end
31
+
32
+ def title
33
+ h1
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Views
4
- module Articles
5
- class Form < Phlex::Component
6
- def template
7
- form_with url: "test" do |f|
8
- f.text_field :name
9
- end
10
- end
11
- end
12
- end
4
+ module Articles
5
+ class Form < Phlex::View
6
+ def template
7
+ form_with url: "test" do |f|
8
+ f.text_field :name
9
+ end
10
+ end
11
+ end
12
+ end
13
13
  end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Views
4
- class Card < Phlex::Component
5
- def template(&block)
6
- article class: "drop-shadow p-5 rounded", &block
7
- end
4
+ class Card < Phlex::View
5
+ def template(&block)
6
+ article class: "drop-shadow p-5 rounded", &block
7
+ end
8
8
 
9
- def title(text)
10
- h3 text, class: "font-bold"
11
- end
12
- end
9
+ def title(text)
10
+ h3 text, class: "font-bold"
11
+ end
12
+ end
13
13
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Views
4
- class Heading < Phlex::Component
5
- def template(&block)
6
- h1(&block)
7
- end
8
- end
4
+ class Heading < Phlex::View
5
+ def template(&block)
6
+ h1(&block)
7
+ end
8
+ end
9
9
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Rails.application.routes.draw do
4
- # Rails routes here
4
+ # Rails routes here
5
5
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ActiveRecord::Schema.define do
4
- # Set up any tables you need to exist for your test suite that don't belong
5
- # in migrations.
4
+ # Set up any tables you need to exist for your test suite that don't belong
5
+ # in migrations.
6
6
  end
data/fixtures/layout.rb CHANGED
@@ -1,31 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Example
4
- class LayoutComponent < Phlex::Component
5
- def initialize(title: "Example")
6
- @title = title
7
- end
4
+ class LayoutComponent < Phlex::View
5
+ def initialize(title: "Example")
6
+ @title = title
7
+ end
8
8
 
9
- def template(&block)
10
- html do
11
- head do
12
- title @title
13
- meta name: "viewport", content: "width=device-width,initial-scale=1"
14
- link href: "/assets/tailwind.css", rel: "stylesheet"
15
- end
9
+ def template(&block)
10
+ html do
11
+ head do
12
+ title @title
13
+ meta name: "viewport", content: "width=device-width,initial-scale=1"
14
+ link href: "/assets/tailwind.css", rel: "stylesheet"
15
+ end
16
16
 
17
- body class: "bg-zinc-100" do
18
- nav class: "p-5", id: "main_nav" do
19
- ul do
20
- li(class: "p-5") { a "Home", href: "/" }
21
- li(class: "p-5") { a "About", href: "/about" }
22
- li(class: "p-5") { a "Contact", href: "/contact" }
23
- end
24
- end
17
+ body class: "bg-zinc-100" do
18
+ nav class: "p-5", id: "main_nav" do
19
+ ul do
20
+ li(class: "p-5") { a "Home", href: "/" }
21
+ li(class: "p-5") { a "About", href: "/about" }
22
+ li(class: "p-5") { a "Contact", href: "/contact" }
23
+ end
24
+ end
25
25
 
26
- div class: "container mx-auto p-5", &block
27
- end
28
- end
29
- end
30
- end
26
+ div class: "container mx-auto p-5", &block
27
+ end
28
+ end
29
+ end
30
+ end
31
31
  end
data/fixtures/page.rb CHANGED
@@ -1,41 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Example
4
- class Page < Phlex::Component
5
- def template
6
- render LayoutComponent.new do
7
- h1 "Hi"
4
+ class Page < Phlex::View
5
+ def template
6
+ render LayoutComponent.new do
7
+ h1 "Hi"
8
8
 
9
- 5.times do
10
- div do
11
- 10.times do
12
- a "Test", href: "something", unique: SecureRandom.uuid, data: { value: 1 }
13
- end
14
- end
15
- end
9
+ 5.times do
10
+ div do
11
+ 10.times do
12
+ a "Test", href: "something", unique: SecureRandom.uuid, data: { value: 1 }
13
+ end
14
+ end
15
+ end
16
16
 
17
- table do
18
- thead do
19
- 10.times do
20
- tr do
21
- th "Hi"
22
- end
23
- end
24
- end
17
+ table do
18
+ thead do
19
+ 10.times do
20
+ tr do
21
+ th "Hi"
22
+ end
23
+ end
24
+ end
25
25
 
26
- tbody do
27
- 100.times do
28
- tr class: "a b c d e f g", id: "something" do
29
- 10.times do
30
- td class: "f g h i j k l" do
31
- span "Test"
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
26
+ tbody do
27
+ 100.times do
28
+ tr class: "a b c d e f g", id: "something" do
29
+ 10.times do
30
+ td class: "f g h i j k l" do
31
+ span "Test"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
41
  end
@@ -7,7 +7,7 @@ Bundler.require :test
7
7
 
8
8
  Combustion.path = "fixtures/dummy"
9
9
  Combustion.initialize! :action_controller do
10
- config.autoload_paths << "#{root}/app"
10
+ config.autoload_paths << "#{root}/app"
11
11
  end
12
12
 
13
- require "component_helper"
13
+ require "view_helper"
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewHelper
4
+ def self.extended(parent)
5
+ parent.class_exec do
6
+ let(:output) { example.call }
7
+ let(:example) { view.new }
8
+ end
9
+ end
10
+
11
+ def view(&block)
12
+ let :view do
13
+ Class.new(Phlex::View, &block)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generates a Phlex view with the given name
3
+
4
+ Example:
5
+ rails generate phlex:view Sidebar
6
+
7
+ This will create:
8
+ app/views/sidebar.rb
@@ -1,6 +1,6 @@
1
1
  <% module_namespacing do -%>
2
2
  module Views
3
- class <%= class_name %> < Phlex::Component
3
+ class <%= class_name %> < ApplicationView
4
4
  def template
5
5
  end
6
6
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Generators
5
+ class ViewGenerator < ::Rails::Generators::NamedBase
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ def create_view
9
+ template "view.rb.erb", File.join("app/views", class_path, "#{file_name}.rb")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ say "Installing Phlex..."
4
+
5
+ application_configuration_path = Rails.root.join("config/application.rb")
6
+ application_configuration_content = File.read(application_configuration_path)
7
+
8
+ pattern = %r(config.autoload_paths << (Rails.root.join\(.app.\)|.\#{root}/app.)\n)
9
+
10
+ unless application_configuration_content.match?(pattern)
11
+ inject_into_class(
12
+ application_configuration_path,
13
+ "Application",
14
+ %( config.autoload_paths << "\#{root}/app"\n)
15
+ )
16
+ end
17
+
18
+ unless Rails.root.join("app/views/application_view.rb").exist?
19
+ create_file(Rails.root.join("app/views/application_view.rb"), <<~RUBY)
20
+ # frozen_string_literal: true
21
+
22
+ module Views
23
+ class ApplicationView < Phlex::View
24
+ include Rails.application.routes.url_helpers
25
+ end
26
+ end
27
+ RUBY
28
+ end
29
+
30
+ say "Phlex successfully installed!"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Overrides::Symbol::Name
4
- refine(Symbol) { alias_method :name, :to_s }
4
+ refine(Symbol) { alias_method :name, :to_s }
5
5
  end
data/lib/phlex/block.rb CHANGED
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- class Block
5
- def initialize(context, &block)
6
- @context = context
7
- @block = block
8
- end
4
+ class Block
5
+ def initialize(context, &block)
6
+ @context = context
7
+ @block = block
8
+ end
9
9
 
10
- def to_proc
11
- method(:call).to_proc
12
- end
10
+ def to_proc
11
+ method(:call).to_proc
12
+ end
13
13
 
14
- def call(*args, **kwargs)
15
- @context.instance_exec(*args, **kwargs, &@block)
16
- end
17
- end
14
+ def call(*args, **kwargs)
15
+ @context.instance_exec(*args, **kwargs, &@block)
16
+ end
17
+ end
18
18
  end
@@ -1,19 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- class Buffered
5
- def initialize(object, buffer:)
6
- @object, @buffer = object, buffer
7
- end
4
+ class Buffered
5
+ def initialize(object, buffer:)
6
+ @object, @buffer = object, buffer
7
+ end
8
8
 
9
- def method_missing(name, *args, **kwargs, &block)
10
- output = @object.public_send(name, *args, **kwargs, &block)
11
- @buffer << output if output.is_a? String
12
- nil
13
- end
9
+ def method_missing(name, *args, **kwargs, &block)
10
+ output = @object.public_send(name, *args, **kwargs, &block)
11
+ @buffer << output if output.is_a? String
12
+ nil
13
+ end
14
14
 
15
- def respond_to_missing?(name)
16
- @object.respond_to?(name)
17
- end
18
- end
15
+ def respond_to_missing?(name)
16
+ @object.respond_to?(name)
17
+ end
18
+ end
19
19
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ class Compiler
5
+ class Formatter < SyntaxTree::Formatter
6
+ def genspace
7
+ -> (n) { "\t" * (n / 2) }
8
+ end
9
+
10
+ def format(node, stackable: true)
11
+ stack << node if stackable
12
+ doc = node.format(self)
13
+ stack.pop if stackable
14
+ doc
15
+ end
16
+
17
+ def flush
18
+ text "" if @open_string_append
19
+
20
+ super
21
+ end
22
+
23
+ def breakable(*args, **kwargs)
24
+ if !@texting && @open_string_append
25
+ @broken = kwargs
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def chain_append(&block)
32
+ @appending = true
33
+
34
+ if @open_string_append
35
+ text '" << '
36
+ elsif @open_chain_append
37
+ text " << "
38
+ else
39
+ text "@_target << "
40
+ end
41
+
42
+ @open_string_append = false
43
+ @open_chain_append = true
44
+
45
+ yield(self)
46
+
47
+ @appending = false
48
+ end
49
+
50
+ def append(&block)
51
+ @appending = true
52
+
53
+ unless @open_string_append
54
+ if @open_chain_append
55
+ text ' << "'
56
+ else
57
+ text '@_target << "'
58
+ end
59
+
60
+ @open_chain_append = false
61
+ @open_string_append = true
62
+ end
63
+
64
+ yield(self)
65
+
66
+ @appending = false
67
+ end
68
+
69
+ def text(value, ...)
70
+ @texting = true
71
+
72
+ unless @appending
73
+ if @open_string_append
74
+ super('"')
75
+ @open_string_append = false
76
+ end
77
+
78
+ @open_chain_append = false
79
+
80
+ breakable(**@broken) if @broken
81
+ end
82
+
83
+ @broken = false
84
+
85
+ super
86
+
87
+ @texting = false
88
+ end
89
+ end
90
+ end
91
+ end