tigerlily-solid 0.2.0

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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@solid
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in solid.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Tigerlily, http://tigerlilyapps.com/
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,138 @@
1
+ # Solid
2
+
3
+ Solid aim to provide a easier and nicer API to create custom Liquid tags and blocks
4
+
5
+ ## Tags
6
+
7
+ To create a new tag, you just have to:
8
+
9
+ - Extend `Solid::Tag`
10
+ - Define a `display` method
11
+ - Give a `tag_name`
12
+
13
+ ```ruby
14
+ class DummyTag < Solid::Tag
15
+
16
+ tag_name :dummy # register in Liquid under the name of `dummy`
17
+
18
+ def display
19
+ 'dummy !!!'
20
+ end
21
+
22
+ end
23
+ ```
24
+
25
+ ```html
26
+ <p>{% dummy %}<p>
27
+ ```
28
+
29
+ ## Arguments
30
+
31
+ This is the simpliest tag ever but, Solid tags can receive rich arguments:
32
+
33
+ ```ruby
34
+ class TypeOfTag < Solid::Tag
35
+
36
+ tag_name :typeof
37
+
38
+ def display(*values)
39
+ ''.tap do |output|
40
+ values.each do |value|
41
+ output << "<p>Type of #{value} is #{value.class.name}</p>"
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+ ```
48
+
49
+ ```html
50
+ {% capture myvar %}eggspam{% endcapture %}
51
+ {% typeof "foo", 42, 4.2, myvar, myoption:"bar", otheroption:myvar %}
52
+ <!-- produce -->
53
+ <p>Type of "foo" is String</p>
54
+ <p>Type of 42 is Integer</p>
55
+ <p>Type of 4.2 is Float</p>
56
+ <p>Type of "eggspam" is String</p>
57
+ <p>Type of {:myoption=>"bar", :otheroption=>"eggspam"} is Hash</p>
58
+ ```
59
+
60
+ ## Context attributes
61
+
62
+ If there is some "global variables" in your liquid context you can declare that
63
+ your tag need to access it:
64
+
65
+ ```ruby
66
+ class HelloTag < Solid::Tag
67
+
68
+ tag_name :hello
69
+
70
+ context_attribute :current_user
71
+
72
+ def display
73
+ "Hello #{current_user.name} !"
74
+ end
75
+
76
+ end
77
+ ```
78
+
79
+ ```html
80
+ <p>{% hello %}</p>
81
+ <!-- produce -->
82
+ <p>Hello Homer</p>
83
+ ```
84
+ ## Blocks
85
+
86
+ Block are just tags with a body. They perform the same argument parsing.
87
+ To render the block body from it's `display` method you just have to `yield`:
88
+
89
+ ```ruby
90
+ class PBlock < Solid::Block
91
+
92
+ tag_name :p
93
+
94
+ def display(options)
95
+ "<p class='#{options[:class]}'>#{yield}</p>"
96
+ end
97
+
98
+ end
99
+ ```
100
+
101
+ ```html
102
+ {% p class:"content" %}
103
+ It works !
104
+ {% endp %}
105
+ <!-- produce -->
106
+ <p class="content">It works !</p>
107
+ ```
108
+
109
+ Of course you are free to yield once, multiple times or even never.
110
+
111
+ ## Conditional Blocks
112
+
113
+ Conditional blocks are blocks with two bodies. If you yield `true` you will receive the main block
114
+ and if you yield `false` you will receive the else block:
115
+
116
+ ```ruby
117
+ class IfAuthorizedToTag < Solid::ConditionalTag
118
+
119
+ tag_name :if_authorized_to
120
+
121
+ context_attribute :current_user
122
+
123
+ def display(permission)
124
+ yield(current_user.authorized_to?(permission))
125
+ end
126
+
127
+ end
128
+ ```
129
+
130
+ ```html
131
+ {% if_authorized_to "publish" %}
132
+ You are authorized !
133
+ {% else %}
134
+ Get out !
135
+ {% endif_authorized_to %}
136
+ ```
137
+
138
+
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -0,0 +1,203 @@
1
+ define('ace/mode/solid_highlight_rules', function(require, exports, module) {
2
+
3
+ var oop = require("../lib/oop");
4
+ var HtmlHighlightRules = require("ace/mode/html_highlight_rules").HtmlHighlightRules;
5
+ var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
6
+
7
+ var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
8
+ var lang = require("../lib/lang");
9
+
10
+ var SolidVariableHighlightRules = function() {
11
+ this.$rules = {
12
+ "start" : [
13
+ {
14
+ token : "variable.context",
15
+ regex : "[a-z_][a-zA-Z0-9_$]*\\b",
16
+ next : 'filter',
17
+ }, {
18
+ token : "constant.language",
19
+ regex : "[a-zA-Z_][a-zA-Z0-9_$]*\\b",
20
+ next : 'filter',
21
+ }, {
22
+ token : "string", // single line
23
+ regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
24
+ }, {
25
+ token : "string", // single line
26
+ regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
27
+ }, {
28
+ token : "string", // backtick string
29
+ regex : "[`](?:(?:\\\\.)|(?:[^'\\\\]))*?[`]"
30
+ }
31
+ ],
32
+ 'filter' : [
33
+ {
34
+ token : "keyword.operator",
35
+ regex : "\\|",
36
+ next : 'filter'
37
+ }, {
38
+ token : "keyword.operator",
39
+ regex : "\:"
40
+ }, {
41
+ token : "support.function",
42
+ regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
43
+ }, {
44
+ token : "string", // single line
45
+ regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
46
+ }, {
47
+ token : "string", // single line
48
+ regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
49
+ }, {
50
+ token : "string", // backtick string
51
+ regex : "[`](?:(?:\\\\.)|(?:[^'\\\\]))*?[`]"
52
+ }
53
+ ]
54
+ };
55
+ };
56
+
57
+ oop.inherits(SolidVariableHighlightRules, TextHighlightRules);
58
+
59
+ exports.SolidVariableHighlightRules = SolidVariableHighlightRules;
60
+
61
+
62
+ var SolidTagHighlightRules = function() {
63
+
64
+ var builtinConstants = lang.arrayToMap(
65
+ ("true|false|nil").split("|")
66
+ );
67
+ this.$rules = {
68
+ "start" : [
69
+ {
70
+ token : "support.function",
71
+ regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b",
72
+ next : "literals"
73
+ }
74
+ ],
75
+ "literals" : [
76
+ {
77
+ token : "string.regexp",
78
+ regex : "[/](?:(?:\\[(?:\\\\]|[^\\]])+\\])|(?:\\\\/|[^\\]/]))*[/]\\w*\\s*(?=[).,;]|$)"
79
+ }, {
80
+ token : "string", // single line
81
+ regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
82
+ }, {
83
+ token : "string", // single line
84
+ regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
85
+ }, {
86
+ token : "string", // backtick string
87
+ regex : "[`](?:(?:\\\\.)|(?:[^'\\\\]))*?[`]"
88
+ }, {
89
+ token : "text", // namespaces aren't symbols
90
+ regex : "::"
91
+ }, {
92
+ token : "constant.class", // class name
93
+ regex : "[A-Z](?:[a-zA-Z_]|\d)+"
94
+ }, {
95
+ token : "constant.symbol",
96
+ regex : "[a-zA-Z_][a-zA-Z0-9_]*\:"
97
+ }, {
98
+ token : "constant.symbol", // symbol
99
+ regex : "[:](?:[A-Za-z_]|[@$](?=[a-zA-Z0-9_]))[a-zA-Z0-9_]*[!=?]?"
100
+ }, {
101
+ token : "constant.numeric", // hex
102
+ regex : "0[xX][0-9a-fA-F](?:[0-9a-fA-F]|_(?=[0-9a-fA-F]))*\\b"
103
+ },{
104
+ token : "constant.numeric", // float
105
+ regex : "[+-]?\\d(?:\\d|_(?=\\d))*(?:(?:\\.\\d(?:\\d|_(?=\\d))*)?(?:[eE][+-]?\\d+)?)?\\b"
106
+ }, {
107
+ token : "support.method",
108
+ regex : "\\.[a-z_$][a-zA-Z0-9_$]*[\\?\\!]?\\b"
109
+ }, {
110
+ token : "constant.language.boolean",
111
+ regex : "(?:true|false)\\b"
112
+ }, {
113
+ token : function(value) {
114
+ if (builtinConstants.hasOwnProperty(value))
115
+ return "constant.language";
116
+ else
117
+ return "variable.context";
118
+ },
119
+ regex : "[a-z_$][a-zA-Z0-9_$]*\\b"
120
+ }, {
121
+ token : "text",
122
+ regex : "\\s+"
123
+ }
124
+ ]
125
+ };
126
+ };
127
+
128
+ oop.inherits(SolidTagHighlightRules, TextHighlightRules);
129
+
130
+ exports.SolidTagHighlightRules = SolidTagHighlightRules;
131
+
132
+ var SolidHighlightRules = function() {
133
+ // TODO: make it work for scriptembed and cssembed
134
+ this.$rules = new HtmlHighlightRules().getRules();
135
+ this.$rules.start.unshift({
136
+ token: "keyword.operator",
137
+ regex: '{%',
138
+ next: 'solid-tag-start'
139
+ });
140
+
141
+ this.embedRules(SolidTagHighlightRules, "solid-tag-", [
142
+ {
143
+ token: ["keyword.operator", "string"],
144
+ regex: '(%})(")',
145
+ next: "tagembed-attribute-list"
146
+ }, {
147
+ token: "keyword.operator",
148
+ regex: '%}',
149
+ next: "start"
150
+ }
151
+ ]);
152
+
153
+ this.embedRules(SolidVariableHighlightRules, "solid-variable-", [
154
+ {
155
+ token: ["keyword.operator", "string"],
156
+ regex: '(}})(")',
157
+ next: "tagembed-attribute-list"
158
+ }, {
159
+ token: "keyword.operator",
160
+ regex: '}}',
161
+ next: "start"
162
+ }
163
+ ]);
164
+
165
+ this.$rules.start.unshift({
166
+ token: "keyword.operator",
167
+ regex: '{{',
168
+ next: 'solid-variable-start'
169
+ });
170
+
171
+ this.$rules['tagembed-attribute-list'].unshift({
172
+ token: ["string", "keyword.operator"],
173
+ regex: '(")({%)',
174
+ next: 'solid-tag-start'
175
+ });
176
+ this.$rules['tagembed-attribute-list'].unshift({
177
+ token: ["string", "keyword.operator"],
178
+ regex: '(")({{)',
179
+ next: 'solid-variable-start'
180
+ });
181
+
182
+ }
183
+
184
+ oop.inherits(SolidHighlightRules, HtmlHighlightRules);
185
+
186
+ exports.SolidHighlightRules = SolidHighlightRules;
187
+ });
188
+
189
+
190
+ define('ace/mode/solid', function(require, exports, module) {
191
+
192
+ var oop = require("../lib/oop");
193
+ var TextMode = require("ace/mode/text").Mode;
194
+ var Tokenizer = require("ace/tokenizer").Tokenizer;
195
+ var SolidHighlightRules = require("ace/mode/solid_highlight_rules").SolidHighlightRules;
196
+
197
+ var Mode = function() {
198
+ this.$tokenizer = new Tokenizer(new SolidHighlightRules().getRules());
199
+ };
200
+ oop.inherits(Mode, TextMode);
201
+
202
+ exports.Mode = Mode;
203
+ });
@@ -0,0 +1,34 @@
1
+ require 'liquid'
2
+
3
+ module Solid
4
+ BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'solid')
5
+
6
+ autoload :Argument, File.join(BASE_PATH, 'argument')
7
+ autoload :Arguments, File.join(BASE_PATH, 'arguments')
8
+ autoload :Block, File.join(BASE_PATH, 'block')
9
+ autoload :ConditionalBlock, File.join(BASE_PATH, 'conditional_block')
10
+ autoload :ContextError, File.join(BASE_PATH, 'context_error')
11
+ autoload :Element, File.join(BASE_PATH, 'element')
12
+ autoload :Iterable, File.join(BASE_PATH, 'iterable')
13
+ autoload :Tag, File.join(BASE_PATH, 'tag')
14
+ autoload :Template, File.join(BASE_PATH, 'template')
15
+ autoload :VERSION, File.join(BASE_PATH, 'version')
16
+
17
+ if defined?(Rails) # Rails only features
18
+ autoload :ModelDrop, File.join(BASE_PATH, 'model_drop')
19
+ require File.join(BASE_PATH, 'engine')
20
+ end
21
+
22
+ class << self
23
+
24
+ def unproxify(object)
25
+ class_name = object.class.name
26
+ if class_name && class_name.end_with?('::LiquidDropClass')
27
+ return object.instance_variable_get('@object')
28
+ end
29
+ object
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,88 @@
1
+ require 'parsr'
2
+
3
+ class Solid::Arguments
4
+ include Enumerable
5
+
6
+ def self.parse(string)
7
+ new('[%s]' % string).parse!
8
+ end
9
+
10
+ attr_accessor :values
11
+
12
+ def initialize(string)
13
+ @string = string
14
+ end
15
+
16
+ def parse!
17
+ self.values = parser.parse(@string)
18
+ self
19
+ end
20
+
21
+ def each(*args, &block)
22
+ self.values.each(*args, &block)
23
+ end
24
+
25
+ def interpolate(context)
26
+ interpolate_one(self.values, context)
27
+ end
28
+
29
+ protected
30
+
31
+ def interpolate_one(value, context)
32
+ case value
33
+ when Solid::Arguments::ContextVariable
34
+ value.evaluate(context)
35
+ when Array
36
+ value.map{ |v| interpolate_one(v, context) }
37
+ when Hash
38
+ Hash[value.map{ |k, v| [interpolate_one(k, context), interpolate_one(v, context)] }]
39
+ else
40
+ value
41
+ end
42
+ end
43
+
44
+ def parser
45
+ unless defined?(@@parser)
46
+ rules = Parsr::Rules::All.dup
47
+ rules.insert(Parsr::Rules::All.index(Parsr::Rules::Constants) + 1, Solid::Arguments::ContextVariableRule)
48
+ @@parser = Parsr.new(*rules)
49
+ end
50
+ @@parser
51
+ end
52
+
53
+ class ContextVariable < Struct.new(:name)
54
+
55
+ def evaluate(context)
56
+ var, *methods = name.split('.')
57
+ object = context[var]
58
+ object = methods.inject(object) do |obj, method|
59
+ if obj.respond_to?(:public_send)
60
+ obj.public_send(method)
61
+ else # 1.8 fallback
62
+ obj.send(method) if obj.respond_to?(method, false)
63
+ end
64
+ end
65
+
66
+ return Solid.unproxify(object)
67
+ end
68
+
69
+ end
70
+
71
+ module ContextVariableRule
72
+
73
+ PATTERN = /[a-zA-Z_][a-zA-Z\d_\.\!\?]*/
74
+
75
+ class << self
76
+
77
+ def match(scanner)
78
+ if scanner.scan(PATTERN)
79
+ variable = Solid::Arguments::ContextVariable.new(scanner.matched)
80
+ return Parsr::Token.new(variable)
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,13 @@
1
+ class Solid::Block < Liquid::Block
2
+
3
+ include Solid::Element
4
+
5
+ def render(context)
6
+ with_context(context) do
7
+ display(*arguments.interpolate(context)) do
8
+ super
9
+ end
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,35 @@
1
+ class Solid::ConditionalBlock < Liquid::Block
2
+ include Solid::Element
3
+
4
+ def initialize(tag_name, variable, tokens)
5
+ @blocks = []
6
+ push_block!
7
+ super
8
+ end
9
+
10
+ def render(context)
11
+ with_context(context) do
12
+ display(*arguments.interpolate(context)) do |condition_satisfied|
13
+ block = condition_satisfied ? @blocks.first : @blocks.last
14
+ render_all(block, context)
15
+ end
16
+ end
17
+ end
18
+
19
+ def unknown_tag(tag, markup, tokens)
20
+ if tag == 'else'
21
+ push_block!
22
+ else
23
+ super
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def push_block!
30
+ block = []
31
+ @blocks.push(block)
32
+ @nodelist = block
33
+ end
34
+
35
+ end
@@ -0,0 +1,2 @@
1
+ class Solid::ContextError < Exception
2
+ end
@@ -0,0 +1,56 @@
1
+ module Solid::Element
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ base.send(:include, InstanceMethods)
6
+ base.send(:include, Solid::Iterable)
7
+ end
8
+
9
+ module InstanceMethods
10
+
11
+ def initialize(tag_name, arguments_string, tokens)
12
+ super
13
+ @arguments = Solid::Arguments.parse(arguments_string)
14
+ end
15
+
16
+ def arguments
17
+ @arguments
18
+ end
19
+
20
+ def with_context(context)
21
+ previous_context = @current_context
22
+ @current_context = context
23
+ yield
24
+ ensure
25
+ @current_context = previous_context
26
+ end
27
+
28
+ def current_context
29
+ @current_context or raise Solid::ContextError.new("There is currently no context, do you forget to call render ?")
30
+ end
31
+
32
+ def display(*args)
33
+ raise NotImplementedError.new("Solid::Element implementations SHOULD define a #display method")
34
+ end
35
+
36
+ end
37
+
38
+ module ClassMethods
39
+
40
+ def tag_name(value=nil)
41
+ if value
42
+ @tag_name = value
43
+ Liquid::Template.register_tag(value.to_s, self)
44
+ end
45
+ @tag_name
46
+ end
47
+
48
+ def context_attribute(name)
49
+ define_method(name) do
50
+ Solid.unproxify(current_context[name.to_s])
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,4 @@
1
+ module Solid
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,18 @@
1
+ module Solid
2
+ module Iterable
3
+ include Enumerable
4
+
5
+ def each(&block)
6
+ self.walk(&block)
7
+ end
8
+
9
+ protected
10
+ def walk(nodes=nil, &block)
11
+ (nodes || self.nodelist).each do |node|
12
+ yield node
13
+ walk(node.nodelist || [], &block) if node.respond_to?(:nodelist)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,118 @@
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}
55
+ @scope = scope.public_send(:#{scope_name})
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)
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 *(Array.public_instance_methods - self.public_instance_methods), :to => :scope
91
+
92
+ protected
93
+
94
+ def limit_to(size)
95
+ @scope = scope.limit(size)
96
+ end
97
+ immutable_method :limit_to
98
+
99
+ def scope
100
+ @scope ||= default_scope
101
+ end
102
+
103
+ def default_scope
104
+ model_class
105
+ end
106
+
107
+ def context
108
+ @context
109
+ end
110
+
111
+ private
112
+
113
+ if Rails.env.test? # Just for cleaner and simpler specs
114
+ def method_missing(name, *args, &block)
115
+ before_method(name.to_s)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,11 @@
1
+ class Solid::Tag < Liquid::Tag
2
+
3
+ include Solid::Element
4
+
5
+ def render(context)
6
+ with_context(context) do
7
+ display(*arguments.interpolate(context))
8
+ end
9
+ end
10
+
11
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Solid
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,23 @@
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 = "tigerlily-solid"
7
+ s.version = Solid::VERSION
8
+ s.authors = ["Jean Boussier", "Yannick François"]
9
+ s.email = ["jean.boussier@tigerlilyapps.com", "yannick@tigerlilyapps.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Helpers for easily creating custom Liquid tags and block}
12
+ #s.description = %q{TODO: Write a gem description}
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_runtime_dependency "liquid"
22
+ s.add_runtime_dependency "parsr", '0.0.4'
23
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ describe Solid::Arguments do
4
+
5
+ def parse(string, context={})
6
+ Solid::Arguments.parse(string).interpolate(context)
7
+ end
8
+
9
+ context 'with a single argument' do
10
+
11
+ context 'of type string' do
12
+
13
+ it 'can parse a simple string (between simple quotes)' do
14
+ parse("'foobar'").should be == ['foobar']
15
+ end
16
+
17
+ it 'can parse a simple string (between double quotes)' do
18
+ parse('"foobar"').should be == ['foobar']
19
+ end
20
+
21
+ it 'should not consider this string as a context var' do
22
+ parse('"foobar"', {'foobar' => 'plop'}).should_not == ['plop']
23
+ end
24
+
25
+ it 'should not be disturbed by a string containing a comma' do
26
+ parse(%{"foo,bar", 'egg,spam'}).should be == ['foo,bar', 'egg,spam']
27
+ end
28
+
29
+ it 'should not be disturbed by a string containing a simple quote' do
30
+ parse('"foo\'bar"').should be == ["foo'bar"]
31
+ end
32
+
33
+ it 'should not be disturbed by a string containing a double quote' do
34
+ parse("'foo\"bar'").should be == ['foo"bar']
35
+ end
36
+
37
+ end
38
+
39
+ context 'of type integer' do
40
+
41
+ it 'should works' do
42
+ parse('42').should be == [42]
43
+ end
44
+
45
+ end
46
+
47
+ context 'of type float' do
48
+
49
+ it 'should works' do
50
+ parse('4.2').should be == [4.2]
51
+ end
52
+
53
+ end
54
+
55
+ context 'of type boolean' do
56
+
57
+ it 'should works with `true`' do
58
+ parse('true').should be == [true]
59
+ end
60
+
61
+ it 'should works with `false`' do
62
+ parse('false').should be == [false]
63
+ end
64
+
65
+ end
66
+
67
+ context 'of type "context var"' do
68
+
69
+ it 'should works' do
70
+ parse('myvar', {'myvar' => 'myvalue'}).should be == ['myvalue']
71
+ end
72
+
73
+ it 'can call methods without arguments' do
74
+ parse('myvar.length', {'myvar' => ' myvalue '}).should be == [9]
75
+ end
76
+
77
+ it 'can evaluate context var deeply unclosed in collections' do
78
+ parse('[{1 => [{2 => myvar}]}]', {'myvar' => 'myvalue'}).first.should be == [{1 => [{2 => 'myvalue'}]}]
79
+ end
80
+
81
+ it 'can call methods chain without arguments' do
82
+ parse('myvar.strip.length', {'myvar' => ' myvalue '}).should be == [7]
83
+ end
84
+
85
+ it 'can call predicate methods' do
86
+ parse('myvar.empty?', {'myvar' => ' myvalue '}).should be == [false]
87
+ end
88
+
89
+ it 'should manage errors'
90
+
91
+ end
92
+
93
+ context 'of type "named parameter"' do
94
+
95
+ it 'should be able to parse a string' do
96
+ parse('foo:"bar"').should be == [{:foo => 'bar'}]
97
+ end
98
+
99
+ it 'should be able to parse an int' do
100
+ parse('foo:42').should be == [{:foo => 42}]
101
+ end
102
+
103
+ it 'should be able to parse a context var' do
104
+ parse('foo:bar', {'bar' => 'baz'}).should be == [{:foo => 'baz'}]
105
+ end
106
+
107
+ it "should not be disturbed by a comma into a named string" do
108
+ parse('foo:"bar,baz"').should be == [{:foo => 'bar,baz'}]
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+
115
+ context 'with multiple arguments' do
116
+
117
+ it 'should return 3 arguments and an option hash' do
118
+ args = parse('1, "2", myvar, myopt:false', {'myvar' => 4.2})
119
+ args.should be == [1, '2', 4.2, {:myopt => false}]
120
+ end
121
+
122
+ it 'should be tolerent about whitespaces around commas and colons' do
123
+ args = parse(" 1\t, '2' ,myvar, myopt: false", {'myvar' => 4.2})
124
+ args.should be == [1, '2', 4.2, {:myopt => false}]
125
+ end
126
+
127
+ end
128
+
129
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ class DummyBlock < Solid::Block
4
+
5
+ def display(condition)
6
+ if condition
7
+ yield
8
+ else
9
+ 'not_yielded'
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+ describe Solid::Block do
16
+
17
+ it_behaves_like "a Solid element"
18
+
19
+ describe '#display' do
20
+
21
+ let(:tokens) { ["dummy", "{% enddummy %}", "outside"] }
22
+
23
+ subject{ DummyBlock.new('dummy', 'yield', tokens) }
24
+
25
+ it 'yielding should render the block content' do
26
+ subject.render('yield' => true).should be == 'dummy'
27
+ end
28
+
29
+ it 'should only render until the {% endblock %} tag' do
30
+ subject.render('yield' => true).should_not include('outside')
31
+ end
32
+
33
+ it 'should not render its content if it do not yield' do
34
+ subject.render('yield' => false).should_not include('dummy')
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ class IfPresent < Solid::ConditionalBlock
4
+
5
+ def display(string)
6
+ yield(!string.strip.empty?)
7
+ end
8
+
9
+ end
10
+
11
+ describe Solid::ConditionalBlock do
12
+
13
+ it_behaves_like "a Solid element"
14
+
15
+ describe '#display' do
16
+
17
+ let(:tokens) { ["present", "{% else %}", "blank", "{% endifpresent %}"] }
18
+
19
+ subject{ IfPresent.new('ifpresent', 'mystring', tokens) }
20
+
21
+ it 'yielding true should render the main block' do
22
+ context = Liquid::Context.new('mystring' => 'blah')
23
+ subject.render(context).should be == 'present'
24
+ end
25
+
26
+ it 'yielding false should render the `else` block' do
27
+ context = Liquid::Context.new('mystring' => '')
28
+ subject.render(context).should be == 'blank'
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,64 @@
1
+ require 'solid'
2
+ shared_examples "a Solid element" do
3
+
4
+ describe '.tag_name' do
5
+
6
+ it 'should register tag to Liquid with given name' do
7
+ Liquid::Template.should_receive(:register_tag).with('dummy', described_class)
8
+ described_class.tag_name 'dummy'
9
+ end
10
+
11
+ it 'should return previously given name' do
12
+ Liquid::Template.stub(:register_tag)
13
+ described_class.tag_name 'dummy'
14
+ described_class.tag_name.should be == 'dummy'
15
+ end
16
+
17
+ end
18
+
19
+ describe '.context_attribute' do
20
+
21
+ let(:element) do
22
+ described_class.context_attribute :current_user
23
+ element = described_class.new('name', 'ARGUMENTS_STRING', ['{% endname %}'])
24
+ end
25
+
26
+ it 'should define a custom accessor to the rendered context' do
27
+ element.stub(:current_context => {'current_user' => 'me'})
28
+ element.current_user.should be == 'me'
29
+ end
30
+
31
+ it 'should raise a Solid::ContextError if called outside render' do
32
+ expect{
33
+ element.current_user
34
+ }.to raise_error(Solid::ContextError)
35
+ end
36
+
37
+ end
38
+
39
+ describe '#arguments' do
40
+
41
+ it 'should instanciate a Solid::Arguments with his arguments_string' do
42
+ Solid::Arguments.should_receive(:parse).with('ARGUMENTS_STRING')
43
+ described_class.new('name', 'ARGUMENTS_STRING', ['{% endname %}'])
44
+ end
45
+
46
+ it 'should store his Solid:Arguments instance' do
47
+ element = described_class.new('name', 'ARGUMENTS_STRING', ['{% endname %}'])
48
+ element.arguments.should be_a(Solid::Arguments)
49
+ end
50
+
51
+ end
52
+
53
+ describe '#display' do
54
+
55
+ it 'should force developper to define it in child class' do
56
+ element = described_class.new('name', 'ARGUMENTS_STRING', ['{% endname %}'])
57
+ expect{
58
+ element.display
59
+ }.to raise_error(NotImplementedError)
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ class DummyTag < Solid::Tag
4
+
5
+ def display(*args)
6
+ args.inspect
7
+ end
8
+
9
+ end
10
+
11
+ describe Solid::Tag do
12
+
13
+ it_behaves_like "a Solid element"
14
+
15
+ subject{ DummyTag.new('dummy', '1, "foo", myvar, myopts: false', 'token') }
16
+
17
+ it 'should works' do
18
+ subject.render('myvar' => 'bar').should be == '[1, "foo", "bar", {:myopts=>false}]'
19
+ end
20
+
21
+ it 'should send all parsed arguments do #display' do
22
+ subject.should_receive(:display).with(1, 'foo', 'bar', :myopts => false).and_return('result')
23
+ subject.render('myvar' => 'bar').should be == 'result'
24
+ end
25
+
26
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Solid::Template do
4
+
5
+ let(:liquid) { %{first_string{% comment %}
6
+ {% if foo %}ifcontent{% endif %}
7
+ {% if foo %}ifsecondcontent{% endif %}
8
+ {% endcomment %}
9
+ {% unless foo %}unlesscontent{% endunless %}
10
+
11
+ } }
12
+
13
+ let(:template) { Solid::Template.parse(liquid) }
14
+
15
+ specify { subject.should be_an(Enumerable) }
16
+
17
+ describe '#each' do
18
+
19
+ let(:yielded_nodes) do
20
+ [].tap do |nodes|
21
+ template.each{ |node| nodes << node }
22
+ end
23
+ end
24
+
25
+ let(:yielded_classes) { yielded_nodes.map(&:class) }
26
+
27
+ it 'should yield parent nodes before child nodes' do
28
+ yielded_classes.index(Liquid::Comment).should be < yielded_classes.index(Liquid::If)
29
+ end
30
+
31
+ it 'should yield first sibling first (No ! really ? ...)' do
32
+ yielded_classes.index(Liquid::Comment).should be < yielded_classes.index(Liquid::Unless)
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,8 @@
1
+ require 'rspec'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'solid'))
3
+
4
+ RSpec.configure do |c|
5
+ c.mock_with :rspec
6
+ end
7
+
8
+ Dir[File.join(File.dirname(__FILE__), '/**/*_examples.rb')].each{ |f| require f }
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tigerlily-solid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jean Boussier
9
+ - Yannick François
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-01-06 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &70127732609260 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *70127732609260
26
+ - !ruby/object:Gem::Dependency
27
+ name: liquid
28
+ requirement: &70127732608840 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70127732608840
37
+ - !ruby/object:Gem::Dependency
38
+ name: parsr
39
+ requirement: &70127732608340 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - =
43
+ - !ruby/object:Gem::Version
44
+ version: 0.0.4
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70127732608340
48
+ description:
49
+ email:
50
+ - jean.boussier@tigerlilyapps.com
51
+ - yannick@tigerlilyapps.com
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - .gitignore
57
+ - .rspec
58
+ - .rvmrc
59
+ - Gemfile
60
+ - LICENSE
61
+ - README.md
62
+ - Rakefile
63
+ - app/assets/javascripts/ace/mode-solid.js
64
+ - lib/solid.rb
65
+ - lib/solid/arguments.rb
66
+ - lib/solid/block.rb
67
+ - lib/solid/conditional_block.rb
68
+ - lib/solid/context_error.rb
69
+ - lib/solid/element.rb
70
+ - lib/solid/engine.rb
71
+ - lib/solid/iterable.rb
72
+ - lib/solid/model_drop.rb
73
+ - lib/solid/tag.rb
74
+ - lib/solid/template.rb
75
+ - lib/solid/version.rb
76
+ - solid.gemspec
77
+ - spec/solid/arguments_spec.rb
78
+ - spec/solid/block_spec.rb
79
+ - spec/solid/conditional_block_spec.rb
80
+ - spec/solid/element_examples.rb
81
+ - spec/solid/tag_spec.rb
82
+ - spec/solid/template_spec.rb
83
+ - spec/spec_helper.rb
84
+ homepage: ''
85
+ licenses: []
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 1.8.10
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Helpers for easily creating custom Liquid tags and block
108
+ test_files:
109
+ - spec/solid/arguments_spec.rb
110
+ - spec/solid/block_spec.rb
111
+ - spec/solid/conditional_block_spec.rb
112
+ - spec/solid/element_examples.rb
113
+ - spec/solid/tag_spec.rb
114
+ - spec/solid/template_spec.rb
115
+ - spec/spec_helper.rb