ruby_ex 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +51 -0
- data/ChangeLog +1763 -0
- data/NEWS +3 -0
- data/README +1 -0
- data/Rakefile +8 -0
- data/SPEC.dyn.yml +10 -0
- data/SPEC.gem.yml +269 -0
- data/SPEC.yml +36 -0
- data/src/abstract.rb +253 -0
- data/src/abstract_node.rb +85 -0
- data/src/algorithms.rb +12 -0
- data/src/algorithms/simulated_annealing.rb +142 -0
- data/src/ask.rb +100 -0
- data/src/attributed_class.rb +303 -0
- data/src/cache.rb +350 -0
- data/src/checkout.rb +12 -0
- data/src/choose.rb +271 -0
- data/src/commands.rb +20 -0
- data/src/commands/command.rb +492 -0
- data/src/commands/datas.rb +16 -0
- data/src/commands/datas/composite.rb +31 -0
- data/src/commands/datas/data.rb +65 -0
- data/src/commands/datas/factory.rb +69 -0
- data/src/commands/datas/temp.rb +26 -0
- data/src/commands/factory.rb +67 -0
- data/src/commands/helpers.rb +81 -0
- data/src/commands/pipe.rb +66 -0
- data/src/commands/runners.rb +16 -0
- data/src/commands/runners/exec.rb +50 -0
- data/src/commands/runners/fork.rb +130 -0
- data/src/commands/runners/runner.rb +140 -0
- data/src/commands/runners/system.rb +57 -0
- data/src/commands/seq.rb +32 -0
- data/src/config_file.rb +95 -0
- data/src/const_regexp.rb +57 -0
- data/src/daemon.rb +135 -0
- data/src/diff.rb +665 -0
- data/src/dlogger.rb +62 -0
- data/src/drb/drb_observable.rb +95 -0
- data/src/drb/drb_observable_pool.rb +27 -0
- data/src/drb/drb_service.rb +44 -0
- data/src/drb/drb_undumped_attributes.rb +56 -0
- data/src/drb/drb_undumped_indexed_object.rb +55 -0
- data/src/drb/insecure_protected_methods.rb +101 -0
- data/src/drb_ex.rb +12 -0
- data/src/dumpable_proc.rb +57 -0
- data/src/filetype.rb +229 -0
- data/src/generate_id.rb +44 -0
- data/src/histogram.rb +222 -0
- data/src/hookable.rb +283 -0
- data/src/hooker.rb +54 -0
- data/src/indexed_node.rb +65 -0
- data/src/io_marshal.rb +99 -0
- data/src/ioo.rb +193 -0
- data/src/labeled_node.rb +62 -0
- data/src/logger_observer.rb +24 -0
- data/src/md5sum.rb +70 -0
- data/src/module/autoload_tree.rb +65 -0
- data/src/module/hierarchy.rb +334 -0
- data/src/module/instance_method_visibility.rb +71 -0
- data/src/node.rb +81 -0
- data/src/object_monitor.rb +143 -0
- data/src/object_monitor_activity.rb +34 -0
- data/src/observable.rb +138 -0
- data/src/observable_pool.rb +291 -0
- data/src/orderedhash.rb +252 -0
- data/src/pp_hierarchy.rb +30 -0
- data/src/random_generators.rb +29 -0
- data/src/random_generators/random_generator.rb +33 -0
- data/src/random_generators/ruby.rb +25 -0
- data/src/ruby_ex.rb +124 -0
- data/src/safe_eval.rb +346 -0
- data/src/sendmail.rb +214 -0
- data/src/service_manager.rb +122 -0
- data/src/shuffle.rb +30 -0
- data/src/spring.rb +134 -0
- data/src/spring_set.rb +134 -0
- data/src/symtbl.rb +108 -0
- data/src/synflow.rb +474 -0
- data/src/thread_mutex.rb +11 -0
- data/src/timeout_ex.rb +79 -0
- data/src/trace.rb +26 -0
- data/src/uri/druby.rb +78 -0
- data/src/uri/file.rb +63 -0
- data/src/uri/ftp_ex.rb +36 -0
- data/src/uri/http_ex.rb +41 -0
- data/src/uri/pgsql.rb +136 -0
- data/src/uri/ssh.rb +87 -0
- data/src/uri/svn.rb +113 -0
- data/src/uri_ex.rb +71 -0
- data/src/verbose_object.rb +70 -0
- data/src/yaml/basenode_ext.rb +63 -0
- data/src/yaml/chop_header.rb +24 -0
- data/src/yaml/transform.rb +450 -0
- data/src/yaml/yregexpath.rb +76 -0
- data/test/algorithms/simulated_annealing_test.rb +102 -0
- data/test/check-pkg-ruby_ex.yml +15 -0
- data/test/check-ruby_ex.yml +12 -0
- data/test/resources/autoload_tree/A.rb +11 -0
- data/test/resources/autoload_tree/B.rb +10 -0
- data/test/resources/autoload_tree/foo/C.rb +18 -0
- data/test/resources/foo.txt +6 -0
- data/test/sanity-suite.yml +12 -0
- data/test/sanity/multiple-requires.yml +20 -0
- data/test/sanity/single-requires.yml +24 -0
- data/test/test-unit-setup.rb +6 -0
- data/test/unit-suite.yml +14 -0
- metadata +269 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
|
2
|
+
# Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
|
3
|
+
# License:: GNU General Public License (GPL).
|
4
|
+
# Revision:: $Id: exec.rb 255 2005-06-01 00:08:46Z ertai $
|
5
|
+
|
6
|
+
require 'commands'
|
7
|
+
|
8
|
+
module Commands
|
9
|
+
|
10
|
+
module Runners
|
11
|
+
|
12
|
+
class Exec < Runner
|
13
|
+
concrete
|
14
|
+
|
15
|
+
def exec ( aCommand, data )
|
16
|
+
Kernel.exec(*aCommand.to_a)
|
17
|
+
end
|
18
|
+
|
19
|
+
end # class Exec
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
test_section __FILE__ do
|
24
|
+
|
25
|
+
class ExecTest < Test::Unit::TestCase
|
26
|
+
|
27
|
+
def setup
|
28
|
+
assert_nothing_raised { @runner = Exec.new }
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_0_initialize
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_simple
|
35
|
+
TempPath.new do |tmp|
|
36
|
+
cmd = %Q[ruby -e "File.open('#{tmp}', 'w') { |f| f.puts :foo }"]
|
37
|
+
pid = fork do
|
38
|
+
@runner.run(cmd.to_cmd)
|
39
|
+
end
|
40
|
+
Process.waitpid pid
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end # class ExecTest
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end # module Runners
|
49
|
+
|
50
|
+
end # module Commands
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
|
2
|
+
# Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
|
3
|
+
# License:: GNU General Public License (GPL).
|
4
|
+
# Revision:: $Id: fork.rb 255 2005-06-01 00:08:46Z ertai $
|
5
|
+
|
6
|
+
require 'commands'
|
7
|
+
|
8
|
+
module Commands
|
9
|
+
|
10
|
+
module Runners
|
11
|
+
|
12
|
+
class Fork < Exec
|
13
|
+
concrete
|
14
|
+
|
15
|
+
#
|
16
|
+
# Hooks declaration
|
17
|
+
#
|
18
|
+
|
19
|
+
hook_declare :exception_raised_during_exec
|
20
|
+
hook_declare :already_killed
|
21
|
+
hook_declare :waitpid
|
22
|
+
hook_declare :failure
|
23
|
+
hook_declare :son
|
24
|
+
hook_declare :kill
|
25
|
+
|
26
|
+
#
|
27
|
+
# Methods
|
28
|
+
#
|
29
|
+
|
30
|
+
def run_impl ( aCommand, data )
|
31
|
+
data.pid = Kernel.fork do
|
32
|
+
begin
|
33
|
+
hook_trigger :son, aCommand, data
|
34
|
+
super
|
35
|
+
rescue Exception => ex
|
36
|
+
hook_trigger :exception_raised_during_exec, ex
|
37
|
+
end
|
38
|
+
end
|
39
|
+
hook_trigger :waitpid, data
|
40
|
+
if data.status.nil? or not data.status.exitstatus.zero?
|
41
|
+
hook_trigger :failure, data
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def abort ( data )
|
47
|
+
if data.pid
|
48
|
+
begin
|
49
|
+
hook_trigger :kill, data
|
50
|
+
hook_trigger :waitpid, data
|
51
|
+
rescue Errno::ESRCH
|
52
|
+
hook_trigger :already_killed, data.pid
|
53
|
+
end
|
54
|
+
end
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def exception_raised_during_exec ( anException )
|
60
|
+
STDERR.reopen(File.new(1))
|
61
|
+
STDERR.puts anException
|
62
|
+
exit! 127
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def kill ( data )
|
67
|
+
Process.kill('-KILL', -data.pid)
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def raise_on_failures
|
72
|
+
hooker_subscribe FailureHooker.new
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
end # class Fork
|
77
|
+
|
78
|
+
|
79
|
+
class FailureHooker
|
80
|
+
class Error < Exception
|
81
|
+
end
|
82
|
+
def failure ( data )
|
83
|
+
output, error = '', ''
|
84
|
+
output = data.output.read rescue nil
|
85
|
+
error = data.error.read rescue nil
|
86
|
+
output.gsub!(/^/, ' ')
|
87
|
+
error.gsub!(/^/, ' ')
|
88
|
+
|
89
|
+
raise Error, <<end
|
90
|
+
---
|
91
|
+
Command failed:
|
92
|
+
exit: #{data.status.exitstatus}
|
93
|
+
output:
|
94
|
+
#{output}
|
95
|
+
error:
|
96
|
+
#{error}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
test_section __FILE__ do
|
103
|
+
|
104
|
+
class ForkTest < Test::Unit::TestCase
|
105
|
+
|
106
|
+
def setup
|
107
|
+
assert_nothing_raised { @runner = Fork.new }
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_0_initialize
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_simple
|
114
|
+
TempPath.new do |tmp|
|
115
|
+
cmd = %Q[ruby -e "sleep 1
|
116
|
+
File.open('#{tmp}', 'w') { |f| f.puts :foo }"]
|
117
|
+
data = @runner.run(cmd.to_cmd)
|
118
|
+
assert(! tmp.exist?)
|
119
|
+
data.waitpid
|
120
|
+
assert(tmp.exist?)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end # class ForkTest
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end # module Runners
|
129
|
+
|
130
|
+
end # module Commands
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
|
2
|
+
# Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
|
3
|
+
# License:: GNU General Public License (GPL).
|
4
|
+
# Revision:: $Id: runner.rb 255 2005-06-01 00:08:46Z ertai $
|
5
|
+
|
6
|
+
require 'commands'
|
7
|
+
|
8
|
+
module Commands
|
9
|
+
|
10
|
+
module Runners
|
11
|
+
|
12
|
+
class Runner
|
13
|
+
|
14
|
+
abstract
|
15
|
+
|
16
|
+
include Hookable
|
17
|
+
include Hooker
|
18
|
+
|
19
|
+
|
20
|
+
#
|
21
|
+
# Accessors
|
22
|
+
#
|
23
|
+
|
24
|
+
attr_accessor :command_data_factory
|
25
|
+
|
26
|
+
|
27
|
+
#
|
28
|
+
# Hooks declaration
|
29
|
+
#
|
30
|
+
|
31
|
+
hook_declare :display_command
|
32
|
+
hook_declare :data
|
33
|
+
hook_declare :before_open
|
34
|
+
hook_declare :before_chdir
|
35
|
+
hook_declare :before_exec
|
36
|
+
hook_declare :exec
|
37
|
+
|
38
|
+
|
39
|
+
#
|
40
|
+
# Construction methods.
|
41
|
+
#
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@command_data_factory = Datas::Factory.new
|
45
|
+
hooker_subscribe self
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
#
|
50
|
+
# Methods.
|
51
|
+
#
|
52
|
+
|
53
|
+
def run ( aCommand )
|
54
|
+
unless aCommand.is_a? Commands::Command
|
55
|
+
raise ArgumentError, 'Need a Runner::Command'
|
56
|
+
end
|
57
|
+
|
58
|
+
hook_trigger :display_command, aCommand
|
59
|
+
|
60
|
+
data = @command_data_factory.create
|
61
|
+
data.input = aCommand.input unless aCommand.input.nil?
|
62
|
+
data.output = aCommand.output unless aCommand.output.nil?
|
63
|
+
data.error = aCommand.error unless aCommand.error.nil?
|
64
|
+
hook_trigger :data, aCommand, data
|
65
|
+
|
66
|
+
run_impl(aCommand, data)
|
67
|
+
|
68
|
+
data
|
69
|
+
end
|
70
|
+
|
71
|
+
def run_impl ( aCommand, data )
|
72
|
+
hook_trigger :before_open, data
|
73
|
+
STDIN.reopen(data.input.to_io_for_commands) unless data.input.nil?
|
74
|
+
STDOUT.reopen(data.output.to_io_for_commands) unless data.output.nil?
|
75
|
+
STDERR.reopen(data.error.to_io_for_commands) unless data.error.nil?
|
76
|
+
|
77
|
+
hook_trigger :before_chdir, data
|
78
|
+
Dir.chdir(aCommand.dir) unless aCommand.dir.nil?
|
79
|
+
|
80
|
+
hook_trigger :before_exec, aCommand, data
|
81
|
+
hook_trigger :exec, aCommand, data
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def make_verbose
|
86
|
+
hooker_subscribe VerboseHooker.new
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def make_debug
|
92
|
+
hooker_subscribe DebugHooker.new
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Hookers
|
98
|
+
#
|
99
|
+
|
100
|
+
class DebugHooker
|
101
|
+
include Hooker
|
102
|
+
|
103
|
+
def log ( m, *a )
|
104
|
+
STDERR.puts
|
105
|
+
STDERR.puts "#{m}: #{a.inspect}"
|
106
|
+
end
|
107
|
+
|
108
|
+
hook_default_method :log
|
109
|
+
|
110
|
+
end # class DebugHooker
|
111
|
+
|
112
|
+
|
113
|
+
class VerboseHooker
|
114
|
+
include Hooker
|
115
|
+
|
116
|
+
def display_command ( m )
|
117
|
+
STDERR.puts "Running: #{m.to_a.join(' ')}"
|
118
|
+
end
|
119
|
+
|
120
|
+
end # class VerboseHooker
|
121
|
+
|
122
|
+
|
123
|
+
end # class Runner
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
test_section __FILE__ do
|
128
|
+
|
129
|
+
class RunnerTest < Test::Unit::TestCase
|
130
|
+
|
131
|
+
def test_abstract
|
132
|
+
assert_raise(TypeError) { Runner.new }
|
133
|
+
end
|
134
|
+
|
135
|
+
end # class RunnerTest
|
136
|
+
end
|
137
|
+
|
138
|
+
end # module Runners
|
139
|
+
|
140
|
+
end # module Commands
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
|
2
|
+
# Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
|
3
|
+
# License:: GNU General Public License (GPL).
|
4
|
+
# Revision:: $Id: system.rb 255 2005-06-01 00:08:46Z ertai $
|
5
|
+
|
6
|
+
require 'commands'
|
7
|
+
|
8
|
+
module Commands
|
9
|
+
|
10
|
+
module Runners
|
11
|
+
|
12
|
+
class WaitpidHooker
|
13
|
+
include Hooker
|
14
|
+
|
15
|
+
def waitpid ( data )
|
16
|
+
data.waitpid
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# This version wait to reach the end of the command before returns.
|
22
|
+
class System < Fork
|
23
|
+
concrete
|
24
|
+
|
25
|
+
hooker_subscribe WaitpidHooker.new
|
26
|
+
|
27
|
+
end # class System
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
test_section __FILE__ do
|
32
|
+
|
33
|
+
class SystemTest < Test::Unit::TestCase
|
34
|
+
|
35
|
+
def setup
|
36
|
+
assert_nothing_raised { @runner = System.new }
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_0_initialize
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_simple
|
43
|
+
TempPath.new do |tmp|
|
44
|
+
cmd = %Q[ruby -e "sleep 0.5
|
45
|
+
File.open('#{tmp}', 'w') { |f| f.puts :foo }"]
|
46
|
+
@runner.run(cmd.to_cmd)
|
47
|
+
assert(tmp.exist?)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end # class SystemTest
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end # module Runners
|
56
|
+
|
57
|
+
end # module Commands
|
data/src/commands/seq.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
|
2
|
+
# Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
|
3
|
+
# License:: GNU General Public License (GPL).
|
4
|
+
# Revision:: $Id: seq.rb 255 2005-06-01 00:08:46Z ertai $
|
5
|
+
|
6
|
+
require 'commands'
|
7
|
+
|
8
|
+
module Commands
|
9
|
+
|
10
|
+
class Seq < Command
|
11
|
+
|
12
|
+
def initialize ( *cmds )
|
13
|
+
@cmds = cmds
|
14
|
+
end
|
15
|
+
|
16
|
+
def run ( *a )
|
17
|
+
datas = []
|
18
|
+
@cmds.each do |cmd|
|
19
|
+
datas.last.waitpid unless datas.empty?
|
20
|
+
datas << cmd.run(*a)
|
21
|
+
end
|
22
|
+
Datas::Composite.new(datas)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_sh
|
26
|
+
strs = @cmds.map { |cmd| "(#{cmd.to_sh})" }
|
27
|
+
"(#{strs.join(' ; ')})#{sh_args}"
|
28
|
+
end
|
29
|
+
|
30
|
+
end # class Seq
|
31
|
+
|
32
|
+
end # module Commands
|
data/src/config_file.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Copyright: Copyright (c) 2004 Nicolas Despres. All rights reserved.
|
2
|
+
# Author: Nicolas Despres <polrop@lrde.epita.fr>.
|
3
|
+
# License: Gnu General Public License.
|
4
|
+
|
5
|
+
# $LastChangedBy: ertai $
|
6
|
+
# $Id: config_file.rb 266 2005-06-01 14:27:18Z ertai $
|
7
|
+
|
8
|
+
|
9
|
+
require 'ruby_ex'
|
10
|
+
require 'pathname'
|
11
|
+
require 'yaml'
|
12
|
+
require 'delegate'
|
13
|
+
|
14
|
+
|
15
|
+
class ConfigFile < SimpleDelegator
|
16
|
+
|
17
|
+
protected :__setobj__
|
18
|
+
|
19
|
+
class FormatError < RuntimeError; end
|
20
|
+
|
21
|
+
attr_reader :default_config
|
22
|
+
|
23
|
+
def initialize(filename, default_config=nil)
|
24
|
+
@filename = Pathname.new(filename)
|
25
|
+
@default_config = default_config.freeze
|
26
|
+
__load__
|
27
|
+
end
|
28
|
+
|
29
|
+
def __load__(&block)
|
30
|
+
config = nil
|
31
|
+
if @filename.exist?
|
32
|
+
@filename.open do |f|
|
33
|
+
f.flock(File::LOCK_EX)
|
34
|
+
config = real_load(f)
|
35
|
+
f.flock(File::LOCK_UN)
|
36
|
+
end
|
37
|
+
block ? block[config] : check_config(config)
|
38
|
+
config.freeze
|
39
|
+
else
|
40
|
+
if $VERBOSE
|
41
|
+
Kernel.warn('warning: use default configuration because no ' +
|
42
|
+
'configuration file has been found')
|
43
|
+
end
|
44
|
+
config = @default_config
|
45
|
+
end
|
46
|
+
__setobj__(config)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Raise a FormatError if the loaded configuration do not fit the format
|
50
|
+
protected
|
51
|
+
def check_config(config)
|
52
|
+
end
|
53
|
+
|
54
|
+
# You can override it to change arguments or Yaml parsing command.
|
55
|
+
protected
|
56
|
+
def real_load(io)
|
57
|
+
YAML::load(io)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
end # class ConfigFile
|
62
|
+
|
63
|
+
|
64
|
+
test_section __FILE__ do
|
65
|
+
|
66
|
+
require 'tempfile'
|
67
|
+
|
68
|
+
|
69
|
+
class ConfigFileTest < Test::Unit::TestCase
|
70
|
+
|
71
|
+
def test_config_file
|
72
|
+
tmp_file = nil
|
73
|
+
Tempfile.open('config_file') do |f|
|
74
|
+
f.puts '---'
|
75
|
+
f.puts 'key1: val1'
|
76
|
+
f.puts 'key2: val2'
|
77
|
+
tmp_file = f
|
78
|
+
end
|
79
|
+
raise 'no temp file' if tmp_file.nil?
|
80
|
+
default_config = { 'key1' => 'def1', 'key2' => 'def2' }
|
81
|
+
config_file = ConfigFile.new('/toto', default_config)
|
82
|
+
assert_equal(default_config, config_file.__getobj__)
|
83
|
+
assert_raises(NoMethodError) { config_file.__setobj__(nil) }
|
84
|
+
config_file = ConfigFile.new(tmp_file.path, default_config)
|
85
|
+
assert_equal({ 'key1' => 'val1', 'key2' => 'val2' },
|
86
|
+
config_file.__getobj__)
|
87
|
+
assert(config_file.__getobj__.frozen?)
|
88
|
+
end
|
89
|
+
|
90
|
+
end # class ConfigFileTest
|
91
|
+
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
|