phlex 0.2.1 → 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 (68) 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/README.md +2 -2
  6. data/Rakefile +2 -2
  7. data/SECURITY.md +5 -0
  8. data/bench.rb +1 -1
  9. data/docs/assets/application.css +2 -0
  10. data/docs/build.rb +4 -4
  11. data/docs/components/callout.rb +5 -5
  12. data/docs/components/code_block.rb +18 -18
  13. data/docs/components/example.rb +23 -23
  14. data/docs/components/heading.rb +5 -5
  15. data/docs/components/layout.rb +42 -41
  16. data/docs/components/markdown.rb +19 -19
  17. data/docs/components/tabs/tab.rb +18 -18
  18. data/docs/components/tabs.rb +21 -21
  19. data/docs/components/title.rb +5 -5
  20. data/docs/page_builder.rb +32 -32
  21. data/docs/pages/application_page.rb +3 -3
  22. data/docs/pages/index.rb +23 -25
  23. data/docs/pages/rails_integration.rb +58 -0
  24. data/docs/pages/templates.rb +238 -238
  25. data/docs/pages/views.rb +175 -0
  26. data/fixtures/compilation/vcall.rb +38 -0
  27. data/fixtures/dummy/app/views/articles/form.rb +9 -9
  28. data/fixtures/dummy/app/views/articles/index.html.erb +3 -0
  29. data/fixtures/dummy/app/views/card.rb +8 -8
  30. data/fixtures/dummy/app/views/heading.rb +9 -0
  31. data/fixtures/dummy/config/routes.rb +1 -1
  32. data/fixtures/dummy/db/schema.rb +2 -2
  33. data/fixtures/layout.rb +24 -24
  34. data/fixtures/page.rb +34 -34
  35. data/fixtures/test_helper.rb +2 -2
  36. data/fixtures/view_helper.rb +16 -0
  37. data/lib/generators/phlex/component/USAGE +1 -1
  38. data/lib/generators/phlex/component/component_generator.rb +8 -8
  39. data/lib/generators/phlex/component/templates/{component.rb.erb → view.rb.erb} +1 -1
  40. data/lib/install/phlex.rb +18 -0
  41. data/lib/overrides/symbol/name.rb +1 -1
  42. data/lib/phlex/block.rb +12 -12
  43. data/lib/phlex/buffered.rb +13 -13
  44. data/lib/phlex/compiler/formatter.rb +91 -0
  45. data/lib/phlex/compiler/generators/standard_element.rb +30 -0
  46. data/lib/phlex/compiler/generators/void_element.rb +29 -0
  47. data/lib/phlex/compiler/optimizers/base_optimizer.rb +34 -0
  48. data/lib/phlex/compiler/optimizers/vcall.rb +29 -0
  49. data/lib/phlex/compiler/visitors/base_visitor.rb +19 -0
  50. data/lib/phlex/compiler/visitors/component.rb +28 -0
  51. data/lib/phlex/compiler/visitors/component_method.rb +28 -0
  52. data/lib/phlex/compiler/visitors/file.rb +17 -0
  53. data/lib/phlex/compiler.rb +50 -0
  54. data/lib/phlex/configuration.rb +3 -3
  55. data/lib/phlex/engine.rb +11 -0
  56. data/lib/phlex/html.rb +136 -18
  57. data/lib/phlex/rails/tag_helpers.rb +23 -23
  58. data/lib/phlex/renderable.rb +33 -27
  59. data/lib/phlex/version.rb +1 -1
  60. data/lib/phlex/view.rb +223 -0
  61. data/lib/phlex.rb +27 -12
  62. data/lib/tasks/phlex_tasks.rake +10 -0
  63. metadata +41 -10
  64. data/CHANGELOG.md +0 -5
  65. data/docs/pages/components.rb +0 -175
  66. data/fixtures/component_helper.rb +0 -16
  67. data/lib/phlex/component.rb +0 -191
  68. data/lib/phlex/rails.rb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2eefd3a74f2982d981f510fbf51664a489edc5ca40bbfddc3be564fd8150646b
4
- data.tar.gz: 8c5c168a439d41ab0c9020a6ebe6b43585cfdda7fc7135782b90df45ee3a5b56
3
+ metadata.gz: 4314dc80b5da3795454b1906f8c51b0b875fc6fea6962c2a77476dd6a4f01d34
4
+ data.tar.gz: 7ca6921fed9c3d2d6122dd0e77e2a97f5f100dc4a003d76c76c8dd407fddf86e
5
5
  SHA512:
6
- metadata.gz: 3cf930ae8e3aa73ddb806de461d0bad1a80a747c38c52c1737631d78a6fe92054f2323d8d9b5f943bf5b028619096232880a3cce6c05ae3abbacfa17f9df31c7
7
- data.tar.gz: 477bfe4a00d3c304671730c2fb1be83804fc7452ce5cfd10f29e1883aa1b2c91e69fe3ca96fa08a9c0c06126292dafa248c1bc655f911ec7c5d8cf17a7395d49
6
+ metadata.gz: 9e7a837a1c2584e7c7f0c7955c2f49f48d4cbc049e19ba2cb707f6cfb3662edca2c05dc5cb80fc59ec6d50416fcc60894ec77bf3cc987cf22aab4ea9c470e95a
7
+ data.tar.gz: 1365cfd00e035b4364a66d89d3657e1d49745e1950d64b7b0fe9a17bcf27551d59447f6cd86451c3279886303cef119a69b8c752acddc81045b0eefc66fd5a4f
data/.editorconfig ADDED
@@ -0,0 +1,5 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 2
data/.rubocop.yml CHANGED
@@ -2,3 +2,9 @@ inherit_from: "https://www.goodcop.style"
2
2
 
3
3
  AllCops:
4
4
  TargetRubyVersion: 2.7
5
+
6
+ Layout/IndentationStyle:
7
+ EnforcedStyle: tabs
8
+
9
+ Layout/IndentationWidth:
10
+ Width: 1
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,23 @@
1
+ # Contributing to Phlex
2
+
3
+ ## Priorities
4
+
5
+ We’re trying to provide the best possible developer experience for the people using Phlex in their apps and the best possible performance for the users of those apps.
6
+
7
+ Phlex is incredibly complex and requires a lot of meta-programming but when you use it, it feels simple. Phlex views feel like plain old Ruby objects. You just subclass and define a couple of methods. That’s it. That’s how it should be.
8
+
9
+ ## Setup
10
+
11
+ - Install dependencies `bundle install`
12
+ - Run the tests `bundle exec sus`
13
+ - Run Rubocop and auto-correct `bundle exec rubocop -A`
14
+
15
+ ## Tests
16
+
17
+ We use the **[Sus](https://github.com/ioquatix/sus)** testing framework. It feels a bit like RSpec with a few small differences. There’s no documentation for Sus at the moment, but you should be able to pick up the basics from reading other tests or looking at the implementation in the Sus repo.
18
+
19
+ You can run all the tests with `bundle exec sus`.
20
+
21
+ ## Documentation
22
+
23
+ Documentation is deployed when it’s merged into the `latest` branch with a release. But you can build and preview the docs locally by running `bin/docs`.
data/README.md CHANGED
@@ -16,9 +16,9 @@ Everyone interacting in Phlex codebases, issue trackers, chat rooms and mailing
16
16
 
17
17
  ### Sponsorship 💖
18
18
 
19
- Maintaining a library like this is a lot of work. Phlex is being actively developed and maintained by **[Joel Drapper](https://github.com/sponsors/joeldrapper)**. If your company benefits from this work or is likely to benefit from it in the future, please consider sponsorship.
19
+ Maintaining a library is a lot of work. If your company benefits from this work or is likely to benefit from it in the future, please consider [sponsorship](https://github.com/sponsors/joeldrapper). Phlex is actively developed and maintained by **[Joel Drapper](https://github.com/sponsors/joeldrapper)**.
20
20
 
21
- ### Security 👮
21
+ ### Security 🚨
22
22
 
23
23
  If you’ve found a potential security issue, please email [joel+security@drapper.me](mailto:joel+security@drapper.me).
24
24
 
data/Rakefile CHANGED
@@ -3,8 +3,8 @@
3
3
  require "bundler/gem_tasks"
4
4
 
5
5
  begin
6
- require "rspec/core/rake_task"
7
- RSpec::Core::RakeTask.new(:spec)
6
+ require "rspec/core/rake_task"
7
+ RSpec::Core::RakeTask.new(:spec)
8
8
  end
9
9
 
10
10
  task default: :spec
data/SECURITY.md ADDED
@@ -0,0 +1,5 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you found a possible security vulnerability in Phlex, please email joel+security@drapper.me.
data/bench.rb CHANGED
@@ -10,5 +10,5 @@ require_relative "fixtures/layout"
10
10
  puts RUBY_DESCRIPTION
11
11
 
12
12
  Benchmark.ips do |x|
13
- x.report("Page") { Example::Page.new.call }
13
+ x.report("Page") { Example::Page.new.call }
14
14
  end
@@ -2,6 +2,8 @@
2
2
  @tailwind components;
3
3
  @tailwind utilities;
4
4
 
5
+ pre { tab-size: 2; }
6
+
5
7
  .tabs input[type="radio"]:checked + label {
6
8
  background: #f8f8f8;
7
9
  z-index: 1;
data/docs/build.rb CHANGED
@@ -8,10 +8,10 @@ require "fileutils"
8
8
  Bundler.require :docs
9
9
 
10
10
  Zeitwerk::Loader.new.tap do |loader|
11
- loader.push_dir(__dir__)
12
- loader.ignore(__FILE__)
13
- loader.setup
14
- loader.eager_load
11
+ loader.push_dir(__dir__)
12
+ loader.ignore(__FILE__)
13
+ loader.setup
14
+ loader.eager_load
15
15
  end
16
16
 
17
17
  FileUtils.mkdir_p("#{__dir__}/dist")
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Callout < Phlex::Component
5
- def template(&block)
6
- div(class: "rounded bg-orange-50 text-sm p-5 border border-orange-100", &block)
7
- end
8
- end
4
+ class Callout < Phlex::View
5
+ def template(&block)
6
+ div(class: "rounded bg-orange-50 text-sm p-5 border border-orange-100", &block)
7
+ end
8
+ end
9
9
  end
@@ -1,26 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class CodeBlock < Phlex::Component
5
- FORMATTER = Rouge::Formatters::HTML.new
4
+ class CodeBlock < Phlex::View
5
+ FORMATTER = Rouge::Formatters::HTML.new
6
6
 
7
- def initialize(code, syntax:)
8
- @code = code
9
- @syntax = syntax
10
- end
7
+ def initialize(code, syntax:)
8
+ @code = code
9
+ @syntax = syntax
10
+ end
11
11
 
12
- def template
13
- pre(class: "highlight p-5 whitespace-pre-wrap bg-stone-50") {
14
- raw FORMATTER.format(
15
- lexer.lex(@code)
16
- )
17
- }
18
- end
12
+ def template
13
+ pre(class: "highlight p-5 whitespace-pre-wrap bg-stone-50") {
14
+ raw FORMATTER.format(
15
+ lexer.lex(@code)
16
+ )
17
+ }
18
+ end
19
19
 
20
- private
20
+ private
21
21
 
22
- def lexer
23
- Rouge::Lexer.find(@syntax)
24
- end
25
- end
22
+ def lexer
23
+ Rouge::Lexer.find(@syntax)
24
+ end
25
+ end
26
26
  end
@@ -1,32 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Example < Phlex::Component
5
- def initialize
6
- @sandbox = Module.new
7
- end
4
+ class Example < Phlex::View
5
+ def initialize
6
+ @sandbox = Module.new
7
+ end
8
8
 
9
- def template(&block)
10
- render Tabs.new do |t|
11
- @t = t
12
- yield self
13
- end
14
- end
9
+ def template(&block)
10
+ render Tabs.new do |t|
11
+ @t = t
12
+ yield self
13
+ end
14
+ end
15
15
 
16
- def tab(name, code)
17
- @t.tab(name) do
18
- render CodeBlock.new(code, syntax: :ruby)
19
- end
16
+ def tab(name, code)
17
+ @t.tab(name) do
18
+ render CodeBlock.new(code, syntax: :ruby)
19
+ end
20
20
 
21
- @sandbox.instance_eval(code)
22
- end
21
+ @sandbox.class_eval(code)
22
+ end
23
23
 
24
- def execute(code)
25
- output = @sandbox.instance_eval(code)
24
+ def execute(code)
25
+ output = @sandbox.class_eval(code)
26
26
 
27
- @t.tab("HTML Output") do
28
- render CodeBlock.new(HtmlBeautifier.beautify(output), syntax: :html)
29
- end
30
- end
31
- end
27
+ @t.tab("HTML Output") do
28
+ render CodeBlock.new(HtmlBeautifier.beautify(output), syntax: :html)
29
+ end
30
+ end
31
+ end
32
32
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Heading < Phlex::Component
5
- def template(&block)
6
- h2(class: "text-xl font-bold mt-10 mb-5", &block)
7
- end
8
- end
4
+ class Heading < Phlex::View
5
+ def template(&block)
6
+ h2(class: "text-xl font-bold mt-10 mb-5", &block)
7
+ end
8
+ end
9
9
  end
@@ -1,45 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Layout < Phlex::Component
5
- register_element :style
6
-
7
- def initialize(title:)
8
- @title = title
9
- end
10
-
11
- def template(&block)
12
- doctype
13
-
14
- html do
15
- head do
16
- meta charset: "utf-8"
17
- title @title
18
- link href: "/application.css", rel: "stylesheet"
19
- style { raw Rouge::Theme.find("github").render(scope: ".highlight") }
20
- end
21
-
22
- body class: "p-12" do
23
- div class: "max-w-screen-lg mx-auto grid grid-cols-4 gap-10" do
24
- header class: "col-span-1" do
25
- a(href: "/", class: "block") { img src: "/assets/logo.png", width: "150" }
26
-
27
- nav do
28
- ul do
29
- li { a "Introduction", href: "/" }
30
- li { a "Templates", href: "/templates" }
31
- li { a "Components", href: "/components" }
32
- li { a "Source code", href: "https://github.com/joeldrapper/phlex" }
33
- end
34
- end
35
- end
36
-
37
- main(class: "col-span-3", &block)
38
-
39
- footer class: "text-sm text-right col-span-4 py-10"
40
- end
41
- end
42
- end
43
- end
44
- end
4
+ class Layout < Phlex::View
5
+ register_element :style
6
+
7
+ def initialize(title:)
8
+ @title = title
9
+ end
10
+
11
+ def template(&block)
12
+ doctype
13
+
14
+ html do
15
+ head do
16
+ meta charset: "utf-8"
17
+ title @title
18
+ link href: "/application.css", rel: "stylesheet"
19
+ style { raw Rouge::Theme.find("github").render(scope: ".highlight") }
20
+ end
21
+
22
+ body class: "p-12" do
23
+ div class: "max-w-screen-lg mx-auto grid grid-cols-4 gap-10" do
24
+ header class: "col-span-1" do
25
+ a(href: "/", class: "block") { img src: "/assets/logo.png", width: "150" }
26
+
27
+ nav do
28
+ ul do
29
+ li { a "Introduction", href: "/" }
30
+ li { a "Templates", href: "/templates" }
31
+ li { a "Views", href: "/views" }
32
+ li { a "Rails integration", href: "/rails-integration" }
33
+ li { a "Source code", href: "https://github.com/joeldrapper/phlex" }
34
+ end
35
+ end
36
+ end
37
+
38
+ main(class: "col-span-3", &block)
39
+
40
+ footer class: "text-sm text-right col-span-4 py-10"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
45
46
  end
@@ -1,26 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Markdown < Phlex::Component
5
- class Render < Redcarpet::Render::HTML
6
- def header(text, level)
7
- case level
8
- when 1
9
- Title.new.call { text }
10
- else
11
- Heading.new.call { text }
12
- end
13
- end
14
- end
4
+ class Markdown < Phlex::View
5
+ class Render < Redcarpet::Render::HTML
6
+ def header(text, level)
7
+ case level
8
+ when 1
9
+ Title.new.call { text }
10
+ else
11
+ Heading.new.call { text }
12
+ end
13
+ end
14
+ end
15
15
 
16
- MARKDOWN = Redcarpet::Markdown.new(Render.new, autolink: true, tables: true)
16
+ MARKDOWN = Redcarpet::Markdown.new(Render.new, autolink: true, tables: true)
17
17
 
18
- def initialize(content)
19
- @content = content
20
- end
18
+ def initialize(content)
19
+ @content = content
20
+ end
21
21
 
22
- def template
23
- raw MARKDOWN.render(@content)
24
- end
25
- end
22
+ def template
23
+ raw MARKDOWN.render(@content)
24
+ end
25
+ end
26
26
  end
@@ -1,26 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Tabs
5
- class Tab < Phlex::Component
6
- def initialize(name:, checked:)
7
- @name = name
8
- @checked = checked
9
- end
4
+ class Tabs
5
+ class Tab < Phlex::View
6
+ def initialize(name:, checked:)
7
+ @name = name
8
+ @checked = checked
9
+ end
10
10
 
11
- def template(&block)
12
- input class: "opacity-0 fixed peer", type: "radio", name: @_parent.unique_identifier, id: unique_identifier, checked: @checked
11
+ def template(&block)
12
+ input class: "opacity-0 fixed peer", type: "radio", name: @_parent.unique_identifier, id: unique_identifier, checked: @checked
13
13
 
14
- label @name, id: "#{unique_identifier}-label", for: unique_identifier, role: "tab", aria_controls: "#{unique_identifier}-panel", class: "order-1 py-2 px-5 bg-white text-sm border border-b-0 border-l-0 font-medium first-of-type:border-l first-of-type:rounded-tl last-of-type:rounded-tr before:absolute before:pointer-events-none before:w-full before:ring before:h-full before:left-0 before:top-0 before:hidden before:rounded peer-focus:before:block cursor-pointer"
14
+ label @name, id: "#{unique_identifier}-label", for: unique_identifier, role: "tab", aria_controls: "#{unique_identifier}-panel", class: "order-1 py-2 px-5 bg-white text-sm border border-b-0 border-l-0 font-medium first-of-type:border-l first-of-type:rounded-tl last-of-type:rounded-tr before:absolute before:pointer-events-none before:w-full before:ring before:h-full before:left-0 before:top-0 before:hidden before:rounded peer-focus:before:block cursor-pointer"
15
15
 
16
- div id: "#{unique_identifier}-panel", role: "tabpanel", aria_labelledby: "#{unique_identifier}-label", class: "tab hidden order-2 w-full border rounded-b rounded-tr overflow-hidden" do
17
- @_parent.instance_exec(&block)
18
- end
19
- end
16
+ div id: "#{unique_identifier}-panel", role: "tabpanel", aria_labelledby: "#{unique_identifier}-label", class: "tab hidden order-2 w-full border rounded-b rounded-tr overflow-hidden" do
17
+ @_parent.instance_exec(&block)
18
+ end
19
+ end
20
20
 
21
- def unique_identifier
22
- @unique_identifier ||= SecureRandom.hex
23
- end
24
- end
25
- end
21
+ def unique_identifier
22
+ @unique_identifier ||= SecureRandom.hex
23
+ end
24
+ end
25
+ end
26
26
  end
@@ -1,30 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Tabs < Phlex::Component
5
- def initialize
6
- @index = 1
7
- end
4
+ class Tabs < Phlex::View
5
+ def initialize
6
+ @index = 1
7
+ end
8
8
 
9
- def template(&block)
10
- div class: "tabs flex flex-wrap relative my-5", role: "tablist" do
11
- content(&block)
12
- end
13
- end
9
+ def template(&block)
10
+ div class: "tabs flex flex-wrap relative my-5", role: "tablist" do
11
+ yield_content(&block)
12
+ end
13
+ end
14
14
 
15
- def tab(name, &block)
16
- render(Tab.new(name: name, checked: first?), &block)
17
- @index += 1
18
- end
15
+ def tab(name, &block)
16
+ render(Tab.new(name: name, checked: first?), &block)
17
+ @index += 1
18
+ end
19
19
 
20
- def unique_identifier
21
- @unique_identifier ||= SecureRandom.hex
22
- end
20
+ def unique_identifier
21
+ @unique_identifier ||= SecureRandom.hex
22
+ end
23
23
 
24
- private
24
+ private
25
25
 
26
- def first?
27
- @index == 1
28
- end
29
- end
26
+ def first?
27
+ @index == 1
28
+ end
29
+ end
30
30
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Components
4
- class Title < Phlex::Component
5
- def template(&block)
6
- h1(class: "text-2xl font-bold my-5", &block)
7
- end
8
- end
4
+ class Title < Phlex::View
5
+ def template(&block)
6
+ h1(class: "text-2xl font-bold my-5", &block)
7
+ end
8
+ end
9
9
  end
data/docs/page_builder.rb CHANGED
@@ -1,36 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class PageBuilder
4
- ROOT = Pages::ApplicationPage
5
-
6
- def self.build_all
7
- ROOT.subclasses.each { |page| new(page).call }
8
- end
9
-
10
- def initialize(page)
11
- @page = page
12
- end
13
-
14
- def call
15
- FileUtils.mkdir_p(directory)
16
- File.write(file, @page.new.call)
17
- end
18
-
19
- private
20
-
21
- def file
22
- "#{directory}/index.html"
23
- end
24
-
25
- def directory
26
- if path == "index"
27
- "#{__dir__}/dist"
28
- else
29
- "#{__dir__}/dist/#{path}"
30
- end
31
- end
32
-
33
- def path
34
- @page.name.downcase.split("::")[1..].join("/")
35
- end
4
+ ROOT = Pages::ApplicationPage
5
+
6
+ def self.build_all
7
+ ROOT.subclasses.each { |page| new(page).call }
8
+ end
9
+
10
+ def initialize(page)
11
+ @page = page
12
+ end
13
+
14
+ def call
15
+ FileUtils.mkdir_p(directory)
16
+ File.write(file, @page.new.call)
17
+ end
18
+
19
+ private
20
+
21
+ def file
22
+ "#{directory}/index.html"
23
+ end
24
+
25
+ def directory
26
+ if path == "index"
27
+ "#{__dir__}/dist"
28
+ else
29
+ "#{__dir__}/dist/#{path}"
30
+ end
31
+ end
32
+
33
+ def path
34
+ @page.name.split("::")[1..].map { _1.gsub(/(.)([A-Z])/, '\1-\2') }.map(&:downcase).join("/")
35
+ end
36
36
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pages
4
- class ApplicationPage < Phlex::Component
5
- include ::Components
6
- end
4
+ class ApplicationPage < Phlex::View
5
+ include ::Components
6
+ end
7
7
  end