merb-core 0.9.2
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.
- data/LICENSE +20 -0
- data/README +21 -0
- data/Rakefile +285 -0
- data/TODO +0 -0
- data/bin/merb +8 -0
- data/bin/merb-specs +5 -0
- data/docs/bootloading.dox +57 -0
- data/docs/documentation_standards +40 -0
- data/docs/new_render_api +51 -0
- data/lib/merb-core.rb +304 -0
- data/lib/merb-core/autoload.rb +29 -0
- data/lib/merb-core/bootloader.rb +601 -0
- data/lib/merb-core/config.rb +284 -0
- data/lib/merb-core/constants.rb +43 -0
- data/lib/merb-core/controller/abstract_controller.rb +531 -0
- data/lib/merb-core/controller/exceptions.rb +257 -0
- data/lib/merb-core/controller/merb_controller.rb +214 -0
- data/lib/merb-core/controller/mime.rb +88 -0
- data/lib/merb-core/controller/mixins/controller.rb +262 -0
- data/lib/merb-core/controller/mixins/render.rb +324 -0
- data/lib/merb-core/controller/mixins/responder.rb +464 -0
- data/lib/merb-core/controller/template.rb +205 -0
- data/lib/merb-core/core_ext.rb +12 -0
- data/lib/merb-core/core_ext/class.rb +192 -0
- data/lib/merb-core/core_ext/hash.rb +422 -0
- data/lib/merb-core/core_ext/kernel.rb +304 -0
- data/lib/merb-core/core_ext/mash.rb +154 -0
- data/lib/merb-core/core_ext/object.rb +136 -0
- data/lib/merb-core/core_ext/object_space.rb +14 -0
- data/lib/merb-core/core_ext/rubygems.rb +28 -0
- data/lib/merb-core/core_ext/set.rb +41 -0
- data/lib/merb-core/core_ext/string.rb +69 -0
- data/lib/merb-core/dispatch/cookies.rb +92 -0
- data/lib/merb-core/dispatch/dispatcher.rb +233 -0
- data/lib/merb-core/dispatch/exceptions.html.erb +297 -0
- data/lib/merb-core/dispatch/request.rb +560 -0
- data/lib/merb-core/dispatch/router.rb +141 -0
- data/lib/merb-core/dispatch/router/behavior.rb +777 -0
- data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
- data/lib/merb-core/dispatch/router/route.rb +212 -0
- data/lib/merb-core/dispatch/session.rb +28 -0
- data/lib/merb-core/dispatch/session/cookie.rb +166 -0
- data/lib/merb-core/dispatch/session/memcached.rb +161 -0
- data/lib/merb-core/dispatch/session/memory.rb +234 -0
- data/lib/merb-core/gem_ext/erubis.rb +19 -0
- data/lib/merb-core/logger.rb +230 -0
- data/lib/merb-core/plugins.rb +25 -0
- data/lib/merb-core/rack.rb +15 -0
- data/lib/merb-core/rack/adapter.rb +42 -0
- data/lib/merb-core/rack/adapter/ebb.rb +22 -0
- data/lib/merb-core/rack/adapter/evented_mongrel.rb +24 -0
- data/lib/merb-core/rack/adapter/fcgi.rb +16 -0
- data/lib/merb-core/rack/adapter/irb.rb +108 -0
- data/lib/merb-core/rack/adapter/mongrel.rb +25 -0
- data/lib/merb-core/rack/adapter/runner.rb +27 -0
- data/lib/merb-core/rack/adapter/thin.rb +27 -0
- data/lib/merb-core/rack/adapter/webrick.rb +35 -0
- data/lib/merb-core/rack/application.rb +77 -0
- data/lib/merb-core/rack/handler/mongrel.rb +97 -0
- data/lib/merb-core/server.rb +184 -0
- data/lib/merb-core/test.rb +10 -0
- data/lib/merb-core/test/helpers.rb +9 -0
- data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
- data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
- data/lib/merb-core/test/helpers/request_helper.rb +257 -0
- data/lib/merb-core/test/helpers/route_helper.rb +33 -0
- data/lib/merb-core/test/helpers/view_helper.rb +121 -0
- data/lib/merb-core/test/matchers.rb +9 -0
- data/lib/merb-core/test/matchers/controller_matchers.rb +269 -0
- data/lib/merb-core/test/matchers/route_matchers.rb +136 -0
- data/lib/merb-core/test/matchers/view_matchers.rb +293 -0
- data/lib/merb-core/test/run_specs.rb +38 -0
- data/lib/merb-core/test/tasks/spectasks.rb +39 -0
- data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
- data/lib/merb-core/test/test_ext/object.rb +14 -0
- data/lib/merb-core/vendor/facets.rb +2 -0
- data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
- data/lib/merb-core/vendor/facets/inflect.rb +211 -0
- data/lib/merb-core/version.rb +11 -0
- data/spec/private/config/adapter_spec.rb +32 -0
- data/spec/private/config/config_spec.rb +139 -0
- data/spec/private/config/environment_spec.rb +13 -0
- data/spec/private/config/spec_helper.rb +1 -0
- data/spec/private/core_ext/hash_spec.rb +506 -0
- data/spec/private/core_ext/kernel_spec.rb +46 -0
- data/spec/private/core_ext/object_spec.rb +39 -0
- data/spec/private/core_ext/set_spec.rb +26 -0
- data/spec/private/core_ext/string_spec.rb +9 -0
- data/spec/private/dispatch/cookies_spec.rb +107 -0
- data/spec/private/dispatch/dispatch_spec.rb +26 -0
- data/spec/private/dispatch/fixture/app/controllers/application.rb +4 -0
- data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +27 -0
- data/spec/private/dispatch/fixture/app/controllers/foo.rb +21 -0
- data/spec/private/dispatch/fixture/app/helpers/global_helpers.rb +8 -0
- data/spec/private/dispatch/fixture/app/views/exeptions/client_error.html.erb +37 -0
- data/spec/private/dispatch/fixture/app/views/exeptions/internal_server_error.html.erb +216 -0
- data/spec/private/dispatch/fixture/app/views/exeptions/not_acceptable.html.erb +38 -0
- data/spec/private/dispatch/fixture/app/views/exeptions/not_found.html.erb +40 -0
- data/spec/private/dispatch/fixture/app/views/foo/bar.html.erb +0 -0
- data/spec/private/dispatch/fixture/app/views/layout/application.html.erb +11 -0
- data/spec/private/dispatch/fixture/config/environments/development.rb +6 -0
- data/spec/private/dispatch/fixture/config/environments/production.rb +5 -0
- data/spec/private/dispatch/fixture/config/environments/test.rb +6 -0
- data/spec/private/dispatch/fixture/config/init.rb +45 -0
- data/spec/private/dispatch/fixture/config/rack.rb +1 -0
- data/spec/private/dispatch/fixture/config/router.rb +35 -0
- data/spec/private/dispatch/fixture/log/development.log +1 -0
- data/spec/private/dispatch/fixture/log/merb.4000.pid +1 -0
- data/spec/private/dispatch/fixture/log/merb_test.log +2040 -0
- data/spec/private/dispatch/fixture/log/production.log +1 -0
- data/spec/private/dispatch/fixture/merb.4000.pid +1 -0
- data/spec/private/dispatch/fixture/public/images/merb.jpg +0 -0
- data/spec/private/dispatch/fixture/public/merb.fcgi +4 -0
- data/spec/private/dispatch/fixture/public/stylesheets/master.css +119 -0
- data/spec/private/dispatch/route_params_spec.rb +24 -0
- data/spec/private/dispatch/spec_helper.rb +1 -0
- data/spec/private/plugins/plugin_spec.rb +81 -0
- data/spec/private/rack/application_spec.rb +43 -0
- data/spec/public/DEFINITIONS +11 -0
- data/spec/public/abstract_controller/controllers/alt_views/layout/application.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_string_controller_layout.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_template_controller_layout.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/show.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/partial/basic_partial_with_multiple_roots/_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_and_custom_location/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_inherited/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/display.rb +54 -0
- data/spec/public/abstract_controller/controllers/filters.rb +167 -0
- data/spec/public/abstract_controller/controllers/helpers.rb +31 -0
- data/spec/public/abstract_controller/controllers/partial.rb +106 -0
- data/spec/public/abstract_controller/controllers/render.rb +86 -0
- data/spec/public/abstract_controller/controllers/views/helpers/capture/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/helpers/concat/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/layout/alt.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/layout/custom.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object_with_action/new.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_app_layout/index.erb +0 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_custom_layout/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/show.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/another_directory/_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/basic_partial/_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/basic_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/basic_partial_with_multiple_roots/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_first.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_second.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/nested_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_in_another_directory/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/_collection.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/_collection.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/_collection.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/_variables.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/_both.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/_with_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/_with_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_partial/_with_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/test_display/foo.html.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/test_render/foo.html.erb +0 -0
- data/spec/public/abstract_controller/controllers/views/wonderful/index.erb +1 -0
- data/spec/public/abstract_controller/display_spec.rb +33 -0
- data/spec/public/abstract_controller/filter_spec.rb +80 -0
- data/spec/public/abstract_controller/helper_spec.rb +13 -0
- data/spec/public/abstract_controller/partial_spec.rb +53 -0
- data/spec/public/abstract_controller/render_spec.rb +70 -0
- data/spec/public/abstract_controller/spec_helper.rb +27 -0
- data/spec/public/boot_loader/boot_loader_spec.rb +33 -0
- data/spec/public/boot_loader/spec_helper.rb +1 -0
- data/spec/public/controller/base_spec.rb +31 -0
- data/spec/public/controller/controllers/base.rb +41 -0
- data/spec/public/controller/controllers/display.rb +40 -0
- data/spec/public/controller/controllers/responder.rb +67 -0
- data/spec/public/controller/controllers/url.rb +7 -0
- data/spec/public/controller/controllers/views/layout/custom.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.xml.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/html_default/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/layout/custom.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.xml.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.js.erb +1 -0
- data/spec/public/controller/display_spec.rb +34 -0
- data/spec/public/controller/log/merb.4000.pid +1 -0
- data/spec/public/controller/responder_spec.rb +95 -0
- data/spec/public/controller/spec_helper.rb +9 -0
- data/spec/public/controller/url_spec.rb +152 -0
- data/spec/public/directory_structure/directory/app/controllers/application.rb +3 -0
- data/spec/public/directory_structure/directory/app/controllers/base.rb +13 -0
- data/spec/public/directory_structure/directory/app/controllers/custom.rb +19 -0
- data/spec/public/directory_structure/directory/app/views/base/template.html.erb +1 -0
- data/spec/public/directory_structure/directory/app/views/wonderful/template.erb +1 -0
- data/spec/public/directory_structure/directory/config/router.rb +3 -0
- data/spec/public/directory_structure/directory/log/merb.4000.pid +1 -0
- data/spec/public/directory_structure/directory/log/merb_test.log +265 -0
- data/spec/public/directory_structure/directory/merb.4000.pid +1 -0
- data/spec/public/directory_structure/directory_spec.rb +44 -0
- data/spec/public/logger/logger_spec.rb +175 -0
- data/spec/public/logger/spec_helper.rb +1 -0
- data/spec/public/reloading/directory/app/controllers/application.rb +3 -0
- data/spec/public/reloading/directory/app/controllers/reload.rb +6 -0
- data/spec/public/reloading/directory/config/init.rb +2 -0
- data/spec/public/reloading/directory/log/merb.4000.pid +1 -0
- data/spec/public/reloading/directory/log/merb_test.log +59 -0
- data/spec/public/reloading/directory/merb.4000.pid +1 -0
- data/spec/public/reloading/reload_spec.rb +80 -0
- data/spec/public/request/multipart_spec.rb +15 -0
- data/spec/public/request/request_spec.rb +207 -0
- data/spec/public/router/default_spec.rb +21 -0
- data/spec/public/router/deferred_spec.rb +22 -0
- data/spec/public/router/namespace_spec.rb +113 -0
- data/spec/public/router/nested_resources_spec.rb +34 -0
- data/spec/public/router/resource_spec.rb +45 -0
- data/spec/public/router/resources_spec.rb +57 -0
- data/spec/public/router/spec_helper.rb +72 -0
- data/spec/public/router/special_spec.rb +44 -0
- data/spec/public/router/string_spec.rb +61 -0
- data/spec/public/template/template_spec.rb +92 -0
- data/spec/public/template/templates/error.html.erb +2 -0
- data/spec/public/template/templates/template.html.erb +1 -0
- data/spec/public/template/templates/template.html.myt +1 -0
- data/spec/public/test/controller_matchers_spec.rb +378 -0
- data/spec/public/test/controllers/controller_assertion_mock.rb +7 -0
- data/spec/public/test/controllers/dispatch_controller.rb +11 -0
- data/spec/public/test/controllers/spec_helper_controller.rb +30 -0
- data/spec/public/test/multipart_request_helper_spec.rb +159 -0
- data/spec/public/test/multipart_upload_text_file.txt +1 -0
- data/spec/public/test/request_helper_spec.rb +153 -0
- data/spec/public/test/route_helper_spec.rb +54 -0
- data/spec/public/test/route_matchers_spec.rb +133 -0
- data/spec/public/test/view_helper_spec.rb +96 -0
- data/spec/public/test/view_matchers_spec.rb +107 -0
- data/spec/spec_helper.rb +71 -0
- metadata +488 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
require 'memcache_util'
|
|
2
|
+
module Merb
|
|
3
|
+
|
|
4
|
+
module SessionMixin #:nodoc:
|
|
5
|
+
|
|
6
|
+
# Adds a before and after dispatch hook for setting up the memcached
|
|
7
|
+
# session store.
|
|
8
|
+
#
|
|
9
|
+
# ==== Parameters
|
|
10
|
+
# base<Class>:: The class to which the SessionMixin is mixed into.
|
|
11
|
+
def setup_session
|
|
12
|
+
before = cookies[_session_id_key]
|
|
13
|
+
request.session, cookies[_session_id_key] = Merb::MemCacheSession.persist(cookies[_session_id_key])
|
|
14
|
+
@_fingerprint = Marshal.dump(request.session.data).hash
|
|
15
|
+
@_new_cookie = cookies[_session_id_key] != before
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Finalizes the session by storing the session ID in a cookie, if the
|
|
19
|
+
# session has changed.
|
|
20
|
+
def finalize_session
|
|
21
|
+
if @_fingerprint != Marshal.dump(request.session.data).hash
|
|
22
|
+
::Cache.put("session:#{request.session.session_id}", request.session.data)
|
|
23
|
+
end
|
|
24
|
+
set_cookie(_session_id_key, request.session.session_id, Time.now + _session_expiry) if (@_new_cookie || request.session.needs_new_cookie)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# ==== Returns
|
|
28
|
+
# String:: The session store type, i.e. "memcache".
|
|
29
|
+
def session_store_type
|
|
30
|
+
"memcache"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Sessions stored in memcached.
|
|
36
|
+
#
|
|
37
|
+
# Requires setup in your +init.rb+:
|
|
38
|
+
#
|
|
39
|
+
# require 'memcache'
|
|
40
|
+
# CACHE = MemCache.new('127.0.0.1:11211', { :namespace => 'my_app' })
|
|
41
|
+
#
|
|
42
|
+
# And a setting in +init.rb+:
|
|
43
|
+
#
|
|
44
|
+
# c[:session_store] = 'memcache'
|
|
45
|
+
class MemCacheSession
|
|
46
|
+
|
|
47
|
+
attr_accessor :session_id
|
|
48
|
+
attr_accessor :data
|
|
49
|
+
attr_accessor :needs_new_cookie
|
|
50
|
+
|
|
51
|
+
# ==== Parameters
|
|
52
|
+
# session_id<String>:: A unique identifier for this session.
|
|
53
|
+
def initialize(session_id)
|
|
54
|
+
@session_id = session_id
|
|
55
|
+
@data = {}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class << self
|
|
59
|
+
|
|
60
|
+
# Generates a new session ID and creates a new session.
|
|
61
|
+
#
|
|
62
|
+
# ==== Returns
|
|
63
|
+
# MemCacheSession:: The new session.
|
|
64
|
+
def generate
|
|
65
|
+
sid = Merb::SessionMixin::rand_uuid
|
|
66
|
+
new(sid)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# ==== Parameters
|
|
70
|
+
# session_id<String:: The ID of the session to retrieve.
|
|
71
|
+
#
|
|
72
|
+
# ==== Returns
|
|
73
|
+
# Array::
|
|
74
|
+
# A pair consisting of a MemCacheSession and the session's ID. If no
|
|
75
|
+
# sessions matched session_id, a new MemCacheSession will be generated.
|
|
76
|
+
def persist(session_id)
|
|
77
|
+
unless session_id.blank?
|
|
78
|
+
session = ::Cache.get("session:#{session_id}")
|
|
79
|
+
if session.nil?
|
|
80
|
+
# Not in memcached, but assume that cookie exists
|
|
81
|
+
session = new(session_id)
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
# No cookie...make a new session_id
|
|
85
|
+
session = generate
|
|
86
|
+
end
|
|
87
|
+
if session.is_a?(MemCacheSession)
|
|
88
|
+
[session, session.session_id]
|
|
89
|
+
else
|
|
90
|
+
# recreate using the rails session as the data
|
|
91
|
+
session_object = MemCacheSession.new(session_id)
|
|
92
|
+
session_object.data = session
|
|
93
|
+
[session_object, session_object.session_id]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Don't try to reload in dev mode.
|
|
99
|
+
def reloadable? #:nodoc:
|
|
100
|
+
false
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Regenerate the session ID.
|
|
106
|
+
def regenerate
|
|
107
|
+
@session_id = Merb::SessionMixin::rand_uuid
|
|
108
|
+
self.needs_new_cookie=true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Recreates the cookie with the default expiration time. Useful during log
|
|
112
|
+
# in for pushing back the expiration date.
|
|
113
|
+
def refresh_expiration
|
|
114
|
+
self.needs_new_cookie=true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Deletes the session by emptying stored data.
|
|
118
|
+
def delete
|
|
119
|
+
@data = {}
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# ==== Returns
|
|
123
|
+
# Boolean:: True if session has been loaded already.
|
|
124
|
+
def loaded?
|
|
125
|
+
!! @data
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# ==== Parameters
|
|
129
|
+
# k<~to_s>:: The key of the session parameter to set.
|
|
130
|
+
# v<~to_s>:: The value of the session parameter to set.
|
|
131
|
+
def []=(k, v)
|
|
132
|
+
@data[k] = v
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# ==== Parameters
|
|
136
|
+
# k<~to_s>:: The key of the session parameter to retrieve.
|
|
137
|
+
#
|
|
138
|
+
# ==== Returns
|
|
139
|
+
# String:: The value of the session parameter.
|
|
140
|
+
def [](k)
|
|
141
|
+
@data[k]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Yields the session data to an each block.
|
|
145
|
+
#
|
|
146
|
+
# ==== Parameter
|
|
147
|
+
# &b:: The block to pass to each.
|
|
148
|
+
def each(&b)
|
|
149
|
+
@data.each(&b)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private
|
|
153
|
+
|
|
154
|
+
# Attempts to redirect any messages to the data object.
|
|
155
|
+
def method_missing(name, *args, &block)
|
|
156
|
+
@data.send(name, *args, &block)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
module Merb
|
|
2
|
+
|
|
3
|
+
module SessionMixin #:nodoc:
|
|
4
|
+
|
|
5
|
+
# Adds a before and after dispatch hook for setting up the memory session
|
|
6
|
+
# store.
|
|
7
|
+
#
|
|
8
|
+
# ==== Parameters
|
|
9
|
+
# base<Class>:: The class to which the SessionMixin is mixed into.
|
|
10
|
+
def setup_session
|
|
11
|
+
before = cookies[_session_id_key]
|
|
12
|
+
request.session , cookies[_session_id_key] = Merb::MemorySession.persist(cookies[_session_id_key])
|
|
13
|
+
@_new_cookie = cookies[_session_id_key] != before
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Finalizes the session by storing the session ID in a cookie, if the
|
|
17
|
+
# session has changed.
|
|
18
|
+
def finalize_session
|
|
19
|
+
set_cookie(_session_id_key, request.session.session_id, Time.now + _session_expiry) if (@_new_cookie || request.session.needs_new_cookie)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# ==== Returns
|
|
23
|
+
# String:: The session store type, i.e. "memory".
|
|
24
|
+
def session_store_type
|
|
25
|
+
"memory"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Sessions stored in memory.
|
|
31
|
+
#
|
|
32
|
+
# And a setting in +merb.yml+:
|
|
33
|
+
#
|
|
34
|
+
# :session_store: memory
|
|
35
|
+
# :memory_session_ttl: 3600 (in seconds, one hour)
|
|
36
|
+
#
|
|
37
|
+
# Sessions will remain in memory until the server is stopped or the time
|
|
38
|
+
# as set in :memory_session_ttl expires.
|
|
39
|
+
class MemorySession
|
|
40
|
+
|
|
41
|
+
attr_accessor :session_id
|
|
42
|
+
attr_accessor :data
|
|
43
|
+
attr_accessor :needs_new_cookie
|
|
44
|
+
|
|
45
|
+
# ==== Parameters
|
|
46
|
+
# session_id<String>:: A unique identifier for this session.
|
|
47
|
+
def initialize(session_id)
|
|
48
|
+
@session_id = session_id
|
|
49
|
+
@data = {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class << self
|
|
53
|
+
|
|
54
|
+
# Generates a new session ID and creates a new session.
|
|
55
|
+
#
|
|
56
|
+
# ==== Returns
|
|
57
|
+
# MemorySession:: The new session.
|
|
58
|
+
def generate
|
|
59
|
+
sid = Merb::SessionMixin::rand_uuid
|
|
60
|
+
MemorySessionContainer[sid] = new(sid)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# ==== Parameters
|
|
64
|
+
# session_id<String:: The ID of the session to retrieve.
|
|
65
|
+
#
|
|
66
|
+
# ==== Returns
|
|
67
|
+
# Array::
|
|
68
|
+
# A pair consisting of a MemorySession and the session's ID. If no
|
|
69
|
+
# sessions matched session_id, a new MemorySession will be generated.
|
|
70
|
+
def persist(session_id)
|
|
71
|
+
if session_id
|
|
72
|
+
session = MemorySessionContainer[session_id]
|
|
73
|
+
end
|
|
74
|
+
unless session
|
|
75
|
+
session = generate
|
|
76
|
+
end
|
|
77
|
+
[session, session.session_id]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Regenerate the Session ID
|
|
83
|
+
def regenerate
|
|
84
|
+
new_sid = Merb::SessionMixin::rand_uuid
|
|
85
|
+
old_sid = @session_id
|
|
86
|
+
MemorySessionContainer[new_sid] = MemorySessionContainer[old_sid]
|
|
87
|
+
@session_id = new_sid
|
|
88
|
+
MemorySessionContainer.delete(old_sid)
|
|
89
|
+
self.needs_new_cookie=true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Recreates the cookie with the default expiration time. Useful during log
|
|
93
|
+
# in for pushing back the expiration date.
|
|
94
|
+
def refresh_expiration
|
|
95
|
+
self.needs_new_cookie=true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Deletes the session by emptying stored data.
|
|
99
|
+
def delete
|
|
100
|
+
@data = {}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# ==== Returns
|
|
104
|
+
# Boolean:: True if session has been loaded already.
|
|
105
|
+
def loaded?
|
|
106
|
+
!! @data
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# ==== Parameters
|
|
110
|
+
# k<~to_s>:: The key of the session parameter to set.
|
|
111
|
+
# v<~to_s>:: The value of the session parameter to set.
|
|
112
|
+
def []=(k, v)
|
|
113
|
+
@data[k] = v
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# ==== Parameters
|
|
117
|
+
# k<~to_s>:: The key of the session parameter to retrieve.
|
|
118
|
+
#
|
|
119
|
+
# ==== Returns
|
|
120
|
+
# String:: The value of the session parameter.
|
|
121
|
+
def [](k)
|
|
122
|
+
@data[k]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Yields the session data to an each block.
|
|
126
|
+
#
|
|
127
|
+
# ==== Parameter
|
|
128
|
+
# &b:: The block to pass to each.
|
|
129
|
+
def each(&b)
|
|
130
|
+
@data.each(&b)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
# Attempts to redirect any messages to the data object.
|
|
136
|
+
def method_missing(name, *args, &block)
|
|
137
|
+
@data.send(name, *args, &block)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Used for handling multiple sessions stored in memory.
|
|
143
|
+
class MemorySessionContainer
|
|
144
|
+
class << self
|
|
145
|
+
|
|
146
|
+
# ==== Parameters
|
|
147
|
+
# ttl<Fixnum>:: Session validity time in seconds. Defaults to 1 hour.
|
|
148
|
+
#
|
|
149
|
+
# ==== Returns
|
|
150
|
+
# MemorySessionContainer:: The new session container.
|
|
151
|
+
def setup(ttl=nil)
|
|
152
|
+
@sessions = Hash.new
|
|
153
|
+
@timestamps = Hash.new
|
|
154
|
+
@mutex = Mutex.new
|
|
155
|
+
@session_ttl = ttl || 60*60 # default 1 hour
|
|
156
|
+
start_timer
|
|
157
|
+
self
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Creates a new session based on the options.
|
|
161
|
+
#
|
|
162
|
+
# ==== Parameters
|
|
163
|
+
# opts<Hash>:: The session options (see below).
|
|
164
|
+
#
|
|
165
|
+
# ==== Options (opts)
|
|
166
|
+
# :session_id<String>:: ID of the session to create in the container.
|
|
167
|
+
# :data<MemorySession>:: The session to create in the container.
|
|
168
|
+
def create(opts={})
|
|
169
|
+
self[opts[:session_id]] = opts[:data]
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# ==== Parameters
|
|
173
|
+
# key<String>:: ID of the session to retrieve.
|
|
174
|
+
#
|
|
175
|
+
# ==== Returns
|
|
176
|
+
# MemorySession:: The session corresponding to the ID.
|
|
177
|
+
def [](key)
|
|
178
|
+
@mutex.synchronize {
|
|
179
|
+
@timestamps[key] = Time.now
|
|
180
|
+
@sessions[key]
|
|
181
|
+
}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# ==== Parameters
|
|
185
|
+
# key<String>:: ID of the session to set.
|
|
186
|
+
# val<MemorySession>:: The session to set.
|
|
187
|
+
def []=(key, val)
|
|
188
|
+
@mutex.synchronize {
|
|
189
|
+
@timestamps[key] = Time.now
|
|
190
|
+
@sessions[key] = val
|
|
191
|
+
}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# ==== Parameters
|
|
195
|
+
# key<String>:: ID of the session to delete.
|
|
196
|
+
def delete(key)
|
|
197
|
+
@mutex.synchronize {
|
|
198
|
+
@sessions.delete(key)
|
|
199
|
+
@timestamps.delete(key)
|
|
200
|
+
}
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Deletes any sessions that have reached their maximum validity.
|
|
204
|
+
def reap_old_sessions
|
|
205
|
+
@timestamps.each do |key,stamp|
|
|
206
|
+
if stamp + @session_ttl < Time.now
|
|
207
|
+
delete(key)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
GC.start
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Starts the timer that will eventually reap outdated sessions.
|
|
214
|
+
def start_timer
|
|
215
|
+
Thread.new do
|
|
216
|
+
loop {
|
|
217
|
+
sleep @session_ttl
|
|
218
|
+
reap_old_sessions
|
|
219
|
+
}
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# ==== Returns
|
|
224
|
+
# Array:: The sessions stored in this container.
|
|
225
|
+
def sessions
|
|
226
|
+
@sessions
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
end # end singleton class
|
|
230
|
+
|
|
231
|
+
end # end MemorySessionContainer
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
Merb::MemorySessionContainer.setup(Merb::Config[:memory_session_ttl])
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'erubis'
|
|
2
|
+
module Erubis
|
|
3
|
+
|
|
4
|
+
class MEruby < Erubis::Eruby
|
|
5
|
+
include PercentLineEnhancer
|
|
6
|
+
include StringBufferEnhancer
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Loads a file, runs it through Erubis and parses it as YAML.
|
|
10
|
+
#
|
|
11
|
+
# ===== Parameters
|
|
12
|
+
# file<String>:: The name of the file to load.
|
|
13
|
+
# binding<Binding>::
|
|
14
|
+
# The binding to use when evaluating the ERB tags. Defaults to the current
|
|
15
|
+
# binding.
|
|
16
|
+
def self.load_yaml_file(file, binding = binding)
|
|
17
|
+
YAML::load(Erubis::MEruby.new(IO.read(File.expand_path(file))).result(binding))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
require "time" # httpdate
|
|
2
|
+
# ==== Public Merb Logger API
|
|
3
|
+
#
|
|
4
|
+
# To replace an existing logger with a new one:
|
|
5
|
+
# Merb::Logger.set_log(log{String, IO},level{Symbol, String})
|
|
6
|
+
#
|
|
7
|
+
# Available logging levels are
|
|
8
|
+
# Merb::Logger::{ Fatal, Error, Warn, Info, Debug }
|
|
9
|
+
#
|
|
10
|
+
# Logging via:
|
|
11
|
+
# Merb.logger.fatal(message<String>,&block)
|
|
12
|
+
# Merb.logger.error(message<String>,&block)
|
|
13
|
+
# Merb.logger.warn(message<String>,&block)
|
|
14
|
+
# Merb.logger.info(message<String>,&block)
|
|
15
|
+
# Merb.logger.debug(message<String>,&block)
|
|
16
|
+
#
|
|
17
|
+
# Logging with autoflush:
|
|
18
|
+
# Merb.logger.fatal!(message<String>,&block)
|
|
19
|
+
# Merb.logger.error!(message<String>,&block)
|
|
20
|
+
# Merb.logger.warn!(message<String>,&block)
|
|
21
|
+
# Merb.logger.info!(message<String>,&block)
|
|
22
|
+
# Merb.logger.debug!(message<String>,&block)
|
|
23
|
+
#
|
|
24
|
+
# Flush the buffer to
|
|
25
|
+
# Merb.logger.flush
|
|
26
|
+
#
|
|
27
|
+
# Remove the current log object
|
|
28
|
+
# Merb.logger.close
|
|
29
|
+
#
|
|
30
|
+
# ==== Private Merb Logger API
|
|
31
|
+
#
|
|
32
|
+
# To initialize the logger you create a new object, proxies to set_log.
|
|
33
|
+
# Merb::Logger.new(log{String, IO},level{Symbol, String})
|
|
34
|
+
module Merb
|
|
35
|
+
|
|
36
|
+
class << self #:nodoc:
|
|
37
|
+
attr_accessor :logger
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Logger
|
|
41
|
+
|
|
42
|
+
attr_accessor :aio
|
|
43
|
+
attr_accessor :level
|
|
44
|
+
attr_accessor :delimiter
|
|
45
|
+
attr_accessor :auto_flush
|
|
46
|
+
attr_reader :buffer
|
|
47
|
+
attr_reader :log
|
|
48
|
+
attr_reader :init_args
|
|
49
|
+
|
|
50
|
+
# ==== Notes
|
|
51
|
+
# Ruby (standard) logger levels:
|
|
52
|
+
# :fatal:: An unhandleable error that results in a program crash
|
|
53
|
+
# :error:: A handleable error condition
|
|
54
|
+
# :warn:: A warning
|
|
55
|
+
# :info:: generic (useful) information about system operation
|
|
56
|
+
# :debug:: low-level information for developers
|
|
57
|
+
Levels =
|
|
58
|
+
{
|
|
59
|
+
:fatal => 7,
|
|
60
|
+
:error => 6,
|
|
61
|
+
:warn => 4,
|
|
62
|
+
:info => 3,
|
|
63
|
+
:debug => 0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Define the write method based on if asynchronous IO an be used.
|
|
69
|
+
#
|
|
70
|
+
# ==== Notes
|
|
71
|
+
# The idea here is that instead of performing an 'if' conditional check on
|
|
72
|
+
# each logging we do it once when the log object is setup.
|
|
73
|
+
def set_write_method
|
|
74
|
+
@log.instance_eval do
|
|
75
|
+
|
|
76
|
+
# ==== Returns
|
|
77
|
+
# Boolean:: True if asynchronous IO can be used.
|
|
78
|
+
def aio?
|
|
79
|
+
@aio = !Merb.environment.to_s.match(/development|test/) &&
|
|
80
|
+
!RUBY_PLATFORM.match(/java|mswin/) &&
|
|
81
|
+
!(@log == STDOUT) &&
|
|
82
|
+
@log.respond_to?(:write_nonblock)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
undef write_method if defined? write_method #:nodoc:
|
|
86
|
+
if aio?
|
|
87
|
+
alias :write_method :write_nonblock
|
|
88
|
+
else
|
|
89
|
+
alias :write_method :write
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Readies a log for writing.
|
|
95
|
+
#
|
|
96
|
+
# ==== Parameters
|
|
97
|
+
# log<IO, String>:: Either an IO object or a name of a logfile.
|
|
98
|
+
def initialize_log(log)
|
|
99
|
+
close if @log # be sure that we don't leave open files laying around.
|
|
100
|
+
|
|
101
|
+
if log.respond_to?(:write)
|
|
102
|
+
@log = log
|
|
103
|
+
elsif File.exist?(log)
|
|
104
|
+
@log = open(log, (File::WRONLY | File::APPEND))
|
|
105
|
+
@log.sync = true
|
|
106
|
+
else
|
|
107
|
+
FileUtils.mkdir_p(File.dirname(log)) unless File.directory?(File.dirname(log))
|
|
108
|
+
@log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
|
|
109
|
+
@log.sync = true
|
|
110
|
+
@log.write("#{Time.now.httpdate} #{delimiter} info #{delimiter} Logfile created\n")
|
|
111
|
+
end
|
|
112
|
+
set_write_method
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
public
|
|
116
|
+
|
|
117
|
+
# To initialize the logger you create a new object, proxies to set_log.
|
|
118
|
+
#
|
|
119
|
+
# ==== Parameters
|
|
120
|
+
# *args:: Arguments to create the log from. See set_logs for specifics.
|
|
121
|
+
def initialize(*args)
|
|
122
|
+
@init_args = args
|
|
123
|
+
set_log(*args)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Replaces an existing logger with a new one.
|
|
127
|
+
#
|
|
128
|
+
# ==== Parameters
|
|
129
|
+
# log<IO, String>:: Either an IO object or a name of a logfile.
|
|
130
|
+
# log_level<~to_sym>::
|
|
131
|
+
# The log level from, e.g. :fatal or :info. Defaults to :error in the
|
|
132
|
+
# production environment and :debug otherwise.
|
|
133
|
+
# delimiter<String>::
|
|
134
|
+
# Delimiter to use between message sections. Defaults to " ~ ".
|
|
135
|
+
# auto_flush<Boolean>::
|
|
136
|
+
# Whether the log should automatically flush after new messages are
|
|
137
|
+
# added. Defaults to false.
|
|
138
|
+
def set_log(log, log_level = nil, delimiter = " ~ ", auto_flush = false)
|
|
139
|
+
if log_level && Levels[log_level.to_sym]
|
|
140
|
+
@level = Levels[log_level.to_sym]
|
|
141
|
+
elsif Merb.environment == "production"
|
|
142
|
+
@level = Levels[:warn]
|
|
143
|
+
else
|
|
144
|
+
@level = Levels[:debug]
|
|
145
|
+
end
|
|
146
|
+
@buffer = []
|
|
147
|
+
@delimiter = delimiter
|
|
148
|
+
@auto_flush = auto_flush
|
|
149
|
+
|
|
150
|
+
initialize_log(log)
|
|
151
|
+
|
|
152
|
+
Merb.logger = self
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Flush the entire buffer to the log object.
|
|
156
|
+
def flush
|
|
157
|
+
return unless @buffer.size > 0
|
|
158
|
+
@log.write_method(@buffer.slice!(0..-1).to_s)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Close and remove the current log object.
|
|
162
|
+
def close
|
|
163
|
+
flush
|
|
164
|
+
@log.close if @log.respond_to?(:close)
|
|
165
|
+
@log = nil
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Appends a message to the log. The methods yield to an optional block and
|
|
169
|
+
# the output of this block will be appended to the message.
|
|
170
|
+
#
|
|
171
|
+
# ==== Parameters
|
|
172
|
+
# string<String>:: The message to be logged. Defaults to nil.
|
|
173
|
+
#
|
|
174
|
+
# ==== Returns
|
|
175
|
+
# String:: The resulting message added to the log file.
|
|
176
|
+
def <<(string = nil)
|
|
177
|
+
message = ""
|
|
178
|
+
message << delimiter
|
|
179
|
+
message << string if string
|
|
180
|
+
message << "\n" unless message[-1] == ?\n
|
|
181
|
+
@buffer << message
|
|
182
|
+
flush if @auto_flush
|
|
183
|
+
|
|
184
|
+
message
|
|
185
|
+
end
|
|
186
|
+
alias :push :<<
|
|
187
|
+
|
|
188
|
+
# Generate the logging methods for Merb.logger for each log level.
|
|
189
|
+
Levels.each_pair do |name, number|
|
|
190
|
+
class_eval <<-LEVELMETHODS, __FILE__, __LINE__
|
|
191
|
+
|
|
192
|
+
# Appends a message to the log if the log level is at least as high as
|
|
193
|
+
# the log level of the logger.
|
|
194
|
+
#
|
|
195
|
+
# ==== Parameters
|
|
196
|
+
# string<String>:: The message to be logged. Defaults to nil.
|
|
197
|
+
#
|
|
198
|
+
# ==== Returns
|
|
199
|
+
# self:: The logger object for chaining.
|
|
200
|
+
def #{name}(message = nil)
|
|
201
|
+
self << message if #{number} >= level
|
|
202
|
+
self
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Appends a message to the log if the log level is at least as high as
|
|
206
|
+
# the log level of the logger. The bang! version of the method also auto
|
|
207
|
+
# flushes the log buffer to disk.
|
|
208
|
+
#
|
|
209
|
+
# ==== Parameters
|
|
210
|
+
# string<String>:: The message to be logged. Defaults to nil.
|
|
211
|
+
#
|
|
212
|
+
# ==== Returns
|
|
213
|
+
# self:: The logger object for chaining.
|
|
214
|
+
def #{name}!(message = nil)
|
|
215
|
+
self << message if #{number} >= level
|
|
216
|
+
flush if #{number} >= level
|
|
217
|
+
self
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# ==== Returns
|
|
221
|
+
# Boolean:: True if this level will be logged by this logger.
|
|
222
|
+
def #{name}?
|
|
223
|
+
#{number} >= level
|
|
224
|
+
end
|
|
225
|
+
LEVELMETHODS
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
end
|