rblade 0.2.5 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +1803 -0
- data/lib/rblade/compiler/compiles_components.rb +34 -60
- data/lib/rblade/compiler/statements/compiles_loops.rb +12 -8
- data/lib/rblade/compiler/statements/compiles_props.rb +2 -2
- data/lib/rblade/compiler/tokenizes_components.rb +7 -2
- data/lib/rblade/component_store.rb +73 -14
- data/lib/rblade/helpers/attributes_manager.rb +6 -19
- data/lib/rblade/rails_template.rb +15 -2
- data/lib/rblade/railtie.rb +2 -1
- data/rblade.gemspec +1 -1
- metadata +3 -2
@@ -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
|
-
|
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
|
-
|
50
|
-
|
51
|
-
code
|
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
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
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
|
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
|
36
|
-
raise StandardError.new "Each
|
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
|
44
|
+
variables, collection = args.split(" in ")
|
41
45
|
|
42
|
-
"_looped_#{@loop_else_counter}=
|
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}=
|
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})&&!attributes.has?(:'#{RBlade
|
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
|
-
"#{key.sub
|
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 =
|
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
|
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 = [".
|
4
|
+
FILE_EXTENSIONS = [".rblade", ".html.rblade"]
|
5
5
|
|
6
6
|
class ComponentStore
|
7
|
-
|
8
|
-
|
9
|
-
path
|
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
|
-
|
12
|
-
|
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
|
-
|
19
|
+
@@component_name_stack << full_name
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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,15 +3,6 @@ module RBlade
|
|
3
3
|
@attributes = {}
|
4
4
|
def initialize attributes
|
5
5
|
@attributes = attributes
|
6
|
-
|
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))
|
12
|
-
end
|
13
|
-
|
14
|
-
@attributes
|
15
6
|
end
|
16
7
|
|
17
8
|
def default(key, default = nil)
|
@@ -26,23 +17,19 @@ module RBlade
|
|
26
17
|
!@attributes[key].nil?
|
27
18
|
end
|
28
19
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
attributes[:_class] = attributes.delete :class
|
33
|
-
end
|
34
|
-
if !attributes[:style].nil?
|
35
|
-
attributes[:_style] = attributes.delete :style
|
36
|
-
end
|
20
|
+
def method_missing(method, *)
|
21
|
+
@attributes.send(method, *)
|
22
|
+
end
|
37
23
|
|
38
|
-
|
24
|
+
def respond_to_missing?(method_name, *args)
|
25
|
+
@attributes.respond_to?(method_name) || super
|
39
26
|
end
|
40
27
|
|
41
28
|
def to_s attributes = nil
|
42
29
|
attributes ||= @attributes
|
43
30
|
|
44
31
|
attributes.map do |key, value|
|
45
|
-
"#{key}=\"#{h(value)}\""
|
32
|
+
"#{key}=\"#{(value == true) ? key : h(value)}\""
|
46
33
|
end.join " "
|
47
34
|
end
|
48
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
|
-
|
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 +
|
27
|
+
setup + ComponentStore.get + code + setdown
|
15
28
|
end
|
16
29
|
end
|
17
30
|
end
|
data/lib/rblade/railtie.rb
CHANGED
@@ -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(:
|
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
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.
|
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-
|
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
|