flavour_saver 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +81 -0
  6. data/Guardfile +12 -0
  7. data/LICENSE +22 -0
  8. data/README.md +339 -0
  9. data/Rakefile +2 -0
  10. data/flavour_saver.gemspec +28 -0
  11. data/lib/flavour_saver/helpers.rb +118 -0
  12. data/lib/flavour_saver/lexer.rb +127 -0
  13. data/lib/flavour_saver/nodes.rb +177 -0
  14. data/lib/flavour_saver/parser.rb +183 -0
  15. data/lib/flavour_saver/partial.rb +29 -0
  16. data/lib/flavour_saver/rails_partial.rb +10 -0
  17. data/lib/flavour_saver/runtime.rb +269 -0
  18. data/lib/flavour_saver/template.rb +19 -0
  19. data/lib/flavour_saver/version.rb +3 -0
  20. data/lib/flavour_saver.rb +78 -0
  21. data/spec/acceptance/backtrack_spec.rb +14 -0
  22. data/spec/acceptance/comment_spec.rb +12 -0
  23. data/spec/acceptance/custom_block_helper_spec.rb +35 -0
  24. data/spec/acceptance/custom_helper_spec.rb +15 -0
  25. data/spec/acceptance/ensure_no_rce_spec.rb +26 -0
  26. data/spec/acceptance/handlebars_qunit_spec.rb +911 -0
  27. data/spec/acceptance/if_else_spec.rb +17 -0
  28. data/spec/acceptance/multi_level_with_spec.rb +15 -0
  29. data/spec/acceptance/one_character_identifier_spec.rb +13 -0
  30. data/spec/acceptance/runtime_run_spec.rb +27 -0
  31. data/spec/acceptance/sections_spec.rb +25 -0
  32. data/spec/acceptance/segment_literals_spec.rb +26 -0
  33. data/spec/acceptance/simple_expression_spec.rb +13 -0
  34. data/spec/fixtures/backtrack.hbs +4 -0
  35. data/spec/fixtures/comment.hbs +1 -0
  36. data/spec/fixtures/custom_block_helper.hbs +3 -0
  37. data/spec/fixtures/custom_helper.hbs +1 -0
  38. data/spec/fixtures/if_else.hbs +5 -0
  39. data/spec/fixtures/multi_level_if.hbs +12 -0
  40. data/spec/fixtures/multi_level_with.hbs +11 -0
  41. data/spec/fixtures/one_character_identifier.hbs +1 -0
  42. data/spec/fixtures/sections.hbs +9 -0
  43. data/spec/fixtures/simple_expression.hbs +1 -0
  44. data/spec/lib/flavour_saver/lexer_spec.rb +187 -0
  45. data/spec/lib/flavour_saver/parser_spec.rb +277 -0
  46. data/spec/lib/flavour_saver/runtime_spec.rb +190 -0
  47. data/spec/lib/flavour_saver/template_spec.rb +5 -0
  48. metadata +243 -0
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -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