bade 0.1.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 77771024dceda938d8320695d2acbd5731588cbb
4
+ data.tar.gz: 6c0b8f8c0f7cb9d17053599a754f0d28e25ea11c
5
+ SHA512:
6
+ metadata.gz: 260bd643d97374af1e3209d25d3ad3c98090808191c8f13482e910e10e707f5fa41cf8160007325bf02066eeb0af01ccd603f93a84746bb6f26a8b95286c4259
7
+ data.tar.gz: 6d9fbcf48c636f38c608bbb37b7096c2e2aadfc208d4bf749fc9e74c87fc2314765752b93fb7124a4a8d663de82749e310bfb12afae38052b26ed4353ef1c78d
data/Bade.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'bade/version'
7
+
8
+
9
+ Gem::Specification.new do |spec|
10
+ spec.name = 'bade'
11
+ spec.version = Bade::VERSION
12
+ spec.authors = ['Roman Kříž']
13
+ spec.email = ['samnung@gmail.com']
14
+ spec.summary = %q{Minimalistic template engine for Ruby.}
15
+ spec.homepage = 'https://github.com/samnung/bade'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = Dir['bin/**/*'] + Dir['lib/**/*'] + %w(Bade.gemspec Gemfile README.md)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1'
24
+ spec.add_development_dependency 'rspec', '~> 3.2'
25
+ spec.add_development_dependency 'rake', '~> 10.4'
26
+ end
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+
2
+ # Bade
3
+
4
+ [![Build Status](https://semaphoreci.com/api/v1/projects/61441d8f-9c6b-41c2-b677-1ef8516649e5/559728/badge.svg)](https://semaphoreci.com/samnung/bade)
5
+
6
+ Minimalistic template engine written in Ruby for Ruby. Developed mainly to help with creating e-books. Highly influenced by [Jade](http://jade-lang.com) and [Slim](http://slim-lang.com).
7
+
8
+ The language is in development state, breaking changes can be made in future. Current version supports minimal list of features to make it working in production for my purposes.
9
+
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'bade'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install bade
26
+
27
+
28
+ ## Development
29
+
30
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
31
+
32
+ To install this gem onto your local machine, run `bundle exec rake install`.
33
+
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/samnung/bade. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
38
+
39
+
40
+ ## TODO
41
+
42
+ - [ ] move all cards from Trello to GitHub
43
+ - [ ] create documentation about syntax
44
+ - [ ] create several examples
45
+
46
+
47
+ ## License
48
+
49
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,80 @@
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
@@ -0,0 +1,336 @@
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
@@ -0,0 +1,16 @@
1
+ require_relative 'node'
2
+
3
+ module Bade
4
+ class Generator
5
+ require_relative 'generator/html_generator'
6
+ require_relative 'generator/ruby_generator'
7
+
8
+ # @param [Node] root
9
+ #
10
+ # @return [Lambda]
11
+ #
12
+ def self.node_to_lambda(root, new_line: "\n", indent: "\t", filename: '')
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../node'
2
+
3
+
4
+ module Bade
5
+ class DoctypeNode < Node
6
+ # @return [String]
7
+ #
8
+ def xml_output
9
+ case self.data
10
+ when 'xml'
11
+ '<?xml version="1.0" encoding="utf-8" ?>'
12
+
13
+ when 'html'
14
+ '<!DOCTYPE html>'
15
+
16
+ else
17
+ raise Parser::ParserInternalError 'Unknown doctype type'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ require_relative '../node'
2
+
3
+
4
+ module Bade
5
+ class KeyValueNode < Node
6
+ attr_forw_accessor :name, :data
7
+
8
+ attr_accessor :value
9
+ end
10
+ end
@@ -0,0 +1,69 @@
1
+ require_relative '../node'
2
+
3
+
4
+ module Bade
5
+ class MixinCommonNode < Node
6
+
7
+ # @return [Array<Node>]
8
+ #
9
+ attr_reader :params
10
+
11
+ def initialize(*args)
12
+ super
13
+
14
+ @params = []
15
+ end
16
+
17
+ def << (node)
18
+ if allowed_parameter_types.include?(node.type)
19
+ node.parent = self
20
+ @params << node
21
+ else
22
+ super
23
+ end
24
+ end
25
+ end
26
+
27
+ class MixinDeclarationNode < MixinCommonNode
28
+ def allowed_parameter_types
29
+ [:mixin_param, :mixin_key_param, :mixin_block_param]
30
+ end
31
+ end
32
+
33
+ class MixinCallNode < MixinCommonNode
34
+ attr_reader :blocks
35
+
36
+ attr_reader :default_block
37
+
38
+ def initialize(*args)
39
+ super
40
+
41
+ @blocks = []
42
+ end
43
+
44
+ def allowed_parameter_types
45
+ [:mixin_param, :mixin_key_param]
46
+ end
47
+
48
+ def << (node)
49
+ if allowed_parameter_types.include?(node.type)
50
+ node.parent = self
51
+ @params << node
52
+ elsif node.type == :mixin_block
53
+ node.parent = self
54
+ @blocks << node
55
+ else
56
+ if @default_block.nil?
57
+ if node.type == :newline
58
+ # skip newlines at start
59
+ return self
60
+ end
61
+
62
+ @default_block = Node.create(:mixin_block, self)
63
+ end
64
+
65
+ @default_block << node
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ require_relative '../node'
2
+
3
+
4
+ module Bade
5
+ class TagNode < Node
6
+ attr_forw_accessor :name, :data
7
+
8
+ # @return [Array<TagAttributeNode>]
9
+ #
10
+ attr_reader :attributes
11
+
12
+ def initialize(*args)
13
+ super(*args)
14
+
15
+ @attributes = []
16
+ end
17
+
18
+ # @param [Node] node
19
+ #
20
+ def << (node)
21
+ if node.type == :tag_attribute
22
+ node.parent = self
23
+ @attributes << node
24
+ else
25
+ super
26
+ end
27
+ end
28
+ end
29
+ end