tigerlily-solid 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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