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.
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