locomotivecms_solid 0.2.1
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.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +152 -0
- data/Rakefile +7 -0
- data/lib/locomotivecms_solid.rb +2 -0
- data/lib/solid/arguments.rb +28 -0
- data/lib/solid/block.rb +13 -0
- data/lib/solid/conditional_block.rb +35 -0
- data/lib/solid/context_error.rb +2 -0
- data/lib/solid/default_security_rules.rb +24 -0
- data/lib/solid/element.rb +51 -0
- data/lib/solid/engine.rb +4 -0
- data/lib/solid/extensions.rb +17 -0
- data/lib/solid/iterable.rb +18 -0
- data/lib/solid/liquid_extensions/assign_tag.rb +21 -0
- data/lib/solid/liquid_extensions/for_tag.rb +102 -0
- data/lib/solid/liquid_extensions/if_tag.rb +44 -0
- data/lib/solid/liquid_extensions/unless_tag.rb +13 -0
- data/lib/solid/liquid_extensions/variable.rb +34 -0
- data/lib/solid/liquid_extensions.rb +87 -0
- data/lib/solid/method_whitelist.rb +56 -0
- data/lib/solid/model_drop.rb +119 -0
- data/lib/solid/parser.rb +265 -0
- data/lib/solid/tag.rb +11 -0
- data/lib/solid/template.rb +24 -0
- data/lib/solid/version.rb +3 -0
- data/lib/solid.rb +48 -0
- data/locomotivecms_solid.gemspec +25 -0
- data/spec/solid/arguments_spec.rb +310 -0
- data/spec/solid/block_spec.rb +39 -0
- data/spec/solid/conditional_block_spec.rb +39 -0
- data/spec/solid/default_security_rules_spec.rb +180 -0
- data/spec/solid/element_examples.rb +67 -0
- data/spec/solid/liquid_extensions/assign_tag_spec.rb +27 -0
- data/spec/solid/liquid_extensions/for_tag_spec.rb +48 -0
- data/spec/solid/liquid_extensions/if_tag_spec.rb +64 -0
- data/spec/solid/liquid_extensions/unless_tag_spec.rb +54 -0
- data/spec/solid/liquid_extensions/variable_spec.rb +25 -0
- data/spec/solid/model_drop_spec.rb +26 -0
- data/spec/solid/tag_spec.rb +26 -0
- data/spec/solid/template_spec.rb +37 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/class_highjacker_examples.rb +33 -0
- data/spec/support/method_whitelist_matchers.rb +17 -0
- data/spec/support/tag_highjacker_examples.rb +33 -0
- metadata +201 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
|
|
3
|
+
class << self
|
|
4
|
+
|
|
5
|
+
def extend_liquid!
|
|
6
|
+
LiquidExtensions.load!
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module LiquidExtensions
|
|
12
|
+
|
|
13
|
+
module ClassHighjacker
|
|
14
|
+
|
|
15
|
+
def load!
|
|
16
|
+
original_class = Liquid.send(:remove_const, demodulized_name)
|
|
17
|
+
original_classes[demodulized_name] = original_class unless original_classes.has_key?(demodulized_name) # avoid loosing reference to original class
|
|
18
|
+
Liquid.const_set(demodulized_name, self)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def unload!
|
|
22
|
+
if original_class = original_classes[demodulized_name]
|
|
23
|
+
Liquid.send(:remove_const, demodulized_name)
|
|
24
|
+
Liquid.const_set(demodulized_name, original_class)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def demodulized_name
|
|
29
|
+
@demodulized_name ||= self.name.split('::').last
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
def original_classes
|
|
34
|
+
@@original_classes ||= {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module TagHighjacker
|
|
40
|
+
|
|
41
|
+
def load!
|
|
42
|
+
original_tag = Liquid::Template.tags[tag_name.to_s]
|
|
43
|
+
original_tags[tag_name] = original_tag unless original_tags.has_key?(tag_name) # avoid loosing reference to original class
|
|
44
|
+
Liquid::Template.register_tag(tag_name, self)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def unload!
|
|
48
|
+
Liquid::Template.register_tag(tag_name, original_tags[tag_name])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def tag_name(name=nil)
|
|
52
|
+
@tag_name = name unless name.nil?
|
|
53
|
+
@tag_name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
protected
|
|
57
|
+
def original_tags
|
|
58
|
+
@@original_tags ||= {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'liquid_extensions')
|
|
64
|
+
|
|
65
|
+
# FIXME: Keep the original ForTag from being modified.
|
|
66
|
+
%w(if_tag unless_tag assign_tag variable).each do |mod|
|
|
67
|
+
require File.join(BASE_PATH, mod)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# FIXME: Keep the original ForTag from being modified.
|
|
71
|
+
ALL = [IfTag, UnlessTag, AssignTag, Variable]
|
|
72
|
+
|
|
73
|
+
class << self
|
|
74
|
+
|
|
75
|
+
def load!
|
|
76
|
+
ALL.each(&:load!)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def unload!
|
|
80
|
+
ALL.each(&:unload!)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
module MethodWhitelist
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
METHODS_WHITELIST = {}
|
|
6
|
+
METHODS_BLACKLIST = {}
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
|
|
10
|
+
def allow(rules)
|
|
11
|
+
rules.each do |owner, method_names|
|
|
12
|
+
list = METHODS_WHITELIST[owner] ||= Set.new
|
|
13
|
+
[method_names].flatten.each do |method_name|
|
|
14
|
+
list.add(method_name.to_sym)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def deny(rules)
|
|
21
|
+
rules.each do |owner, method_names|
|
|
22
|
+
list = METHODS_BLACKLIST[owner] ||= Set.new
|
|
23
|
+
[method_names].flatten.each do |method_name|
|
|
24
|
+
list.add(method_name.to_sym)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def safely_respond_to?(object, method)
|
|
33
|
+
return false unless object.respond_to?(method, false)
|
|
34
|
+
method = object.method(method)
|
|
35
|
+
(!inherited?(object, method) || whitelisted?(method)) && !blacklisted?(method)
|
|
36
|
+
end
|
|
37
|
+
module_function :safely_respond_to?
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def whitelisted?(method)
|
|
42
|
+
METHODS_WHITELIST.has_key?(method.owner) && METHODS_WHITELIST[method.owner].include?(method.name)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def blacklisted?(method)
|
|
46
|
+
METHODS_BLACKLIST.has_key?(method.owner) && METHODS_BLACKLIST[method.owner].include?(method.name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def inherited?(object, method)
|
|
50
|
+
method.owner != object.class && !object.methods(false).include?(method.name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'default_security_rules')
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
class Solid::ModelDrop < Liquid::Drop
|
|
2
|
+
|
|
3
|
+
module ModelExtension
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
module ClassMethods
|
|
7
|
+
|
|
8
|
+
def to_drop
|
|
9
|
+
"#{self.name}Drop".constantize.new(current_scope || self)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class_attribute :dynamic_methods
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
|
|
20
|
+
def model(model_name=nil)
|
|
21
|
+
if model_name
|
|
22
|
+
@model_name = model_name
|
|
23
|
+
else
|
|
24
|
+
@model_name ||= self.name.gsub(/Drop$/, '')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def model_class
|
|
29
|
+
@model_class ||= self.model.to_s.camelize.constantize
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def immutable_method(method_name)
|
|
33
|
+
self.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
|
34
|
+
def #{method_name}_with_immutation(*args, &block)
|
|
35
|
+
self.dup.tap do |clone|
|
|
36
|
+
clone.#{method_name}_without_immutation(*args, &block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
END_EVAL
|
|
40
|
+
self.alias_method_chain method_name, :immutation
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def respond(options={})
|
|
44
|
+
raise ArgumentError.new(":to option should be a Regexp") unless options[:to].is_a?(Regexp)
|
|
45
|
+
raise ArgumentError.new(":with option is mandatory") unless options[:with].present?
|
|
46
|
+
self.dynamic_methods ||= []
|
|
47
|
+
self.dynamic_methods += [[options[:to], options[:with]]]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def allow_scopes(*scopes)
|
|
51
|
+
@allowed_scopes = scopes
|
|
52
|
+
scopes.each do |scope_name|
|
|
53
|
+
self.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
|
54
|
+
def #{scope_name}(*args)
|
|
55
|
+
@scope = scope.public_send(:#{scope_name}, *args)
|
|
56
|
+
end
|
|
57
|
+
END_EVAL
|
|
58
|
+
self.immutable_method(scope_name)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
delegate :model_class, :to => 'self.class'
|
|
65
|
+
|
|
66
|
+
respond :to => /limited_to_(\d+)/, :with => :limit_to
|
|
67
|
+
|
|
68
|
+
def initialize(base_scope=nil, context=nil)
|
|
69
|
+
@scope = base_scope
|
|
70
|
+
@context ||= context
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def all
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def each(&block)
|
|
78
|
+
scope.each(&block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def before_method(method_name, *args)
|
|
82
|
+
self.class.dynamic_methods.each do |pattern, method|
|
|
83
|
+
if match_data = pattern.match(method_name)
|
|
84
|
+
return self.send(method, *match_data[1..-1])
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
raise NoMethodError.new("undefined method `#{method_name}' for #{self.inspect}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
delegate :to_a, to: :each
|
|
91
|
+
delegate *(Array.public_instance_methods - self.public_instance_methods), to: :to_a
|
|
92
|
+
|
|
93
|
+
protected
|
|
94
|
+
|
|
95
|
+
def limit_to(size)
|
|
96
|
+
@scope = scope.limit(size.to_i)
|
|
97
|
+
end
|
|
98
|
+
immutable_method :limit_to
|
|
99
|
+
|
|
100
|
+
def scope
|
|
101
|
+
@scope ||= default_scope
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def default_scope
|
|
105
|
+
model_class
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def context
|
|
109
|
+
@context
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
if Rails.env.test? # Just for cleaner and simpler specs
|
|
115
|
+
def method_missing(name, *args, &block)
|
|
116
|
+
before_method(name.to_s)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
data/lib/solid/parser.rb
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
require 'ripper'
|
|
3
|
+
|
|
4
|
+
class Solid::Parser
|
|
5
|
+
|
|
6
|
+
class ContextVariable < Struct.new(:name)
|
|
7
|
+
def evaluate(context)
|
|
8
|
+
Solid.to_liquid(context[name], context)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Literal < Struct.new(:value)
|
|
13
|
+
def evaluate(context)
|
|
14
|
+
Solid.to_liquid(value, context)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class LiteralArray < Literal
|
|
19
|
+
def evaluate(context)
|
|
20
|
+
value.map{ |v| v.evaluate(context) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class LiteralRange < Struct.new(:start_value, :end_value, :exclusive)
|
|
25
|
+
def evaluate(context)
|
|
26
|
+
Range.new(start_value.evaluate(context), end_value.evaluate(context), exclusive)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class LiteralHash < Literal
|
|
31
|
+
def evaluate(context)
|
|
32
|
+
Hash[value.map{ |k, v| [k.evaluate(context), v.evaluate(context)] }]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class MethodCall < Struct.new(:receiver, :name, :arguments)
|
|
37
|
+
include Solid::MethodWhitelist
|
|
38
|
+
BUILTIN_HANDLERS = {
|
|
39
|
+
:'&&' => ->(left, right) { left && right },
|
|
40
|
+
:'||' => ->(left, right) { left || right }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def evaluate(context)
|
|
44
|
+
Solid.to_liquid(pluck(receiver.evaluate(context), name, *arguments.map {|arg| arg.evaluate(context) }), context)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
def pluck(object, method, *args)
|
|
50
|
+
if BUILTIN_HANDLERS.has_key?(method)
|
|
51
|
+
BUILTIN_HANDLERS[method].call(object, *args)
|
|
52
|
+
elsif safely_respond_to?(object, method)
|
|
53
|
+
object.public_send(method, *args)
|
|
54
|
+
elsif object.respond_to?(:[]) && args.empty?
|
|
55
|
+
object[method]
|
|
56
|
+
elsif object.respond_to?(:before_method)
|
|
57
|
+
object.before_method(method, *args)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
|
|
65
|
+
def parse(string)
|
|
66
|
+
new(string).parse
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def initialize(string)
|
|
72
|
+
@string = string
|
|
73
|
+
@sexp = nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def parse
|
|
77
|
+
@sexp = Ripper.sexp(@string)
|
|
78
|
+
dive_in or raise 'Ripper changed?'
|
|
79
|
+
parse_one(@sexp)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Looks for a structure like
|
|
83
|
+
# [:program, [[:array, [#stuff#]]]] or
|
|
84
|
+
# [:program, [[:array, nil]]]
|
|
85
|
+
def dive_in
|
|
86
|
+
@sexp = @sexp[1]
|
|
87
|
+
@sexp = @sexp.first
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse_one(argument)
|
|
91
|
+
type = argument.shift
|
|
92
|
+
handler = "handle_#{type.to_s.sub('@', '')}"
|
|
93
|
+
raise Solid::SyntaxError, "unknown Ripper type: #{type.inspect}" unless respond_to?(handler)
|
|
94
|
+
public_send handler, *argument
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# # spam
|
|
98
|
+
# [:@ident, "spam", [1, 33]] or
|
|
99
|
+
# # true
|
|
100
|
+
# [:@kw, "true", [1, 23]]
|
|
101
|
+
def handle_var_ref(var_ref)
|
|
102
|
+
parse_one(var_ref)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# # foo: 42
|
|
106
|
+
# [[:assoc_new, [:@label, "foo:", [1, 1]], [:@int, "42", [1, 5]]]]
|
|
107
|
+
def handle_bare_assoc_hash(assoc_hash)
|
|
108
|
+
LiteralHash.new assoc_hash.map {|(_, *key_value)| key_value.map(&method(:parse_one)) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# # {foo: 42}
|
|
112
|
+
# [:assoclist_from_args, [[:assoc_new, [:@label, "foo:", [1, 2]], [:@int, "42", [1, 7]]]]]
|
|
113
|
+
def handle_hash(hash)
|
|
114
|
+
handle_bare_assoc_hash(hash.last)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# # myvar.length
|
|
118
|
+
#
|
|
119
|
+
# [:var_ref, [:@ident, "myvar", [1, 1]]]
|
|
120
|
+
# :"."
|
|
121
|
+
# [:@ident, "length", [1, 7]]
|
|
122
|
+
def handle_call(receiver_sexp, method_call, method_sexp)
|
|
123
|
+
receiver = parse_one(receiver_sexp)
|
|
124
|
+
method = method_sexp[1]
|
|
125
|
+
MethodCall.new receiver, method, []
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# # myvar
|
|
129
|
+
#
|
|
130
|
+
# since 1.9.3
|
|
131
|
+
# [:vcall, [:@ident, "myvar", [1, 0]]]
|
|
132
|
+
def handle_vcall(expression)
|
|
133
|
+
parse_one(expression)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# # myvar.split(',', 2)
|
|
137
|
+
#
|
|
138
|
+
# [:call, [:var_ref, [:@ident, "myvar", [1, 1]]], :".", [:@ident, "split", [1, 7]]]
|
|
139
|
+
# [:arg_paren, [:args_add_block, [
|
|
140
|
+
# [:string_literal, [:string_content, [:@tstring_content, ",", [1, 14]]]],
|
|
141
|
+
# [:@int, "2", [1, 18]]
|
|
142
|
+
# ], false]]
|
|
143
|
+
def handle_method_add_arg(call_sexp, args_sexp)
|
|
144
|
+
method_call = parse_one(call_sexp)
|
|
145
|
+
method_call.arguments = method_call_args(args_sexp)
|
|
146
|
+
method_call
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# # args list: (',', 2)
|
|
150
|
+
# [:arg_paren, [:args_add_block, [
|
|
151
|
+
# [:string_literal, [:string_content, [:@tstring_content, ",", [1, 14]]]],
|
|
152
|
+
# [:@int, "2", [1, 18]]
|
|
153
|
+
# ], false]]
|
|
154
|
+
#
|
|
155
|
+
# 1 args list: ()
|
|
156
|
+
# [:arg_paren, nil]
|
|
157
|
+
def method_call_args(args_sexp)
|
|
158
|
+
return [] if args_sexp[1].nil?
|
|
159
|
+
args_sexp = args_sexp.last[1]
|
|
160
|
+
args_sexp.map(&method(:parse_one))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# # !true
|
|
164
|
+
# [:!, [:var_ref, [:@kw, "true", [1, 1]]]]
|
|
165
|
+
def handle_unary(operator, operand)
|
|
166
|
+
MethodCall.new(parse_one(operand), operator, [])
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# # 1 + 2
|
|
170
|
+
# [:@int, "1", [1, 0]], :*, [:@int, "2", [1, 4]]
|
|
171
|
+
def handle_binary(left_operand, operator, right_operand)
|
|
172
|
+
receiver = parse_one(left_operand)
|
|
173
|
+
MethodCall.new(receiver, operator, [parse_one(right_operand)])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# # [1]
|
|
177
|
+
# [[:@int, "1", [1, 1]]
|
|
178
|
+
def handle_array(array)
|
|
179
|
+
LiteralArray.new((array || []).map(&method(:parse_one)))
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# # (1)
|
|
183
|
+
# [[:@int, "42", [1, 2]]]
|
|
184
|
+
def handle_paren(content)
|
|
185
|
+
parse_one(content.first)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# # 1..10
|
|
189
|
+
# [[:@int, "1", [1, 0]], [:@int, "10", [1, 4]]]
|
|
190
|
+
def handle_dot2(start_value, end_value)
|
|
191
|
+
LiteralRange.new(parse_one(start_value), parse_one(end_value), false)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# # 1...10
|
|
195
|
+
# [[:@int, "1", [1, 0]], [:@int, "10", [1, 4]]]
|
|
196
|
+
def handle_dot3(start_value, end_value)
|
|
197
|
+
LiteralRange.new(parse_one(start_value), parse_one(end_value), true)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# # 'mystring'
|
|
201
|
+
# [:string_content, [:@tstring_content, "mystring", [1, 14]]]
|
|
202
|
+
# TODO: handle string interpolation
|
|
203
|
+
def handle_string_literal(string_content)
|
|
204
|
+
Literal.new(parse_one(string_content))
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def handle_string_content(*parts)
|
|
208
|
+
parts.map(&method(:parse_one)).join
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# [:@tstring_content, "mystring", [1, 14]]
|
|
212
|
+
def handle_tstring_content(string_content, lineno_column)
|
|
213
|
+
string_content
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# # /bb|[^b]{2}/
|
|
217
|
+
# [[:@tstring_content, "bb|[^b]{2}", [1, 2]]].first.first
|
|
218
|
+
# TODO: handle regexp interpolation
|
|
219
|
+
def handle_regexp_literal(regexp_literal, lineno_column)
|
|
220
|
+
Literal.new Regexp.new(regexp_literal.first[1])
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
KEYWORDS = {
|
|
224
|
+
'true' => Literal.new(true),
|
|
225
|
+
'false' => Literal.new(false),
|
|
226
|
+
'nil' => Literal.new(nil),
|
|
227
|
+
}
|
|
228
|
+
# # true
|
|
229
|
+
# "true", [1, 33]
|
|
230
|
+
def handle_kw(keyword, lineno_column)
|
|
231
|
+
raise Solid::SyntaxError, 'unknown Ripper sexp' unless KEYWORDS.has_key? keyword
|
|
232
|
+
KEYWORDS[keyword]
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# # spam
|
|
236
|
+
# "spam", [1, 23]
|
|
237
|
+
def handle_ident(identifier, lineno_column)
|
|
238
|
+
ContextVariable.new identifier
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# # Spam
|
|
242
|
+
# "Spam", [1, 23]
|
|
243
|
+
def handle_const(constant, lineno_column)
|
|
244
|
+
ContextVariable.new constant
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# # 42
|
|
248
|
+
# "42", [1, 2]
|
|
249
|
+
def handle_int(int, lineno_column)
|
|
250
|
+
Literal.new int.to_i
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# # 4.2
|
|
254
|
+
# "4.2", [1, 2]
|
|
255
|
+
def handle_float(float, lineno_column)
|
|
256
|
+
Literal.new float.to_f
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# # foo:
|
|
260
|
+
# "foo:", [1, 2]
|
|
261
|
+
def handle_label(label, lineno_column)
|
|
262
|
+
Literal.new label[0..-2].to_sym
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
end
|
data/lib/solid/tag.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
|
|
3
|
+
class Solid::Template < Liquid::Template
|
|
4
|
+
extend Forwardable
|
|
5
|
+
include Solid::Iterable
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
def parse(source)
|
|
10
|
+
template = Solid::Template.new
|
|
11
|
+
template.parse(source)
|
|
12
|
+
template
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def_delegators :root, :nodelist
|
|
18
|
+
|
|
19
|
+
# Avoid issues with ActiveSupport::Cache which freeze all objects passed to it like an ass
|
|
20
|
+
# And anyway once frozen Liquid::Templates are unable to render anything
|
|
21
|
+
def freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
data/lib/solid.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'liquid'
|
|
2
|
+
|
|
3
|
+
module Solid
|
|
4
|
+
BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'solid')
|
|
5
|
+
|
|
6
|
+
require File.join(BASE_PATH, 'extensions')
|
|
7
|
+
|
|
8
|
+
autoload :Argument, File.join(BASE_PATH, 'argument')
|
|
9
|
+
autoload :Arguments, File.join(BASE_PATH, 'arguments')
|
|
10
|
+
autoload :Block, File.join(BASE_PATH, 'block')
|
|
11
|
+
autoload :ConditionalBlock, File.join(BASE_PATH, 'conditional_block')
|
|
12
|
+
autoload :ContextError, File.join(BASE_PATH, 'context_error')
|
|
13
|
+
autoload :Element, File.join(BASE_PATH, 'element')
|
|
14
|
+
autoload :Iterable, File.join(BASE_PATH, 'iterable')
|
|
15
|
+
autoload :MethodWhitelist, File.join(BASE_PATH, 'method_whitelist')
|
|
16
|
+
autoload :Parser, File.join(BASE_PATH, 'parser')
|
|
17
|
+
autoload :Tag, File.join(BASE_PATH, 'tag')
|
|
18
|
+
autoload :Template, File.join(BASE_PATH, 'template')
|
|
19
|
+
autoload :VERSION, File.join(BASE_PATH, 'version')
|
|
20
|
+
|
|
21
|
+
if defined?(Rails) # Rails only features
|
|
22
|
+
autoload :ModelDrop, File.join(BASE_PATH, 'model_drop')
|
|
23
|
+
require File.join(BASE_PATH, 'engine')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require File.join(BASE_PATH, 'liquid_extensions')
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
|
|
30
|
+
def unproxify(object)
|
|
31
|
+
class_name = object.class.name
|
|
32
|
+
if class_name && class_name.end_with?('::LiquidDropClass')
|
|
33
|
+
return object.instance_variable_get('@object')
|
|
34
|
+
end
|
|
35
|
+
object
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_liquid(object, context)
|
|
39
|
+
object = object.to_liquid
|
|
40
|
+
object.context = context if object.respond_to?(:context=)
|
|
41
|
+
object
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
SyntaxError = Class.new(Liquid::SyntaxError)
|
|
47
|
+
|
|
48
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "solid/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "locomotivecms_solid"
|
|
7
|
+
s.version = Solid::VERSION
|
|
8
|
+
s.authors = ["Jean Boussier", "Yannick François", "Didier Lafforgue"]
|
|
9
|
+
s.email = ["jean.boussier@tigerlilyapps.com", "yannick@tigerlilyapps.com", "didier.lafforgue@gmail.com"]
|
|
10
|
+
s.homepage = ""
|
|
11
|
+
s.summary = %q{Helpers for easily creating custom Liquid tags and block}
|
|
12
|
+
s.description = %q{The Solid gem from the TigerLily team but modified to work with LocomotiveCMS}
|
|
13
|
+
|
|
14
|
+
s.files = `git ls-files`.split("\n")
|
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
17
|
+
s.require_paths = ["lib"]
|
|
18
|
+
|
|
19
|
+
# specify any dependencies here; for example:
|
|
20
|
+
s.add_development_dependency "rspec"
|
|
21
|
+
s.add_development_dependency "rake"
|
|
22
|
+
s.add_development_dependency "i18n"
|
|
23
|
+
s.add_development_dependency "activesupport", "~> 3"
|
|
24
|
+
s.add_runtime_dependency "locomotive_liquid", '~> 2.4.2'
|
|
25
|
+
end
|