component_embedded_ruby 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 834b63b9cc7bf826b998ff5397391e477001e549da31d058243745db63d633e4
4
- data.tar.gz: 6742bc330ce2dd5b9fdce7325759338bc966cc016955ca2156ee44e2187e1187
3
+ metadata.gz: 00cd94a77693e37fb2723ead82ea26bb40f2e03fd605b33cb41a0f1075bc6ef0
4
+ data.tar.gz: 00b7ebbfe21f7e9b057b83493016e71409a9657f85f30fd719f95c632827f8a8
5
5
  SHA512:
6
- metadata.gz: 24c07b6614384d476903ab99d892373625cf017b3428c6a5ba2b00a328f07b0dd614ce178235e23d4a333662379719acdef3dc5f28be74eb0d1e68f4144e4cf8
7
- data.tar.gz: 181d8e02bc4746860064231b6e19cc834c1b550c0c686aca1f12703bda0894030d9e1c444c2b104edec7e364606a675b8746e789331e52789c807ec2c812a3f7
6
+ metadata.gz: 6619a460eaf27c83545de59f2b8fa5475592d41af7e082371b2c616f6fcb686518dfeb0e5223c5de7a63ba2672abf4f3ab0e0d77ff02a4dbda8a22cbf146af52
7
+ data.tar.gz: 2f48caa5773e5ab51f52aebc485a32a344bf60feb6515c3287aa0cbe2be3bcd8f8ca2befc3a1384eb9e480209c3fa37a41513b3b86265d105174d07a5702ea3e
@@ -3,18 +3,35 @@ name: Ruby
3
3
  on: [push]
4
4
 
5
5
  jobs:
6
- build:
7
-
6
+ test:
8
7
  runs-on: ubuntu-latest
9
-
8
+ strategy:
9
+ matrix:
10
+ ruby_version: [2.5.x, 2.6.x, 2.7.x]
10
11
  steps:
11
12
  - uses: actions/checkout@v1
12
13
  - name: Set up Ruby 2.6
13
14
  uses: actions/setup-ruby@v1
14
15
  with:
15
- ruby-version: 2.6.x
16
+ ruby-version: ${{ matrix.ruby_version }}
16
17
  - name: Build and test with Rake
17
18
  run: |
18
- gem install bundler
19
+ gem install bundler:1.17.3
19
20
  bundle install --jobs 4 --retry 3
20
21
  bundle exec rake
22
+ - name: Rubocop
23
+ run: |
24
+ bundle exec rubocop
25
+ lint:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v1
29
+ - name: Set up Ruby 2.6
30
+ uses: actions/setup-ruby@v1
31
+ with:
32
+ ruby-version: 2.6.x
33
+ - name: Rubocop
34
+ run: |
35
+ gem install bundler
36
+ bundle install --jobs 4 --retry 3
37
+ bundle exec rubocop
@@ -0,0 +1,44 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+
4
+ Metrics/MethodLength:
5
+ Max: 20
6
+ Exclude:
7
+ - 'test/**/*.rb'
8
+
9
+ Metrics/ClassLength:
10
+ Max: 150
11
+ Exclude:
12
+ - 'test/**/*.rb'
13
+
14
+ Metrics/AbcSize:
15
+ Enabled: false
16
+
17
+ Security/Eval:
18
+ Exclude:
19
+ - 'test/**/*.rb'
20
+
21
+ Lint/MissingSuper:
22
+ Enabled: false
23
+
24
+ Style/Documentation:
25
+ Enabled: false
26
+
27
+ Style/NegatedWhile:
28
+ Enabled: false
29
+
30
+ Style/StringLiterals:
31
+ EnforcedStyle: double_quotes
32
+
33
+ Style/CaseLikeIf:
34
+ Enabled: false
35
+
36
+ Style/MultipleComparison:
37
+ Enabled: false
38
+
39
+ Naming/MemoizedInstanceVariableName:
40
+ EnforcedStyleForLeadingUnderscores: required
41
+
42
+ Naming/AccessorMethodName:
43
+ Exclude:
44
+ - 'test/**/*.rb'
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
6
 
5
7
  gem "rails", github: "rails/rails"
6
8
 
@@ -86,11 +86,12 @@ GIT
86
86
  PATH
87
87
  remote: .
88
88
  specs:
89
- component_embedded_ruby (0.2.0)
89
+ component_embedded_ruby (0.3.0)
90
90
 
91
91
  GEM
92
92
  remote: https://rubygems.org/
93
93
  specs:
94
+ ast (2.4.1)
94
95
  builder (3.2.4)
95
96
  coderay (1.1.2)
96
97
  concurrent-ruby (1.1.7)
@@ -115,6 +116,9 @@ GEM
115
116
  nio4r (2.5.4)
116
117
  nokogiri (1.10.10)
117
118
  mini_portile2 (~> 2.4.0)
119
+ parallel (1.19.2)
120
+ parser (2.7.2.0)
121
+ ast (~> 2.4.1)
118
122
  pry (0.12.2)
119
123
  coderay (~> 1.1.0)
120
124
  method_source (~> 0.9.0)
@@ -126,7 +130,22 @@ GEM
126
130
  nokogiri (>= 1.6)
127
131
  rails-html-sanitizer (1.3.0)
128
132
  loofah (~> 2.3)
133
+ rainbow (3.0.0)
129
134
  rake (13.0.1)
135
+ regexp_parser (1.8.2)
136
+ rexml (3.2.4)
137
+ rubocop (1.0.0)
138
+ parallel (~> 1.10)
139
+ parser (>= 2.7.1.5)
140
+ rainbow (>= 2.2.2, < 4.0)
141
+ regexp_parser (>= 1.8)
142
+ rexml
143
+ rubocop-ast (>= 0.6.0)
144
+ ruby-progressbar (~> 1.7)
145
+ unicode-display_width (>= 1.4.0, < 2.0)
146
+ rubocop-ast (1.0.1)
147
+ parser (>= 2.7.1.5)
148
+ ruby-progressbar (1.10.1)
130
149
  sprockets (4.0.2)
131
150
  concurrent-ruby (~> 1.0)
132
151
  rack (> 1, < 3)
@@ -137,6 +156,7 @@ GEM
137
156
  thor (1.0.1)
138
157
  tzinfo (2.0.2)
139
158
  concurrent-ruby (~> 1.0)
159
+ unicode-display_width (1.7.0)
140
160
  view_component (2.20.0)
141
161
  activesupport (>= 5.0.0, < 7.0)
142
162
  websocket-driver (0.7.3)
@@ -154,6 +174,7 @@ DEPENDENCIES
154
174
  pry (~> 0.12.2)
155
175
  rails!
156
176
  rake (~> 13.0)
177
+ rubocop (~> 1.0)
157
178
  view_component (> 2.15)
158
179
 
159
180
  BUNDLED WITH
data/README.md CHANGED
@@ -9,7 +9,7 @@ Strict HTML templating with support for components.
9
9
  extra_classes %>`, instead this logic should be pushed up to components.
10
10
  * Component rendering has a single dependency, a `render` method being present
11
11
  in the rendering context.
12
- * Easy Rails integration by registering `crb` as a template handler.
12
+ * automatic Rails support for `crb` extension
13
13
 
14
14
  ### Usage
15
15
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rake/testtask"
3
5
 
@@ -7,4 +9,4 @@ Rake::TestTask.new(:test) do |t|
7
9
  t.test_files = FileList["test/**/*_test.rb"]
8
10
  end
9
11
 
10
- task :default => :test
12
+ task default: :test
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "component_embedded_ruby"
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path("lib", __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require "component_embedded_ruby/version"
5
6
 
@@ -9,8 +10,8 @@ Gem::Specification.new do |spec|
9
10
  spec.authors = ["Blake Williams"]
10
11
  spec.email = ["blake@blakewilliams.me"]
11
12
 
12
- spec.summary = %q{HTML templates with embedded Ruby components}
13
- spec.description = %q{HTML templates with embedded Ruby components}
13
+ spec.summary = "HTML templates with embedded Ruby components"
14
+ spec.description = "HTML templates with embedded Ruby components"
14
15
  spec.homepage = "https://github.com/blakewilliams/component_embedded_ruby"
15
16
  spec.license = "MIT"
16
17
 
@@ -26,17 +27,19 @@ Gem::Specification.new do |spec|
26
27
 
27
28
  # Specify which files should be added to the gem when it is released.
28
29
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
29
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
30
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
30
31
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
31
32
  end
32
33
  spec.bindir = "exe"
33
34
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
35
  spec.require_paths = ["lib"]
36
+ spec.required_ruby_version = ">= 2.5"
35
37
 
36
38
  spec.add_development_dependency "bundler", "~> 1.17"
37
- spec.add_development_dependency "rake", "~> 13.0"
38
39
  spec.add_development_dependency "minitest", "~> 5.0"
40
+ spec.add_development_dependency "pry", "~> 0.12.2"
39
41
  spec.add_development_dependency "rails", "> 6.0"
42
+ spec.add_development_dependency "rake", "~> 13.0"
43
+ spec.add_development_dependency "rubocop", "~> 1.0"
40
44
  spec.add_development_dependency "view_component", "> 2.15"
41
- spec.add_development_dependency "pry", "~> 0.12.2"
42
45
  end
@@ -1,12 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "component_embedded_ruby/version"
2
4
  require "component_embedded_ruby/lexer"
3
5
  require "component_embedded_ruby/parser"
4
6
  require "component_embedded_ruby/eval"
5
7
  require "component_embedded_ruby/node"
6
- require "component_embedded_ruby/renderer"
8
+ require "component_embedded_ruby/compiler"
7
9
  require "component_embedded_ruby/template"
8
10
  require "component_embedded_ruby/unexpected_token_error"
9
11
 
12
+ # Rails support
13
+ require "component_embedded_ruby/rails_handler" if defined?(Rails::Railtie)
14
+
10
15
  module ComponentEmbeddedRuby
11
16
  class Error < StandardError; end
12
17
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
- class Renderer
4
+ class Compiler
3
5
  def initialize(nodes, output_var_name: "__crb_out", skip_return: false)
4
6
  @nodes = Array(nodes)
5
7
  @functions = {}
@@ -8,25 +10,25 @@ module ComponentEmbeddedRuby
8
10
  end
9
11
 
10
12
  def to_ruby
11
- <<~EOF
13
+ <<~RUBY
12
14
  #{output_var_name} = '';
13
15
 
14
16
  #{nodes.map(&method(:render)).join("\n")}
15
17
 
16
- #{output_var_name if !@skip_return};
17
- EOF
18
+ #{output_var_name unless @skip_return};
19
+ RUBY
18
20
  end
19
21
 
20
22
  private
21
23
 
22
- def render(node)
24
+ def render(node) # rubocop:disable Metrics/PerceivedComplexity
23
25
  if node.component?
24
- <<~EOF
26
+ <<~RUBY
25
27
  #{children_to_ruby(node)}
26
- #{output_var_name}.<< render(#{node.component_class}.new(#{attributes_for_component(node).join(",")})) { |component|
27
- __c_#{node.hash.to_s.gsub("-", "_")}
28
+ #{output_var_name}.<< render(#{node.component_class}.new(#{attributes_for_component(node).join(',')})) { |component|
29
+ __c_#{node.hash.to_s.gsub('-', '_')}
28
30
  };
29
- EOF
31
+ RUBY
30
32
  elsif node.ruby?
31
33
  if node.output_ruby?
32
34
  "#{output_var_name}.<< (#{node.children.value}).to_s;\n"
@@ -36,22 +38,22 @@ module ComponentEmbeddedRuby
36
38
  elsif node.text?
37
39
  "#{output_var_name}.<< \"#{node.children}\";\n"
38
40
  elsif node.html?
39
- <<~EOF
41
+ <<~RUBY
40
42
  #{output_var_name}.<< \"<#{node.tag}\";
41
43
  #{attributes_for_tag(node).join("\n")};
42
44
  #{output_var_name}.<< \">\";
43
45
  #{node.children.map(&method(:render)).join("\n")}
44
46
  #{output_var_name}.<< \"</#{node.tag}>\";
45
- EOF
47
+ RUBY
46
48
  end
47
49
  end
48
50
 
49
- attr_reader :output_var_name
51
+ attr_reader :output_var_name, :nodes
50
52
 
51
53
  def children_to_ruby(node)
52
54
  self.class.new(
53
55
  node.children,
54
- output_var_name: "__c_#{node.hash.to_s.gsub("-", "_")}",
56
+ output_var_name: "__c_#{node.hash.to_s.gsub('-', '_')}",
55
57
  skip_return: true
56
58
  ).to_ruby
57
59
  end
@@ -69,17 +71,15 @@ module ComponentEmbeddedRuby
69
71
  def attributes_for_tag(node)
70
72
  node.attributes.map do |key, value|
71
73
  if value.is_a?(Eval)
72
- <<~EOF
73
- #{output_var_name}.<< " #{key}=\\"";
74
- #{output_var_name}.<< (#{value.value}).to_s;
75
- #{output_var_name}.<< "\\"";
76
- EOF
74
+ <<~RUBY
75
+ #{output_var_name}.<< " #{key}=\\"";
76
+ #{output_var_name}.<< (#{value.value}).to_s;
77
+ #{output_var_name}.<< "\\"";
78
+ RUBY
77
79
  else
78
- %W(#{output_var_name}.<< "key="";\n)
80
+ %W[#{output_var_name}.<< "key="";\n]
79
81
  end
80
82
  end
81
83
  end
82
-
83
- attr_reader :nodes
84
84
  end
85
85
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  class Eval
3
5
  attr_reader :value, :output
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "component_embedded_ruby/lexer/input_reader"
4
+ require "component_embedded_ruby/lexer/string_reader"
5
+ require "component_embedded_ruby/lexer/ruby_code_reader"
2
6
 
3
7
  module ComponentEmbeddedRuby
4
8
  class Lexer
@@ -11,7 +15,7 @@ module ComponentEmbeddedRuby
11
15
  @tokens = []
12
16
  end
13
17
 
14
- def lex
18
+ def lex # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
15
19
  while !reader.eof?
16
20
  char = reader.current_char
17
21
 
@@ -24,7 +28,7 @@ module ComponentEmbeddedRuby
24
28
  elsif char == "="
25
29
  add_token(:equals, "=")
26
30
  reader.next
27
- elsif char == "\""
31
+ elsif char == '"'
28
32
  add_token(:string, read_quoted_string)
29
33
  elsif char == "/"
30
34
  add_token(:slash, "/")
@@ -36,11 +40,13 @@ module ComponentEmbeddedRuby
36
40
  else
37
41
  add_token(:ruby, read_ruby_string)
38
42
  end
39
- elsif is_letter?(char)
43
+ elsif letter?(char)
44
+ position = Position.new(reader.current_line, reader.current_column)
45
+
40
46
  if @tokens[-1]&.type == :close_carrot
41
- add_token(:string, read_body_string)
47
+ add_token(:string, read_body_string, position)
42
48
  else
43
- add_token(:identifier, read_string)
49
+ add_token(:identifier, read_string, position)
44
50
  end
45
51
  else
46
52
  reader.next
@@ -53,17 +59,16 @@ module ComponentEmbeddedRuby
53
59
  private
54
60
 
55
61
  attr_reader :reader
56
- attr_accessor :position
57
62
 
58
- def add_token(type, value)
59
- token = Token.new(type, value, Position.new(reader.current_line, reader.current_column))
63
+ def add_token(type, value, position = Position.new(reader.current_line, reader.current_column))
64
+ token = Token.new(type, value, position)
60
65
  @tokens << token
61
66
  end
62
67
 
63
68
  def read_string
64
69
  string = ""
65
70
 
66
- while is_letter?(reader.current_char) && !reader.eof?
71
+ while letter?(reader.current_char) && !reader.eof?
67
72
  string += reader.current_char
68
73
  reader.next
69
74
  end
@@ -79,6 +84,7 @@ module ComponentEmbeddedRuby
79
84
 
80
85
  while !unescaped_quote?
81
86
  raise "unterminated string" if reader.eof?
87
+
82
88
  string += reader.current_char
83
89
  reader.next
84
90
  end
@@ -103,41 +109,14 @@ module ComponentEmbeddedRuby
103
109
  end
104
110
 
105
111
  def read_ruby_string
106
- inner_string_count = 0
107
- inner_bracket_count = 0
108
-
109
- string = ""
110
-
111
- reader.next
112
-
113
- previous_token = nil
114
-
115
- loop do
116
- break if inner_bracket_count == 0 && inner_string_count % 2 == 0 && reader.current_char == "}"
117
- char = reader.current_char
118
- string += char
119
-
120
- # TODO handle " and ' separately
121
- if inner_string_count % 2 == 0 && char == "{"
122
- inner_bracket_count += 1
123
- elsif inner_string_count % 2 == 0 && char == "}"
124
- inner_bracket_count -= 1
125
- elsif previous_token != "\\" && char == "\"" || char == "'"
126
- inner_string_count -= 1
127
- end
128
-
129
- previous_token = char
130
- reader.next
131
- end
132
-
133
- string
112
+ RubyCodeReader.new(reader).read_until_closing_tag
134
113
  end
135
114
 
136
115
  def unescaped_quote?
137
- reader.current_char == "\"" && reader.peek_behind != "\\"
116
+ reader.current_char == '"' && reader.peek_behind != '\\'
138
117
  end
139
118
 
140
- def is_letter?(char)
119
+ def letter?(char)
141
120
  ascii = char.ord
142
121
  (ascii >= 48 && ascii <= 57) || (ascii >= 65 && ascii <= 122) || ascii == 45 || ascii == 95 || ascii == 58
143
122
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  class Lexer
3
5
  class InputReader
@@ -7,8 +9,8 @@ module ComponentEmbeddedRuby
7
9
  @input = input.freeze
8
10
  @position = 0
9
11
 
10
- @current_line = 0
11
- @current_column = 0
12
+ @current_line = 1
13
+ @current_column = 1
12
14
  end
13
15
 
14
16
  def eof?
@@ -30,7 +32,7 @@ module ComponentEmbeddedRuby
30
32
  def next
31
33
  if current_char == "\n"
32
34
  @current_line += 1
33
- @current_column = 0
35
+ @current_column = 1
34
36
  else
35
37
  @current_column += 1
36
38
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ComponentEmbeddedRuby
4
+ class Lexer
5
+ class RubyCodeReader
6
+ def initialize(input_reader)
7
+ @input_reader = input_reader
8
+ end
9
+
10
+ def read_until_closing_tag
11
+ string = ""
12
+
13
+ loop do
14
+ input_reader.next
15
+ current_char = input_reader.current_char
16
+
17
+ break if current_char == "}"
18
+
19
+ string += current_char
20
+
21
+ if current_char == "{"
22
+ string += RubyCodeReader.new(input_reader).read_until_closing_tag
23
+
24
+ # RubyCodeReader reads until "}", so we have to re-add that
25
+ # character when reading nested code
26
+ string += "}"
27
+ elsif current_char == "'" || current_char == '"'
28
+ # TODO: this *may* need to handle % syntax strings too
29
+ string += StringReader.new(
30
+ input_reader,
31
+ quote_type: current_char,
32
+ can_interpolate: current_char == '"'
33
+ ).read_until_closing_quote
34
+ end
35
+ end
36
+
37
+ string
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :input_reader
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ComponentEmbeddedRuby
4
+ class Lexer
5
+ class StringReader
6
+ def initialize(input_reader, quote_type:, can_interpolate:)
7
+ @input_reader = input_reader
8
+ @quote_type = quote_type
9
+ @can_interpolate = can_interpolate
10
+ end
11
+
12
+ def read_until_closing_quote
13
+ string = ""
14
+
15
+ previous_char = nil
16
+ current_char = input_reader.current_char
17
+
18
+ loop do
19
+ input_reader.next
20
+ previous_char = current_char
21
+ current_char = input_reader.current_char
22
+
23
+ string += current_char
24
+ break if current_char == quote_type && string[-1] != "\\"
25
+
26
+ if can_interpolate && string[-2..-1] == '#{' && string[-3] != "\\"
27
+ string += RubyCodeReader.new(input_reader).read_until_closing_tag
28
+ string += "}"
29
+ end
30
+ end
31
+
32
+ string
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :input_reader, :quote_type, :can_interpolate
38
+ end
39
+ end
40
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  class Node
3
5
  attr_reader :tag, :attributes, :children
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "component_embedded_ruby/parser/base"
2
4
  require "component_embedded_ruby/parser/root_parser"
3
5
  require "component_embedded_ruby/parser/attribute_parser"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  module Parser
3
5
  # Internal: Parses an HTML tag attributes into a hash of key values
@@ -17,9 +19,7 @@ module ComponentEmbeddedRuby
17
19
  def call
18
20
  attributes = {}
19
21
 
20
- while current_token.type != :close_carrot && current_token.type != :slash
21
- attributes.merge!(parse_attribute)
22
- end
22
+ attributes.merge!(parse_attribute) while current_token.type == :identifier
23
23
 
24
24
  attributes
25
25
  end
@@ -37,7 +37,7 @@ module ComponentEmbeddedRuby
37
37
  end
38
38
 
39
39
  def parse_value
40
- value_token = expect_any(:string, :ruby)
40
+ value_token = expect_any(:string, :ruby, expected_message: "a string or ruby code")
41
41
 
42
42
  if value_token.type == :string
43
43
  value_token.value
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  module Parser
3
5
  class Base
@@ -19,25 +21,17 @@ module ComponentEmbeddedRuby
19
21
 
20
22
  def expect(type)
21
23
  token = current_token
24
+ raise UnexpectedTokenError.new(type, current_token) if token.type != type
22
25
 
23
- if token.type != type
24
- raise UnexpectedTokenError.new(:string, current_token)
25
- else
26
- token_reader.next
27
- end
28
-
26
+ token_reader.next
29
27
  token
30
28
  end
31
29
 
32
- def expect_any(*types)
30
+ def expect_any(*types, expected_message:)
33
31
  token = current_token
32
+ raise UnexpectedTokenError.new(expected_message, token) unless types.include?(token.type)
34
33
 
35
- if !types.include?(token.type)
36
- raise UnexpectedTokenError.new(:string, token)
37
- else
38
- token_reader.next
39
- end
40
-
34
+ token_reader.next
41
35
  token
42
36
  end
43
37
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  module Parser
3
5
  # Internal: Used for parsing multiple adjacent tag, string, and emedded
@@ -12,7 +14,8 @@ module ComponentEmbeddedRuby
12
14
  results = []
13
15
 
14
16
  while current_token
15
- if current_token.type == :open_carrot
17
+ case current_token.type
18
+ when :open_carrot
16
19
  # If we run into a </ we are likely at the end of parsing a tag so
17
20
  # this should return and let the `TagParser` complete parsing
18
21
  #
@@ -22,17 +25,15 @@ module ComponentEmbeddedRuby
22
25
  # children, and will use another instance of `RootParser` to reads its children
23
26
  # 3. The new RootParser reads `Hello`, then runs into `</`, so it should return `["Hello"]`
24
27
  # and allow the `TagParser` to finish reading `</h1>`
25
- if peek_token.type == :slash
26
- return results
27
- else
28
- results << TagParser.new(token_reader).call
29
- end
30
- elsif current_token.type == :string || current_token.type == :identifier
28
+ return results if peek_token.type == :slash
29
+
30
+ results << TagParser.new(token_reader).call
31
+ when :string, :identifier
31
32
  # If we're reading a string, or some other identifier that is on
32
33
  # its own, we can skip instantiating a new parser and parse it directly ourselves
33
34
  results << Node.new(nil, nil, current_token.value)
34
35
  token_reader.next
35
- elsif current_token.type == :ruby || current_token.type == :ruby_no_eval
36
+ when :ruby, :ruby_no_eval
36
37
  # If we run into Ruby code that should be evaluated inside of the
37
38
  # template, we want to create an `Eval`. The compliation step
38
39
  # handles `Eval` objects specially since it's making template
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  module Parser
3
5
  # Internal: Parses an HTML tag into a Node object
@@ -39,9 +41,7 @@ module ComponentEmbeddedRuby
39
41
  expect(:slash)
40
42
  close_tag = expect(:identifier).value
41
43
 
42
- if close_tag != tag
43
- raise "Mismatched tags. expected #{tag}, got #{current_token.value}"
44
- end
44
+ raise "Mismatched tags. expected #{tag}, got #{current_token.value}" if close_tag != tag
45
45
 
46
46
  expect(:close_carrot)
47
47
 
@@ -51,23 +51,23 @@ module ComponentEmbeddedRuby
51
51
 
52
52
  private
53
53
 
54
+ def parse_children
55
+ if children?
56
+ RootParser.new(token_reader).call
57
+ else
58
+ []
59
+ end
60
+ end
61
+
54
62
  # If the next two elements are </, we can safely asume it's meant to
55
63
  # close the current tag and lets us avoid having to attempt parsing
56
64
  # children.
57
- def has_children?
65
+ def children?
58
66
  return true if current_token.type != :open_carrot
59
67
  return true if peek_token&.type != :slash
60
68
 
61
69
  false
62
70
  end
63
-
64
- def parse_children
65
- if has_children?
66
- RootParser.new(token_reader).call
67
- else
68
- []
69
- end
70
- end
71
71
  end
72
72
  end
73
73
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  module Parser
3
5
  class TokenReader
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ComponentEmbeddedRuby
4
+ class RailsHandler
5
+ def self.call(template, source = nil)
6
+ source ||= template.source
7
+
8
+ Template.new(source).to_ruby
9
+ end
10
+ end
11
+ end
12
+
13
+ ActionView::Template.register_template_handler(:crb, ComponentEmbeddedRuby::RailsHandler)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  class Template
3
5
  def initialize(template)
@@ -7,11 +9,11 @@ module ComponentEmbeddedRuby
7
9
  def to_ruby
8
10
  tokens = Lexer.new(@template).lex
9
11
  nodes = Parser.parse(tokens)
10
- Renderer.new(nodes).to_ruby
12
+ Compiler.new(nodes).to_ruby
11
13
  end
12
14
 
13
15
  def to_s(binding: TOPLEVEL_BINDING)
14
- eval(to_ruby, binding)
16
+ eval(to_ruby, binding) # rubocop:disable Security/Eval
15
17
  end
16
18
  end
17
19
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
4
  class UnexpectedTokenError < StandardError
3
5
  attr_reader :expected, :got
@@ -8,14 +10,30 @@ module ComponentEmbeddedRuby
8
10
  end
9
11
 
10
12
  def message
11
- "Unexpected token at column #{got.position}, got #{got.value}#{expected_message}."
13
+ <<~MESSAGE.strip
14
+ Unexpected token at line #{got.position.line}, column #{got.position.column}
15
+ Got `#{got.value}`#{expected_message}
16
+ MESSAGE
12
17
  end
13
18
 
14
19
  private
15
20
 
16
21
  def expected_message
17
- if expected != nil
18
- " but expected #{expected}"
22
+ " but expected #{user_readable_expected}" unless expected.nil?
23
+ end
24
+
25
+ def user_readable_expected
26
+ case expected
27
+ when :open_carrot
28
+ "`<`"
29
+ when :close_carrot
30
+ "`>`"
31
+ when :equals
32
+ "`=`"
33
+ when :slash
34
+ "`/`"
35
+ else
36
+ expected
19
37
  end
20
38
  end
21
39
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ComponentEmbeddedRuby
2
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
3
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: component_embedded_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Blake Williams
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-25 00:00:00.000000000 Z
11
+ date: 2020-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -25,33 +25,33 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.17'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '13.0'
33
+ version: '5.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '13.0'
40
+ version: '5.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: minitest
42
+ name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '5.0'
47
+ version: 0.12.2
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '5.0'
54
+ version: 0.12.2
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rails
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -67,33 +67,47 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '6.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: view_component
70
+ name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">"
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '2.15'
75
+ version: '13.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">"
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '2.15'
82
+ version: '13.0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: pry
84
+ name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.12.2
89
+ version: '1.0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.12.2
96
+ version: '1.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: view_component
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.15'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.15'
97
111
  description: HTML templates with embedded Ruby components
98
112
  email:
99
113
  - blake@blakewilliams.me
@@ -103,6 +117,7 @@ extra_rdoc_files: []
103
117
  files:
104
118
  - ".github/workflows/ruby.yml"
105
119
  - ".gitignore"
120
+ - ".rubocop.yml"
106
121
  - ".travis.yml"
107
122
  - Gemfile
108
123
  - Gemfile.lock
@@ -113,9 +128,12 @@ files:
113
128
  - bin/setup
114
129
  - component_embedded_ruby.gemspec
115
130
  - lib/component_embedded_ruby.rb
131
+ - lib/component_embedded_ruby/compiler.rb
116
132
  - lib/component_embedded_ruby/eval.rb
117
133
  - lib/component_embedded_ruby/lexer.rb
118
134
  - lib/component_embedded_ruby/lexer/input_reader.rb
135
+ - lib/component_embedded_ruby/lexer/ruby_code_reader.rb
136
+ - lib/component_embedded_ruby/lexer/string_reader.rb
119
137
  - lib/component_embedded_ruby/node.rb
120
138
  - lib/component_embedded_ruby/parser.rb
121
139
  - lib/component_embedded_ruby/parser/attribute_parser.rb
@@ -123,7 +141,7 @@ files:
123
141
  - lib/component_embedded_ruby/parser/root_parser.rb
124
142
  - lib/component_embedded_ruby/parser/tag_parser.rb
125
143
  - lib/component_embedded_ruby/parser/token_reader.rb
126
- - lib/component_embedded_ruby/renderer.rb
144
+ - lib/component_embedded_ruby/rails_handler.rb
127
145
  - lib/component_embedded_ruby/template.rb
128
146
  - lib/component_embedded_ruby/unexpected_token_error.rb
129
147
  - lib/component_embedded_ruby/version.rb
@@ -141,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
159
  requirements:
142
160
  - - ">="
143
161
  - !ruby/object:Gem::Version
144
- version: '0'
162
+ version: '2.5'
145
163
  required_rubygems_version: !ruby/object:Gem::Requirement
146
164
  requirements:
147
165
  - - ">="