sparkle 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +21 -0
- data/LICENSE +21 -0
- data/README.rdoc +86 -0
- data/Rakefile +11 -0
- data/lib/sparkle.rb +9 -0
- data/lib/sparkle/controller.rb +18 -0
- data/lib/sparkle/helper.rb +17 -0
- data/lib/sparkle/refreshable.rb +51 -0
- data/lib/sparkle/version.rb +3 -0
- data/sparkle.gemspec +25 -0
- data/test/controller_test.rb +40 -0
- data/test/helper_test.rb +25 -0
- data/test/refreshable_test.rb +31 -0
- data/test/test_helper.rb +40 -0
- metadata +135 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sparkle (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activesupport (2.3.11)
|
10
|
+
flexmock (0.8.11)
|
11
|
+
rake (0.8.7)
|
12
|
+
|
13
|
+
PLATFORMS
|
14
|
+
ruby
|
15
|
+
|
16
|
+
DEPENDENCIES
|
17
|
+
activesupport (= 2.3.11)
|
18
|
+
bundler (>= 1.0.0)
|
19
|
+
flexmock (= 0.8.11)
|
20
|
+
rake (= 0.8.7)
|
21
|
+
sparkle!
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2011 Evan Petrie
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
= Sparkle
|
2
|
+
|
3
|
+
* http://github.com/kuinak/sparkle
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
Cache partials and update them asynchronously after a page renders. Slow
|
8
|
+
method calls happen synchronously as the page renders for the first time.
|
9
|
+
Subsequent page loads retrieve cached objects and render the page quickly, and
|
10
|
+
then Ajax calls can be used to refresh components asynchronously after the
|
11
|
+
page loads.
|
12
|
+
|
13
|
+
== Usage
|
14
|
+
|
15
|
+
Add sparkle to your Gemfile (or whatever if you're not using bundler):
|
16
|
+
|
17
|
+
gem "sparkle"
|
18
|
+
|
19
|
+
Include the refreshable module into a class that performs a slow action and
|
20
|
+
tell it what method to execute when refreshing:
|
21
|
+
|
22
|
+
class FacebookFeed
|
23
|
+
include Sparkle::Refreshable
|
24
|
+
sparkle_refresh_method :retrieve_feed
|
25
|
+
|
26
|
+
def initialize(access_token)
|
27
|
+
@access_token = access_token
|
28
|
+
end
|
29
|
+
|
30
|
+
def retrieve_feed
|
31
|
+
# go do something slow
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Get refreshable objects using the fetch method in your controller or helper
|
36
|
+
methods. Any cache misses will pass the parameters passed to the initializer
|
37
|
+
method of the class:
|
38
|
+
|
39
|
+
@facebook_feed = FacebookFeed.sparkle_fetch("some_access_token")
|
40
|
+
|
41
|
+
Change calls from render to render_sparkle for partials you want to be
|
42
|
+
refreshed. Note that you cannot use instance variables in any of the
|
43
|
+
refreshable partials, all variables used must be passed as locals:
|
44
|
+
|
45
|
+
render_sparkle(:partial => "users/facebook_feed", :locals => { :facebook_feed => @facebook_feed })
|
46
|
+
|
47
|
+
When the template is rendered, it will attempt to use a cached version of any
|
48
|
+
objects that include Sparkle::Refreshable. If a cached copy of any local
|
49
|
+
variable is found, a cache key variable called "sparkle_cache_key" will be
|
50
|
+
set. You can use this variable to reference elements of the DOM that should be
|
51
|
+
refreshed (below example in Haml):
|
52
|
+
|
53
|
+
- html_options = sparkle_cache_key ? {:id => sparkle_cache_key, :class => "refreshable"} : {}
|
54
|
+
%div { html_options }
|
55
|
+
- facebook_feed.each do |item|
|
56
|
+
...
|
57
|
+
|
58
|
+
Add some javascripts to bind the refresh action to DOM elements that need to
|
59
|
+
be refreshed:
|
60
|
+
|
61
|
+
$(document).ready(function(){
|
62
|
+
$(".refreshable").each(function(index, e) {
|
63
|
+
var id = $(e).attr("id");
|
64
|
+
$.ajax({
|
65
|
+
url: "/sparkle/fetch/" + id,
|
66
|
+
success: function(html) {
|
67
|
+
$(e).replaceWith(html);
|
68
|
+
}
|
69
|
+
});
|
70
|
+
});
|
71
|
+
});
|
72
|
+
|
73
|
+
Include the controller module into an existing controller in your application:
|
74
|
+
|
75
|
+
class ApplicationController < ActionController::Base
|
76
|
+
include Sparkle::Controller
|
77
|
+
...
|
78
|
+
|
79
|
+
Add a route to config/routes.rb to the fetch action in the controller you
|
80
|
+
included the Sparkle::Controller module in:
|
81
|
+
|
82
|
+
map.connect "/sparkle/fetch/:key", :controller => "application", :action => "fetch"
|
83
|
+
|
84
|
+
== License
|
85
|
+
|
86
|
+
The MIT License, see LICENSE
|
data/Rakefile
ADDED
data/lib/sparkle.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Sparkle
|
2
|
+
module Controller
|
3
|
+
|
4
|
+
def fetch
|
5
|
+
cache_entry = Rails.cache.read(params[:key])
|
6
|
+
partial = cache_entry[:partial]
|
7
|
+
locals = cache_entry[:locals]
|
8
|
+
begin
|
9
|
+
locals.values.each { |value| value._sparkle_refresh! if value.class.include?(Refreshable) }
|
10
|
+
rescue Exception => e
|
11
|
+
session[:sparkle_exception] = e
|
12
|
+
end
|
13
|
+
render :partial => partial, :locals => locals.merge(:sparkle_cache_key => nil)
|
14
|
+
raise session.delete(:sparkle_exception) if session[:sparkle_exception]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sparkle
|
2
|
+
module Helper
|
3
|
+
|
4
|
+
def render_sparkle options
|
5
|
+
key = rand(2**128).to_s(36)
|
6
|
+
Rails.cache.write(key, options)
|
7
|
+
if options[:locals].values.any? { |value| value._sparkle_cached_ if value.class.include?(Refreshable) }
|
8
|
+
options[:locals][:sparkle_cache_key] = key
|
9
|
+
else
|
10
|
+
options[:locals][:sparkle_cache_key] = nil
|
11
|
+
end
|
12
|
+
render options
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Sparkle
|
2
|
+
module Refreshable
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
base.class_eval do
|
7
|
+
class_inheritable_accessor :_sparkle_cache_refresh_method
|
8
|
+
attr_accessor :_sparkle_cached_
|
9
|
+
attr_accessor :_sparkle_cache_key_
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def _sparkle_refresh!
|
14
|
+
self.send self.class._sparkle_cache_refresh_method
|
15
|
+
Rails.cache.write(self._sparkle_cache_key_, self)
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
def sparkle_refresh_method method_name
|
21
|
+
self._sparkle_cache_refresh_method = method_name.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def sparkle_fetch(*args)
|
25
|
+
key = _sparkle_cache_key(*args)
|
26
|
+
if object = Rails.cache.read(key)
|
27
|
+
begin
|
28
|
+
object._sparkle_cached_ = true
|
29
|
+
rescue TypeError => e
|
30
|
+
object = object.dup
|
31
|
+
object._sparkle_cached_ = true
|
32
|
+
end
|
33
|
+
else
|
34
|
+
object = new(*args)
|
35
|
+
object.send object.class._sparkle_cache_refresh_method
|
36
|
+
object._sparkle_cached_ = false
|
37
|
+
object._sparkle_cache_key_ = key
|
38
|
+
Rails.cache.write(key, object)
|
39
|
+
end
|
40
|
+
object
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def _sparkle_cache_key(*args)
|
46
|
+
Digest::SHA1.hexdigest("#{name}:#{args.join('|')}")
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/sparkle.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/sparkle/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "sparkle"
|
6
|
+
s.version = Sparkle::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Evan Petrie"]
|
9
|
+
s.email = ["ejp@yahoo.com"]
|
10
|
+
s.homepage = "http://rubygems.org/gems/sparkle"
|
11
|
+
s.summary = "Asyncronous partial caching"
|
12
|
+
s.description = "Cache partials and update them asynchronously after a page renders. Slow method calls happen synchronously as the page renders for the first time. Subsequent page loads retrieve cached objects and render the page quickly, and then Ajax calls can be used to refresh components asynchronously after the page loads."
|
13
|
+
|
14
|
+
s.required_rubygems_version = ">= 1.3.6"
|
15
|
+
s.rubyforge_project = "sparkle"
|
16
|
+
|
17
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
18
|
+
s.add_development_dependency "rake", "0.8.7"
|
19
|
+
s.add_development_dependency "flexmock", "0.8.11"
|
20
|
+
s.add_development_dependency "activesupport", "2.3.11"
|
21
|
+
|
22
|
+
s.files = `git ls-files`.split("\n")
|
23
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
24
|
+
s.require_path = 'lib'
|
25
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require "flexmock/test_unit"
|
3
|
+
require "sparkle/controller"
|
4
|
+
require "test/test_helper"
|
5
|
+
|
6
|
+
class TestController < Test::Unit::TestCase
|
7
|
+
|
8
|
+
include Sparkle::Controller
|
9
|
+
|
10
|
+
def params
|
11
|
+
@params ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def session
|
15
|
+
@session ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_fetch
|
19
|
+
local = RefreshMe.new
|
20
|
+
flexmock(local).should_receive(:_sparkle_refresh!).once
|
21
|
+
Rails.cache.write(:foo, {:partial => "partial", :locals => {:local => local}})
|
22
|
+
params[:key] = :foo
|
23
|
+
flexmock(self).should_receive(:render).with(:partial => "partial", :locals => {:sparkle_cache_key => nil, :local => local}).once
|
24
|
+
fetch
|
25
|
+
assert_nil session[:sparkle_exception]
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_fetch_with_exception
|
29
|
+
local = RefreshMe.new
|
30
|
+
flexmock(local).should_receive(:_sparkle_refresh!).once.and_raise(RuntimeError.new)
|
31
|
+
Rails.cache.write(:foo, {:partial => "partial", :locals => {:local => local}})
|
32
|
+
params[:key] = :foo
|
33
|
+
flexmock(self).should_receive(:render).with(:partial => "partial", :locals => {:sparkle_cache_key => nil, :local => local}).once
|
34
|
+
assert_raise RuntimeError do
|
35
|
+
fetch
|
36
|
+
end
|
37
|
+
assert_nil session[:sparkle_exception]
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/test/helper_test.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require "flexmock/test_unit"
|
3
|
+
require "sparkle/helper"
|
4
|
+
require "test/test_helper"
|
5
|
+
|
6
|
+
class TestHelper < Test::Unit::TestCase
|
7
|
+
|
8
|
+
include Sparkle::Helper
|
9
|
+
|
10
|
+
def rand(limit); 1; end
|
11
|
+
|
12
|
+
def test_render_sparkle
|
13
|
+
local = RefreshMe.new
|
14
|
+
flexmock(self).should_receive(:render).with(:locals => {:local => local, :sparkle_cache_key => nil}).once
|
15
|
+
render_sparkle(:locals => {:local => local})
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_render_sparkle_with_cached_local
|
19
|
+
local = RefreshMe.new
|
20
|
+
flexmock(local).should_receive(:_sparkle_cached_).and_return(true).once
|
21
|
+
flexmock(self).should_receive(:render).with(:locals => {:local => local, :sparkle_cache_key => "1"}).once
|
22
|
+
render_sparkle(:locals => {:local => local})
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require "flexmock/test_unit"
|
3
|
+
require "sparkle/helper"
|
4
|
+
require "test/test_helper"
|
5
|
+
|
6
|
+
class RefreshableTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
Rails.cache.clear
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_sparkle_refresh
|
13
|
+
flexmock(RefreshMe).should_receive(:_sparkle_cache_key).and_return("cache_key")
|
14
|
+
refreshable = RefreshMe.sparkle_fetch
|
15
|
+
flexmock(refreshable).should_receive(:slow_method).once
|
16
|
+
refreshable._sparkle_refresh!
|
17
|
+
assert_equal refreshable, Rails.cache.read("cache_key")
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_sparkle_fetch
|
21
|
+
flexmock(RefreshMe).should_receive(:_sparkle_cache_key).and_return("cache_key")
|
22
|
+
assert_nil Rails.cache.read("cache_key")
|
23
|
+
refreshable = RefreshMe.sparkle_fetch
|
24
|
+
assert_equal refreshable, Rails.cache.read("cache_key")
|
25
|
+
assert_equal false, refreshable._sparkle_cached_
|
26
|
+
refreshable = RefreshMe.sparkle_fetch
|
27
|
+
assert_equal refreshable, Rails.cache.read("cache_key")
|
28
|
+
assert_equal true, refreshable._sparkle_cached_
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "sparkle/refreshable"
|
2
|
+
require "active_support"
|
3
|
+
|
4
|
+
class Rails
|
5
|
+
|
6
|
+
class Cache
|
7
|
+
|
8
|
+
def read(key)
|
9
|
+
@cache ||= {}
|
10
|
+
@cache[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(key, value)
|
14
|
+
@cache ||= {}
|
15
|
+
@cache[key] = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def clear
|
19
|
+
@cache = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.cache
|
25
|
+
@cache ||= Cache.new
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class RefreshMe
|
31
|
+
|
32
|
+
include ClassInheritableAttributes
|
33
|
+
include Sparkle::Refreshable
|
34
|
+
|
35
|
+
sparkle_refresh_method :slow_method
|
36
|
+
|
37
|
+
def slow_method
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sparkle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Evan Petrie
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-02-23 00:00:00 -08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: bundler
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 0
|
30
|
+
- 0
|
31
|
+
version: 1.0.0
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rake
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
- 8
|
44
|
+
- 7
|
45
|
+
version: 0.8.7
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: flexmock
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
- 8
|
58
|
+
- 11
|
59
|
+
version: 0.8.11
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: activesupport
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 2
|
71
|
+
- 3
|
72
|
+
- 11
|
73
|
+
version: 2.3.11
|
74
|
+
type: :development
|
75
|
+
version_requirements: *id004
|
76
|
+
description: Cache partials and update them asynchronously after a page renders. Slow method calls happen synchronously as the page renders for the first time. Subsequent page loads retrieve cached objects and render the page quickly, and then Ajax calls can be used to refresh components asynchronously after the page loads.
|
77
|
+
email:
|
78
|
+
- ejp@yahoo.com
|
79
|
+
executables: []
|
80
|
+
|
81
|
+
extensions: []
|
82
|
+
|
83
|
+
extra_rdoc_files: []
|
84
|
+
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- Gemfile.lock
|
89
|
+
- LICENSE
|
90
|
+
- README.rdoc
|
91
|
+
- Rakefile
|
92
|
+
- lib/sparkle.rb
|
93
|
+
- lib/sparkle/controller.rb
|
94
|
+
- lib/sparkle/helper.rb
|
95
|
+
- lib/sparkle/refreshable.rb
|
96
|
+
- lib/sparkle/version.rb
|
97
|
+
- sparkle.gemspec
|
98
|
+
- test/controller_test.rb
|
99
|
+
- test/helper_test.rb
|
100
|
+
- test/refreshable_test.rb
|
101
|
+
- test/test_helper.rb
|
102
|
+
has_rdoc: true
|
103
|
+
homepage: http://rubygems.org/gems/sparkle
|
104
|
+
licenses: []
|
105
|
+
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
segments:
|
116
|
+
- 0
|
117
|
+
version: "0"
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
segments:
|
123
|
+
- 1
|
124
|
+
- 3
|
125
|
+
- 6
|
126
|
+
version: 1.3.6
|
127
|
+
requirements: []
|
128
|
+
|
129
|
+
rubyforge_project: sparkle
|
130
|
+
rubygems_version: 1.3.6
|
131
|
+
signing_key:
|
132
|
+
specification_version: 3
|
133
|
+
summary: Asyncronous partial caching
|
134
|
+
test_files: []
|
135
|
+
|