bade 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bade/runtime.rb CHANGED
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Epuber
3
4
  module Runtime
4
5
  require_relative 'runtime/block'
6
+ require_relative 'runtime/mixin'
5
7
  require_relative 'runtime/render_binding'
6
8
  end
7
9
  end
@@ -1,9 +1,32 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Bade
3
4
  module Runtime
4
5
  class RuntimeError < ::StandardError; end
5
6
 
6
7
  class Block
8
+ class MissingBlockDefinitionError < RuntimeError
9
+ # @return [String]
10
+ #
11
+ attr_accessor :name
12
+
13
+ # @return [Symbol] context of missing block, allowed values are :render and :call
14
+ #
15
+ attr_accessor :context
16
+
17
+ def initialize(name, context, msg = nil)
18
+ super()
19
+
20
+ self.name = name
21
+ self.context = context
22
+
23
+ @message = msg
24
+ end
25
+
26
+ def message
27
+ @message || "Block `#{name}` must have block definition to #{context}."
28
+ end
29
+ end
7
30
 
8
31
  # @return [Proc]
9
32
  #
@@ -13,28 +36,51 @@ module Bade
13
36
  #
14
37
  attr_reader :name
15
38
 
16
- # @param [String] name
39
+ # @return [RenderBinding]
40
+ #
41
+ attr_reader :render_binding
42
+
43
+ # @param [String] name name of the block
44
+ # @param [RenderBinding] render_binding reference to current binding instance
45
+ # @param [Proc] block reference to lambda
17
46
  #
18
- def initialize(name, &block)
47
+ def initialize(name, render_binding, &block)
19
48
  @name = name
20
- @block = lambda &block unless block.nil?
49
+ @render_binding = render_binding
50
+ @block = block
21
51
  end
22
52
 
53
+ # --- Calling methods
54
+
23
55
  def call(*args)
24
- @block.call(*args) unless @block.nil?
56
+ call!(*args) unless @block.nil?
25
57
  end
26
58
 
27
59
  def call!(*args)
28
60
  if @block.nil?
29
- raise RuntimeError, "`#{@name}` must have block definition"
61
+ raise MissingBlockDefinitionError.new(name, :call)
30
62
  else
31
- @block.call(*args)
63
+ render_binding.__buff.concat(@block.call(*args))
64
+ end
65
+ end
66
+
67
+ # --- Rendering methods
68
+
69
+ def render(*args)
70
+ if @block.nil?
71
+ ''
72
+ else
73
+ render!(*args)
32
74
  end
33
75
  end
34
- end
35
- end
36
76
 
37
- def block(name, &block)
38
- Runtime::Block.new(name, &block)
77
+ def render!(*args)
78
+ if @block.nil?
79
+ raise MissingBlockDefinitionError.new(name, :render)
80
+ else
81
+ @block.call(*args).join
82
+ end
83
+ end
84
+ end
39
85
  end
40
86
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bade
4
+ module Runtime
5
+ require_relative 'block'
6
+
7
+ class Mixin < Block
8
+ def call!(blocks, *args)
9
+ block.call(blocks, *args)
10
+ rescue ArgumentError => e
11
+ case e.message
12
+ when /\Awrong number of arguments \(given ([0-9]+), expected ([0-9]+)\)\Z/, /\Awrong number of arguments \(([0-9]+) for ([0-9]+)\)\Z/
13
+ # handle incorrect parameters count
14
+
15
+ # minus one, because first argument is always hash of blocks
16
+ given = $1.to_i - 1
17
+ expected = $2.to_i - 1
18
+ raise ArgumentError, "wrong number of arguments (given #{given}, expected #{expected}) for mixin `#{name}`"
19
+
20
+ when /\Aunknown keyword: (.*)\Z/
21
+ # handle unknown key-value parameter
22
+ key_name = $1
23
+ raise ArgumentError, "unknown key-value argument `#{key_name}` for mixin `#{name}`"
24
+
25
+ else
26
+ raise
27
+ end
28
+
29
+ rescue Block::MissingBlockDefinitionError => e
30
+ msg = case e.context
31
+ when :call
32
+ "Mixin `#{name}` requires block to get called of block `#{e.name}`"
33
+ when :render
34
+ "Mixin `#{name}` requires block to get rendered content of block `#{e.name}`"
35
+ else
36
+ raise ::ArgumentError, "Unknown context #{e.context} of error #{e}!"
37
+ end
38
+
39
+ raise Block::MissingBlockDefinitionError.new(e.name, e.context, msg)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require_relative 'block'
3
4
 
@@ -6,27 +7,70 @@ module Bade
6
7
  class RenderBinding
7
8
  class KeyError < ::StandardError; end
8
9
 
10
+ # @return [Array<Array<String>>]
11
+ #
12
+ attr_accessor :__buffs_stack
13
+
14
+ # @return [Hash<String, Mixin>]
15
+ #
16
+ attr_accessor :__mixins
17
+
18
+ # Holds
19
+ # @return [String]
20
+ #
21
+ attr_accessor :__new_line, :__base_indent
22
+
9
23
  # @param vars [Hash]
10
24
  #
11
25
  def initialize(vars = {})
12
- @vars = vars
26
+ __reset
27
+
28
+ vars.each do |key, value|
29
+ raise KeyError, "Already defined variable #{key.inspect} in this binding" if respond_to?(key.to_sym)
30
+
31
+ define_singleton_method(key) do
32
+ value
33
+ end
34
+ end
13
35
  end
14
36
 
15
- def method_missing(name, *args)
16
- raise KeyError, "Not found value for key `#{name}'" unless @vars.key?(name)
17
- @vars[name]
37
+ # Resets this binding to default state, this method should be envoked after running the template lambda
38
+ #
39
+ # @return [nil]
40
+ #
41
+ def __reset
42
+ @__buffs_stack = [[]]
43
+ @__mixins = Hash.new { |_hash, key| raise "Undefined mixin '#{key}'" }
18
44
  end
19
45
 
20
46
  # @return [Binding]
21
47
  #
22
- def get_binding
48
+ def __get_binding
23
49
  binding
24
50
  end
25
51
 
26
52
  # Shortcut for creating blocks
27
53
  #
28
- def __create_block(*args, &block)
29
- Bade::Runtime::Block.new(*args, &block)
54
+ def __create_block(name, &block)
55
+ Bade::Runtime::Block.new(name, self, &block)
56
+ end
57
+
58
+ def __create_mixin(name, &block)
59
+ Bade::Runtime::Mixin.new(name, self, &block)
60
+ end
61
+
62
+ # --- Methods for dealing with pushing and poping buffers in stack
63
+
64
+ def __buff
65
+ __buffs_stack.last
66
+ end
67
+
68
+ def __buffs_push
69
+ __buffs_stack.push([])
70
+ end
71
+
72
+ def __buffs_pop
73
+ __buffs_stack.pop
30
74
  end
31
75
 
32
76
  # Escape input text with html escapes
@@ -35,14 +79,14 @@ module Bade
35
79
  #
36
80
  # @return [String]
37
81
  #
38
- def html_escaped(text)
82
+ def __html_escaped(text)
39
83
  text.sub('&', '&amp;')
40
84
  .sub('<', '&lt;')
41
85
  .sub('>', '&gt;')
42
86
  .sub('"', '&quot;')
43
87
  end
44
88
 
45
- def tag_render_attribute(name, *values)
89
+ def __tag_render_attribute(name, *values)
46
90
  values = values.compact
47
91
  return if values.empty?
48
92
 
data/lib/bade/version.rb CHANGED
@@ -1,4 +1,5 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Bade
3
- VERSION = '0.1.4'
4
+ VERSION = '0.2.0'
4
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Kříž
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-25 00:00:00.000000000 Z
11
+ date: 2016-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -63,21 +63,24 @@ files:
63
63
  - Gemfile
64
64
  - README.md
65
65
  - lib/bade.rb
66
- - lib/bade/document.rb
66
+ - lib/bade/ast/document.rb
67
+ - lib/bade/ast/node.rb
68
+ - lib/bade/ast/node/doctype_node.rb
69
+ - lib/bade/ast/node/key_value_node.rb
70
+ - lib/bade/ast/node/mixin_node.rb
71
+ - lib/bade/ast/node/tag_node.rb
72
+ - lib/bade/ast/node/value_node.rb
73
+ - lib/bade/ast/node_registrator.rb
74
+ - lib/bade/ast/string_serializer.rb
67
75
  - lib/bade/generator.rb
68
- - lib/bade/generator/html_generator.rb
69
- - lib/bade/generator/ruby_generator.rb
70
- - lib/bade/node.rb
71
- - lib/bade/node/doctype_node.rb
72
- - lib/bade/node/key_value_node.rb
73
- - lib/bade/node/mixin_node.rb
74
- - lib/bade/node/tag_node.rb
75
76
  - lib/bade/parser.rb
77
+ - lib/bade/precompiled.rb
76
78
  - lib/bade/renderer.rb
77
- - lib/bade/ruby_extensions/object.rb
79
+ - lib/bade/ruby_extensions/array.rb
78
80
  - lib/bade/ruby_extensions/string.rb
79
81
  - lib/bade/runtime.rb
80
82
  - lib/bade/runtime/block.rb
83
+ - lib/bade/runtime/mixin.rb
81
84
  - lib/bade/runtime/render_binding.rb
82
85
  - lib/bade/version.rb
83
86
  homepage: https://github.com/epuber-io/bade
@@ -92,7 +95,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
92
95
  requirements:
93
96
  - - ">="
94
97
  - !ruby/object:Gem::Version
95
- version: '0'
98
+ version: '2.0'
96
99
  required_rubygems_version: !ruby/object:Gem::Requirement
97
100
  requirements:
98
101
  - - ">="
@@ -100,9 +103,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
103
  version: '0'
101
104
  requirements: []
102
105
  rubyforge_project:
103
- rubygems_version: 2.4.8
106
+ rubygems_version: 2.5.1
104
107
  signing_key:
105
108
  specification_version: 4
106
109
  summary: Minimalistic template engine for Ruby.
107
110
  test_files: []
108
- has_rdoc:
data/lib/bade/document.rb DELETED
@@ -1,33 +0,0 @@
1
-
2
- require_relative 'node'
3
-
4
-
5
- module Bade
6
- class Document
7
- # Root node of this document
8
- #
9
- # @return [Bade::Node]
10
- #
11
- attr_accessor :root
12
-
13
- # Path to this document, but only if it is defined from file
14
- #
15
- # @return [String, nil]
16
- #
17
- attr_reader :file_path
18
-
19
- # @return [Array<Bade::Document>]
20
- #
21
- attr_reader :sub_documents
22
-
23
- # @param root [Bade::Node]
24
- #
25
- def initialize(root: nil, file_path: nil)
26
- @root = root || Node.new(:root)
27
- @root.parent = self
28
-
29
- @file_path = file_path
30
- @sub_documents = []
31
- end
32
- end
33
- end
@@ -1,80 +0,0 @@
1
- require_relative '../generator'
2
-
3
- module Bade
4
- class HTMLGenerator < Generator
5
-
6
- # @param [Node] root node
7
- # @return [String]
8
- #
9
- def self.node_to_lambda(root, new_line: "\n", indent: "\t")
10
- str = node_to_html_array(root, new_line: new_line, indent: indent).join
11
-
12
- lambda {
13
- str
14
- }
15
- end
16
-
17
-
18
- private
19
-
20
- # @param [Node] root node
21
- # @return [Array<String>]
22
- #
23
- def self.node_to_html_array(root, new_line: "\n", indent: "\t", indent_level: 0)
24
- unless root.kind_of? Node
25
- return [ root.inspect ]
26
- end
27
-
28
- buff = []
29
-
30
- if indent_level > 0 and not indent.empty?
31
- buff << indent * indent_level
32
- end
33
-
34
- append_childrens = lambda { |indent_plus|
35
- root.childrens.each { |node|
36
- buff += node_to_html_array(node, new_line: new_line, indent: indent, indent_level: indent_level+indent_plus)
37
- }
38
- }
39
-
40
-
41
- case root.type
42
- when :root
43
- append_childrens.call(0)
44
-
45
- when :text
46
- buff << root.data
47
-
48
- when :tag
49
- attributes = formatted_attributes root
50
-
51
- if attributes.length > 0
52
- buff << "<#{root.data} #{attributes}>" + new_line
53
- else
54
- buff << "<#{root.data}>" + new_line
55
- end
56
-
57
- append_childrens.call(1)
58
-
59
- buff << "</#{root.data}>" + new_line
60
- end
61
-
62
- buff
63
- end
64
-
65
- # @param [Node] tag_node
66
- #
67
- # @return [String] formatted attributes
68
- #
69
- def self.formatted_attributes(tag_node)
70
-
71
- attributes = tag_node.childrens.select { |child|
72
- child.type == :tag_attribute
73
- }.map { |attr|
74
- "#{attr.data}=\"#{attr.childrens.first.data}\""
75
- }
76
-
77
- attributes.join ' '
78
- end
79
- end
80
- end
@@ -1,336 +0,0 @@
1
- require_relative '../generator'
2
- require_relative '../runtime'
3
- require_relative '../document'
4
-
5
- module Bade
6
- class RubyGenerator < Generator
7
-
8
- BUFF_NAME = '__buff'
9
- MIXINS_NAME = '__mixins'
10
- START_STRING = "
11
- lambda {
12
- #{BUFF_NAME} = []
13
- #{MIXINS_NAME} = Hash.new { |hash, key| raise \"Undefined mixin '\#{key}'\" }
14
- "
15
-
16
- END_STRING = "
17
- #{BUFF_NAME}.join
18
- }"
19
-
20
- # @param [Document] document
21
- #
22
- # @return [Proc]
23
- #
24
- def self.document_to_lambda(document, new_line: "\n", indent: "\t", filename: '')
25
- generator = self.new(new_line, indent)
26
- generator.generate_lambda(document, filename)
27
- end
28
-
29
- # @param [Document] document
30
- #
31
- # @return [String]
32
- #
33
- def self.document_to_lambda_string(document, new_line: "\n", indent: "\t", filename: '')
34
- generator = self.new(new_line, indent)
35
- generator.generate_lambda_string(document)
36
- end
37
-
38
-
39
-
40
- # @param [String] new_line_string
41
- # @param [String] indent_string
42
- #
43
- def initialize(new_line_string, indent_string)
44
- @new_line_string = new_line_string
45
- @indent_string = indent_string
46
- end
47
-
48
- # @param [Document] document
49
- # @param [String] filename
50
- #
51
- def generate_lambda(document, filename)
52
- eval(generate_lambda_string(document), nil, filename)
53
- end
54
-
55
- # @param [Document] document
56
- #
57
- # @return [String] string to parse with Ruby
58
- #
59
- def generate_lambda_string(document)
60
- @buff = []
61
- @indent = 0
62
- @code_indent = 1
63
-
64
- @buff << START_STRING
65
-
66
- visit_document(document)
67
-
68
- @buff << END_STRING
69
-
70
- @buff.join("\n")
71
- end
72
-
73
- # @param [String] text
74
- #
75
- def buff_print_text(text, indent: false, new_line: false)
76
- indent_text = if indent
77
- @indent_string * @indent
78
- else
79
- ''
80
- end
81
-
82
- prepended_text = indent_text + text
83
-
84
- if prepended_text.length > 0
85
- buff_code %Q{#{BUFF_NAME} << %Q{#{prepended_text}}}
86
- end
87
- end
88
-
89
- def buff_code(text)
90
- @buff << "\t" * @code_indent + text
91
- end
92
-
93
-
94
- # @param document [Bade::Document]
95
- #
96
- def visit_document(document)
97
- document.sub_documents.each do |sub_document|
98
- visit_document(sub_document)
99
- end
100
-
101
- buff_code("# ----- start file #{document.file_path}") unless document.file_path.nil?
102
- visit_node(document.root)
103
- buff_code("# ----- end file #{document.file_path}") unless document.file_path.nil?
104
- end
105
-
106
- # @param current_node [Node]
107
- #
108
- def visit_node_childrens(current_node)
109
- visit_nodes(current_node.childrens)
110
- end
111
-
112
- # @param nodes [Array<Node>]
113
- #
114
- def visit_nodes(nodes)
115
- nodes.each { |node|
116
- visit_node(node)
117
- }
118
- end
119
-
120
- # @param current_node [Node]
121
- #
122
- def visit_node(current_node)
123
- case current_node.type
124
- when :root
125
- visit_node_childrens(current_node)
126
-
127
- when :text
128
- buff_print_text current_node.data
129
-
130
- when :tag
131
- visit_tag(current_node)
132
-
133
- when :ruby_code
134
- buff_code current_node.data
135
-
136
- when :html_comment
137
- buff_print_text '<!-- '
138
- visit_node_childrens(current_node)
139
- buff_print_text ' -->'
140
-
141
- when :comment
142
- comment_text = current_node.childrens.select { |node|
143
- !node.data.nil?
144
- }.map { |node|
145
- node.data
146
- }.join(@new_line_string + '#')
147
-
148
- buff_code '#' + comment_text
149
-
150
- when :doctype
151
- buff_print_text current_node.xml_output
152
-
153
- when :mixin_declaration
154
- params = formatted_mixin_params(current_node)
155
- buff_code "#{MIXINS_NAME}['#{current_node.data}'] = lambda { |#{params}|"
156
-
157
- indent {
158
- blocks_name_declaration(current_node)
159
- visit_node_childrens(current_node)
160
- }
161
-
162
- buff_code '}'
163
-
164
- when :mixin_call
165
- params = formatted_mixin_params(current_node)
166
- buff_code "#{MIXINS_NAME}['#{current_node.data}'].call(#{params})"
167
-
168
- when :output
169
- data = current_node.data
170
- output_code = if current_node.escaped
171
- "\#{html_escaped(#{data})}"
172
- else
173
- "\#{#{data}}"
174
- end
175
- buff_print_text output_code
176
-
177
- when :newline
178
- buff_print_text @new_line_string if @new_line_string.length > 0
179
-
180
- when :import
181
- # nothing
182
-
183
- else
184
- raise "Unknown type #{current_node.type}"
185
- end
186
- end
187
-
188
- # @param [TagNode] current_node
189
- #
190
- def visit_tag(current_node)
191
- attributes = formatted_attributes current_node
192
-
193
- text = "<#{current_node.name}"
194
-
195
- if attributes.length > 0
196
- text += "#{attributes}"
197
- end
198
-
199
- other_than_new_lines = current_node.childrens.any? { |node|
200
- node.type != :newline
201
- }
202
-
203
- if other_than_new_lines
204
- text += '>'
205
- else
206
- text += '/>'
207
- end
208
-
209
- buff_print_text text, new_line: true, indent: true
210
-
211
- if other_than_new_lines
212
- last_node = current_node.childrens.last
213
- is_last_newline = !last_node.nil? && last_node.type == :newline
214
- nodes = if is_last_newline
215
- current_node.childrens[0...-1]
216
- else
217
- current_node.childrens
218
- end
219
-
220
- indent do
221
- visit_nodes(nodes)
222
- end
223
-
224
- buff_print_text "</#{current_node.name}>", new_line: true, indent: true
225
-
226
- # print new line after the tag
227
- visit_node(last_node) if is_last_newline
228
- end
229
- end
230
-
231
- # @param [TagNode] tag_node
232
- #
233
- # @return [String] formatted attributes
234
- #
235
- def formatted_attributes(tag_node)
236
- all_attributes = Hash.new { |hash, key| hash[key] = [] }
237
- xml_attributes = []
238
-
239
- tag_node.attributes.each do |attr|
240
- unless all_attributes.include?(attr.name)
241
- xml_attributes << attr.name
242
- end
243
-
244
- all_attributes[attr.name] << attr.value
245
- end
246
-
247
- xml_attributes.map do |attr_name|
248
- joined = all_attributes[attr_name].join('), (')
249
- %Q{\#{tag_render_attribute('#{attr_name}', (#{joined}))}}
250
- end.join
251
- end
252
-
253
- def indent(plus = 1)
254
- @code_indent += plus
255
- yield
256
- @code_indent -= plus
257
- end
258
-
259
- # @param [MixinCommonNode] mixin_node
260
- #
261
- # @return [String] formatted params
262
- #
263
- def formatted_mixin_params(mixin_node)
264
- params = mixin_node.params
265
- result = []
266
-
267
-
268
-
269
- if mixin_node.type == :mixin_call
270
- if mixin_node.blocks.length > 0
271
- buff_code '__blocks = {}'
272
-
273
- mixin_node.blocks.each { |block|
274
- block_name = block.data ? block.data : 'default_block'
275
- buff_code "__blocks['#{block_name}'] = __create_block('#{block_name}') do"
276
- indent {
277
- visit_node_childrens(block)
278
- }
279
- buff_code 'end'
280
- }
281
-
282
- result << '__blocks.dup'
283
- else
284
- result << '{}'
285
- end
286
- elsif mixin_node.type == :mixin_declaration
287
- result << '__blocks'
288
- end
289
-
290
-
291
- # normal params
292
- result += params.select { |param|
293
- param.type == :mixin_param
294
- }.map { |param|
295
- param.data
296
- }
297
-
298
- result += params.select { |param|
299
- param.type == :mixin_key_param
300
- }.map { |param|
301
- "#{param.name}: #{param.value}"
302
- }
303
-
304
- result.join(', ')
305
- end
306
-
307
-
308
- # @param [String] block_name
309
- #
310
- def block_name_declaration(block_name)
311
- buff_code "#{block_name} = __blocks.delete('#{block_name}') { __create_block('#{block_name}') }"
312
- end
313
-
314
- # @param [MixinDeclarationNode] mixin_node
315
- #
316
- def blocks_name_declaration(mixin_node)
317
- mixin_node.params.select { |param|
318
- param.type == :mixin_block_param
319
- }.each { |param|
320
- block_name_declaration(param.data)
321
- }
322
-
323
- block_name_declaration('default_block')
324
- end
325
-
326
-
327
-
328
- # @param [String] str
329
- #
330
- # @return [Void]
331
- #
332
- def escape_double_quotes!(str)
333
- str.gsub!(/"/, '\"')
334
- end
335
- end
336
- end