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.
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