tigerlily-solid 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +138 -0
- data/Rakefile +7 -0
- data/app/assets/javascripts/ace/mode-solid.js +203 -0
- data/lib/solid.rb +34 -0
- data/lib/solid/arguments.rb +88 -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/element.rb +56 -0
- data/lib/solid/engine.rb +4 -0
- data/lib/solid/iterable.rb +18 -0
- data/lib/solid/model_drop.rb +118 -0
- data/lib/solid/tag.rb +11 -0
- data/lib/solid/template.rb +24 -0
- data/lib/solid/version.rb +3 -0
- data/solid.gemspec +23 -0
- data/spec/solid/arguments_spec.rb +129 -0
- data/spec/solid/block_spec.rb +39 -0
- data/spec/solid/conditional_block_spec.rb +33 -0
- data/spec/solid/element_examples.rb +64 -0
- data/spec/solid/tag_spec.rb +26 -0
- data/spec/solid/template_spec.rb +37 -0
- data/spec/spec_helper.rb +8 -0
- metadata +115 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2@solid
|
data/Gemfile
ADDED
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.
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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
|
+
});
|
data/lib/solid.rb
ADDED
@@ -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
|
data/lib/solid/block.rb
ADDED
@@ -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,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
|
data/lib/solid/engine.rb
ADDED
@@ -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
|
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/solid.gemspec
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
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
|