lesmok 0.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +119 -0
- data/Rakefile +12 -0
- data/lesmok.gemspec +29 -0
- data/lib/lesmok.rb +20 -0
- data/lib/lesmok/acid.rb +11 -0
- data/lib/lesmok/acid/drop.rb +64 -0
- data/lib/lesmok/acid/meltable.rb +59 -0
- data/lib/lesmok/backwards_compatibility.rb +13 -0
- data/lib/lesmok/caching/helpers.rb +29 -0
- data/lib/lesmok/config.rb +39 -0
- data/lib/lesmok/liquidations.rb +21 -0
- data/lib/lesmok/railing/action_view_handler.rb +75 -0
- data/lib/lesmok/tags.rb +51 -0
- data/lib/lesmok/tags/cached_include.rb +70 -0
- data/lib/lesmok/tags/erb_include.rb +53 -0
- data/lib/lesmok/tags/error_logging.rb +27 -0
- data/lib/lesmok/version.rb +3 -0
- data/spec/lesmok/caching_helpers_spec.rb +14 -0
- data/spec/lesmok/config_spec.rb +28 -0
- data/spec/lesmok/liquidations_spec.rb +28 -0
- data/spec/lesmok/tags_spec.rb +24 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 17819eb07b14118356c7b0bc52573cd7d0e649b4
|
4
|
+
data.tar.gz: 2cd95f97b6a584a5ef855b67562085f7ab3854d6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4210bd01b9e0fbad2a799c486f8264509db2d9ecdaf2611bce6521851756046b63fc5fd8daaf461eefefc1a237721b4a7aff16a37e55c3ea836fbf470309fcbc
|
7
|
+
data.tar.gz: 8072cd00bdc59f49b299465e7a8031e9e962d9b15c8702bed003695b9f8bf8955b8e7209f51c2eb56eaad22b07b0d19c131c2a3e854787e7174af04c0c28de9e
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Sixty AS
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# Lesmok
|
2
|
+
|
3
|
+
Liquid ExtentionS for MOre Komfort
|
4
|
+
|
5
|
+
## Liquid markup language
|
6
|
+
|
7
|
+
Read about Liquid at:
|
8
|
+
|
9
|
+
- http://liquidmarkup.org/
|
10
|
+
- https://github.com/Shopify/liquid
|
11
|
+
|
12
|
+
# Using Lesmok
|
13
|
+
|
14
|
+
## Drop the Base...
|
15
|
+
|
16
|
+
Make any class "meltable" quickly:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
class FilmFanatic::Movie
|
20
|
+
include ::Lesmok::Acid::Meltable
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
... and you can start using it in your Liquid templates immediately.
|
25
|
+
|
26
|
+
NOTE: All methods are now delegated (ACID - All Content Is Delegated),
|
27
|
+
so you don't want to do this if your liquid templates are "untrustworthy"
|
28
|
+
or your object has potentially destructive methods.
|
29
|
+
|
30
|
+
## Control the Beat...
|
31
|
+
|
32
|
+
The `Lesmok::Acid::Drop` liquid-drop class is used by default unless you:
|
33
|
+
|
34
|
+
- specify a `to_liquid` method explicitly in your class,
|
35
|
+
- override `liquify_drop_klass` to return your preferred sub-class of `Liquid::Drop`,
|
36
|
+
- or create an adjacent `*Drop` class with same naming as your model class.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class FilmFanatic::MovieDrop < ::Lesmok::Acid::Drop # Quick-n-dirty start.
|
40
|
+
alias :movie :source_object # For readability.
|
41
|
+
def rave # Start adding presenter methods you need.
|
42
|
+
"OMG! #{movie.title} is so awesome!"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
As you clean up, you may want to have more explicit drop control and migrate away from using Acid Drop:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class FilmFanatic::MovieDrop < ::Liquid::Drop
|
51
|
+
include ::Lesmok::Acid::Droppable # To keep compatible with Acid::Drop
|
52
|
+
alias :movie :source_object
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
|
57
|
+
## Keep your cache
|
58
|
+
|
59
|
+
Given that your object has a cache key, you can use the `cached_include` tag in your liquid templates:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class FilmFanatic::Movie
|
63
|
+
include ::Lesmok::Acid::Meltable
|
64
|
+
def cache_key
|
65
|
+
"fantastic-movie-#{self.imdb_id}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Then from your template:
|
71
|
+
|
72
|
+
```liquid
|
73
|
+
{% cached_include 'my/movie/reviews/liquid/template' for { cache_on: fantastic_movie } %}
|
74
|
+
```
|
75
|
+
|
76
|
+
|
77
|
+
# Installation
|
78
|
+
|
79
|
+
## Get tha gem...
|
80
|
+
|
81
|
+
Add this line to your application's Gemfile:
|
82
|
+
|
83
|
+
gem 'lesmok'
|
84
|
+
|
85
|
+
And then execute:
|
86
|
+
|
87
|
+
$ bundle
|
88
|
+
|
89
|
+
Or install it yourself as:
|
90
|
+
|
91
|
+
$ gem install lesmok
|
92
|
+
|
93
|
+
|
94
|
+
## Initialization and configuration
|
95
|
+
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
Lesmok.configure do |conf|
|
99
|
+
conf.logger = Rails.logger
|
100
|
+
conf.available_cache_stores = {
|
101
|
+
default: Rails.cache,
|
102
|
+
redis: $redis # Using Redis is entirely optional.
|
103
|
+
}
|
104
|
+
conf.debugging_enabled = Rails.env.development?
|
105
|
+
conf.caching_enabled = proc { Rails.env.production? }
|
106
|
+
conf.raise_errors_enabled = Rails.env.development?
|
107
|
+
end
|
108
|
+
Lesmok::Liquid::Tags.register_tags
|
109
|
+
```
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
## Contributing
|
114
|
+
|
115
|
+
1. Fork it
|
116
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
117
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
118
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
119
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lesmok.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lesmok/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "lesmok"
|
8
|
+
spec.version = Lesmok::VERSION
|
9
|
+
spec.authors = ["Sixty AS", "Kent Dahl"]
|
10
|
+
spec.email = ["info@sixty.no", "kent@sixty.no"]
|
11
|
+
spec.summary = %q{Liquid ExtentionS for MOre Komfort}
|
12
|
+
spec.description = %q{Collection of utility classes, tags etc for use with the Liquid templating system.}
|
13
|
+
spec.homepage = "https://github.com/sixtyno/lesmok"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake", "~>0"
|
23
|
+
|
24
|
+
spec.add_dependency 'activesupport' # , '~> 3.2'
|
25
|
+
spec.add_dependency 'liquid' #, '~> 2.5'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'rspec', '~>3.0'
|
28
|
+
|
29
|
+
end
|
data/lib/lesmok.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'lesmok/version'
|
2
|
+
require 'lesmok/config'
|
3
|
+
require 'lesmok/tags'
|
4
|
+
require 'lesmok/acid'
|
5
|
+
require 'lesmok/backwards_compatibility'
|
6
|
+
|
7
|
+
module Lesmok
|
8
|
+
module ClassMethods
|
9
|
+
def config
|
10
|
+
@configuration ||= Config.new
|
11
|
+
end
|
12
|
+
def configure
|
13
|
+
yield(config)
|
14
|
+
end
|
15
|
+
def logger
|
16
|
+
config.logger
|
17
|
+
end
|
18
|
+
end
|
19
|
+
extend ClassMethods
|
20
|
+
end
|
data/lib/lesmok/acid.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module Lesmok
|
2
|
+
module Acid
|
3
|
+
|
4
|
+
##
|
5
|
+
# Base module for creating liquid drops
|
6
|
+
# which defaults to allowing maximum access
|
7
|
+
# class for liquid drops used with Lesmok.
|
8
|
+
#
|
9
|
+
module Droppable
|
10
|
+
include Helpers
|
11
|
+
|
12
|
+
attr_reader :source_object # Object we delegate to
|
13
|
+
attr_reader :acid_options # Any customization options.
|
14
|
+
def initialize(source, opts = {})
|
15
|
+
@source_object = source
|
16
|
+
@acid_options = opts
|
17
|
+
end
|
18
|
+
|
19
|
+
## Liquify...
|
20
|
+
def to_liquid
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
## Solidify
|
25
|
+
def to_solid
|
26
|
+
@source_object
|
27
|
+
end
|
28
|
+
|
29
|
+
## We default to sending anything through to the source object.
|
30
|
+
def before_method(method_name)
|
31
|
+
if allow_delegating_method_to_source?(method_name)
|
32
|
+
return @source_object.send(method_name)
|
33
|
+
else
|
34
|
+
msg = "[#{self.class}] The method `#{method_name}` is not defined on #{@source_object.inspect[0..127]}."
|
35
|
+
Lesmok.logger.warn(msg) if Lesmok.config.debugging?
|
36
|
+
raise NotImplementedError.new(msg) if Lesmok.config.raise_errors?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def allow_delegating_method_to_source?(name)
|
41
|
+
@source_object.respond_to?(name) # TODO: && !name =~ /(\!\=)$/
|
42
|
+
end
|
43
|
+
|
44
|
+
def respond_to_missing?(method_name, include_private = false)
|
45
|
+
allow_delegating_method_to_source?(method_name) || super
|
46
|
+
end
|
47
|
+
|
48
|
+
## Ensure before_method fallbacks are used both from liquid and when using drop directly.
|
49
|
+
def method_missing(method_name, *args)
|
50
|
+
(args.present?) ? super(method_name, *args) : before_method(method_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Base fallback class for creating Liquid drops with Lesmok
|
57
|
+
#
|
58
|
+
class Drop < ::Liquid::Drop
|
59
|
+
include Droppable
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Lesmok
|
2
|
+
module Acid
|
3
|
+
|
4
|
+
module Helpers
|
5
|
+
##
|
6
|
+
# Short-hands for use when switching context
|
7
|
+
# between liquid and Ruby code, to ensure
|
8
|
+
# you get the drop OR the solid source, w/o any overhead.
|
9
|
+
#
|
10
|
+
def melt ; to_liquid ; end # "Melts" the object into a liquid.
|
11
|
+
def cast ; to_solid ; end # "Casts" (as in metal forging) back into the original object.
|
12
|
+
def solid ; to_solid ; end
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Indicate that a model can be liquified.
|
17
|
+
#
|
18
|
+
module Meltable
|
19
|
+
include Helpers
|
20
|
+
|
21
|
+
## Liquify...
|
22
|
+
def to_liquid
|
23
|
+
@liquid_drop ||= liquify_dynamically
|
24
|
+
end
|
25
|
+
|
26
|
+
## Solidify
|
27
|
+
def to_solid
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Fallback to trying to find the drop class dynamically
|
33
|
+
# or use generic AcidDrop if it can't be found.
|
34
|
+
#
|
35
|
+
def liquify_dynamically
|
36
|
+
return @liquid_drop if @liquid_drop
|
37
|
+
klass = liquify_drop_klass || AcidDrop
|
38
|
+
@liquid_drop = klass.new(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# We expect Drop classes to be in same namespace as the object class.
|
43
|
+
#
|
44
|
+
def liquify_drop_klass
|
45
|
+
str = self.class.name + 'Drop'
|
46
|
+
klass = str.split('::').inject(Object) do |mod, class_name|
|
47
|
+
mod.const_get(class_name)
|
48
|
+
end
|
49
|
+
klass
|
50
|
+
rescue NameError => err
|
51
|
+
msg = "[#{self.class}] Could not find liquid drop class..."
|
52
|
+
::Lesmok.logger.warn(msg) if Lesmok.config.debugging?
|
53
|
+
Drop
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
module Lesmok
|
3
|
+
module Caching
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
module ExpiryCalculation
|
7
|
+
def calculate_expiry(cached_on_obj = nil, expire_in_option = nil, jitter_factor = 0.05)
|
8
|
+
expire_in ||= cached_on_obj && cached_on_obj.respond_to?(:cache_expire_in) && cached_on_obj.cache_expire_in
|
9
|
+
expire_in ||= (expire_in_option || 5).to_i * 60 # TODO: Is option authorative or fallback only?
|
10
|
+
expire_in += rand * expire_in * jitter_factor if jitter_factor # 5 % random additional time to avoid all expiring at once.
|
11
|
+
expire_in
|
12
|
+
end
|
13
|
+
extend self
|
14
|
+
end
|
15
|
+
module GlobalKeyHandling
|
16
|
+
def global_cache_scope
|
17
|
+
"Lesmok:Liquid"
|
18
|
+
end
|
19
|
+
def full_cache_key_for(cached_on_obj, template_name, global_scope = nil)
|
20
|
+
cache_val = cached_on_obj.to_s if cached_on_obj.kind_of?(String)
|
21
|
+
cache_val ||= cached_on_obj.respond_to?(:cache_key) && cached_on_obj.cache_key
|
22
|
+
"#{global_scope || global_cache_scope}:#{I18n.locale}:cached_include:#{template_name}:#{cache_val}"
|
23
|
+
end
|
24
|
+
extend self
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Lesmok
|
2
|
+
class Config
|
3
|
+
attr_accessor :logger
|
4
|
+
attr_accessor :cache
|
5
|
+
attr_accessor :caching_enabled
|
6
|
+
attr_accessor :available_cache_stores
|
7
|
+
attr_accessor :debugging_enabled
|
8
|
+
attr_accessor :raise_errors_enabled
|
9
|
+
|
10
|
+
alias :raise_errors? :raise_errors_enabled
|
11
|
+
alias :debugging? :debugging_enabled
|
12
|
+
|
13
|
+
def logger
|
14
|
+
@logger ||= (rails? && ::Rails.logger)
|
15
|
+
end
|
16
|
+
|
17
|
+
def cache
|
18
|
+
@cache ||= find_cache_store(:default) || (rails? && ::Rails.cache) || nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def caching?
|
22
|
+
return false if !@caching_enabled
|
23
|
+
@caching_enabled.kind_of?(Proc) ? @caching_enabled.call : true
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_cache_store(name = nil)
|
27
|
+
name ||= :default
|
28
|
+
avail_stores = (available_cache_stores || {})
|
29
|
+
store = avail_stores[name.to_sym]
|
30
|
+
store ||= avail_stores[:default]
|
31
|
+
store ||= cache
|
32
|
+
end
|
33
|
+
|
34
|
+
def rails?
|
35
|
+
Object.const_defined? "Rails"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'liquid'
|
2
|
+
|
3
|
+
# Optional `to_liquid` definitions injected into various classes.
|
4
|
+
#
|
5
|
+
# Require this file explicitly to use.
|
6
|
+
|
7
|
+
class Symbol
|
8
|
+
def to_liquid
|
9
|
+
to_s
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Struct
|
14
|
+
def to_liquid
|
15
|
+
hash = Hash.new
|
16
|
+
self.members.each do |k|
|
17
|
+
hash[k.to_s] = self[k]
|
18
|
+
end
|
19
|
+
hash
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
##
|
2
|
+
#
|
3
|
+
# To register for use in Rails:
|
4
|
+
# ActionView::Template.register_template_handler :liquid, ::Lesmok::Railing::ActionViewHandler'
|
5
|
+
#
|
6
|
+
# Based on example by Roy van der Meij:
|
7
|
+
# - http://royvandermeij.com/blog/2011/09/21/create-a-liquid-handler-for-rails-3-dot-1/
|
8
|
+
#
|
9
|
+
module Lesmok
|
10
|
+
module Railing
|
11
|
+
|
12
|
+
class ActionViewHandler
|
13
|
+
def self.lesmok_options
|
14
|
+
@lesmok_options ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.call(template)
|
18
|
+
"#{self}.new(self).render(#{template.source.inspect}, local_assigns)"
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(view)
|
22
|
+
@view = view
|
23
|
+
end
|
24
|
+
|
25
|
+
def render(template, local_assigns = {})
|
26
|
+
@view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'
|
27
|
+
|
28
|
+
assigns = @view.assigns
|
29
|
+
|
30
|
+
if @view.content_for?(:layout)
|
31
|
+
assigns["content_for_layout"] = @view.content_for(:layout)
|
32
|
+
end
|
33
|
+
assigns.merge!(local_assigns.stringify_keys)
|
34
|
+
|
35
|
+
controller = @view.controller
|
36
|
+
filters = if controller.respond_to?(:liquid_filters, true)
|
37
|
+
controller.send(:liquid_filters)
|
38
|
+
elsif controller.respond_to?(:master_helper_module)
|
39
|
+
[controller.master_helper_module]
|
40
|
+
else
|
41
|
+
[controller._helpers]
|
42
|
+
end
|
43
|
+
|
44
|
+
liquid = ::Liquid::Template.parse(template)
|
45
|
+
render_assigns = assigns.with_indifferent_access
|
46
|
+
render_opts = {:filters => filters, :registers => {:action_view => @view, :controller => @view.controller}}
|
47
|
+
if self.class.lesmok_options[:rethrow_errors]
|
48
|
+
text = liquid.render!(render_assigns, render_opts)
|
49
|
+
else
|
50
|
+
text = liquid.render(render_assigns, render_opts)
|
51
|
+
end
|
52
|
+
if ::Lesmok.config.debugging?
|
53
|
+
log_any_liquid_errors(liquid.errors)
|
54
|
+
log_any_liquid_errors(liquid.warnings, 'warning')
|
55
|
+
end
|
56
|
+
|
57
|
+
text.html_safe
|
58
|
+
end
|
59
|
+
|
60
|
+
def log_any_liquid_errors(errors, type_str = 'error')
|
61
|
+
return if errors.blank?
|
62
|
+
log = ::Rails.logger
|
63
|
+
log.warn "[Lesmok] Template #{type_str}s (#{errors.size}) detected!"
|
64
|
+
errors.each do |err|
|
65
|
+
log.debug " -- Liquid #{type_str}: #{err}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def compilable?
|
70
|
+
false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end # Liquid
|
75
|
+
end # Lesmok
|
data/lib/lesmok/tags.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'liquid'
|
2
|
+
|
3
|
+
require 'lesmok/tags/error_logging'
|
4
|
+
require 'lesmok/tags/cached_include'
|
5
|
+
require 'lesmok/tags/erb_include'
|
6
|
+
|
7
|
+
module Lesmok
|
8
|
+
module Tags
|
9
|
+
|
10
|
+
def self.register_tags
|
11
|
+
::Liquid::Template.register_tag('erb_include', ::Lesmok::Tags::ErbInclude)
|
12
|
+
::Liquid::Template.register_tag('cached_include', ::Lesmok::Tags::CachedInclude)
|
13
|
+
::Liquid::Template.register_tag('debug_comment', ::Lesmok::Tags::DebugComment)
|
14
|
+
::Liquid::Template.register_tag('include', ::Lesmok::Tags::DebugInclude) if Lesmok.config.debugging?
|
15
|
+
::Liquid::Template.register_tag('csrf', ::Lesmok::Tags::Csrf)
|
16
|
+
end
|
17
|
+
|
18
|
+
class DebugComment < ::Liquid::Block
|
19
|
+
include ErrorLogging
|
20
|
+
def render(context)
|
21
|
+
return '' unless Lesmok.config.debugging?
|
22
|
+
with_exception_logging(context) do
|
23
|
+
"<!-- LIQUID DEBUG: #{super} -->"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class DebugInclude < ::Liquid::Include
|
29
|
+
include ErrorLogging
|
30
|
+
def render(context)
|
31
|
+
with_exception_logging(context) do
|
32
|
+
result = super
|
33
|
+
if context.errors.present?
|
34
|
+
::Lesmok.logger.debug " -- Liquid errors (#{context.errors.size}) seen in: #{@template_name}"
|
35
|
+
end
|
36
|
+
result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Csrf < ::Liquid::Tag
|
42
|
+
def render(context)
|
43
|
+
controller = context.registers[:controller]
|
44
|
+
name = controller.send(:request_forgery_protection_token).to_s
|
45
|
+
value = controller.send(:form_authenticity_token)
|
46
|
+
%(<input type="hidden" name="#{name}" value="#{value}">)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'liquid'
|
2
|
+
require 'lesmok/caching/helpers'
|
3
|
+
|
4
|
+
|
5
|
+
module Lesmok
|
6
|
+
module Tags
|
7
|
+
|
8
|
+
class CachedInclude < ::Liquid::Include
|
9
|
+
include ErrorLogging
|
10
|
+
|
11
|
+
include ::Lesmok::Caching::Helpers
|
12
|
+
include ExpiryCalculation
|
13
|
+
extend GlobalKeyHandling
|
14
|
+
|
15
|
+
def render(context)
|
16
|
+
return super unless fragment_caching_enabled?
|
17
|
+
cached_on_obj = context[@attributes['cache_on']]
|
18
|
+
cache_val = cached_on_obj && cached_on_obj.respond_to?(:cache_key) && cached_on_obj.cache_key
|
19
|
+
cache_val ||= context[@attributes['cache_key']]
|
20
|
+
|
21
|
+
template_name = context[@template_name]
|
22
|
+
|
23
|
+
## Catch cases where cached_include is used incorrectly.
|
24
|
+
if cache_val.blank?
|
25
|
+
if Lesmok.config.debugging?
|
26
|
+
Lesmok.logger.warn "[#{self.class}] No valid cache key given for '#{template_name}' template!"
|
27
|
+
Lesmok.logger.debug " -- No cache key given nor found for object: #{cached_on_obj.inspect.truncate(64)}"
|
28
|
+
end
|
29
|
+
if Lesmok.config.raise_errors?
|
30
|
+
raise ArgumentError.new("No valid cache key! given for '#{template_name}' template!")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
return super unless cache_val.present?
|
35
|
+
|
36
|
+
## Allow sub-scoping w/o manually creating cache key.
|
37
|
+
cache_subscope = context[@attributes['cache_scope']]
|
38
|
+
cache_val += ":SUBSCOPE-#{cache_subscope}" if cache_subscope.present?
|
39
|
+
|
40
|
+
expire_in = calculate_expiry(cached_on_obj, context[@attributes['expire_in']])
|
41
|
+
cache_key = self.class.full_cache_key_for(cache_val, template_name)
|
42
|
+
cache_store = select_cache_store_for(context)
|
43
|
+
Lesmok.logger.debug "[#{self.class}] Lookup #{cache_key} in #{cache_store}..." if Lesmok.config.debugging?
|
44
|
+
result = cache_store.fetch(cache_key, expires_in: expire_in) do
|
45
|
+
Lesmok.logger.debug "[#{self.class}] --- cache miss on #{cache_key} in #{cache_store}!" if Lesmok.config.debugging?
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
if context.errors.present?
|
50
|
+
::Lesmok.logger.debug " -- Liquid errors (#{context.errors.size}) seen in: #{@template_name}"
|
51
|
+
end
|
52
|
+
|
53
|
+
result
|
54
|
+
rescue Exception => err
|
55
|
+
log_exception(err, context)
|
56
|
+
""
|
57
|
+
end
|
58
|
+
|
59
|
+
def select_cache_store_for(context)
|
60
|
+
cache_store_name = context[@attributes['cache_store']]
|
61
|
+
cache_store = Lesmok.config.find_cache_store(cache_store_name)
|
62
|
+
end
|
63
|
+
|
64
|
+
def fragment_caching_enabled?
|
65
|
+
Lesmok.config.caching?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'liquid'
|
2
|
+
|
3
|
+
module Lesmok
|
4
|
+
module Tags
|
5
|
+
|
6
|
+
class ErbInclude < ::Liquid::Include
|
7
|
+
include ErrorLogging
|
8
|
+
|
9
|
+
class ErbRenderer
|
10
|
+
attr_reader :context
|
11
|
+
def initialize(liquid_context, template_name)
|
12
|
+
@context = liquid_context
|
13
|
+
@template_name = template_name
|
14
|
+
end
|
15
|
+
def extract_vars
|
16
|
+
# Not needed if we use action_view directly?
|
17
|
+
ctrl.instance_variables.each do |key|
|
18
|
+
val = ctrl.instance_variable_get(key)
|
19
|
+
self.instance_variable_set(key, val)
|
20
|
+
end if ctrl
|
21
|
+
end
|
22
|
+
|
23
|
+
def ctrl
|
24
|
+
context.registers[:controller]
|
25
|
+
end
|
26
|
+
|
27
|
+
def view
|
28
|
+
context.registers[:action_view]
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(args = {})
|
32
|
+
template_name = context[@template_name]
|
33
|
+
default_args = {
|
34
|
+
partial: template_name,
|
35
|
+
template: template_name,
|
36
|
+
registers: context.registers,
|
37
|
+
locals: context.scopes.last.with_indifferent_access, # TODO: Make this work?
|
38
|
+
liquid_context: context,
|
39
|
+
}
|
40
|
+
render_args = default_args.merge(args)
|
41
|
+
ctrl.send(:render, render_args)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
def render(context)
|
45
|
+
with_exception_logging(context) do
|
46
|
+
erb = ErbRenderer.new(context, @template_name)
|
47
|
+
erb.call
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Lesmok
|
2
|
+
module Tags
|
3
|
+
|
4
|
+
module ErrorLogging
|
5
|
+
def log_exception(err, context)
|
6
|
+
template_name = context[@template_name]
|
7
|
+
err.message << " (in template '#{template_name}')" rescue nil
|
8
|
+
err.blame_file! "#{template_name}.liquid" rescue nil
|
9
|
+
Lesmok.logger.error "[#{self.class}] Liquid error in '#{template_name}': #{err.to_s} \n - #{err.backtrace.first(15).join("\n - ")}"
|
10
|
+
rescue => err
|
11
|
+
Lesmok.logger.error "[#{self.class}] META ERROR: Liquid exception reporting failure: #{err.to_s} \n - #{err.backtrace.first(15).join("\n - ")}"
|
12
|
+
ensure
|
13
|
+
raise err if Lesmok.config.debugging?
|
14
|
+
""
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_exception_logging(context)
|
18
|
+
begin
|
19
|
+
yield
|
20
|
+
rescue => err
|
21
|
+
log_exception(err, context)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'lesmok'
|
2
|
+
describe ::Lesmok::Caching::Helpers do
|
3
|
+
|
4
|
+
describe described_class::ExpiryCalculation do
|
5
|
+
it "should calculate expiry" do
|
6
|
+
exp = described_class.calculate_expiry
|
7
|
+
expect(exp).to be >= 300
|
8
|
+
expect(exp).to be <= 315
|
9
|
+
|
10
|
+
expect(described_class.calculate_expiry(nil, 10, nil)).to be == 600
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'lesmok'
|
2
|
+
describe ::Lesmok::Config do
|
3
|
+
|
4
|
+
it "should lookup cache with fallback" do
|
5
|
+
config = described_class.new
|
6
|
+
dummy_cache = double
|
7
|
+
config.cache = dummy_cache
|
8
|
+
|
9
|
+
expect(config.cache).to be == dummy_cache
|
10
|
+
expect(config.find_cache_store).to be == dummy_cache
|
11
|
+
expect(config.find_cache_store(nil)).to be == dummy_cache
|
12
|
+
expect(config.find_cache_store(:default)).to be == dummy_cache
|
13
|
+
expect(config.find_cache_store(:unknown)).to be == dummy_cache
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should allow caching to be toggled dynamically" do
|
17
|
+
config = described_class.new
|
18
|
+
cache_level = 1
|
19
|
+
config.caching_enabled = proc { cache_level > 1 }
|
20
|
+
expect(config.caching?).to be false
|
21
|
+
cache_level = 2
|
22
|
+
expect(config.caching?).to be true
|
23
|
+
cache_level = 0
|
24
|
+
expect(config.caching?).to be false
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'lesmok/liquidations'
|
2
|
+
|
3
|
+
describe 'Lesmok liquidations' do
|
4
|
+
|
5
|
+
describe 'Symbols' do
|
6
|
+
let(:template){ Liquid::Template.parse("GOT:{{ sym }}") }
|
7
|
+
it "should render symbols" do
|
8
|
+
text = template.render( 'sym' => :symbol )
|
9
|
+
expect(text).to include "GOT:symbol"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'Structs' do
|
14
|
+
let(:anon_klass){ Struct.new(:name, :age) }
|
15
|
+
let(:template){ Liquid::Template.parse("{{ user.name }}/{{ user.age }}") }
|
16
|
+
it "should handle structs" do
|
17
|
+
user = anon_klass.new("OlaNordmann", 77)
|
18
|
+
expect(user.age).to eql(77)
|
19
|
+
text = template.render( 'user' => user )
|
20
|
+
expect(text).to include "OlaNordmann"
|
21
|
+
expect(text).to include "77"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'lesmok'
|
2
|
+
describe ::Lesmok::Tags do
|
3
|
+
|
4
|
+
before(:all) do
|
5
|
+
described_class.register_tags
|
6
|
+
end
|
7
|
+
|
8
|
+
describe described_class::DebugComment do
|
9
|
+
let(:template){ Liquid::Template.parse("{% debug_comment %} HUH HUH {% enddebug_comment %}") }
|
10
|
+
it "should render contents in debugging mode" do
|
11
|
+
Lesmok.config.debugging_enabled = true
|
12
|
+
text = template.render
|
13
|
+
expect(text).to include "HUH HUH"
|
14
|
+
expect(text).to include "<!--"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should not render contents by default" do
|
18
|
+
Lesmok.config.debugging_enabled = false
|
19
|
+
text = template.render
|
20
|
+
expect(text).to be == ""
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lesmok
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0.pre1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sixty AS
|
8
|
+
- Kent Dahl
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-08-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.5'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.5'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: activesupport
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: liquid
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rspec
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '3.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '3.0'
|
84
|
+
description: Collection of utility classes, tags etc for use with the Liquid templating
|
85
|
+
system.
|
86
|
+
email:
|
87
|
+
- info@sixty.no
|
88
|
+
- kent@sixty.no
|
89
|
+
executables: []
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files: []
|
92
|
+
files:
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- lesmok.gemspec
|
98
|
+
- lib/lesmok.rb
|
99
|
+
- lib/lesmok/acid.rb
|
100
|
+
- lib/lesmok/acid/drop.rb
|
101
|
+
- lib/lesmok/acid/meltable.rb
|
102
|
+
- lib/lesmok/backwards_compatibility.rb
|
103
|
+
- lib/lesmok/caching/helpers.rb
|
104
|
+
- lib/lesmok/config.rb
|
105
|
+
- lib/lesmok/liquidations.rb
|
106
|
+
- lib/lesmok/railing/action_view_handler.rb
|
107
|
+
- lib/lesmok/tags.rb
|
108
|
+
- lib/lesmok/tags/cached_include.rb
|
109
|
+
- lib/lesmok/tags/erb_include.rb
|
110
|
+
- lib/lesmok/tags/error_logging.rb
|
111
|
+
- lib/lesmok/version.rb
|
112
|
+
- spec/lesmok/caching_helpers_spec.rb
|
113
|
+
- spec/lesmok/config_spec.rb
|
114
|
+
- spec/lesmok/liquidations_spec.rb
|
115
|
+
- spec/lesmok/tags_spec.rb
|
116
|
+
homepage: https://github.com/sixtyno/lesmok
|
117
|
+
licenses:
|
118
|
+
- MIT
|
119
|
+
metadata: {}
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">"
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 1.3.1
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 2.4.5
|
137
|
+
signing_key:
|
138
|
+
specification_version: 4
|
139
|
+
summary: Liquid ExtentionS for MOre Komfort
|
140
|
+
test_files:
|
141
|
+
- spec/lesmok/caching_helpers_spec.rb
|
142
|
+
- spec/lesmok/config_spec.rb
|
143
|
+
- spec/lesmok/liquidations_spec.rb
|
144
|
+
- spec/lesmok/tags_spec.rb
|
145
|
+
has_rdoc:
|