padrino-contrib 0.1.13 → 0.2.0
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.
Potentially problematic release.
This version of padrino-contrib might be problematic. Click here for more details.
- checksums.yaml +15 -0
- data/.gitignore +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +20 -1
- data/README.md +38 -0
- data/Rakefile +1 -2
- data/lib/padrino-contrib.rb +1 -0
- data/lib/padrino-contrib/auto_locale.rb +60 -5
- data/lib/padrino-contrib/exception_notifier.rb +10 -3
- data/lib/padrino-contrib/helpers/breadcrumbs.rb +185 -0
- data/lib/padrino-contrib/version.rb +1 -1
- data/spec/auto_locale_spec.rb +126 -0
- data/spec/breadcrumb_helpers_spec.rb +133 -0
- data/spec/exception_notifier_spec.rb +102 -0
- data/spec/spec_helper.rb +21 -1
- data/spec/views/exception_notifier/errors.erb +1 -0
- data/spec/views/exception_notifier/layouts/layout.erb +2 -0
- metadata +34 -44
- data/README.rdoc +0 -32
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OGM1NTY5NDMyOWVkZDVhNGZkZjQxNTRkMzYzNDEzZDY1ODFmZWZiOQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MzJkM2EyN2EwOWQ4NjAzNWMwMWM4MzI4N2U1OTA1NzE5Yzk2MWE4Nw==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YjdmODEyZjQ3MmE5OGE4Y2ZmMGE2MGEyNTk3NTFhMTA4YTlkZWMxMzg4ZjYw
|
10
|
+
ZTQ0NDUwN2RjNjk5OGUzMzAxODg1NmU2NmRmYzYyOTA0MTU1ZTc3ZmFmNmRi
|
11
|
+
ZmNiYzA2OTQ0YTRhMjk3MGVkMjk0OTU3MjI0MWE5NTIzNDMzYWI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ODk0ZDk5ZmE5OWQ2ODY2YjU5ZmFhYmM4MGFjZDFhYmQ4YWJlYjJjN2FmMGNm
|
14
|
+
YWM5NTFhNDg5YTM5YTZmNzYxNjIwYTI3ZTEzZjU0NTVmYzc2NWU2ODU1YjUx
|
15
|
+
MWE5ZDcxYWYyMTA2OTY0Mzk2MWJjOWIwZWEwODIzMzFjNmRjZDU=
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -1,4 +1,23 @@
|
|
1
|
-
source "
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in padrino-contrib.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
gem 'i18n'
|
7
|
+
|
8
|
+
group :development do
|
9
|
+
gem 'rake'
|
10
|
+
end
|
11
|
+
|
12
|
+
group :test, :development do
|
13
|
+
gem 'pry-debugger' unless ENV['TRAVIS']
|
14
|
+
end
|
15
|
+
|
16
|
+
group :test do
|
17
|
+
gem 'padrino-core'
|
18
|
+
gem 'padrino-helpers'
|
19
|
+
gem 'padrino-mailer'
|
20
|
+
gem 'rspec'
|
21
|
+
gem 'rack-test'
|
22
|
+
gem 'webrat'
|
23
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# padrino-contrib
|
2
|
+
|
3
|
+
## Contributed Plugins and Utilities
|
4
|
+
|
5
|
+
This package includes a variety of add-on components for Padrino Framework:
|
6
|
+
|
7
|
+
* breadcrumbs - Simple breadcrumb navigation helpers
|
8
|
+
* exception_notifier - Send errors through mail or/and to redmine
|
9
|
+
* auto_locale - Switch for you automatically the I18n.locale
|
10
|
+
* flash_session - Middleware that help you in passing your session in the URI, when it should be in the cookie.
|
11
|
+
* orm_ar_permalink - Generate permalink for a specified column on ActiveRecord
|
12
|
+
* orm_ar_permalink_i18n - Generate permalink for a specified multi language column(s) on ActiveRecord
|
13
|
+
* orm_ar_translate - Translate for you your ActiveRecord columns
|
14
|
+
* orm_mm_permalink - Generate permalink for a specified column on MongoMapper
|
15
|
+
* orm_mm_search - Full text search in MongoMapper in specified columns
|
16
|
+
* helpers_assets_compressor - Joins and compress your js/css with yui-compressor
|
17
|
+
|
18
|
+
## Use
|
19
|
+
|
20
|
+
In your Padrino project edit:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
# Gemfile
|
24
|
+
gem 'padrino-contrib'
|
25
|
+
|
26
|
+
# boot.rb
|
27
|
+
require 'padrino-contrib/exception_notifier'
|
28
|
+
# require 'padrino-contrib/orm/active_record/permalink'
|
29
|
+
# require 'padrino-contrib/orm/active_record/textile'
|
30
|
+
# require 'padrino-contrib/helpers/breadcrumbs'
|
31
|
+
|
32
|
+
Padrino.load! # Remember to add contribs before that line!
|
33
|
+
|
34
|
+
# app.rb
|
35
|
+
class MyApp < Padrino::Application
|
36
|
+
register Padrino::Contrib::ExceptionNotifier
|
37
|
+
end
|
38
|
+
```
|
data/Rakefile
CHANGED
@@ -21,8 +21,7 @@ end
|
|
21
21
|
task :release => :bump
|
22
22
|
|
23
23
|
desc "Run complete application spec suite"
|
24
|
-
RSpec::Core::RakeTask.new(
|
25
|
-
t.skip_bundler = true
|
24
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
26
25
|
t.pattern = './spec/**/*_spec.rb'
|
27
26
|
t.rspec_opts = %w(-fs --color --fail-fast)
|
28
27
|
end
|
data/lib/padrino-contrib.rb
CHANGED
@@ -9,6 +9,7 @@ module Padrino
|
|
9
9
|
autoload :AssetsCompressor, 'padrino-contrib/helpers/assets_compressor.rb'
|
10
10
|
autoload :JQuery, 'padrino-contrib/helpers/jquery.rb'
|
11
11
|
autoload :Flash, 'padrino-contrib/helpers/flash.rb'
|
12
|
+
autoload :Breadcrumbs, 'padrino-contrib/helpers/breadcrumbs.rb'
|
12
13
|
end # Helpers
|
13
14
|
end # Contrib
|
14
15
|
end # Padrino
|
@@ -9,6 +9,7 @@ module Padrino
|
|
9
9
|
# class MyApp < Padrino::Application
|
10
10
|
# register AutoLocale
|
11
11
|
# set :locales, [:en, :ru, :de] # First locale is the default locale
|
12
|
+
# set :locale_exclusive_paths, ['/js', '/css', '/img'] # asset uri paths which shouldn't be localized
|
12
13
|
# end
|
13
14
|
#
|
14
15
|
# # view.haml
|
@@ -22,7 +23,9 @@ module Padrino
|
|
22
23
|
# This reload the page changing the I18n.locale
|
23
24
|
#
|
24
25
|
def switch_to_lang(lang)
|
25
|
-
|
26
|
+
return unless settings.locales.include?(lang)
|
27
|
+
return url("/#{lang}", false) if request.path_info == '/'
|
28
|
+
url(request.path_info.sub(/\/#{I18n.locale}/, "/#{lang}"), false)
|
26
29
|
end
|
27
30
|
end # Helpers
|
28
31
|
|
@@ -30,17 +33,69 @@ module Padrino
|
|
30
33
|
app.helpers Padrino::Contrib::AutoLocale::Helpers
|
31
34
|
app.extend ClassMethods
|
32
35
|
app.set :locales, [:en]
|
36
|
+
app.set :locale_exclusive_paths, []
|
37
|
+
@@exclusive_paths = false
|
33
38
|
app.before do
|
39
|
+
# Gather excluded paths
|
40
|
+
unless @@exclusive_paths.is_a?(Array)
|
41
|
+
# auto include sinatra-assetpack configuration
|
42
|
+
if settings.respond_to?(:assets) and
|
43
|
+
settings.assets.respond_to?(:served) and
|
44
|
+
settings.assets.served.is_a?(Hash)
|
45
|
+
@@exclusive_paths = settings.locale_exclusive_paths + settings.assets.served.keys
|
46
|
+
else
|
47
|
+
@@exclusive_paths = settings.locale_exclusive_paths
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Default to the first locale
|
52
|
+
I18n.locale = settings.locales.first
|
53
|
+
|
54
|
+
# First check if the path starts with a known locale
|
34
55
|
if request.path_info =~ /^\/(#{settings.locales.join('|')})\b/
|
35
56
|
I18n.locale = $1.to_sym
|
57
|
+
|
58
|
+
# Then check if the path is excluded
|
59
|
+
elsif AutoLocale.excluded_path?(request.path_info, @@exclusive_paths)
|
60
|
+
next
|
61
|
+
|
62
|
+
# Root path "/" needs special treatment, as it doesn't contain any language parameter.
|
63
|
+
elsif request.path_info =~ /^\/?$/
|
64
|
+
# Try to guess the preferred language from the http header
|
65
|
+
for browser_locale in (request.env['HTTP_ACCEPT_LANGUAGE'] || '').split(",")
|
66
|
+
locale = browser_locale.split(";").first.downcase.sub('-', '_')
|
67
|
+
if settings.locales.include?(locale.to_sym)
|
68
|
+
I18n.locale = locale.to_sym
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
# Then redirect from "/" to "/:lang" to match the new routing urls
|
73
|
+
redirect url("/#{I18n.locale.to_s}/", false)
|
74
|
+
|
75
|
+
# Return 404 not found for everything else
|
36
76
|
else
|
37
|
-
|
38
|
-
not_found if request.path_info !~ /^\/?$/
|
77
|
+
not_found
|
39
78
|
end
|
40
79
|
end
|
41
80
|
|
42
81
|
def self.padrino_route_added(route, verb, path, args, options, block)
|
43
|
-
|
82
|
+
##
|
83
|
+
# TODO: Regex original_path needs to be served as well.
|
84
|
+
#
|
85
|
+
return unless route.original_path.is_a?(String)
|
86
|
+
excluded_paths = block.binding.eval('settings').locale_exclusive_paths
|
87
|
+
return if AutoLocale.excluded_path?(route.original_path, excluded_paths)
|
88
|
+
route.path = "/:lang#{route.original_path}" unless route.original_path =~ /:lang/
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.excluded_path?(path, excluded_paths)
|
92
|
+
excluded_paths.detect do |excluded_path|
|
93
|
+
if excluded_path.is_a?(Regexp)
|
94
|
+
!!excluded_path.match(path)
|
95
|
+
elsif excluded_path.is_a?(String)
|
96
|
+
path.start_with?(excluded_path.end_with?("/") ? excluded_path : "#{excluded_path}/")
|
97
|
+
end
|
98
|
+
end
|
44
99
|
end
|
45
100
|
end
|
46
101
|
|
@@ -50,7 +105,7 @@ module Padrino
|
|
50
105
|
#
|
51
106
|
def url(*args)
|
52
107
|
params = args.extract_options!
|
53
|
-
params[:lang]
|
108
|
+
params[:lang] ||= I18n.locale
|
54
109
|
args << params
|
55
110
|
super(*args)
|
56
111
|
end
|
@@ -23,16 +23,23 @@ module Padrino
|
|
23
23
|
app.set :exceptions_subject, "Exception" unless app.respond_to?(:exceptions_subject)
|
24
24
|
app.set :exceptions_to, "errors@localhost.local" unless app.respond_to?(:exceptions_to)
|
25
25
|
app.set :exceptions_from, "foo@bar.local" unless app.respond_to?(:exceptions_from)
|
26
|
-
app.set :exceptions_layout, :
|
26
|
+
app.set :exceptions_layout, :application unless app.respond_to?(:exceptions_layout)
|
27
27
|
app.set :exceptions_views, app.views unless app.respond_to?(:exceptions_views)
|
28
28
|
app.set :redmine, {} unless app.respond_to?(:redmine)
|
29
|
+
app.set :exceptions_params_filter, ['password', 'password_confirmation'] unless app.respond_to?(:exceptions_params_filter)
|
29
30
|
app.error 500 do
|
30
31
|
boom = env['sinatra.error']
|
31
32
|
body = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n ")
|
32
33
|
body += "\n\n---Env:\n"
|
33
34
|
env.each { |k,v| body += "\n#{k}: #{v}" }
|
34
35
|
body += "\n\n---Params:\n"
|
35
|
-
params.each
|
36
|
+
params.each do |k,v|
|
37
|
+
if settings.exceptions_params_filter.include?(k)
|
38
|
+
body += "\n#{k.inspect} => [FILTERED]"
|
39
|
+
else
|
40
|
+
body += "\n#{k.inspect} => #{v.inspect}"
|
41
|
+
end
|
42
|
+
end
|
36
43
|
logger.error body
|
37
44
|
settings.redmine.each { |k,v| body += "\n#{k.to_s.capitalize}: #{v}" }
|
38
45
|
app.email do
|
@@ -45,7 +52,7 @@ module Padrino
|
|
45
52
|
content_type 'text/html', :charset => "utf-8"
|
46
53
|
render settings.exceptions_page, :layout => settings.exceptions_layout, :views => settings.exceptions_views
|
47
54
|
end
|
48
|
-
app.
|
55
|
+
app.not_found do
|
49
56
|
response.status = 404
|
50
57
|
content_type 'text/html', :charset => "utf-8"
|
51
58
|
render settings.exceptions_page, :layout => settings.exceptions_layout, :views => settings.exceptions_views
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Contrib
|
3
|
+
module Helpers
|
4
|
+
class Breadcrumb
|
5
|
+
attr_accessor :home, :items
|
6
|
+
|
7
|
+
DEFAULT_URL = "/"
|
8
|
+
DEFAULT_CAPTION ="Home Page"
|
9
|
+
|
10
|
+
##
|
11
|
+
# Initialize breadcrumbs with default value.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# before do
|
15
|
+
# @breadcrumbs = breadcrumbs.new
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
def initialize
|
19
|
+
reset!
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Set the custom home (Parent) link.
|
24
|
+
#
|
25
|
+
# @param [String] url
|
26
|
+
# The url href.
|
27
|
+
#
|
28
|
+
# @param [String] caption
|
29
|
+
# The text caption.
|
30
|
+
#
|
31
|
+
# @param [Hash] options
|
32
|
+
# The HTML options to include in li.
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# breadcrumbs.set_home "/HomeFoo", "Foo Home", :id => "home-breadcrumb"
|
36
|
+
#
|
37
|
+
def set_home(url, caption, options = {})
|
38
|
+
self.home = {
|
39
|
+
:url => url.to_s,
|
40
|
+
:caption => caption.to_s.humanize.html_safe,
|
41
|
+
:name => :home,
|
42
|
+
:options => options
|
43
|
+
}
|
44
|
+
reset
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Reset breadcrumbs to default or personal home.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# breadcrumbs.reset
|
52
|
+
#
|
53
|
+
def reset
|
54
|
+
self.items = []
|
55
|
+
self.items << home
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Reset breadcrumbs to default home.
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# breadcrumbs.reset!
|
63
|
+
#
|
64
|
+
def reset!
|
65
|
+
self.home = {
|
66
|
+
:name => :home,
|
67
|
+
:url => DEFAULT_URL,
|
68
|
+
:caption => DEFAULT_CAPTION,
|
69
|
+
:options => {}
|
70
|
+
}
|
71
|
+
reset
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Add a new breadcrumbs.
|
76
|
+
#
|
77
|
+
# @param [String] name
|
78
|
+
# The name of resource.
|
79
|
+
# @param [Symbol] name
|
80
|
+
# The name of resource.
|
81
|
+
#
|
82
|
+
# @param [String] url
|
83
|
+
# The url href.
|
84
|
+
#
|
85
|
+
# @param [String] caption
|
86
|
+
# The text caption.
|
87
|
+
#
|
88
|
+
# @param [Hash] options
|
89
|
+
# The HTML options to include in li.
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# breadcrumbs.add "foo", "/foo", "Foo Link", :id => "foo-id"
|
93
|
+
# breadcrumbs.add :foo, "/foo", "Foo Link", :class => "foo-class"
|
94
|
+
#
|
95
|
+
def add(name, url, caption, options = {})
|
96
|
+
items << {
|
97
|
+
:name => name.to_sym,
|
98
|
+
:url => url.to_s,
|
99
|
+
:caption => caption.to_s.humanize.html_safe,
|
100
|
+
:options => options
|
101
|
+
}
|
102
|
+
end
|
103
|
+
alias :<< :add
|
104
|
+
|
105
|
+
##
|
106
|
+
# Remove a breadcrumb.
|
107
|
+
#
|
108
|
+
# @param [String] name
|
109
|
+
# The name of resource to delete from breadcrumbs list.
|
110
|
+
#
|
111
|
+
# @example
|
112
|
+
# breadcrumbs.del "foo"
|
113
|
+
# breadcrumbs.del :foo
|
114
|
+
#
|
115
|
+
def del(name)
|
116
|
+
items.delete_if { |item| item[:name] == name.to_sym }
|
117
|
+
end
|
118
|
+
end # Breadcrumb
|
119
|
+
|
120
|
+
module Breadcrumbs
|
121
|
+
##
|
122
|
+
# Render breadcrumbs to view.
|
123
|
+
#
|
124
|
+
# @param [Breadcrumbs] breadcrumbs
|
125
|
+
# The breadcrumbs to render into view.
|
126
|
+
#
|
127
|
+
# @param [Boolean] bootstrap
|
128
|
+
# If true, render separation (useful with Twitter Bootstrap).
|
129
|
+
#
|
130
|
+
# @param [String] active
|
131
|
+
# CSS class style set to active breadcrumb.
|
132
|
+
#
|
133
|
+
# @param [Hash] options
|
134
|
+
# The HTML options to include in ul.
|
135
|
+
#
|
136
|
+
# @return [String] Unordered list with breadcrumbs
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# = breadcrumbs @breacrumbs
|
140
|
+
# # Generates:
|
141
|
+
# # <ul>
|
142
|
+
# # <li><a href="/foo">Foo Link</a></li>
|
143
|
+
# # <li class="active"><a href="/bar">Bar Link</a></li>
|
144
|
+
# # </ul>
|
145
|
+
#
|
146
|
+
def breadcrumbs(breadcrumbs, bootstrap = false, active = "active", options = {})
|
147
|
+
content = ActiveSupport::SafeBuffer.new
|
148
|
+
breadcrumbs.items[0..-2].each do |item|
|
149
|
+
content << render_item(item, bootstrap)
|
150
|
+
end
|
151
|
+
last = breadcrumbs.items.last
|
152
|
+
last_options = last[:options]
|
153
|
+
last = link_to(last[:caption], last[:url])
|
154
|
+
|
155
|
+
classes = [options[:class], last_options[:class]].map { |class_name| class_name.to_s.split(/\s/) }
|
156
|
+
classes[0] << "breadcrumb"
|
157
|
+
classes[1] << active if active
|
158
|
+
options[:class], last_options[:class] = classes.map { |class_name| class_name * " " }
|
159
|
+
|
160
|
+
content << content_tag(:li, last, last_options)
|
161
|
+
content_tag(:ul, content, options)
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
##
|
166
|
+
# Private method to return list item.
|
167
|
+
#
|
168
|
+
# @param [Hash] item
|
169
|
+
# The breadcrumb item.
|
170
|
+
#
|
171
|
+
# @param [Boolean] bootstrap
|
172
|
+
# If true, render separation (useful with Twitter Bootstrap).
|
173
|
+
#
|
174
|
+
# @return [String] List item with breadcrumb
|
175
|
+
#
|
176
|
+
def render_item(item, bootstrap)
|
177
|
+
content = ActiveSupport::SafeBuffer.new
|
178
|
+
content << link_to(item[:caption], item[:url])
|
179
|
+
content << content_tag(:span, "/", :class => "divider") if bootstrap
|
180
|
+
content_tag(:li, content, item[:options])
|
181
|
+
end
|
182
|
+
end # Breadcrumbs
|
183
|
+
end # Helpers
|
184
|
+
end # Contrib
|
185
|
+
end # Padrino
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Padrino::Contrib::AutoLocale do
|
4
|
+
before :all do
|
5
|
+
mock_app {
|
6
|
+
register Padrino::Contrib::AutoLocale
|
7
|
+
set :locales, [ :es, :en, :ru ]
|
8
|
+
|
9
|
+
get('/') { 'root' }
|
10
|
+
get('/bar') { 'bar' }
|
11
|
+
get(:foo) { 'foo' }
|
12
|
+
}
|
13
|
+
end
|
14
|
+
after(:each) { I18n.locale = I18n.default_locale }
|
15
|
+
|
16
|
+
describe 'when requesting a localized path' do
|
17
|
+
it 'sets I18n.locale to the requested locale' do
|
18
|
+
expect { get '/ru' }.to change { I18n.locale }.to :ru
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns 404 if requesting an unsupported locale' do
|
22
|
+
get '/ja'
|
23
|
+
expect(last_response).to be_not_found
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when requesting the root path '/'" do
|
28
|
+
it 'redirects to the default locale' do
|
29
|
+
get '/'
|
30
|
+
expect(last_response).to be_redirect
|
31
|
+
follow_redirect!
|
32
|
+
expect(last_request.path_info).to eq "/#{@app.locales.first}/"
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'sets I18n.locale to the first locale' do
|
36
|
+
expect { get '/' }.to change { I18n.locale }.to @app.locales.first
|
37
|
+
end
|
38
|
+
|
39
|
+
it "doesn't choke when the HTTP_ACCEPT_LANGUAGE header is not present" do
|
40
|
+
expect do
|
41
|
+
get '/', { }, 'HTTP_ACCEPT_LANGUAGE' => nil
|
42
|
+
end.not_to raise_exception NoMethodError
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'when setting locale_exclusive_paths' do
|
47
|
+
before :each do
|
48
|
+
mock_app {
|
49
|
+
register Padrino::Contrib::AutoLocale
|
50
|
+
set :locales, [ :es, :en ]
|
51
|
+
set :locale_exclusive_paths, [ '/unlocalized' ]
|
52
|
+
get('/bar') { 'bar' }
|
53
|
+
get('/unlocalized/path') { 'unlocalized path' }
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns 404 if the path is not excluded' do
|
58
|
+
get '/bar'
|
59
|
+
expect(last_response).to be_not_found
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when the path is excluded' do
|
63
|
+
it 'does not prepend :lang to the route' do
|
64
|
+
expect(@app.routes.last.original_path).not_to match /:lang/
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'lets the request through' do
|
68
|
+
get '/unlocalized/path'
|
69
|
+
expect(last_response).to be_ok
|
70
|
+
expect(last_response.body).to eq 'unlocalized path'
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'sets I18n.locale to the first locale' do
|
74
|
+
expect { get '/unlocalized/path' }.to change { I18n.locale }.to @app.locales.first
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'allows excluding the root path "/"' do
|
79
|
+
@app.locale_exclusive_paths << /^\/?$/
|
80
|
+
@app.get('/') { 'root path' }
|
81
|
+
get '/'
|
82
|
+
expect(last_response).not_to be_redirect
|
83
|
+
expect(last_response.body).to eq 'root path'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'overloaded #url' do
|
88
|
+
before(:each) { get '/en' }
|
89
|
+
|
90
|
+
it 'prepends the current lang' do
|
91
|
+
expect(@app.url(:foo)).to eq '/en/foo'
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'allows overriding lang' do
|
95
|
+
expect(@app.url(:foo, :lang => 'ru')).to eq '/ru/foo'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#switch_to_lang' do
|
100
|
+
before :all do
|
101
|
+
mock_app {
|
102
|
+
register Padrino::Contrib::AutoLocale
|
103
|
+
set :locales, [ :es, :en ]
|
104
|
+
set :locale_exclusive_paths, [ /^\/?$/, '/unlocalized' ]
|
105
|
+
|
106
|
+
get('/(:lang)') { switch_to_lang(:en) }
|
107
|
+
get('/unlocalized/path') { switch_to_lang(:en) }
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns the current localized path switched to the requested lang' do
|
112
|
+
get '/es'
|
113
|
+
expect(last_response.body).to eq '/en'
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'switches the unlocalized root path to the requested lang' do
|
117
|
+
get '/'
|
118
|
+
expect(last_response.body).to eq '/en'
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'returns the same path if the path is not localized' do
|
122
|
+
get '/unlocalized/path'
|
123
|
+
expect(last_response.body).to eq '/unlocalized/path'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "BreadcrumbHelpers" do
|
4
|
+
include Padrino::Helpers::OutputHelpers
|
5
|
+
include Padrino::Helpers::TagHelpers
|
6
|
+
include Padrino::Helpers::AssetTagHelpers
|
7
|
+
include Padrino::Contrib::Helpers::Breadcrumbs
|
8
|
+
|
9
|
+
let(:breadcrumb){ Padrino::Helpers::Breadcrumb.new }
|
10
|
+
after(:each) { breadcrumb.reset! }
|
11
|
+
|
12
|
+
describe "for Breadcrumbs#breadcrumbs method" do
|
13
|
+
it 'should support breadcrumbs which is Padrino::Helpers::Breadcrumbs instance.' do
|
14
|
+
breadcrumb.add "foo", "/foo", "foo link"
|
15
|
+
expect(breadcrumbs(breadcrumb)).to have_selector(:a, :content => "Foo link", :href => "/foo")
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should support bootstrap' do
|
19
|
+
breadcrumb.add "foo", "/foo", "foo link"
|
20
|
+
expect(breadcrumbs(breadcrumb, true)).to have_selector(:span, :content => "/", :class => "divider")
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should support active' do
|
24
|
+
breadcrumb.add "foo", "/foo", "foo link"
|
25
|
+
expect(breadcrumbs(breadcrumb, nil, "custom-active")).to have_selector(:li, :class => "custom-active")
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should support options' do
|
29
|
+
actual_html = breadcrumbs(breadcrumb, nil, nil, :id => "breadcrumbs-id", :class => "breadcrumbs-class")
|
30
|
+
expect(actual_html).to have_selector(:ul, :class => "breadcrumbs-class breadcrumb", :id => "breadcrumbs-id")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "for #add method" do
|
35
|
+
it 'should support name of string and symbol type' do
|
36
|
+
breadcrumb.add "foo", "/foo", "Foo Link"
|
37
|
+
breadcrumb.add :bar, "/bar", "Bar Link"
|
38
|
+
|
39
|
+
actual_html = breadcrumbs(breadcrumb)
|
40
|
+
expect(actual_html).to have_selector(:a, :content => "Foo link", :href => "/foo")
|
41
|
+
expect(actual_html).to have_selector(:a, :content => "Bar link", :href => "/bar")
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should support url' do
|
45
|
+
breadcrumb.add :foo, "/foo", "Foo Link"
|
46
|
+
expect(breadcrumbs(breadcrumb)).to have_selector(:a, :href => "/foo")
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should support caption' do
|
50
|
+
breadcrumb.add :foo, "/foo", "Foo Link"
|
51
|
+
expect(breadcrumbs(breadcrumb)).to have_selector(:a, :content => "Foo link")
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should support options' do
|
55
|
+
breadcrumb.add :foo, "/foo", "Foo Link", :id => "foo-id", :class => "foo-class"
|
56
|
+
breadcrumb.add :bar, "/bar", "Bar Link", :id => "bar-id", :class => "bar-class"
|
57
|
+
|
58
|
+
actual_html = breadcrumbs(breadcrumb)
|
59
|
+
expect(actual_html).to have_selector(:li, :class => "foo-class", :id => "foo-id")
|
60
|
+
expect(actual_html).to have_selector(:li, :class => "bar-class active", :id => "bar-id")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "for #del method" do
|
65
|
+
it 'should support name of string type' do
|
66
|
+
breadcrumb.add "foo", "/foo", "Foo Link"
|
67
|
+
breadcrumb.add :bar, "/bar", "Bar Link"
|
68
|
+
|
69
|
+
breadcrumb.del "foo"
|
70
|
+
breadcrumb.del "bar"
|
71
|
+
|
72
|
+
actual_html = breadcrumbs(breadcrumb)
|
73
|
+
expect(actual_html).not_to have_selector(:a, :content => "Foo link", :href => "/foo")
|
74
|
+
expect(actual_html).not_to have_selector(:a, :content => "Bar link", :href => "/bar")
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should support name of symbol type' do
|
78
|
+
breadcrumb.add "foo", "/foo", "Foo Link"
|
79
|
+
breadcrumb.add :bar, "/bar", "Bar Link"
|
80
|
+
|
81
|
+
breadcrumb.del :foo
|
82
|
+
breadcrumb.del :bar
|
83
|
+
|
84
|
+
actual_html = breadcrumbs(breadcrumb)
|
85
|
+
expect(actual_html).not_to have_selector(:a, :content => "Foo link", :href => "/foo")
|
86
|
+
expect(actual_html).not_to have_selector(:a, :content => "Bar link", :href => "/bar")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "for #set_home method" do
|
91
|
+
it 'should modified home item elements.' do
|
92
|
+
breadcrumb.set_home("/custom", "Custom Home Page")
|
93
|
+
expect(breadcrumbs(breadcrumb)).to have_selector(:a, :content => "Custom home page", :href => "/custom")
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should support options' do
|
97
|
+
breadcrumb.set_home("/custom", "Custom Home Page", :id => "home-id")
|
98
|
+
|
99
|
+
actual_html = breadcrumbs(breadcrumb)
|
100
|
+
expect(actual_html).to have_selector(:li, :id => "home-id")
|
101
|
+
expect(actual_html).to have_selector(:a, :content => "Custom home page", :href => "/custom")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "for #reset method" do
|
106
|
+
it 'should be #items which contains only home item.' do
|
107
|
+
breadcrumb.set_home("/custom", "Custom Home Page")
|
108
|
+
breadcrumb.add "foo", "/foo", "Foo Link"
|
109
|
+
breadcrumb.add :bar, "/bar", "Bar Link"
|
110
|
+
|
111
|
+
breadcrumb.reset
|
112
|
+
|
113
|
+
actual_html = breadcrumbs(breadcrumb)
|
114
|
+
expect(actual_html).to have_selector(:a, :content => "Custom home page", :href => "/custom")
|
115
|
+
expect(actual_html).not_to have_selector(:a, :content => "Foo link", :href => "/foo")
|
116
|
+
expect(actual_html).not_to have_selector(:a, :content => "Bar link", :href => "/bar")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "for #reset! method" do
|
121
|
+
it 'should be #items which contains only default home item.' do
|
122
|
+
breadcrumb.add "foo", "/foo", "foo link"
|
123
|
+
breadcrumb.add :bar, "/bar", "Bar Link"
|
124
|
+
|
125
|
+
breadcrumb.reset!
|
126
|
+
|
127
|
+
actual_html = breadcrumbs(breadcrumb)
|
128
|
+
expect(actual_html).to have_selector(:a, :content => "Home Page", :href => "/")
|
129
|
+
expect(actual_html).not_to have_selector(:a, :content => "Foo link", :href => "/foo")
|
130
|
+
expect(actual_html).not_to have_selector(:a, :content => "Bar link", :href => "/bar")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mail'
|
3
|
+
|
4
|
+
describe Padrino::Contrib::ExceptionNotifier do
|
5
|
+
before :each do
|
6
|
+
mock_app {
|
7
|
+
register Padrino::Helpers
|
8
|
+
register Padrino::Mailer
|
9
|
+
set :delivery_method, :test => { } # Resembles a more realistic SMTP configuration
|
10
|
+
register Padrino::Contrib::ExceptionNotifier
|
11
|
+
set :exceptions_views, Padrino.root('views/exception_notifier')
|
12
|
+
set :exceptions_page, 'errors.erb'
|
13
|
+
set :exceptions_layout, "layout.erb"
|
14
|
+
|
15
|
+
get(:boom) { 420 / 0 }
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def last_email
|
20
|
+
Mail::TestMailer.deliveries.last
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when an exception is raised' do
|
24
|
+
it 'sends a notification email' do
|
25
|
+
expect { get '/boom' }.to change { Mail::TestMailer.deliveries }
|
26
|
+
expect(last_email.subject).to eq '[Exception] ZeroDivisionError - divided by 0'
|
27
|
+
expect(last_email.to).to eq [ 'errors@localhost.local' ]
|
28
|
+
expect(last_email.from).to eq [ 'foo@bar.local' ]
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'allows customizing the email subject' do
|
32
|
+
@app.set :exceptions_subject, 'Custom subject'
|
33
|
+
get '/boom'
|
34
|
+
expect(last_email.subject).to eq '[Custom subject] ZeroDivisionError - divided by 0'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'allows customizing the recipients' do
|
38
|
+
@app.set :exceptions_to, 'Foo <foo@example.com>, bar@example.com'
|
39
|
+
get '/boom'
|
40
|
+
expect(last_email.to).to eq [ 'foo@example.com', 'bar@example.com' ]
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'allows customizing the sender' do
|
44
|
+
@app.set :exceptions_from, 'Foo <foo@example.com>'
|
45
|
+
get '/boom'
|
46
|
+
expect(last_email.from).to eq [ 'foo@example.com' ]
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'sends the request params' do
|
50
|
+
get '/boom', :foo => 'bar'
|
51
|
+
expect(last_email.body).to match /"foo" => "bar"/
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'filters out password and password_confirmation params' do
|
55
|
+
get '/boom', :password => 'super_secret', :password_confirmation => 'super_secret'
|
56
|
+
expect(last_email.body).to match /"password" => \[FILTERED\]/
|
57
|
+
expect(last_email.body).to match /"password_confirmation" => \[FILTERED\]/
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'allows customizing filtered params' do
|
61
|
+
@app.set :exceptions_params_filter, [ 'foo' ]
|
62
|
+
get '/boom', :foo => 'bar'
|
63
|
+
expect(last_email.body).to match /"foo" => \[FILTERED\]/
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'renders an error page' do
|
67
|
+
get '/boom'
|
68
|
+
expect(last_response.status).to eq 500
|
69
|
+
expect(last_response.body).to eq "Exceptions layout\n500"
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'allows customizing the error layout' do
|
73
|
+
@app.set :exceptions_layout, nil
|
74
|
+
get '/boom'
|
75
|
+
expect(last_response.body).to eq '500'
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'requires a view to be configured for the error page' do
|
79
|
+
@app.instance_eval { undef :exceptions_page }
|
80
|
+
expect { get '/boom' }.to raise_exception NoMethodError
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when a 404 is returned' do
|
85
|
+
it 'renders an error page' do
|
86
|
+
get '/not_found'
|
87
|
+
expect(last_response.status).to eq 404
|
88
|
+
expect(last_response.body).to eq "Exceptions layout\n404"
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'allows customizing the error layout' do
|
92
|
+
@app.set :exceptions_layout, nil
|
93
|
+
get '/not_found'
|
94
|
+
expect(last_response.body).to eq '404'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'requires a view to be configured for the error page' do
|
98
|
+
@app.instance_eval { undef :exceptions_page }
|
99
|
+
expect { get '/not_found' }.to raise_exception NoMethodError
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
PADRINO_ENV = 'test'
|
2
|
+
PADRINO_ROOT = File.dirname(__FILE__) unless defined?(PADRINO_ROOT)
|
1
3
|
require 'rubygems' unless defined?(Gem)
|
2
|
-
require '
|
4
|
+
require 'bundler'
|
5
|
+
Bundler.require(:default, PADRINO_ENV)
|
6
|
+
|
3
7
|
require 'padrino-contrib'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.include Rack::Test::Methods
|
11
|
+
config.include Webrat::Matchers
|
12
|
+
|
13
|
+
# Sets up a Sinatra::Base subclass defined with the block
|
14
|
+
# given. Used in setup or individual spec methods to establish
|
15
|
+
# the application.
|
16
|
+
def mock_app(base=Padrino::Application, &block)
|
17
|
+
@app = Sinatra.new(base, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def app
|
21
|
+
@app
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= response.status %>
|
metadata
CHANGED
@@ -1,44 +1,35 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: padrino-contrib
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
- 13
|
10
|
-
version: 0.1.13
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Davide D'Agostino
|
14
8
|
- Nathan Esquenazi
|
15
9
|
- Arthur Chiu
|
16
10
|
autorequire:
|
17
11
|
bindir: bin
|
18
12
|
cert_chain: []
|
19
|
-
|
20
|
-
date: 2012-02-14 00:00:00 Z
|
13
|
+
date: 2014-08-14 00:00:00.000000000 Z
|
21
14
|
dependencies: []
|
22
|
-
|
23
15
|
description: Contributed plugins and utilities for the Padrino Ruby Web Framework
|
24
16
|
email: padrinorb@gmail.com
|
25
17
|
executables: []
|
26
|
-
|
27
18
|
extensions: []
|
28
|
-
|
29
19
|
extra_rdoc_files: []
|
30
|
-
|
31
|
-
files:
|
20
|
+
files:
|
32
21
|
- .gitignore
|
22
|
+
- .travis.yml
|
33
23
|
- Gemfile
|
34
24
|
- LICENSE
|
35
|
-
- README.
|
25
|
+
- README.md
|
36
26
|
- Rakefile
|
37
27
|
- lib/padrino-contrib.rb
|
38
28
|
- lib/padrino-contrib/auto_locale.rb
|
39
29
|
- lib/padrino-contrib/exception_notifier.rb
|
40
30
|
- lib/padrino-contrib/flash_session.rb
|
41
31
|
- lib/padrino-contrib/helpers/assets_compressor.rb
|
32
|
+
- lib/padrino-contrib/helpers/breadcrumbs.rb
|
42
33
|
- lib/padrino-contrib/helpers/flash.rb
|
43
34
|
- lib/padrino-contrib/helpers/jquery.rb
|
44
35
|
- lib/padrino-contrib/orm/active_record/permalink.rb
|
@@ -49,42 +40,41 @@ files:
|
|
49
40
|
- lib/padrino-contrib/orm/mongo_mapper/search.rb
|
50
41
|
- lib/padrino-contrib/version.rb
|
51
42
|
- padrino-contrib.gemspec
|
43
|
+
- spec/auto_locale_spec.rb
|
52
44
|
- spec/autoload_spec.rb
|
45
|
+
- spec/breadcrumb_helpers_spec.rb
|
46
|
+
- spec/exception_notifier_spec.rb
|
53
47
|
- spec/spec_helper.rb
|
48
|
+
- spec/views/exception_notifier/errors.erb
|
49
|
+
- spec/views/exception_notifier/layouts/layout.erb
|
54
50
|
homepage: http://www.padrinorb.com
|
55
51
|
licenses: []
|
56
|
-
|
52
|
+
metadata: {}
|
57
53
|
post_install_message:
|
58
54
|
rdoc_options: []
|
59
|
-
|
60
|
-
require_paths:
|
55
|
+
require_paths:
|
61
56
|
- lib
|
62
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
none: false
|
73
|
-
requirements:
|
74
|
-
- - ">="
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
hash: 3
|
77
|
-
segments:
|
78
|
-
- 0
|
79
|
-
version: "0"
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
80
67
|
requirements: []
|
81
|
-
|
82
68
|
rubyforge_project: padrino-contrib
|
83
|
-
rubygems_version:
|
69
|
+
rubygems_version: 2.0.7
|
84
70
|
signing_key:
|
85
|
-
specification_version:
|
71
|
+
specification_version: 4
|
86
72
|
summary: Contributed plugins and utilities for Padrino Framework
|
87
|
-
test_files:
|
73
|
+
test_files:
|
74
|
+
- spec/auto_locale_spec.rb
|
88
75
|
- spec/autoload_spec.rb
|
76
|
+
- spec/breadcrumb_helpers_spec.rb
|
77
|
+
- spec/exception_notifier_spec.rb
|
89
78
|
- spec/spec_helper.rb
|
90
|
-
|
79
|
+
- spec/views/exception_notifier/errors.erb
|
80
|
+
- spec/views/exception_notifier/layouts/layout.erb
|
data/README.rdoc
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
= Contributed Plugins and Utilities
|
2
|
-
|
3
|
-
This package includes a variety of add-on components for Padrino Framework:
|
4
|
-
|
5
|
-
* exception_notifier - Send errors through mail or/and to redmine
|
6
|
-
* auto_locale - Switch for you automatically the I18n.locale
|
7
|
-
* flash_session - Middleware that help you in passing your session in the URI, when it should be in the cookie.
|
8
|
-
* orm_ar_permalink - Generate permalink for a specified column on ActiveRecord
|
9
|
-
* orm_ar_permalink_i18n - Generate permalink for a specified multi language column(s) on ActiveRecord
|
10
|
-
* orm_ar_translate - Translate for you your ActiveRecord columns
|
11
|
-
* orm_mm_permalink - Generate permalink for a specified column on MongoMapper
|
12
|
-
* orm_mm_search - Full text search in MongoMapper in specified columns
|
13
|
-
* helpers_assets_compressor - Joins and compress your js/css with yui-compressor
|
14
|
-
|
15
|
-
=== Use
|
16
|
-
|
17
|
-
In your Padrino project edit:
|
18
|
-
|
19
|
-
# Gemfile
|
20
|
-
gem 'padrino-contrib'
|
21
|
-
|
22
|
-
# boot.rb
|
23
|
-
require 'padrino-contrib/exception_notifier'
|
24
|
-
# require 'padrino-contrib/orm/active_record/permalink'
|
25
|
-
# require 'padrino-contrib/orm/active_record/textile'
|
26
|
-
|
27
|
-
Padrino.load! # Remember to add contribs before that line!
|
28
|
-
|
29
|
-
# app.rb
|
30
|
-
class MyApp < Padrino::Application
|
31
|
-
register Padrino::Contrib::ExceptionNotifier
|
32
|
-
end
|