bade 0.1.4 → 0.2.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 +4 -4
- data/Bade.gemspec +1 -0
- data/Gemfile +3 -0
- data/README.md +3 -2
- data/lib/bade.rb +1 -0
- data/lib/bade/ast/document.rb +53 -0
- data/lib/bade/ast/node.rb +50 -0
- data/lib/bade/ast/node/doctype_node.rb +25 -0
- data/lib/bade/ast/node/key_value_node.rb +21 -0
- data/lib/bade/ast/node/mixin_node.rb +45 -0
- data/lib/bade/ast/node/tag_node.rb +23 -0
- data/lib/bade/ast/node/value_node.rb +32 -0
- data/lib/bade/ast/node_registrator.rb +82 -0
- data/lib/bade/ast/string_serializer.rb +72 -0
- data/lib/bade/generator.rb +353 -6
- data/lib/bade/parser.rb +199 -129
- data/lib/bade/precompiled.rb +61 -0
- data/lib/bade/renderer.rb +151 -31
- data/lib/bade/ruby_extensions/array.rb +45 -0
- data/lib/bade/ruby_extensions/string.rb +50 -20
- data/lib/bade/runtime.rb +2 -0
- data/lib/bade/runtime/block.rb +56 -10
- data/lib/bade/runtime/mixin.rb +43 -0
- data/lib/bade/runtime/render_binding.rb +53 -9
- data/lib/bade/version.rb +2 -1
- metadata +16 -14
- data/lib/bade/document.rb +0 -33
- data/lib/bade/generator/html_generator.rb +0 -80
- data/lib/bade/generator/ruby_generator.rb +0 -336
- data/lib/bade/node.rb +0 -116
- data/lib/bade/node/doctype_node.rb +0 -21
- data/lib/bade/node/key_value_node.rb +0 -10
- data/lib/bade/node/mixin_node.rb +0 -69
- data/lib/bade/node/tag_node.rb +0 -29
- data/lib/bade/ruby_extensions/object.rb +0 -11
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
|
6
|
+
module Bade
|
7
|
+
class Precompiled
|
8
|
+
# @return [String]
|
9
|
+
#
|
10
|
+
attr_accessor :code_string
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
#
|
14
|
+
attr_accessor :source_file_path
|
15
|
+
|
16
|
+
# # @return [Proc]
|
17
|
+
# #
|
18
|
+
# attr_accessor :lambda_instance
|
19
|
+
|
20
|
+
# @param [String, File] file file instance or path to file
|
21
|
+
#
|
22
|
+
def self.from_yaml_file(file)
|
23
|
+
file = if file.is_a?(String)
|
24
|
+
File.new(file, 'r')
|
25
|
+
else
|
26
|
+
file
|
27
|
+
end
|
28
|
+
|
29
|
+
hash = YAML.load(file)
|
30
|
+
file_path = hash[:source_file_path]
|
31
|
+
content = hash[:code_string]
|
32
|
+
|
33
|
+
new(content, file_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [String] code
|
37
|
+
#
|
38
|
+
def initialize(code, source_file_path = nil)
|
39
|
+
@code_string = code
|
40
|
+
@source_file_path = source_file_path
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [String, File] file file instance or path to file
|
44
|
+
#
|
45
|
+
def write_yaml_to_file(file)
|
46
|
+
file = if file.is_a?(String)
|
47
|
+
File.new(file, 'w')
|
48
|
+
else
|
49
|
+
file
|
50
|
+
end
|
51
|
+
|
52
|
+
content = {
|
53
|
+
source_file_path: source_file_path,
|
54
|
+
code_string: code_string,
|
55
|
+
}.to_yaml
|
56
|
+
|
57
|
+
file.write(content)
|
58
|
+
file.flush
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/bade/renderer.rb
CHANGED
@@ -1,12 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
|
-
|
3
|
+
require 'pathname'
|
3
4
|
require_relative 'parser'
|
4
5
|
require_relative 'generator'
|
5
6
|
require_relative 'runtime'
|
7
|
+
require_relative 'precompiled'
|
6
8
|
|
7
9
|
|
8
10
|
module Bade
|
9
11
|
class Renderer
|
12
|
+
class LoadError < ::LoadError
|
13
|
+
# @return [String]
|
14
|
+
#
|
15
|
+
attr_reader :loading_path
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
#
|
19
|
+
attr_reader :reference_path
|
20
|
+
|
21
|
+
# @param [String] loading_path currently loaded path
|
22
|
+
# @param [String] reference_path reference file from which is load performed
|
23
|
+
# @param [String] msg standard message
|
24
|
+
#
|
25
|
+
def initialize(loading_path, reference_path, msg=nil)
|
26
|
+
super(msg)
|
27
|
+
@loading_path = loading_path
|
28
|
+
@reference_path = reference_path
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
TEMPLATE_FILE_NAME = '(__template__)'
|
33
|
+
|
10
34
|
# @return [String]
|
11
35
|
#
|
12
36
|
attr_accessor :source_text
|
@@ -19,9 +43,31 @@ module Bade
|
|
19
43
|
#
|
20
44
|
attr_accessor :locals
|
21
45
|
|
22
|
-
# @
|
46
|
+
# @return [Binding]
|
23
47
|
#
|
24
|
-
|
48
|
+
attr_accessor :lambda_binding
|
49
|
+
|
50
|
+
# @return [RenderBinding]
|
51
|
+
#
|
52
|
+
attr_accessor :render_binding
|
53
|
+
|
54
|
+
|
55
|
+
# ----------------------------------------------------------------------------- #
|
56
|
+
# Internal attributes
|
57
|
+
|
58
|
+
# @return [Hash<String, Document>] absolute path => document
|
59
|
+
#
|
60
|
+
def parsed_documents
|
61
|
+
@parsed_documents ||= {}
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# ----------------------------------------------------------------------------- #
|
66
|
+
# Factory methods
|
67
|
+
|
68
|
+
# @param [String] source source string that should be parsed
|
69
|
+
#
|
70
|
+
# @return [Renderer] preconfigured instance of this class
|
25
71
|
#
|
26
72
|
def self.from_source(source, file_path = nil)
|
27
73
|
inst = new
|
@@ -30,9 +76,9 @@ module Bade
|
|
30
76
|
inst
|
31
77
|
end
|
32
78
|
|
33
|
-
# @param
|
79
|
+
# @param [String, File] file file path or file instance, file that should be loaded and parsed
|
34
80
|
#
|
35
|
-
# @return [
|
81
|
+
# @return [Renderer] preconfigured instance of this class
|
36
82
|
#
|
37
83
|
def self.from_file(file)
|
38
84
|
path = if file.is_a?(File)
|
@@ -44,13 +90,18 @@ module Bade
|
|
44
90
|
from_source(nil, path)
|
45
91
|
end
|
46
92
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
93
|
+
# Method to create Renderer from Precompiled object, for example when you want to reuse precompiled object from disk
|
94
|
+
#
|
95
|
+
# @param [Precompiled] precompiled
|
96
|
+
#
|
97
|
+
# @return [Renderer] preconfigured instance of this class
|
98
|
+
#
|
99
|
+
def self.from_precompiled(precompiled)
|
100
|
+
inst = new
|
101
|
+
inst.precompiled = precompiled
|
102
|
+
inst
|
51
103
|
end
|
52
104
|
|
53
|
-
|
54
105
|
# ----------------------------------------------------------------------------- #
|
55
106
|
# DSL methods
|
56
107
|
|
@@ -60,76 +111,145 @@ module Bade
|
|
60
111
|
# @return [self]
|
61
112
|
#
|
62
113
|
def with_locals(locals = {})
|
114
|
+
self.render_binding = nil
|
115
|
+
|
63
116
|
self.locals = locals
|
64
117
|
self
|
65
118
|
end
|
66
119
|
|
120
|
+
def with_binding(binding)
|
121
|
+
self.lambda_binding = binding
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
67
125
|
|
68
126
|
# ----------------------------------------------------------------------------- #
|
69
127
|
# Getters
|
70
128
|
|
71
|
-
# @return [Bade::Node]
|
129
|
+
# @return [Bade::AST::Node]
|
72
130
|
#
|
73
131
|
def root_document
|
74
|
-
@
|
132
|
+
@root_document ||= _parsed_document(source_text, file_path)
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [Precompiled]
|
136
|
+
#
|
137
|
+
attr_writer :precompiled
|
138
|
+
|
139
|
+
# @return [Precompiled]
|
140
|
+
#
|
141
|
+
def precompiled
|
142
|
+
@precompiled ||= Precompiled.new(Generator.document_to_lambda_string(root_document), file_path)
|
75
143
|
end
|
76
144
|
|
77
145
|
# @return [String]
|
78
146
|
#
|
79
|
-
def lambda_string
|
80
|
-
|
147
|
+
def lambda_string
|
148
|
+
precompiled.code_string
|
81
149
|
end
|
82
150
|
|
151
|
+
# @return [RenderBinding]
|
152
|
+
#
|
153
|
+
def render_binding
|
154
|
+
@render_binding ||= Runtime::RenderBinding.new(locals || {})
|
155
|
+
end
|
156
|
+
|
157
|
+
# @return [Proc]
|
158
|
+
#
|
159
|
+
def lambda_instance
|
160
|
+
if lambda_binding
|
161
|
+
lambda_binding.eval(lambda_string, file_path || TEMPLATE_FILE_NAME)
|
162
|
+
else
|
163
|
+
render_binding.instance_eval(lambda_string, file_path || TEMPLATE_FILE_NAME)
|
164
|
+
end
|
165
|
+
end
|
83
166
|
|
84
167
|
# ----------------------------------------------------------------------------- #
|
85
168
|
# Render
|
86
169
|
|
87
|
-
# @
|
170
|
+
# @param [Binding] binding custom binding for evaluating the template, but it is not recommended to use, use :locals and #with_locals instead
|
171
|
+
# @param [String] new_line newline string, default is \n
|
172
|
+
# @param [String] indent indent string, default is two spaces
|
88
173
|
#
|
89
|
-
|
90
|
-
|
91
|
-
|
174
|
+
# @return [String] rendered content of template
|
175
|
+
#
|
176
|
+
def render(binding: nil, new_line: nil, indent: nil)
|
177
|
+
self.lambda_binding = binding unless binding.nil? # backward compatibility
|
178
|
+
|
179
|
+
run_vars = {
|
180
|
+
Generator::NEW_LINE_NAME.to_sym => new_line,
|
181
|
+
Generator::BASE_INDENT_NAME.to_sym => indent,
|
182
|
+
}
|
183
|
+
run_vars.reject! { |_key, value| value.nil? } # remove nil values
|
92
184
|
|
93
|
-
lambda_instance
|
94
|
-
lambda_instance.call
|
185
|
+
lambda_instance.call(**run_vars)
|
95
186
|
end
|
96
187
|
|
97
188
|
|
98
189
|
|
99
190
|
private
|
100
191
|
|
101
|
-
# @param
|
192
|
+
# @param [String] content source code of the template
|
193
|
+
# @param [String] file_path reference path to template file
|
102
194
|
#
|
103
|
-
# @return [Bade::Document]
|
195
|
+
# @return [Bade::AST::Document]
|
104
196
|
#
|
105
197
|
def _parsed_document(content, file_path)
|
106
198
|
content = if file_path.nil? && content.nil?
|
107
|
-
raise LoadError, "Don't know what to do with nil values for both content and path"
|
199
|
+
raise LoadError.new(nil, file_path, "Don't know what to do with nil values for both content and path")
|
108
200
|
elsif !file_path.nil? && content.nil?
|
109
201
|
File.read(file_path)
|
110
202
|
else
|
111
203
|
content
|
112
204
|
end
|
113
205
|
|
114
|
-
parsed_document =
|
206
|
+
parsed_document = parsed_documents[file_path]
|
115
207
|
return parsed_document unless parsed_document.nil?
|
116
208
|
|
117
209
|
parser = Parser.new(file_path: file_path)
|
118
|
-
|
119
210
|
document = parser.parse(content)
|
120
211
|
|
121
212
|
parser.dependency_paths.each do |path|
|
122
|
-
|
123
|
-
|
124
|
-
sub_path
|
125
|
-
elsif File.exists?("#{sub_path}.bade")
|
126
|
-
"#{sub_path}.bade"
|
127
|
-
end
|
213
|
+
new_path = _find_file!(path, file_path)
|
214
|
+
next if new_path.nil?
|
128
215
|
|
129
216
|
document.sub_documents << _parsed_document(nil, new_path)
|
130
217
|
end
|
131
218
|
|
132
219
|
document
|
133
220
|
end
|
221
|
+
|
222
|
+
# Tries to find file with name, if no file could be found or there are multiple files matching the name error is raised
|
223
|
+
#
|
224
|
+
# @param [String] name name of the file that should be found
|
225
|
+
# @param [String] reference_path path to file from which is loading/finding
|
226
|
+
#
|
227
|
+
# @return [String, nil] returns nil when this file should be skipped otherwise absolute path to file
|
228
|
+
#
|
229
|
+
def _find_file!(name, reference_path)
|
230
|
+
sub_path = File.expand_path(name, File.dirname(reference_path))
|
231
|
+
|
232
|
+
if File.exists?(sub_path)
|
233
|
+
return if sub_path.end_with?('.rb') # handled in Generator
|
234
|
+
sub_path
|
235
|
+
else
|
236
|
+
bade_path = "#{sub_path}.bade"
|
237
|
+
rb_path = "#{sub_path}.rb"
|
238
|
+
|
239
|
+
bade_exist = File.exists?(bade_path)
|
240
|
+
rb_exist = File.exists?(rb_path)
|
241
|
+
relative = Pathname.new(reference_path).relative_path_from(Pathname.new(File.dirname(self.file_path))).to_s
|
242
|
+
|
243
|
+
if bade_exist && rb_exist
|
244
|
+
raise LoadError.new(name, reference_path, "Found both .bade and .rb files for `#{name}` in file #{relative}, change the import path so it references uniq file.")
|
245
|
+
elsif bade_exist
|
246
|
+
return bade_path
|
247
|
+
elsif rb_exist
|
248
|
+
return # handled in Generator
|
249
|
+
else
|
250
|
+
raise LoadError.new(name, reference_path, "Can't find file matching name `#{name}` referenced from file #{relative}")
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
134
254
|
end
|
135
255
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Array
|
4
|
+
# Returns index of last matching item when iterating from back to start of +self+.
|
5
|
+
#
|
6
|
+
# Returns nil when the first item does not match (when iterating from back).
|
7
|
+
#
|
8
|
+
# @return [Fixnum]
|
9
|
+
#
|
10
|
+
def rindex_last_matching(&block)
|
11
|
+
return nil if empty?
|
12
|
+
|
13
|
+
index = nil
|
14
|
+
|
15
|
+
current_index = count - 1
|
16
|
+
reverse_each do |item|
|
17
|
+
if block.call(item)
|
18
|
+
index = current_index
|
19
|
+
current_index -= 1
|
20
|
+
else
|
21
|
+
break
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
index
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns count of items that matches, iteration starts at the end and stops on first not matching item.
|
29
|
+
#
|
30
|
+
# @return [Fixnum] count of items
|
31
|
+
#
|
32
|
+
def rcount_matching(&block)
|
33
|
+
count = 0
|
34
|
+
|
35
|
+
reverse_each do |item|
|
36
|
+
if block.call(item)
|
37
|
+
count += 1
|
38
|
+
else
|
39
|
+
break
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
count
|
44
|
+
end
|
45
|
+
end
|
@@ -1,4 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class String
|
4
|
+
SPACE_CHAR = ' '
|
5
|
+
TAB_CHAR = "\t"
|
2
6
|
|
3
7
|
# Creates new string surrounded by single quotes
|
4
8
|
#
|
@@ -9,30 +13,37 @@ class String
|
|
9
13
|
end
|
10
14
|
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
# @param [Int] indent
|
15
|
-
# @param [Int] tabsize
|
16
|
-
#
|
17
|
-
def remove_indent(indent, tabsize)
|
18
|
-
self.dup.remove_indent!(indent, tabsize)
|
16
|
+
def blank?
|
17
|
+
strip.length == 0
|
19
18
|
end
|
20
19
|
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
def remove_last(count = 1)
|
22
|
+
slice(0, length - count)
|
23
|
+
end
|
24
|
+
|
25
|
+
def remove_last!(count = 1)
|
26
|
+
slice!(length - count, count)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def remove_first(count = 1)
|
31
|
+
slice(count, length - count)
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_first!(count = 1)
|
35
|
+
slice!(0, count)
|
36
|
+
end
|
37
|
+
|
38
|
+
def __chars_count_for_indent(indent, tabsize)
|
28
39
|
count = 0
|
29
40
|
self.each_char do |char|
|
41
|
+
break if indent <= 0
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
elsif char == ' '
|
43
|
+
case char
|
44
|
+
when SPACE_CHAR
|
34
45
|
indent -= 1
|
35
|
-
|
46
|
+
when TAB_CHAR
|
36
47
|
if indent - tabsize < 0
|
37
48
|
raise StandardError, 'malformed tabs'
|
38
49
|
end
|
@@ -45,7 +56,26 @@ class String
|
|
45
56
|
count += 1
|
46
57
|
end
|
47
58
|
|
48
|
-
|
59
|
+
count
|
60
|
+
end
|
61
|
+
|
62
|
+
# Remove indent
|
63
|
+
#
|
64
|
+
# @param [Int] indent
|
65
|
+
# @param [Int] tabsize
|
66
|
+
#
|
67
|
+
def remove_indent(indent, tabsize)
|
68
|
+
remove_first(__chars_count_for_indent(indent, tabsize))
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Remove indent
|
73
|
+
#
|
74
|
+
# @param [Int] indent
|
75
|
+
# @param [Int] tabsize
|
76
|
+
#
|
77
|
+
def remove_indent!(indent, tabsize)
|
78
|
+
remove_first!(__chars_count_for_indent(indent, tabsize))
|
49
79
|
end
|
50
80
|
|
51
81
|
|
@@ -59,9 +89,9 @@ class String
|
|
59
89
|
count = 0
|
60
90
|
|
61
91
|
self.each_char do |char|
|
62
|
-
if char ==
|
92
|
+
if char == SPACE_CHAR
|
63
93
|
count += 1
|
64
|
-
elsif char ==
|
94
|
+
elsif char == TAB_CHAR
|
65
95
|
count += tabsize
|
66
96
|
else
|
67
97
|
break
|