rack-bug 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +21 -0
- data/README.md +115 -0
- data/Rakefile +6 -19
- data/Thorfile +109 -0
- data/lib/rack/bug.rb +4 -2
- data/lib/rack/bug/filtered_backtrace.rb +38 -0
- data/lib/rack/bug/options.rb +4 -4
- data/lib/rack/bug/panel.rb +12 -12
- data/lib/rack/bug/panel_app.rb +8 -8
- data/lib/rack/bug/panels/active_record_panel.rb +11 -11
- data/lib/rack/bug/panels/active_record_panel/activerecord_extensions.rb +3 -3
- data/lib/rack/bug/panels/cache_panel.rb +1 -1
- data/lib/rack/bug/panels/cache_panel/memcache_extension.rb +4 -4
- data/lib/rack/bug/panels/cache_panel/panel_app.rb +11 -11
- data/lib/rack/bug/panels/cache_panel/stats.rb +20 -20
- data/lib/rack/bug/panels/log_panel.rb +27 -10
- data/lib/rack/bug/panels/log_panel/rails_extension.rb +2 -2
- data/lib/rack/bug/panels/memory_panel.rb +8 -8
- data/lib/rack/bug/panels/rails_info_panel.rb +5 -5
- data/lib/rack/bug/panels/redis_panel.rb +3 -3
- data/lib/rack/bug/panels/redis_panel/redis_extension.rb +3 -3
- data/lib/rack/bug/panels/redis_panel/stats.rb +17 -13
- data/lib/rack/bug/panels/request_variables_panel.rb +7 -7
- data/lib/rack/bug/panels/sphinx_panel.rb +44 -0
- data/lib/rack/bug/panels/sphinx_panel/sphinx_extension.rb +13 -0
- data/lib/rack/bug/panels/sphinx_panel/stats.rb +96 -0
- data/lib/rack/bug/panels/sql_panel.rb +1 -1
- data/lib/rack/bug/panels/sql_panel/panel_app.rb +7 -7
- data/lib/rack/bug/panels/sql_panel/query.rb +13 -23
- data/lib/rack/bug/panels/sql_panel/sql_extension.rb +2 -2
- data/lib/rack/bug/panels/templates_panel.rb +1 -1
- data/lib/rack/bug/panels/templates_panel/actionview_extension.rb +1 -1
- data/lib/rack/bug/panels/templates_panel/rendering.rb +14 -14
- data/lib/rack/bug/panels/templates_panel/trace.rb +8 -8
- data/lib/rack/bug/panels/timer_panel.rb +10 -10
- data/lib/rack/bug/params_signature.rb +18 -18
- data/lib/rack/bug/public/__rack_bug__/bookmarklet.js +7 -5
- data/lib/rack/bug/render.rb +16 -16
- data/lib/rack/bug/toolbar.rb +39 -33
- data/lib/rack/bug/views/panels/log.html.erb +4 -6
- data/lib/rack/bug/views/panels/redis.html.erb +14 -0
- data/lib/rack/bug/views/panels/sphinx.html.erb +32 -0
- data/rack-bug.gemspec +102 -97
- data/spec/fixtures/config.ru +3 -1
- data/spec/fixtures/sample_app.rb +7 -6
- data/spec/rack/bug/panels/active_record_panel_spec.rb +6 -6
- data/spec/rack/bug/panels/cache_panel_spec.rb +46 -46
- data/spec/rack/bug/panels/log_panel_spec.rb +8 -7
- data/spec/rack/bug/panels/memory_panel_spec.rb +6 -6
- data/spec/rack/bug/panels/rails_info_panel_spec.rb +5 -5
- data/spec/rack/bug/panels/redis_panel_spec.rb +31 -19
- data/spec/rack/bug/panels/sql_panel_spec.rb +45 -45
- data/spec/rack/bug/panels/templates_panel_spec.rb +18 -18
- data/spec/rack/bug/panels/timer_panel_spec.rb +12 -12
- data/spec/rack/toolbar_spec.rb +34 -28
- data/spec/spec_helper.rb +19 -9
- metadata +44 -13
- data/README.rdoc +0 -29
- data/VERSION +0 -1
data/History.txt
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
== 0.3.0 / 2010-05-28
|
2
|
+
|
3
|
+
* New features
|
4
|
+
|
5
|
+
* Log panel includes log level and timestamp (Tim Connor)
|
6
|
+
* Sphinx panel (George Chatzigeorgiou)
|
7
|
+
* Backtraces for Redis panel (Luke Melia & Joey Aghion)
|
8
|
+
|
9
|
+
* Minor fixes
|
10
|
+
|
11
|
+
* Don't "enable" rack bug if you hit cancel on the bookmarklet prompt (Mischa Fierer)
|
12
|
+
|
13
|
+
* Compatibilty
|
14
|
+
|
15
|
+
* backtrace filtering now supports more than just Rails (Alex Chaffee)
|
16
|
+
* compatibility with current rack-test (Luke Melia & Joey Aghion)
|
17
|
+
* update Sinatra sample app (Tim Conner)
|
18
|
+
|
19
|
+
== 0.2.1
|
20
|
+
|
21
|
+
* The beginning of recorded history
|
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
Rack::Bug
|
2
|
+
=========
|
3
|
+
|
4
|
+
* Repository: [http://github.com/brynary/rack-bug](http://github.com/brynary/rack-bug)
|
5
|
+
|
6
|
+
Description
|
7
|
+
-----------
|
8
|
+
|
9
|
+
Rack::Bug adds a diagnostics toolbar to Rack apps. When enabled, it injects a floating div
|
10
|
+
allowing exploration of logging, database queries, template rendering times, etc.
|
11
|
+
|
12
|
+
Features
|
13
|
+
--------
|
14
|
+
|
15
|
+
* Password-based security
|
16
|
+
* IP-based security
|
17
|
+
* Rack::Bug instrumentation/reporting is broken up into panels.
|
18
|
+
* Panels in default configuration:
|
19
|
+
* Rails Info
|
20
|
+
* Timer
|
21
|
+
* Request Variables
|
22
|
+
* SQL
|
23
|
+
* Active Record
|
24
|
+
* Cache
|
25
|
+
* Templates
|
26
|
+
* Log
|
27
|
+
* Memory
|
28
|
+
* Other bundled panels:
|
29
|
+
* Redis
|
30
|
+
* Sphinx
|
31
|
+
* The API for adding your own panels is simple and powerful
|
32
|
+
|
33
|
+
Rails quick start
|
34
|
+
---------------------------
|
35
|
+
|
36
|
+
script/plugin install git://github.com/brynary/rack-bug.git
|
37
|
+
|
38
|
+
In config/environments/development.rb, add:
|
39
|
+
|
40
|
+
config.middleware.use "Rack::Bug",
|
41
|
+
:secret_key => "someverylongandveryhardtoguesspreferablyrandomstring"
|
42
|
+
|
43
|
+
Add the bookmarklet to your browser:
|
44
|
+
|
45
|
+
open http://RAILS_APP/__rack_bug__/bookmarklet.html
|
46
|
+
|
47
|
+
Using with non-Rails Rack apps
|
48
|
+
------------------------------
|
49
|
+
Nothing should prevent this from being possible. Please contribute docs if you do this. :-)
|
50
|
+
|
51
|
+
Configuring custom panels
|
52
|
+
-------------------------
|
53
|
+
|
54
|
+
Specify the set of panels you want, in the order you want them to appear:
|
55
|
+
|
56
|
+
require "rack/bug"
|
57
|
+
|
58
|
+
ActionController::Dispatcher.middleware.use Rack::Bug,
|
59
|
+
:secret_key => "someverylongandveryhardtoguesspreferablyrandomstring",
|
60
|
+
:panel_classes => [
|
61
|
+
Rack::Bug::TimerPanel,
|
62
|
+
Rack::Bug::RequestVariablesPanel,
|
63
|
+
Rack::Bug::RedisPanel,
|
64
|
+
Rack::Bug::TemplatesPanel,
|
65
|
+
Rack::Bug::LogPanel,
|
66
|
+
Rack::Bug::MemoryPanel
|
67
|
+
]
|
68
|
+
|
69
|
+
|
70
|
+
Running Rack::Bug in staging or production
|
71
|
+
------------------------------------------
|
72
|
+
|
73
|
+
We have have found that Rack::Bug is fast enough to run in production for specific troubleshooting efforts.
|
74
|
+
|
75
|
+
### Configuration ####
|
76
|
+
|
77
|
+
Add the middleware configuration to an initializer or the appropriate environment files, taking the rest of this section into consideration.
|
78
|
+
|
79
|
+
### Security ####
|
80
|
+
|
81
|
+
Restrict access to particular IP addresses:
|
82
|
+
|
83
|
+
require "ipaddr"
|
84
|
+
|
85
|
+
ActionController::Dispatcher.middleware.use "Rack::Bug"
|
86
|
+
:secret_key => "someverylongandveryhardtoguesspreferablyrandomstring",
|
87
|
+
:ip_masks => [IPAddr.new("2.2.2.2/0")]
|
88
|
+
|
89
|
+
Restrict access using a password:
|
90
|
+
|
91
|
+
ActionController::Dispatcher.middleware.use "Rack::Bug",
|
92
|
+
:secret_key => "someverylongandveryhardtoguesspreferablyrandomstring",
|
93
|
+
:password => "yourpassword"
|
94
|
+
|
95
|
+
|
96
|
+
Authors
|
97
|
+
-------
|
98
|
+
|
99
|
+
- Maintained by [Bryan Helmkamp](mailto:bryan@brynary.com)
|
100
|
+
- Contributions from Luke Melia, Joey Aghion, and more
|
101
|
+
|
102
|
+
Thanks
|
103
|
+
------
|
104
|
+
Inspiration for Rack::Bug is primarily from the Django debug toolbar. Additional ideas from Rails footnotes, Rack's ShowException middleware, Oink, and Rack::Cache
|
105
|
+
|
106
|
+
Thanks to Weplay.com for supporting the development of Rack::Bug
|
107
|
+
|
108
|
+
Development
|
109
|
+
-----------
|
110
|
+
For development, you'll need to install the following gems: rspec, rack-test, webrat, sinatra
|
111
|
+
|
112
|
+
License
|
113
|
+
-------
|
114
|
+
|
115
|
+
See MIT-LICENSE.txt in this directory.
|
data/Rakefile
CHANGED
@@ -1,24 +1,6 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require "spec/rake/spectask"
|
3
3
|
|
4
|
-
$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
|
5
|
-
require "rack/bug"
|
6
|
-
|
7
|
-
begin
|
8
|
-
require 'jeweler'
|
9
|
-
Jeweler::Tasks.new do |s|
|
10
|
-
s.name = "rack-bug"
|
11
|
-
s.author = "Bryan Helmkamp"
|
12
|
-
s.email = "bryan" + "@" + "brynary.com"
|
13
|
-
s.homepage = "http://github.com/brynary/rack-bug"
|
14
|
-
s.summary = "Debugging toolbar for Rack applications implemented as middleware"
|
15
|
-
# s.description = "TODO"
|
16
|
-
s.extra_rdoc_files = %w(README.rdoc MIT-LICENSE.txt)
|
17
|
-
end
|
18
|
-
rescue LoadError
|
19
|
-
puts "Jeweler not available. Install it with: gem install jeweler"
|
20
|
-
end
|
21
|
-
|
22
4
|
Spec::Rake::SpecTask.new do |t|
|
23
5
|
t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
24
6
|
end
|
@@ -33,4 +15,9 @@ Spec::Rake::SpecTask.new(:rcov) do |t|
|
|
33
15
|
end
|
34
16
|
|
35
17
|
desc "Run the specs"
|
36
|
-
task :default => :spec
|
18
|
+
task :default => :spec
|
19
|
+
|
20
|
+
desc 'Removes trailing whitespace'
|
21
|
+
task :whitespace do
|
22
|
+
sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;}
|
23
|
+
end
|
data/Thorfile
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
module GemHelpers
|
2
|
+
|
3
|
+
def generate_gemspec
|
4
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
|
5
|
+
require "rack/bug"
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "rack-bug"
|
9
|
+
s.version = Rack::Bug::VERSION
|
10
|
+
s.author = "Bryan Helmkamp"
|
11
|
+
s.email = "bryan@brynary.com"
|
12
|
+
s.homepage = "http://github.com/brynary/rack-bug"
|
13
|
+
s.summary = "Debugging toolbar for Rack applications implemented as middleware"
|
14
|
+
# s.description = "TODO"
|
15
|
+
s.rubyforge_project = "rack-bug"
|
16
|
+
|
17
|
+
require "git"
|
18
|
+
repo = Git.open(".")
|
19
|
+
|
20
|
+
s.files = normalize_files(repo.ls_files.keys - repo.lib.ignored_files)
|
21
|
+
s.test_files = normalize_files(Dir['spec/**/*.rb'] - repo.lib.ignored_files)
|
22
|
+
|
23
|
+
s.has_rdoc = true
|
24
|
+
s.extra_rdoc_files = %w[README.md MIT-LICENSE.txt]
|
25
|
+
|
26
|
+
s.add_dependency "rack", ">= 1.0"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def normalize_files(array)
|
31
|
+
# only keep files, no directories, and sort
|
32
|
+
array.select do |path|
|
33
|
+
File.file?(path)
|
34
|
+
end.sort
|
35
|
+
end
|
36
|
+
|
37
|
+
# Adds extra space when outputting an array. This helps create better version
|
38
|
+
# control diffs, because otherwise it is all on the same line.
|
39
|
+
def prettyify_array(gemspec_ruby, array_name)
|
40
|
+
gemspec_ruby.gsub(/s\.#{array_name.to_s} = \[.+?\]/) do |match|
|
41
|
+
leadin, files = match[0..-2].split("[")
|
42
|
+
leadin + "[\n #{files.split(",").join(",\n ")}\n ]"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def read_gemspec
|
47
|
+
@read_gemspec ||= eval(File.read("rack-bug.gemspec"))
|
48
|
+
end
|
49
|
+
|
50
|
+
def sh(command)
|
51
|
+
puts command
|
52
|
+
system command
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Default < Thor
|
57
|
+
include GemHelpers
|
58
|
+
|
59
|
+
desc "gemspec", "Regenerate rack-bug.gemspec"
|
60
|
+
def gemspec
|
61
|
+
File.open("rack-bug.gemspec", "w") do |file|
|
62
|
+
gemspec_ruby = generate_gemspec.to_ruby
|
63
|
+
gemspec_ruby = prettyify_array(gemspec_ruby, :files)
|
64
|
+
gemspec_ruby = prettyify_array(gemspec_ruby, :test_files)
|
65
|
+
gemspec_ruby = prettyify_array(gemspec_ruby, :extra_rdoc_files)
|
66
|
+
|
67
|
+
file.write gemspec_ruby
|
68
|
+
end
|
69
|
+
|
70
|
+
puts "Wrote gemspec to rack-bug.gemspec"
|
71
|
+
read_gemspec.validate
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "build", "Build a rack-bug gem"
|
75
|
+
def build
|
76
|
+
sh "gem build rack-bug.gemspec"
|
77
|
+
FileUtils.mkdir_p "pkg"
|
78
|
+
FileUtils.mv read_gemspec.file_name, "pkg"
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "install", "Install the latest built gem"
|
82
|
+
def install
|
83
|
+
sh "gem install --local pkg/#{read_gemspec.file_name}"
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "release", "Release the current branch to GitHub and Gemcutter"
|
87
|
+
def release
|
88
|
+
gemspec
|
89
|
+
build
|
90
|
+
Release.new.tag
|
91
|
+
Release.new.gem
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Release < Thor
|
96
|
+
include GemHelpers
|
97
|
+
|
98
|
+
desc "tag", "Tag the gem on the origin server"
|
99
|
+
def tag
|
100
|
+
release_tag = "v#{read_gemspec.version}"
|
101
|
+
sh "git tag -a #{release_tag} -m 'Tagging #{release_tag}'"
|
102
|
+
sh "git push origin #{release_tag}"
|
103
|
+
end
|
104
|
+
|
105
|
+
desc "gem", "Push the gem to Gemcutter"
|
106
|
+
def gem
|
107
|
+
sh "gem push pkg/#{read_gemspec.file_name}"
|
108
|
+
end
|
109
|
+
end
|
data/lib/rack/bug.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "rack"
|
2
2
|
|
3
3
|
module Rack::Bug
|
4
|
+
autoload :FilteredBacktrace, "rack/bug/filtered_backtrace"
|
4
5
|
autoload :Options, "rack/bug/options"
|
5
6
|
autoload :Panel, "rack/bug/panel"
|
6
7
|
autoload :PanelApp, "rack/bug/panel_app"
|
@@ -19,8 +20,9 @@ module Rack::Bug
|
|
19
20
|
autoload :SQLPanel, "rack/bug/panels/sql_panel"
|
20
21
|
autoload :TemplatesPanel, "rack/bug/panels/templates_panel"
|
21
22
|
autoload :TimerPanel, "rack/bug/panels/timer_panel"
|
23
|
+
autoload :SphinxPanel, "rack/bug/panels/sphinx_panel"
|
22
24
|
|
23
|
-
VERSION =
|
25
|
+
VERSION = "0.3.0"
|
24
26
|
|
25
27
|
class SecurityError < StandardError
|
26
28
|
end
|
@@ -40,4 +42,4 @@ module Rack::Bug
|
|
40
42
|
def self.new(*args, &block)
|
41
43
|
Toolbar.new(*args, &block)
|
42
44
|
end
|
43
|
-
end
|
45
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rack
|
2
|
+
module Bug
|
3
|
+
module FilteredBacktrace
|
4
|
+
|
5
|
+
def backtrace
|
6
|
+
@backtrace
|
7
|
+
end
|
8
|
+
|
9
|
+
def has_backtrace?
|
10
|
+
filtered_backtrace.any?
|
11
|
+
end
|
12
|
+
|
13
|
+
def filtered_backtrace
|
14
|
+
@filtered_backtrace ||= @backtrace.map{|l| l.to_s.strip }.select do |line|
|
15
|
+
root_for_backtrace_filtering.nil? ||
|
16
|
+
(line.index(root_for_backtrace_filtering) == 0) && !(line.index(root_for_backtrace_filtering("vendor")) == 0)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def root_for_backtrace_filtering(sub_path = nil)
|
21
|
+
if defined?(Rails) && Rails.respond_to?(:root)
|
22
|
+
sub_path ? Rails.root.join(sub_path) : Rails.root
|
23
|
+
else
|
24
|
+
root = if defined?(RAILS_ROOT)
|
25
|
+
RAILS_ROOT
|
26
|
+
elsif defined?(ROOT)
|
27
|
+
ROOT
|
28
|
+
elsif defined?(Sinatra::Application)
|
29
|
+
Sinatra::Application.root
|
30
|
+
else
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
sub_path ? ::File.join(root, sub_path) : root
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/rack/bug/options.rb
CHANGED
@@ -9,7 +9,7 @@ module Rack::Bug
|
|
9
9
|
define_method("#{key}?") { || !! read_option(key) }
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
option_accessor :secret_key
|
14
14
|
option_accessor :ip_masks
|
15
15
|
option_accessor :password
|
@@ -45,7 +45,7 @@ module Rack::Bug
|
|
45
45
|
end
|
46
46
|
|
47
47
|
private
|
48
|
-
|
48
|
+
|
49
49
|
def read_option(key)
|
50
50
|
options[option_name(key)]
|
51
51
|
end
|
@@ -61,7 +61,7 @@ module Rack::Bug
|
|
61
61
|
else raise ArgumentError
|
62
62
|
end
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
def initialize_options(options={})
|
66
66
|
@default_options = {
|
67
67
|
'rack-bug.ip_masks' => [IPAddr.new("127.0.0.1")],
|
@@ -84,6 +84,6 @@ module Rack::Bug
|
|
84
84
|
}
|
85
85
|
self.options = options
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
end
|
89
89
|
end
|
data/lib/rack/bug/panel.rb
CHANGED
@@ -2,14 +2,14 @@ require "erb"
|
|
2
2
|
|
3
3
|
module Rack
|
4
4
|
module Bug
|
5
|
-
|
5
|
+
|
6
6
|
# Panels are also Rack middleware
|
7
7
|
class Panel
|
8
8
|
include Render
|
9
9
|
include ERB::Util
|
10
|
-
|
10
|
+
|
11
11
|
attr_reader :request
|
12
|
-
|
12
|
+
|
13
13
|
def initialize(app)
|
14
14
|
if panel_app
|
15
15
|
@app = Rack::Cascade.new([panel_app, app])
|
@@ -17,7 +17,7 @@ module Rack
|
|
17
17
|
@app = app
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def call(env)
|
22
22
|
before(env)
|
23
23
|
status, headers, body = @app.call(env)
|
@@ -26,25 +26,25 @@ module Rack
|
|
26
26
|
env["rack-bug.panels"] << self
|
27
27
|
return [status, headers, body]
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def panel_app
|
31
31
|
nil
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def has_content?
|
35
35
|
true
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
def before(env)
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
def after(env, status, headers, body)
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
def render(template)
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
end
|
50
|
-
end
|
50
|
+
end
|
data/lib/rack/bug/panel_app.rb
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
module Rack
|
2
2
|
module Bug
|
3
|
-
|
3
|
+
|
4
4
|
class PanelApp
|
5
5
|
include Rack::Bug::Render
|
6
|
-
|
6
|
+
|
7
7
|
attr_reader :request
|
8
|
-
|
8
|
+
|
9
9
|
def call(env)
|
10
10
|
@request = Rack::Request.new(env)
|
11
11
|
dispatch
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def render_template(*args)
|
15
15
|
Rack::Response.new([super]).to_a
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def params
|
19
19
|
@request.GET
|
20
20
|
end
|
@@ -26,8 +26,8 @@ module Rack
|
|
26
26
|
def validate_params
|
27
27
|
ParamsSignature.new(request).validate!
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
end
|
33
|
-
end
|
33
|
+
end
|