locomotivecms-solid 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE +20 -0
  7. data/README.md +152 -0
  8. data/Rakefile +7 -0
  9. data/lib/locomotivecms-solid.rb +2 -0
  10. data/lib/solid.rb +48 -0
  11. data/lib/solid/arguments.rb +26 -0
  12. data/lib/solid/block.rb +13 -0
  13. data/lib/solid/conditional_block.rb +35 -0
  14. data/lib/solid/context_error.rb +2 -0
  15. data/lib/solid/default_security_rules.rb +24 -0
  16. data/lib/solid/element.rb +51 -0
  17. data/lib/solid/engine.rb +4 -0
  18. data/lib/solid/extensions.rb +17 -0
  19. data/lib/solid/iterable.rb +18 -0
  20. data/lib/solid/liquid_extensions.rb +87 -0
  21. data/lib/solid/liquid_extensions/assign_tag.rb +21 -0
  22. data/lib/solid/liquid_extensions/for_tag.rb +102 -0
  23. data/lib/solid/liquid_extensions/if_tag.rb +44 -0
  24. data/lib/solid/liquid_extensions/unless_tag.rb +13 -0
  25. data/lib/solid/liquid_extensions/variable.rb +34 -0
  26. data/lib/solid/method_whitelist.rb +56 -0
  27. data/lib/solid/model_drop.rb +119 -0
  28. data/lib/solid/parser.rb +108 -0
  29. data/lib/solid/parser/ripper.rb +220 -0
  30. data/lib/solid/parser/ruby_parser.rb +88 -0
  31. data/lib/solid/tag.rb +11 -0
  32. data/lib/solid/template.rb +24 -0
  33. data/lib/solid/version.rb +3 -0
  34. data/locomotivecms-solid.gemspec +26 -0
  35. data/spec/solid/arguments_spec.rb +314 -0
  36. data/spec/solid/block_spec.rb +39 -0
  37. data/spec/solid/conditional_block_spec.rb +39 -0
  38. data/spec/solid/default_security_rules_spec.rb +180 -0
  39. data/spec/solid/element_examples.rb +67 -0
  40. data/spec/solid/liquid_extensions/assign_tag_spec.rb +27 -0
  41. data/spec/solid/liquid_extensions/for_tag_spec.rb +48 -0
  42. data/spec/solid/liquid_extensions/if_tag_spec.rb +64 -0
  43. data/spec/solid/liquid_extensions/unless_tag_spec.rb +54 -0
  44. data/spec/solid/liquid_extensions/variable_spec.rb +25 -0
  45. data/spec/solid/model_drop_spec.rb +26 -0
  46. data/spec/solid/parser/ripper_spec.rb +14 -0
  47. data/spec/solid/parser/ruby_parser_spec.rb +7 -0
  48. data/spec/solid/tag_spec.rb +26 -0
  49. data/spec/solid/template_spec.rb +37 -0
  50. data/spec/spec_helper.rb +8 -0
  51. data/spec/support/class_highjacker_examples.rb +33 -0
  52. data/spec/support/method_whitelist_matchers.rb +17 -0
  53. data/spec/support/parser_examples.rb +261 -0
  54. data/spec/support/tag_highjacker_examples.rb +33 -0
  55. 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,14 @@
1
+ require 'spec_helper'
2
+
3
+ ripper_support = true
4
+ begin
5
+ require 'ripper'
6
+ rescue LoadError
7
+ ripper_support = false
8
+ end
9
+
10
+ describe Solid::Parser::Ripper do
11
+
12
+ it_should_behave_like 'a solid parser'
13
+
14
+ end if ripper_support
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Solid::Parser::RubyParser do
4
+
5
+ it_should_behave_like 'a solid parser'
6
+
7
+ 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'), 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