lesmok 0.2.0.pre1
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/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:
|