flavour_saver 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +81 -0
- data/Guardfile +12 -0
- data/LICENSE +22 -0
- data/README.md +339 -0
- data/Rakefile +2 -0
- data/flavour_saver.gemspec +28 -0
- data/lib/flavour_saver/helpers.rb +118 -0
- data/lib/flavour_saver/lexer.rb +127 -0
- data/lib/flavour_saver/nodes.rb +177 -0
- data/lib/flavour_saver/parser.rb +183 -0
- data/lib/flavour_saver/partial.rb +29 -0
- data/lib/flavour_saver/rails_partial.rb +10 -0
- data/lib/flavour_saver/runtime.rb +269 -0
- data/lib/flavour_saver/template.rb +19 -0
- data/lib/flavour_saver/version.rb +3 -0
- data/lib/flavour_saver.rb +78 -0
- data/spec/acceptance/backtrack_spec.rb +14 -0
- data/spec/acceptance/comment_spec.rb +12 -0
- data/spec/acceptance/custom_block_helper_spec.rb +35 -0
- data/spec/acceptance/custom_helper_spec.rb +15 -0
- data/spec/acceptance/ensure_no_rce_spec.rb +26 -0
- data/spec/acceptance/handlebars_qunit_spec.rb +911 -0
- data/spec/acceptance/if_else_spec.rb +17 -0
- data/spec/acceptance/multi_level_with_spec.rb +15 -0
- data/spec/acceptance/one_character_identifier_spec.rb +13 -0
- data/spec/acceptance/runtime_run_spec.rb +27 -0
- data/spec/acceptance/sections_spec.rb +25 -0
- data/spec/acceptance/segment_literals_spec.rb +26 -0
- data/spec/acceptance/simple_expression_spec.rb +13 -0
- data/spec/fixtures/backtrack.hbs +4 -0
- data/spec/fixtures/comment.hbs +1 -0
- data/spec/fixtures/custom_block_helper.hbs +3 -0
- data/spec/fixtures/custom_helper.hbs +1 -0
- data/spec/fixtures/if_else.hbs +5 -0
- data/spec/fixtures/multi_level_if.hbs +12 -0
- data/spec/fixtures/multi_level_with.hbs +11 -0
- data/spec/fixtures/one_character_identifier.hbs +1 -0
- data/spec/fixtures/sections.hbs +9 -0
- data/spec/fixtures/simple_expression.hbs +1 -0
- data/spec/lib/flavour_saver/lexer_spec.rb +187 -0
- data/spec/lib/flavour_saver/parser_spec.rb +277 -0
- data/spec/lib/flavour_saver/runtime_spec.rb +190 -0
- data/spec/lib/flavour_saver/template_spec.rb +5 -0
- metadata +243 -0
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/flavour_saver/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["James Harton"]
|
6
|
+
gem.email = ["james@resistor.io"]
|
7
|
+
gem.description = %q{FlavourSaver is a pure-ruby implimentation of the Handlebars templating language}
|
8
|
+
gem.summary = %q{Handlebars.js without the .js}
|
9
|
+
gem.homepage = "http://jamesotron.github.com/FlavourSaver/"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "flavour_saver"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = FlavourSaver::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rake'
|
19
|
+
gem.add_development_dependency 'guard-rspec'
|
20
|
+
gem.add_development_dependency 'rspec-core'
|
21
|
+
gem.add_development_dependency 'rspec-mocks'
|
22
|
+
gem.add_development_dependency 'rspec-expectations'
|
23
|
+
gem.add_development_dependency 'guard-bundler'
|
24
|
+
gem.add_development_dependency 'activesupport'
|
25
|
+
|
26
|
+
gem.add_dependency 'rltk', '~> 2.2.0'
|
27
|
+
gem.add_dependency 'tilt'
|
28
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module FlavourSaver
|
4
|
+
module Helpers
|
5
|
+
class Defaults
|
6
|
+
def with(args)
|
7
|
+
yield.contents args
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(collection)
|
11
|
+
r = []
|
12
|
+
count = 0
|
13
|
+
collection.each do |element|
|
14
|
+
r << yield.contents(element, 'index' => count)
|
15
|
+
count += 1
|
16
|
+
end
|
17
|
+
yield.rendered!
|
18
|
+
r.join ''
|
19
|
+
end
|
20
|
+
|
21
|
+
def if(truthy)
|
22
|
+
truthy = false if truthy.respond_to?(:size) && (truthy.size == 0)
|
23
|
+
if truthy
|
24
|
+
yield.contents
|
25
|
+
else
|
26
|
+
yield.inverse
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def unless(falsy,&b)
|
31
|
+
self.if(!falsy,&b)
|
32
|
+
end
|
33
|
+
|
34
|
+
def this
|
35
|
+
@source || self
|
36
|
+
end
|
37
|
+
|
38
|
+
def log(message)
|
39
|
+
FS.logger.debug("FlavourSaver: #{message}")
|
40
|
+
''
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Decorator < Defaults
|
45
|
+
|
46
|
+
def initialize(locals, source)
|
47
|
+
@source = source
|
48
|
+
mixin = Module.new do
|
49
|
+
locals.each do |name,impl|
|
50
|
+
define_method name, &impl
|
51
|
+
end
|
52
|
+
end
|
53
|
+
extend(mixin)
|
54
|
+
end
|
55
|
+
|
56
|
+
def array?
|
57
|
+
!!@source.is_a?(Array)
|
58
|
+
end
|
59
|
+
|
60
|
+
def [](accessor)
|
61
|
+
if array?
|
62
|
+
if accessor.match /[0-9]+/
|
63
|
+
return @source.at(accessor.to_i)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
@source[accessor]
|
67
|
+
end
|
68
|
+
|
69
|
+
def respond_to?(name)
|
70
|
+
super || @source.respond_to?(name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_missing(name,*args,&b)
|
74
|
+
# I would rather have it raise a NameError, but Moustache
|
75
|
+
# compatibility requires that missing helpers return
|
76
|
+
# nothing. A good place for bugs to hide.
|
77
|
+
@source.send(name, *args, &b) if @source.respond_to? name
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module_function
|
82
|
+
|
83
|
+
def registered_helpers
|
84
|
+
@registered_helpers ||= {}
|
85
|
+
end
|
86
|
+
|
87
|
+
def register_helper(method,&b)
|
88
|
+
if method.respond_to? :name
|
89
|
+
registered_helpers[method.name.to_sym] = method
|
90
|
+
elsif b
|
91
|
+
registered_helpers[method.to_sym] = b
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def deregister_helper(*names)
|
96
|
+
names.each do |name|
|
97
|
+
registered_helpers.delete(name.to_sym)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def reset_helpers
|
102
|
+
@registered_helpers = {}
|
103
|
+
end
|
104
|
+
|
105
|
+
def decorate_with(context, helper_names=[], locals={})
|
106
|
+
helpers = if helper_names.any?
|
107
|
+
helper_names = helper_names.map(&:to_sym)
|
108
|
+
registered_helpers.select { |k,v| helper_names.member? k }.merge(locals)
|
109
|
+
else
|
110
|
+
helpers = registered_helpers
|
111
|
+
end
|
112
|
+
helpers = helpers.merge(locals)
|
113
|
+
Decorator.new(helpers, context)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'rltk'
|
2
|
+
|
3
|
+
module FlavourSaver
|
4
|
+
class Lexer < RLTK::Lexer
|
5
|
+
|
6
|
+
rule /{{{/, :default do
|
7
|
+
push_state :expression
|
8
|
+
:TEXPRST
|
9
|
+
end
|
10
|
+
|
11
|
+
rule /{{/, :default do
|
12
|
+
push_state :expression
|
13
|
+
:EXPRST
|
14
|
+
end
|
15
|
+
|
16
|
+
rule /#/, :expression do
|
17
|
+
:HASH
|
18
|
+
end
|
19
|
+
|
20
|
+
rule /\//, :expression do
|
21
|
+
:FWSL
|
22
|
+
end
|
23
|
+
|
24
|
+
rule /&/, :expression do
|
25
|
+
:AMP
|
26
|
+
end
|
27
|
+
|
28
|
+
rule /\^/, :expression do
|
29
|
+
:HAT
|
30
|
+
end
|
31
|
+
|
32
|
+
rule /@/, :expression do
|
33
|
+
:AT
|
34
|
+
end
|
35
|
+
|
36
|
+
rule />/, :expression do
|
37
|
+
:GT
|
38
|
+
end
|
39
|
+
|
40
|
+
rule /([1-9][0-9]*(\.[0-9]+)?)/, :expression do |n|
|
41
|
+
[ :NUMBER, n ]
|
42
|
+
end
|
43
|
+
|
44
|
+
rule /true/, :expression do |i|
|
45
|
+
[ :BOOL, true ]
|
46
|
+
end
|
47
|
+
|
48
|
+
rule /false/, :expression do |i|
|
49
|
+
[ :BOOL, false ]
|
50
|
+
end
|
51
|
+
|
52
|
+
rule /\!/, :expression do
|
53
|
+
push_state :comment
|
54
|
+
:BANG
|
55
|
+
end
|
56
|
+
|
57
|
+
rule /([^}}]*)/, :comment do |comment|
|
58
|
+
pop_state
|
59
|
+
[ :COMMENT, comment ]
|
60
|
+
end
|
61
|
+
|
62
|
+
rule /else/, :expression do
|
63
|
+
:ELSE
|
64
|
+
end
|
65
|
+
|
66
|
+
rule /([A-Za-z]\w*)/, :expression do |name|
|
67
|
+
[ :IDENT, name ]
|
68
|
+
end
|
69
|
+
|
70
|
+
rule /\./, :expression do
|
71
|
+
:DOT
|
72
|
+
end
|
73
|
+
|
74
|
+
rule /\=/, :expression do
|
75
|
+
:EQ
|
76
|
+
end
|
77
|
+
|
78
|
+
rule /"/, :expression do
|
79
|
+
push_state :string
|
80
|
+
end
|
81
|
+
|
82
|
+
rule /(\\"|[^"])*/, :string do |str|
|
83
|
+
[ :STRING, str ]
|
84
|
+
end
|
85
|
+
|
86
|
+
rule /"/, :string do
|
87
|
+
pop_state
|
88
|
+
end
|
89
|
+
|
90
|
+
# Handlebars allows methods with hyphens in them. Ruby doesn't, so
|
91
|
+
# we'll assume you're trying to index the context with the identifier
|
92
|
+
# and call the result.
|
93
|
+
rule /([A-Za-z][a-z0-9_-]*[a-z0-9])/, :expression do |str|
|
94
|
+
[ :LITERAL, str ]
|
95
|
+
end
|
96
|
+
|
97
|
+
rule /\[/, :expression do
|
98
|
+
push_state :segment_literal
|
99
|
+
end
|
100
|
+
|
101
|
+
rule /([^\]]+)/, :segment_literal do |l|
|
102
|
+
[ :LITERAL, l ]
|
103
|
+
end
|
104
|
+
|
105
|
+
rule /]/, :segment_literal do
|
106
|
+
pop_state
|
107
|
+
end
|
108
|
+
|
109
|
+
rule /\s+/, :expression do
|
110
|
+
:WHITE
|
111
|
+
end
|
112
|
+
|
113
|
+
rule /}}}/, :expression do
|
114
|
+
pop_state
|
115
|
+
:TEXPRE
|
116
|
+
end
|
117
|
+
|
118
|
+
rule /}}/, :expression do
|
119
|
+
pop_state
|
120
|
+
:EXPRE
|
121
|
+
end
|
122
|
+
|
123
|
+
rule /.*?(?={{|\z)/m, :default do |output|
|
124
|
+
[ :OUT, output ]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'rltk'
|
2
|
+
require 'rltk/ast'
|
3
|
+
require 'flavour_saver/nodes'
|
4
|
+
|
5
|
+
module FlavourSaver
|
6
|
+
class Node < RLTK::ASTNode
|
7
|
+
def inspect
|
8
|
+
to_s.inspect
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TemplateItemNode < Node; end
|
13
|
+
|
14
|
+
class TemplateNode < Node
|
15
|
+
child :items, [TemplateItemNode]
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
items.map(&:to_s).join ''
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class OutputNode < TemplateItemNode
|
23
|
+
value :value, String
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ValueNode < Node
|
31
|
+
def to_s
|
32
|
+
value.inspect
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
value.inspect
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class StringNode < ValueNode
|
41
|
+
value :value, String
|
42
|
+
end
|
43
|
+
|
44
|
+
class NumberNode < ValueNode
|
45
|
+
value :value, String
|
46
|
+
end
|
47
|
+
|
48
|
+
class BooleanNode < ValueNode
|
49
|
+
end
|
50
|
+
|
51
|
+
class TrueNode < BooleanNode
|
52
|
+
value :value, TrueClass
|
53
|
+
end
|
54
|
+
|
55
|
+
class FalseNode < BooleanNode
|
56
|
+
value :value, FalseClass
|
57
|
+
end
|
58
|
+
|
59
|
+
class CallNode < Node
|
60
|
+
value :name, String
|
61
|
+
value :arguments, Array
|
62
|
+
|
63
|
+
def arguments_to_str(str='')
|
64
|
+
str = str.dup # RLTK magic?
|
65
|
+
arguments.each do |arg|
|
66
|
+
str << ' '
|
67
|
+
if arg.respond_to? :join
|
68
|
+
str << arg.join('.')
|
69
|
+
elsif arg.respond_to? :keys
|
70
|
+
arg.each do |k,v|
|
71
|
+
str << "#{k}: #{v.inspect}"
|
72
|
+
end
|
73
|
+
else
|
74
|
+
str << arg.inspect
|
75
|
+
end
|
76
|
+
end
|
77
|
+
str
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
arguments_to_str(name)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class LocalVarNode < CallNode
|
86
|
+
def to_s
|
87
|
+
arguments_to_str("@#{name}")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class LiteralCallNode < CallNode
|
92
|
+
def to_s
|
93
|
+
arguments_to_str("[#{name.inspect}]")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class ParentCallNode < CallNode
|
98
|
+
value :depth, Fixnum
|
99
|
+
|
100
|
+
def to_callnode
|
101
|
+
CallNode.new(name,arguments)
|
102
|
+
end
|
103
|
+
def to_s
|
104
|
+
"#{'../' * depth}#{super}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class ExpressionNode < TemplateItemNode
|
109
|
+
child :method, [CallNode]
|
110
|
+
def to_s
|
111
|
+
"{{#{method.map(&:to_s).join '.'}}}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class BlockExpressionNode < ExpressionNode
|
116
|
+
child :contents, TemplateNode
|
117
|
+
child :closer, CallNode
|
118
|
+
|
119
|
+
def name
|
120
|
+
method.first.name
|
121
|
+
end
|
122
|
+
|
123
|
+
def to_s
|
124
|
+
"{{##{method.map(&:to_s).join ''}}}#{contents.to_s}{{/#{closer.name}}}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def inspect
|
128
|
+
r = "{{##{method.map(&:to_s).join ''}}}\n"
|
129
|
+
r << " "
|
130
|
+
r << contents.inspect.split("\n").join("\n ")
|
131
|
+
r
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class BlockExpressionNodeWithElse < BlockExpressionNode
|
136
|
+
child :alternate, TemplateNode
|
137
|
+
|
138
|
+
def to_s
|
139
|
+
"{{##{method.map(&:to_s).join ''}}}#{contents.to_s}{{else}}#{alternate.to_s}{{/#{closer.name}}}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def inspect
|
143
|
+
r = "{{##{method.map(&:to_s).join ''}}}\n"
|
144
|
+
r << contents.inspect.split("\n").join("\n ")
|
145
|
+
r << "\n {{else}}\n"
|
146
|
+
r << alternate.inspect.split("\n").join("\n ")
|
147
|
+
r
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class SafeExpressionNode < ExpressionNode
|
152
|
+
def to_s
|
153
|
+
"{{{#{method.map(&:to_s).join '.'}}}}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class CommentNode < TemplateItemNode
|
158
|
+
value :comment, String
|
159
|
+
def to_s
|
160
|
+
"{{! #{comment.strip}}}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class PartialNode < TemplateItemNode
|
165
|
+
value :name, String
|
166
|
+
child :context_call, [CallNode]
|
167
|
+
child :context_value, ValueNode
|
168
|
+
|
169
|
+
def context
|
170
|
+
context_call.any? ? context_call : context_value
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
"{{>#{name}}}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|