locomotivecms-solid 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README.md +152 -0
- data/Rakefile +7 -0
- data/lib/locomotivecms-solid.rb +2 -0
- data/lib/solid.rb +48 -0
- data/lib/solid/arguments.rb +26 -0
- data/lib/solid/block.rb +13 -0
- data/lib/solid/conditional_block.rb +35 -0
- data/lib/solid/context_error.rb +2 -0
- data/lib/solid/default_security_rules.rb +24 -0
- data/lib/solid/element.rb +51 -0
- data/lib/solid/engine.rb +4 -0
- data/lib/solid/extensions.rb +17 -0
- data/lib/solid/iterable.rb +18 -0
- data/lib/solid/liquid_extensions.rb +87 -0
- data/lib/solid/liquid_extensions/assign_tag.rb +21 -0
- data/lib/solid/liquid_extensions/for_tag.rb +102 -0
- data/lib/solid/liquid_extensions/if_tag.rb +44 -0
- data/lib/solid/liquid_extensions/unless_tag.rb +13 -0
- data/lib/solid/liquid_extensions/variable.rb +34 -0
- data/lib/solid/method_whitelist.rb +56 -0
- data/lib/solid/model_drop.rb +119 -0
- data/lib/solid/parser.rb +108 -0
- data/lib/solid/parser/ripper.rb +220 -0
- data/lib/solid/parser/ruby_parser.rb +88 -0
- data/lib/solid/tag.rb +11 -0
- data/lib/solid/template.rb +24 -0
- data/lib/solid/version.rb +3 -0
- data/locomotivecms-solid.gemspec +26 -0
- data/spec/solid/arguments_spec.rb +314 -0
- data/spec/solid/block_spec.rb +39 -0
- data/spec/solid/conditional_block_spec.rb +39 -0
- data/spec/solid/default_security_rules_spec.rb +180 -0
- data/spec/solid/element_examples.rb +67 -0
- data/spec/solid/liquid_extensions/assign_tag_spec.rb +27 -0
- data/spec/solid/liquid_extensions/for_tag_spec.rb +48 -0
- data/spec/solid/liquid_extensions/if_tag_spec.rb +64 -0
- data/spec/solid/liquid_extensions/unless_tag_spec.rb +54 -0
- data/spec/solid/liquid_extensions/variable_spec.rb +25 -0
- data/spec/solid/model_drop_spec.rb +26 -0
- data/spec/solid/parser/ripper_spec.rb +14 -0
- data/spec/solid/parser/ruby_parser_spec.rb +7 -0
- data/spec/solid/tag_spec.rb +26 -0
- data/spec/solid/template_spec.rb +37 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/class_highjacker_examples.rb +33 -0
- data/spec/support/method_whitelist_matchers.rb +17 -0
- data/spec/support/parser_examples.rb +261 -0
- data/spec/support/tag_highjacker_examples.rb +33 -0
- metadata +204 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'solid'
|
2
|
+
|
3
|
+
shared_examples "a Solid element" do
|
4
|
+
|
5
|
+
describe '.tag_name' do
|
6
|
+
|
7
|
+
it 'should register tag to Liquid with given name' do
|
8
|
+
Liquid::Template.should_receive(:register_tag).with('dummy', described_class)
|
9
|
+
described_class.tag_name 'dummy'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should return previously given name' do
|
13
|
+
Liquid::Template.stub(:register_tag)
|
14
|
+
described_class.tag_name 'dummy'
|
15
|
+
described_class.tag_name.should be == 'dummy'
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '.context_attribute' do
|
21
|
+
|
22
|
+
let(:element) do
|
23
|
+
described_class.context_attribute :current_user
|
24
|
+
Solid::Arguments.stub(:parse).with('ARGUMENTS_STRING')
|
25
|
+
element = described_class.new('name', 'ARGUMENTS_STRING', ['{% endname %}'])
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should define a custom accessor to the rendered context' do
|
29
|
+
element.stub(:current_context => {'current_user' => 'me'})
|
30
|
+
element.current_user.should be == 'me'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should raise a Solid::ContextError if called outside render' do
|
34
|
+
expect{
|
35
|
+
element.current_user
|
36
|
+
}.to raise_error(Solid::ContextError)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#arguments' do
|
42
|
+
|
43
|
+
it 'should instanciate a Solid::Arguments with his arguments_string' do
|
44
|
+
Solid::Arguments.should_receive(:parse).with('ARGUMENTS_STRING')
|
45
|
+
described_class.new('name', 'ARGUMENTS_STRING', ['{% endname %}'])
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should store his Solid:Arguments instance' do
|
49
|
+
element = described_class.new('name', '1', ['{% endname %}'])
|
50
|
+
element.arguments.should be_a(Solid::Arguments)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#display' do
|
56
|
+
|
57
|
+
it 'should force developper to define it in child class' do
|
58
|
+
Solid::Arguments.stub(:parse).with('ARGUMENTS_STRING')
|
59
|
+
element = described_class.new('name', 'ARGUMENTS_STRING', ['{% endname %}'])
|
60
|
+
expect{
|
61
|
+
element.display
|
62
|
+
}.to raise_error(NotImplementedError)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Solid::LiquidExtensions::AssignTag do
|
4
|
+
it_should_behave_like 'a tag highjacker'
|
5
|
+
|
6
|
+
context 'when Liquid::Assign is replaced' do
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
described_class.load!
|
10
|
+
end
|
11
|
+
|
12
|
+
after :each do
|
13
|
+
described_class.unload!
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should allow complex expression inside an assign tag' do
|
17
|
+
template = Solid::Template.parse(%(
|
18
|
+
{% assign included = foo.include?('bar') %}
|
19
|
+
{{ included }}
|
20
|
+
))
|
21
|
+
output = template.render('foo' => ' bar ').strip
|
22
|
+
output.should be == 'true'
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# FIXME: disabled for now
|
4
|
+
|
5
|
+
# describe Solid::LiquidExtensions::ForTag do
|
6
|
+
# it_should_behave_like 'a tag highjacker'
|
7
|
+
|
8
|
+
# context 'when Liquid::For is replaced' do
|
9
|
+
|
10
|
+
# before :each do
|
11
|
+
# described_class.load!
|
12
|
+
# end
|
13
|
+
|
14
|
+
# after :each do
|
15
|
+
# described_class.unload!
|
16
|
+
# end
|
17
|
+
|
18
|
+
# it 'should allow complex expression inside a for tag' do
|
19
|
+
# template = Solid::Template.parse(%(
|
20
|
+
# {% for foo in foos.concat(foos.reverse).flatten %}
|
21
|
+
# {{ foo }}
|
22
|
+
# {% endfor %}
|
23
|
+
# ))
|
24
|
+
# output = template.render('foos' => [1, 2, 3]).gsub(/[^\d]/, '')
|
25
|
+
# output.should be == '123321'
|
26
|
+
# end
|
27
|
+
|
28
|
+
# it 'should still provide the "forloop" object' do
|
29
|
+
# template = Solid::Template.parse(%(
|
30
|
+
# {% for foo in foos %}{{ forloop.first }},{{ forloop.index0 }}/{{ forloop.length }},{{ forloop.last }}|{% endfor %}
|
31
|
+
# ))
|
32
|
+
# output = template.render('foos' => [1, 2, 3]).strip
|
33
|
+
# output.should be == "true,0/3,false|false,1/3,false|false,2/3,true|"
|
34
|
+
# end
|
35
|
+
|
36
|
+
# it 'should consider all non iterable objects as empty arrays' do
|
37
|
+
# template = Solid::Template.parse(%(
|
38
|
+
# {% for foo in foos %}
|
39
|
+
# {{ foo }}
|
40
|
+
# {% endfor %}
|
41
|
+
# ))
|
42
|
+
# output = template.render('foos' => nil).strip
|
43
|
+
# output.should be == ''
|
44
|
+
# end
|
45
|
+
|
46
|
+
# end
|
47
|
+
|
48
|
+
# end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Solid::LiquidExtensions::IfTag do
|
4
|
+
it_should_behave_like 'a tag highjacker'
|
5
|
+
|
6
|
+
context 'when Liquid::If is replaced' do
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
described_class.load!
|
10
|
+
end
|
11
|
+
|
12
|
+
after :each do
|
13
|
+
described_class.unload!
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should allow complex expression inside an if tag' do
|
17
|
+
template = Solid::Template.parse(%(
|
18
|
+
{% if foo.include?('bar') %}
|
19
|
+
Hello !
|
20
|
+
{% endif %}
|
21
|
+
))
|
22
|
+
output = template.render('foo' => ' bar ').strip
|
23
|
+
output.should be == 'Hello !'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should render nothing if the predicate return false' do
|
27
|
+
template = Solid::Template.parse(%(
|
28
|
+
{% if foo.include?('bar') %}
|
29
|
+
Hello !
|
30
|
+
{% endif %}
|
31
|
+
))
|
32
|
+
output = template.render('foo' => ' plop ').strip
|
33
|
+
output.should be == ''
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should still accept an else tag' do
|
37
|
+
template = Solid::Template.parse(%(
|
38
|
+
{% if foo.include?('bar') %}
|
39
|
+
Hello !
|
40
|
+
{% else %}
|
41
|
+
Failed
|
42
|
+
{% endif %}
|
43
|
+
))
|
44
|
+
output = template.render('foo' => ' spam ').strip
|
45
|
+
output.should be == 'Failed'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should still accept some elsif tags' do
|
49
|
+
template = Solid::Template.parse(%(
|
50
|
+
{% if foo.include?('bar') %}
|
51
|
+
Hello !
|
52
|
+
{% elsif foo.include?('spam') %}
|
53
|
+
World !
|
54
|
+
{% else %}
|
55
|
+
Failed
|
56
|
+
{% endif %}
|
57
|
+
))
|
58
|
+
output = template.render('foo' => ' spam ').strip
|
59
|
+
output.should be == 'World !'
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Solid::LiquidExtensions::UnlessTag do
|
4
|
+
it_should_behave_like 'a tag highjacker'
|
5
|
+
|
6
|
+
context 'when Liquid::Unless is replaced' do
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
described_class.load!
|
10
|
+
end
|
11
|
+
|
12
|
+
after :each do
|
13
|
+
described_class.unload!
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should allow complex expression inside an unless tag' do
|
17
|
+
template = Solid::Template.parse(%(
|
18
|
+
{% unless foo.include?('bar') %}
|
19
|
+
Hello !
|
20
|
+
{% endunless %}
|
21
|
+
))
|
22
|
+
output = template.render('foo' => ' spam ').strip
|
23
|
+
output.should be == 'Hello !'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should still accept an else tag' do
|
27
|
+
template = Solid::Template.parse(%(
|
28
|
+
{% unless foo.include?('bar') %}
|
29
|
+
Hello !
|
30
|
+
{% else %}
|
31
|
+
Failed
|
32
|
+
{% endunless %}
|
33
|
+
))
|
34
|
+
output = template.render('foo' => ' bar ').strip
|
35
|
+
output.should be == 'Failed'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should still accept some elsif tags' do
|
39
|
+
template = Solid::Template.parse(%(
|
40
|
+
{% unless foo.include?('spam') %}
|
41
|
+
Hello !
|
42
|
+
{% elsif foo.include?('spam') %}
|
43
|
+
World !
|
44
|
+
{% else %}
|
45
|
+
Failed
|
46
|
+
{% endunless %}
|
47
|
+
))
|
48
|
+
output = template.render('foo' => ' spam ').strip
|
49
|
+
output.should be == 'World !'
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Solid::LiquidExtensions::Variable do
|
4
|
+
it_should_behave_like 'a class highjacker'
|
5
|
+
|
6
|
+
context 'real world example' do
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
described_class.load!
|
10
|
+
end
|
11
|
+
|
12
|
+
after :each do
|
13
|
+
described_class.unload!
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:template) { template = Solid::Template.parse('{{ foo.include?("bar") | upcase }}') }
|
17
|
+
|
18
|
+
it 'should allow method call with arguments in variables brackets' do
|
19
|
+
template.render('foo' => 'egg bar spam').should be == 'TRUE'
|
20
|
+
template.render('foo' => '').should be == 'FALSE'
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'ostruct'
|
4
|
+
Rails = OpenStruct.new(env: OpenStruct.new(test?: true))
|
5
|
+
require 'solid/model_drop'
|
6
|
+
|
7
|
+
describe Solid::ModelDrop do
|
8
|
+
describe "array methods" do
|
9
|
+
|
10
|
+
it "work on a drop" do
|
11
|
+
drop = described_class.new([1, 2, 3])
|
12
|
+
drop.reverse.should be == [3, 2, 1]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "go through #each" do
|
16
|
+
klass = Class.new(described_class) do
|
17
|
+
def each
|
18
|
+
super.select(&:odd?)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
drop = klass.new([1, 2, 3])
|
22
|
+
drop.reverse.should be == [3, 1]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
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
@@ -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'), File.join(File.dirname(__FILE__), '/**/*_matchers.rb')].each{ |f| require f }
|
@@ -0,0 +1,33 @@
|
|
1
|
+
shared_examples "a class highjacker" do
|
2
|
+
|
3
|
+
context 'class highjacking' do
|
4
|
+
|
5
|
+
let(:highjacked_class_name) { "Liquid::#{described_class.demodulized_name}" }
|
6
|
+
|
7
|
+
def highjacked_class
|
8
|
+
highjacked_class_name.split('::').inject(Object) { |klass, const_name| klass.const_get(const_name) }
|
9
|
+
end
|
10
|
+
|
11
|
+
after :each do
|
12
|
+
described_class.unload!
|
13
|
+
end
|
14
|
+
|
15
|
+
let!(:original_class_id) { highjacked_class.object_id }
|
16
|
+
|
17
|
+
it 'should be able to replace original class' do
|
18
|
+
expect{
|
19
|
+
described_class.load!
|
20
|
+
}.to change{ highjacked_class.object_id }.from(original_class_id).to(described_class.object_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should be able to restore original class' do
|
24
|
+
described_class.load!
|
25
|
+
expect{
|
26
|
+
described_class.unload!
|
27
|
+
}.to change{ highjacked_class.object_id }.from(described_class.object_id).to(original_class_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|