flavour_saver 0.3.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 +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
|