rblade 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,13 +9,13 @@ module RBlade
9
9
 
10
10
  def compile!(tokens)
11
11
  tokens.each do |token|
12
- if [:component, :component_start, :component_end].none? token.type
12
+ if [:component, :component_start, :component_end, :component_unsafe_end].none? token.type
13
13
  next
14
14
  end
15
15
 
16
16
  token.value = if token.type == :component_start
17
17
  compile_token_start token
18
- elsif token.type == :component_end
18
+ elsif token.type == :component_end || token.type == :component_unsafe_end
19
19
  compile_token_end token
20
20
  else
21
21
  compile_token_start(token) + compile_token_end(token)
@@ -30,11 +30,15 @@ module RBlade
30
30
 
31
31
  @component_stack << {
32
32
  name: token.value[:name],
33
- attributes: token.value[:attributes],
34
33
  index: @component_count
35
34
  }
36
35
 
37
- "_comp_#{@component_count}_swap=_out;_out='';"
36
+ attributes = compile_attributes token.value[:attributes]
37
+
38
+ code = "_c#{@component_count}_swap=_out;_out='';"
39
+ code << "_c#{@component_count}_attr={#{attributes.join(",")}};"
40
+
41
+ code
38
42
  end
39
43
 
40
44
  def compile_token_end token
@@ -42,72 +46,42 @@ module RBlade
42
46
  if component.nil?
43
47
  raise StandardError.new "Unexpected closing tag (#{token.value[:name]})"
44
48
  end
45
- if token.value[:name] != component[:name]
49
+ if token.type == :component_end && token.value[:name] != component[:name]
46
50
  raise StandardError.new "Unexpected closing tag (#{token.value[:name]}) expecting #{component[:name]}"
47
51
  end
48
52
 
49
- attributes = compile_attributes component[:attributes]
50
-
51
- code = "def _component(slot,attributes);#{attributes[:assignments].join}_out='';"
52
- code << "_stacks=[];"
53
- code << "attributes=RBlade::AttributesManager.new(attributes);"
54
- code << ComponentStore.fetchComponent(token.value[:name])
55
- code << "RBlade::StackManager.get(_stacks) + _out;end;"
56
- code << "_slot=_out;_out=_comp_#{component[:index]}_swap;"
57
- code << "_out<<_component(_slot,{#{attributes[:arguments].join(",")}});"
53
+ code = "_slot=_out;_out=_c#{component[:index]}_swap;"
54
+ code << "_out<<#{ComponentStore.component(component[:name])}(_slot,_c#{component[:index]}_attr);"
55
+ code << "_slot=nil;_c#{component[:index]}_swap=nil;"
58
56
 
59
57
  code
60
58
  end
61
59
 
62
- def compile_attributes attributes
63
- attribute_arguments = []
64
- attribute_assignments = []
65
-
66
- attributes.each do |attribute|
67
- if attribute[:type] == "class"
68
- attribute_arguments.push "'class': RBlade::ClassManager.new(#{attribute[:value]})"
69
- attribute_assignments.push "_class = attributes[:class];"
70
-
71
- next
72
- end
73
- if attribute[:type] == "style"
74
- attribute_arguments.push "'style': RBlade::StyleManager.new(#{attribute[:value]})"
75
- attribute_assignments.push "_style = attributes[:style];"
76
-
77
- next
78
- end
79
- if attribute[:type] == "attributes"
80
- attribute_arguments.push "**(#{attribute[:value]}).to_h"
81
-
82
- next
83
- end
84
-
85
- if attribute[:type] == "string"
86
- attribute_arguments.push "'#{attribute[:name]}': '#{RBlade.escape_quotes(attribute[:value])}'"
87
- end
88
-
89
- if attribute[:type] == "ruby"
90
- attribute_arguments.push "'#{attribute[:name]}': (#{attribute[:value]})"
91
- end
92
-
93
- if attribute[:type] == "pass_through"
94
- attribute_arguments.push "#{attribute[:name]}:"
95
- end
96
-
97
- if attribute[:type] == "empty"
98
- attribute_arguments.push "'#{attribute[:name]}': true"
99
- end
100
-
101
- variable_name = attribute[:name]&.tr("-", "_")
102
- if !variable_name.nil? && variable_name.match(/^[A-Za-z_][A-Za-z0-9_]*$/)
103
- keywords = %w[__FILE__ __LINE__ alias and begin BEGIN break case class def defined? do else elsif end END ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield attributes _out slot]
104
- next if keywords.include? variable_name
60
+ def get_method_name name
61
+ name.gsub(/[^a-zA-Z0-9_]/, "_")
62
+ end
105
63
 
106
- attribute_assignments.push "#{variable_name} = attributes[:'#{attribute[:name]}'];"
64
+ def compile_attributes attributes
65
+ attributes.map do |attribute|
66
+ case attribute[:type]
67
+ when "class"
68
+ "'class': RBlade::ClassManager.new(#{attribute[:value]})"
69
+ when "style"
70
+ "'style': RBlade::StyleManager.new(#{attribute[:value]})"
71
+ when "attributes"
72
+ "**(#{attribute[:value]})"
73
+ when "string"
74
+ "'#{attribute[:name]}': '#{RBlade.escape_quotes(attribute[:value])}'"
75
+ when "ruby"
76
+ "'#{attribute[:name]}': (#{attribute[:value]})"
77
+ when "pass_through"
78
+ "#{attribute[:name]}:"
79
+ when "empty"
80
+ "'#{attribute[:name]}': true"
81
+ else
82
+ raise StandardError.new "Component compiler: unexpected attribute type (#{attribute[:type]})"
107
83
  end
108
84
  end
109
-
110
- {arguments: attribute_arguments, assignments: attribute_assignments}
111
85
  end
112
86
  end
113
87
  end
@@ -22,24 +22,28 @@ module RBlade
22
22
  end
23
23
 
24
24
  def compileEach args
25
- if args&.count != 1
25
+ if args.nil? || args.count > 2
26
26
  raise StandardError.new "Each statement: wrong number of arguments (given #{args&.count || 0}, expecting 1)"
27
27
  end
28
+ # Allow variables to be a key, value pair
29
+ args = args.join ","
28
30
 
29
- variables, collection = args[0].split(" in ")
31
+ variables, collection = args.split(" in ")
30
32
 
31
33
  "#{collection}.each do |#{variables}|;"
32
34
  end
33
35
 
34
36
  def compileEachElse args
35
- if args&.count != 1
36
- raise StandardError.new "Each else statement: wrong number of arguments (given #{args&.count || 0}, expecting 1)"
37
+ if args.nil? || args.count > 2
38
+ raise StandardError.new "Each statement: wrong number of arguments (given #{args&.count || 0}, expecting 1)"
37
39
  end
40
+ # Allow variables to be a key, value pair
41
+ args = args.join ","
38
42
  @loop_else_counter += 1
39
43
 
40
- variables, collection = args[0].split(" in ")
44
+ variables, collection = args.split(" in ")
41
45
 
42
- "_looped_#{@loop_else_counter}=true;#{collection}.each do |#{variables}|;_looped_#{@loop_else_counter}=false;"
46
+ "_looped_#{@loop_else_counter}=false;#{collection}.each do |#{variables}|;_looped_#{@loop_else_counter}=true;"
43
47
  end
44
48
 
45
49
  def compileFor args
@@ -56,13 +60,13 @@ module RBlade
56
60
  end
57
61
  @loop_else_counter += 1
58
62
 
59
- "_looped_#{@loop_else_counter}=true;for #{args[0]};_looped_#{@loop_else_counter}=false;"
63
+ "_looped_#{@loop_else_counter}=false;for #{args[0]};_looped_#{@loop_else_counter}=true;"
60
64
  end
61
65
 
62
66
  def compileEmpty
63
67
  @loop_else_counter -= 1
64
68
 
65
- "end;if _looped_#{@loop_else_counter + 1};"
69
+ "end;if !_looped_#{@loop_else_counter + 1};"
66
70
  end
67
71
 
68
72
  def compileNext args
@@ -11,9 +11,9 @@ module RBlade
11
11
  props = extractProps args[0]
12
12
  props.map do |key, value|
13
13
  if value == "_required"
14
- "if !defined?(#{key});raise \"Props statement: #{key} is not defined\";end;"
14
+ "if !defined?(#{key})&&!attributes.has?(:'#{RBlade.escape_quotes(key)}');raise \"Props statement: #{key} is not defined\";end;#{key.sub(/[^a-zA-Z0-9_]/, "_")}=attributes.default(:'#{RBlade.escape_quotes(key)}');"
15
15
  else
16
- "if !defined?(#{key});#{key}=#{value};end;"
16
+ "#{key.sub(/[^a-zA-Z0-9_]/, "_")}=attributes.default(:'#{RBlade.escape_quotes(key)}',#{value});"
17
17
  end
18
18
  end.join
19
19
  end
@@ -6,7 +6,7 @@ module RBlade
6
6
  tokens.map! do |token|
7
7
  next(token) if token.type != :unprocessed
8
8
 
9
- segments = tokenizeComponentOpeningTags token.value
9
+ segments = tokenizeComponentTags token.value
10
10
 
11
11
  i = 0
12
12
  while i < segments.count
@@ -15,6 +15,9 @@ module RBlade
15
15
 
16
16
  segments.delete_at i + 1
17
17
  i += 1
18
+ elsif segments[i] == "<//>"
19
+ segments[i] = Token.new(type: :component_unsafe_end)
20
+ i += 1
18
21
  elsif segments[i] == "<" && segments[i + 1]&.match(/x[-:]/)
19
22
  name = segments[i + 1][2..]
20
23
  raw_attributes = (segments[i + 2] != ">") ? tokenizeAttributes(segments[i + 2]) : nil
@@ -97,7 +100,7 @@ module RBlade
97
100
  attributes
98
101
  end
99
102
 
100
- def tokenizeComponentOpeningTags value
103
+ def tokenizeComponentTags value
101
104
  value.split(%r/
102
105
  # Opening and self-closing tags
103
106
  (?:
@@ -147,6 +150,8 @@ module RBlade
147
150
  \s*
148
151
  >
149
152
  )
153
+ |
154
+ (<\/\/>)
150
155
  /xm)
151
156
  end
152
157
 
@@ -1,28 +1,34 @@
1
1
  require "rblade/compiler"
2
2
 
3
3
  module RBlade
4
- FILE_EXTENSIONS = [".blade", ".html.blade"]
4
+ FILE_EXTENSIONS = [".rblade", ".html.rblade"]
5
5
 
6
6
  class ComponentStore
7
- def self.fetchComponent name
8
- namespace = nil
9
- path = name
7
+ # Retrieve the method name for a component, and compile it if it hasn't already been compiled
8
+ def self.component full_name
9
+ # If this is a relative path, prepend with the previous component name's base
10
+ if full_name.start_with? "."
11
+ full_name = @@component_name_stack.last.gsub(/\.[^\.]+$/, "") + full_name
12
+ end
10
13
 
11
- if name.match? "::"
12
- namespace, path = name.split("::")
14
+ # Ensure each component is only compiled once
15
+ unless @@component_method_names[full_name].nil?
16
+ return @@component_method_names[full_name]
13
17
  end
14
18
 
15
- path.tr! ".", "/"
19
+ @@component_name_stack << full_name
16
20
 
17
- @@template_paths[namespace]&.each do |base_path|
18
- FILE_EXTENSIONS.each do |extension|
19
- if File.exist? base_path + path + extension
20
- return RBlade::Compiler.compileString File.read(base_path + path + extension)
21
- end
22
- end
21
+ namespace = nil
22
+ name = full_name
23
+
24
+ if name.match? "::"
25
+ namespace, name = full_name.split("::")
23
26
  end
24
27
 
25
- raise StandardError.new "Unknown component #{name}"
28
+ method_name = compile_component full_name, File.read(find_component_file(namespace, name))
29
+ @@component_name_stack.pop
30
+
31
+ method_name
26
32
  end
27
33
 
28
34
  def self.add_path path, namespace = nil
@@ -35,8 +41,61 @@ module RBlade
35
41
  @@template_paths[namespace] << path
36
42
  end
37
43
 
44
+ def self.view_name view_name
45
+ @@component_name_stack.push view_name
46
+ end
47
+
48
+ def self.get
49
+ @@component_definitions
50
+ end
51
+
52
+ def self.clear
53
+ @@component_definitions = ""
54
+ @@component_method_names = {}
55
+ @@component_name_stack = []
56
+ end
57
+
58
+ def self.find_component_file namespace, name
59
+ file_path = name.tr ".", "/"
60
+
61
+ @@template_paths[namespace]&.each do |base_path|
62
+ FILE_EXTENSIONS.each do |extension|
63
+ if File.exist? base_path + file_path + extension
64
+ return "#{base_path}#{file_path}#{extension}"
65
+ end
66
+ if File.exist? base_path + file_path + "/index" + extension
67
+ # Account for index files for relative components
68
+ @@component_name_stack << @@component_name_stack.pop + ".index"
69
+ return "#{base_path}#{file_path}/index#{extension}"
70
+ end
71
+ end
72
+ end
73
+
74
+ raise StandardError.new "Unknown component #{namespace}::#{name}"
75
+ end
76
+ private_class_method :find_component_file
77
+
78
+ def self.compile_component(name, code)
79
+ @@component_method_names[name] = "_c#{@@component_method_names.count}"
80
+
81
+ compiled_component = RBlade::Compiler.compileString(code)
82
+
83
+ @@component_definitions \
84
+ << "def #{@@component_method_names[name]}(slot,attributes);_out='';" \
85
+ << "_stacks=[];" \
86
+ << "attributes=RBlade::AttributesManager.new(attributes);" \
87
+ << compiled_component \
88
+ << "RBlade::StackManager.get(_stacks) + _out;end;"
89
+
90
+ @@component_method_names[name]
91
+ end
92
+ private_class_method :compile_component
93
+
38
94
  private
39
95
 
96
+ @@component_definitions = ""
97
+ @@component_name_stack = []
98
+ @@component_method_names = {}
40
99
  @@template_paths = {}
41
100
  end
42
101
  end
@@ -3,34 +3,33 @@ module RBlade
3
3
  @attributes = {}
4
4
  def initialize attributes
5
5
  @attributes = attributes
6
+ end
6
7
 
7
- if !@attributes[:_class].nil?
8
- @attributes[:class] = mergeClasses(@attributes[:class], @attributes.delete(:_class))
9
- end
10
- if !@attributes[:_style].nil?
11
- @attributes[:style] = mergeClasses(@attributes[:style], @attributes.delete(:_style))
8
+ def default(key, default = nil)
9
+ if @attributes[key].nil? && !default.nil?
10
+ @attributes[key] = default
12
11
  end
13
12
 
14
- @attributes.freeze
13
+ @attributes[key]
15
14
  end
16
15
 
17
- def to_h
18
- attributes = @attributes.dup
19
- if !attributes[:class].nil?
20
- attributes[:_class] = attributes.delete :class
21
- end
22
- if !attributes[:style].nil?
23
- attributes[:_style] = attributes.delete :style
24
- end
16
+ def has?(key)
17
+ !@attributes[key].nil?
18
+ end
19
+
20
+ def method_missing(method, *)
21
+ @attributes.send(method, *)
22
+ end
25
23
 
26
- attributes
24
+ def respond_to_missing?(method_name, *args)
25
+ @attributes.respond_to?(method_name) || super
27
26
  end
28
27
 
29
28
  def to_s attributes = nil
30
29
  attributes ||= @attributes
31
30
 
32
31
  attributes.map do |key, value|
33
- "#{key}=\"#{h(value)}\""
32
+ "#{key}=\"#{(value == true) ? key : h(value)}\""
34
33
  end.join " "
35
34
  end
36
35
 
@@ -1,5 +1,6 @@
1
1
  require "rails"
2
2
  require "rblade/compiler"
3
+ require "rblade/component_store"
3
4
  require "rblade/helpers/attributes_manager"
4
5
  require "rblade/helpers/class_manager"
5
6
  require "rblade/helpers/stack_manager"
@@ -8,10 +9,22 @@ require "rblade/helpers/style_manager"
8
9
  module RBlade
9
10
  class RailsTemplate
10
11
  def call(template, source = nil)
11
- RBlade::StackManager.clear
12
+ unless template.nil?
13
+ view_name = template.short_identifier
14
+ .delete_prefix('app/views/')
15
+ .delete_suffix('.rblade')
16
+ .delete_suffix('.html')
17
+ .tr('/', '.')
18
+
19
+ # Let the component store know about the current view for relative components
20
+ RBlade::ComponentStore.view_name(
21
+ "view::#{view_name}"
22
+ )
23
+ end
12
24
  setup = "_out='';_stacks=[];"
25
+ code = RBlade::Compiler.compileString(source || template.source)
13
26
  setdown = "RBlade::StackManager.get(_stacks) + _out"
14
- setup + RBlade::Compiler.compileString(source || template.source) + setdown
27
+ setup + ComponentStore.get + code + setdown
15
28
  end
16
29
  end
17
30
  end
@@ -5,10 +5,11 @@ require "rblade/component_store"
5
5
  module RBlade
6
6
  class Railtie < ::Rails::Railtie
7
7
  initializer :rblade, before: :load_config_initializers do |app|
8
- ActionView::Template.register_template_handler(:blade, RBlade::RailsTemplate.new)
8
+ ActionView::Template.register_template_handler(:rblade, RBlade::RailsTemplate.new)
9
9
 
10
10
  RBlade::ComponentStore.add_path(Rails.root.join("app", "views", "components"))
11
11
  RBlade::ComponentStore.add_path(Rails.root.join("app", "views", "layouts"), "layout")
12
+ RBlade::ComponentStore.add_path(Rails.root.join("app", "views"), "view")
12
13
  end
13
14
  end
14
15
  end
data/rblade.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rblade"
3
- s.version = "0.2.4"
3
+ s.version = "0.3.0"
4
4
  s.summary = "Blade templates for ruby"
5
5
  s.description = "A port of the Laravel blade templating engine to ruby"
6
6
  s.authors = ["Simon J"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rblade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon J
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-23 00:00:00.000000000 Z
11
+ date: 2024-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -76,6 +76,7 @@ files:
76
76
  - ".standard.yml"
77
77
  - CHANGELOG.md
78
78
  - Gemfile
79
+ - README.md
79
80
  - Rakefile
80
81
  - do
81
82
  - docker-compose.yml