locomotivecms_solid 0.2.1

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.
Files changed (50) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +9 -0
  5. data/Gemfile +4 -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/arguments.rb +28 -0
  11. data/lib/solid/block.rb +13 -0
  12. data/lib/solid/conditional_block.rb +35 -0
  13. data/lib/solid/context_error.rb +2 -0
  14. data/lib/solid/default_security_rules.rb +24 -0
  15. data/lib/solid/element.rb +51 -0
  16. data/lib/solid/engine.rb +4 -0
  17. data/lib/solid/extensions.rb +17 -0
  18. data/lib/solid/iterable.rb +18 -0
  19. data/lib/solid/liquid_extensions/assign_tag.rb +21 -0
  20. data/lib/solid/liquid_extensions/for_tag.rb +102 -0
  21. data/lib/solid/liquid_extensions/if_tag.rb +44 -0
  22. data/lib/solid/liquid_extensions/unless_tag.rb +13 -0
  23. data/lib/solid/liquid_extensions/variable.rb +34 -0
  24. data/lib/solid/liquid_extensions.rb +87 -0
  25. data/lib/solid/method_whitelist.rb +56 -0
  26. data/lib/solid/model_drop.rb +119 -0
  27. data/lib/solid/parser.rb +265 -0
  28. data/lib/solid/tag.rb +11 -0
  29. data/lib/solid/template.rb +24 -0
  30. data/lib/solid/version.rb +3 -0
  31. data/lib/solid.rb +48 -0
  32. data/locomotivecms_solid.gemspec +25 -0
  33. data/spec/solid/arguments_spec.rb +310 -0
  34. data/spec/solid/block_spec.rb +39 -0
  35. data/spec/solid/conditional_block_spec.rb +39 -0
  36. data/spec/solid/default_security_rules_spec.rb +180 -0
  37. data/spec/solid/element_examples.rb +67 -0
  38. data/spec/solid/liquid_extensions/assign_tag_spec.rb +27 -0
  39. data/spec/solid/liquid_extensions/for_tag_spec.rb +48 -0
  40. data/spec/solid/liquid_extensions/if_tag_spec.rb +64 -0
  41. data/spec/solid/liquid_extensions/unless_tag_spec.rb +54 -0
  42. data/spec/solid/liquid_extensions/variable_spec.rb +25 -0
  43. data/spec/solid/model_drop_spec.rb +26 -0
  44. data/spec/solid/tag_spec.rb +26 -0
  45. data/spec/solid/template_spec.rb +37 -0
  46. data/spec/spec_helper.rb +8 -0
  47. data/spec/support/class_highjacker_examples.rb +33 -0
  48. data/spec/support/method_whitelist_matchers.rb +17 -0
  49. data/spec/support/tag_highjacker_examples.rb +33 -0
  50. metadata +201 -0
data/.gitignore ADDED
@@ -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/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - ruby-head
6
+ - rbx-19mode
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: rbx-19mode
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.
data/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # Solid
2
+
3
+ Solid aim to provide a easier and nicer API to create custom Liquid tags and blocks
4
+
5
+ ## Installation
6
+
7
+ Due to a name conflict the gem is called tigerlily-solid. So to use it:
8
+
9
+ ```
10
+ gem 'tigerlily-solid', :require => 'solid'
11
+ ```
12
+
13
+ ## Build Status [![Build Status](https://secure.travis-ci.org/tigerlily/solid.png)](http://travis-ci.org/tigerlily/solid)
14
+
15
+ ## Tags
16
+
17
+ To create a new tag, you just have to:
18
+
19
+ - Extend `Solid::Tag`
20
+ - Define a `display` method
21
+ - Give a `tag_name`
22
+
23
+ ```ruby
24
+ class DummyTag < Solid::Tag
25
+
26
+ tag_name :dummy # register in Liquid under the name of `dummy`
27
+
28
+ def display
29
+ 'dummy !!!'
30
+ end
31
+
32
+ end
33
+ ```
34
+
35
+ ```html
36
+ <p>{% dummy %}<p>
37
+ ```
38
+
39
+ ## Arguments
40
+
41
+ This is the simpliest tag ever but, Solid tags can receive rich arguments:
42
+
43
+ ```ruby
44
+ class TypeOfTag < Solid::Tag
45
+
46
+ tag_name :typeof
47
+
48
+ def display(*values)
49
+ ''.tap do |output|
50
+ values.each do |value|
51
+ output << "<p>Type of #{value} is #{value.class.name}</p>"
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+ ```
58
+
59
+ ```html
60
+ {% capture myvar %}eggspam{% endcapture %}
61
+ {% typeof "foo", 42, 4.2, myvar, myoption:"bar", otheroption:myvar %}
62
+ <!-- produce -->
63
+ <p>Type of "foo" is String</p>
64
+ <p>Type of 42 is Integer</p>
65
+ <p>Type of 4.2 is Float</p>
66
+ <p>Type of "eggspam" is String</p>
67
+ <p>Type of {:myoption=>"bar", :otheroption=>"eggspam"} is Hash</p>
68
+ ```
69
+
70
+ ## Context attributes
71
+
72
+ If there is some "global variables" in your liquid context you can declare that
73
+ your tag need to access it:
74
+
75
+ ```ruby
76
+ class HelloTag < Solid::Tag
77
+
78
+ tag_name :hello
79
+
80
+ context_attribute :current_user
81
+
82
+ def display
83
+ "Hello #{current_user.name} !"
84
+ end
85
+
86
+ end
87
+ ```
88
+
89
+ ```html
90
+ <p>{% hello %}</p>
91
+ <!-- produce -->
92
+ <p>Hello Homer</p>
93
+ ```
94
+ ## Blocks
95
+
96
+ Block are just tags with a body. They perform the same argument parsing.
97
+ To render the block body from it's `display` method you just have to `yield`:
98
+
99
+ ```ruby
100
+ class PBlock < Solid::Block
101
+
102
+ tag_name :p
103
+
104
+ def display(options)
105
+ "<p class='#{options[:class]}'>#{yield}</p>"
106
+ end
107
+
108
+ end
109
+ ```
110
+
111
+ ```html
112
+ {% p class:"content" %}
113
+ It works !
114
+ {% endp %}
115
+ <!-- produce -->
116
+ <p class="content">It works !</p>
117
+ ```
118
+
119
+ Of course you are free to yield once, multiple times or even never.
120
+
121
+ ## Conditional Blocks
122
+
123
+ Conditional blocks are blocks with two bodies. If you yield `true` you will receive the main block
124
+ and if you yield `false` you will receive the else block:
125
+
126
+ ```ruby
127
+ class IfAuthorizedToTag < Solid::ConditionalBlock
128
+
129
+ tag_name :if_authorized_to
130
+
131
+ context_attribute :current_user
132
+
133
+ def display(permission)
134
+ yield(current_user.authorized_to?(permission))
135
+ end
136
+
137
+ end
138
+ ```
139
+
140
+ ```html
141
+ {% if_authorized_to "publish" %}
142
+ You are authorized !
143
+ {% else %}
144
+ Get out !
145
+ {% endif_authorized_to %}
146
+ ```
147
+
148
+ ## License
149
+
150
+ Solid is released under the MIT license:
151
+
152
+ http://www.opensource.org/licenses/MIT
data/Rakefile ADDED
@@ -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,2 @@
1
+ # Thanks to this file, the gem will be automatically loaded at startup.
2
+ require 'solid'
@@ -0,0 +1,28 @@
1
+ require 'ripper'
2
+
3
+ class Solid::Arguments
4
+ include Enumerable
5
+
6
+ def self.parse(string)
7
+ new("[#{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 = Solid::Parser.parse(@string)
18
+ self
19
+ end
20
+
21
+ def each(*args, &block)
22
+ values.each(*args, &block)
23
+ end
24
+
25
+ def interpolate(context)
26
+ values.evaluate(context)
27
+ end
28
+ 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, context = {})
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[1]
14
+ render_all(block, context) if block
15
+ end
16
+ end
17
+ end
18
+
19
+ def unknown_tag(tag, markup, tokens, context = {})
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,24 @@
1
+ Solid::MethodWhitelist
2
+ .allow(
3
+ BasicObject => [:!, :!=, :==],
4
+ Object => [:present?, :blank?, :in?, :to_json],
5
+ Kernel => [:nil?, :!~],
6
+ Module => [:==],
7
+ Enumerable => [:sort],
8
+ Comparable => [:<, :<=, :==, :>, :>=, :between?],
9
+ Numeric => [:blank?,
10
+ :second, :seconds, :minute, :minutes, :hour, :hours, :day, :days, :week, :weeks,
11
+ :bytes, :kilobytes, :megabytes, :gigabytes, :terabytes, :petabytes, :exabytes],
12
+ Integer => [:multiple_of?, :month, :months, :year, :years, :to_json],
13
+ ).deny(
14
+ Module => [:const_get, :const_set, :const_defined?, :freeze, :ancestors],
15
+ Class => [:new, :allocate, :superclass],
16
+ )
17
+
18
+ if defined?(JSON::Ext::Generator::GeneratorMethods::Fixnum) &&
19
+ defined?(JSON::Ext::Generator::GeneratorMethods::Bignum)
20
+ Solid::MethodWhitelist.allow(
21
+ JSON::Ext::Generator::GeneratorMethods::Fixnum => [:to_json],
22
+ JSON::Ext::Generator::GeneratorMethods::Bignum => [:to_json],
23
+ )
24
+ end
@@ -0,0 +1,51 @@
1
+ module Solid::Element
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ base.send(:include, Solid::Iterable)
6
+ end
7
+
8
+ def initialize(tag_name, arguments_string, tokens, context = {})
9
+ super
10
+ @arguments = Solid::Arguments.parse(arguments_string)
11
+ end
12
+
13
+ def arguments
14
+ @arguments
15
+ end
16
+
17
+ def with_context(context)
18
+ previous_context = @current_context
19
+ @current_context = context
20
+ yield
21
+ ensure
22
+ @current_context = previous_context
23
+ end
24
+
25
+ def current_context
26
+ @current_context or raise Solid::ContextError.new("There is currently no context, do you forget to call render ?")
27
+ end
28
+
29
+ def display(*args)
30
+ raise NotImplementedError.new("Solid::Element implementations SHOULD define a #display method")
31
+ end
32
+
33
+ module ClassMethods
34
+
35
+ def tag_name(value=nil)
36
+ if value
37
+ @tag_name = value
38
+ Liquid::Template.register_tag(value.to_s, self)
39
+ end
40
+ @tag_name
41
+ end
42
+
43
+ def context_attribute(name)
44
+ define_method(name) do
45
+ Solid.unproxify(current_context[name.to_s])
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,4 @@
1
+ module Solid
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ module LiquidSafe
2
+ def to_liquid
3
+ self
4
+ end
5
+ end
6
+
7
+ class Symbol
8
+ include LiquidSafe
9
+ end
10
+
11
+ class Regexp
12
+ include LiquidSafe
13
+ end
14
+
15
+ class Time
16
+ extend LiquidSafe
17
+ 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,21 @@
1
+ module Solid
2
+ module LiquidExtensions
3
+ class AssignTag < Solid::Tag
4
+ extend TagHighjacker
5
+
6
+ tag_name :assign
7
+
8
+ def initialize(tag_name, assignment, tokens, context = {})
9
+ @assigned_variable, expression = assignment.split('=', 2)
10
+ @assigned_variable = @assigned_variable.strip
11
+ super(tag_name, expression, tokens, context)
12
+ end
13
+
14
+ def display(expression_result)
15
+ current_context.scopes.last[@assigned_variable] = expression_result
16
+ ''
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,102 @@
1
+ module Solid
2
+ module LiquidExtensions
3
+
4
+ # This for block reimplementation is deliberately backward incompatible
5
+ # since all strange features supported by the original for loop like
6
+ # "reversed" or "limit: 20 offset: 40" are favourably replaced by pure ruby
7
+ # methods like `Array#reversed` or `Array#slice`
8
+ class ForTag < Solid::Block
9
+ extend TagHighjacker
10
+
11
+ tag_name :for
12
+
13
+ def initialize(tag_name, expression, tokens, context = {})
14
+ @variable_name, iterable_expression = expression.split(/\s+in\s+/, 2).map(&:strip)
15
+ super(tag_name, iterable_expression, tokens, context)
16
+ end
17
+
18
+ def display(collection)
19
+ forloop = loop_for(collection)
20
+ output = []
21
+ collection = [] unless collection.respond_to?(:each_with_index)
22
+ collection.each_with_index do |element, index|
23
+ current_context.stack do
24
+ current_context[@variable_name] = element.to_liquid
25
+ current_context['forloop'] = forloop
26
+ output << yield
27
+ forloop.inc!
28
+ end
29
+ end
30
+ output.join
31
+ end
32
+
33
+ protected
34
+ def loop_for(collection)
35
+ if paginated?(collection)
36
+ PaginatedForLoop.new(collection)
37
+ else
38
+ ForLoop.new(collection)
39
+ end
40
+ end
41
+
42
+ def paginated?(collection)
43
+ defined?(WillPaginate) and
44
+ (collection.singleton_class < WillPaginate::CollectionMethods or
45
+ collection.singleton_class < WillPaginate::Mongoid::CollectionMethods)
46
+ end
47
+
48
+ end
49
+
50
+ class ForLoop < Liquid::Drop
51
+
52
+ def initialize(collection)
53
+ @collection = collection
54
+ @index0 = 0
55
+ end
56
+
57
+ def index0
58
+ @index0
59
+ end
60
+
61
+ def index
62
+ index0 + 1
63
+ end
64
+
65
+ def rindex
66
+ length - index0
67
+ end
68
+
69
+ def rindex0
70
+ length - index0 - 1
71
+ end
72
+
73
+ def length
74
+ @collection.length
75
+ end
76
+
77
+ def first
78
+ index0 == 0
79
+ end
80
+
81
+ def last
82
+ index == length
83
+ end
84
+
85
+ def inc!
86
+ @index0 += 1
87
+ end
88
+
89
+ end
90
+
91
+ class PaginatedForLoop < ForLoop
92
+
93
+ def initialize(collection)
94
+ super
95
+ @index0 = collection.offset || 0
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end
102
+
@@ -0,0 +1,44 @@
1
+ module Solid
2
+ module LiquidExtensions
3
+ class IfTag < Liquid::Block
4
+ include Solid::Element
5
+ extend TagHighjacker
6
+
7
+ tag_name :if
8
+
9
+ def initialize(tag_name, expression, tokens, context = {})
10
+ @blocks = []
11
+ push_block!(expression)
12
+ super
13
+ end
14
+
15
+ def render(context)
16
+ with_context(context) do
17
+ @blocks.each do |expression, blocks|
18
+ if expression.evaluate(context)
19
+ return render_all(blocks, context)
20
+ end
21
+ end
22
+ end
23
+ ''
24
+ end
25
+
26
+ def unknown_tag(tag, expression, tokens, context = {})
27
+ if tag == 'elsif'
28
+ push_block!(expression)
29
+ elsif tag == 'else'
30
+ push_block!('true')
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ def push_block!(expression)
37
+ block = []
38
+ @blocks.push([Solid::Parser.parse(expression), block])
39
+ @nodelist = block
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ module Solid
2
+ module LiquidExtensions
3
+ class UnlessTag < Solid::LiquidExtensions::IfTag
4
+
5
+ tag_name :unless
6
+
7
+ def initialize(tag_name, expression, tokens, context = {})
8
+ super(tag_name, "!(#{expression})", tokens, context)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ module Solid
2
+ module LiquidExtensions
3
+ class Variable < ::Liquid::Variable
4
+ extend ClassHighjacker
5
+
6
+ def initialize(markup)
7
+ super
8
+ @expression = Solid::Parser.parse(@name)
9
+ end
10
+
11
+ def render(context)
12
+ return '' if @name.nil?
13
+ value = @expression.evaluate(context)
14
+ apply_filters_on(value, context)
15
+ end
16
+
17
+ protected
18
+
19
+ def apply_filters_on(value, context)
20
+ @filters.inject(value) do |output, filter|
21
+ filterargs = filter[1].to_a.collect do |a|
22
+ context[a]
23
+ end
24
+ begin
25
+ output = context.invoke(filter[0], output, *filterargs)
26
+ rescue FilterNotFound
27
+ raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end