bade 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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