locomotivecms-solid 0.2.2
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.
- 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
|