hayabusa 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/bin/check_running.rb +69 -0
- data/bin/hayabusa_benchmark.rb +82 -0
- data/bin/hayabusa_cgi.rb +84 -0
- data/bin/hayabusa_fcgi.fcgi +159 -0
- data/bin/hayabusa_fcgi.rb +159 -0
- data/bin/knjappserver_start.rb +42 -0
- data/conf/apache2_cgi_rhtml_conf.conf +10 -0
- data/conf/apache2_fcgi_rhtml_conf.conf +22 -0
- data/hayabusa.gemspec +151 -0
- data/lib/hayabusa.rb +518 -0
- data/lib/hayabusa_cgi_session.rb +128 -0
- data/lib/hayabusa_cgi_tools.rb +102 -0
- data/lib/hayabusa_custom_io.rb +22 -0
- data/lib/hayabusa_database.rb +125 -0
- data/lib/hayabusa_erb_handler.rb +27 -0
- data/lib/hayabusa_ext/cleaner.rb +140 -0
- data/lib/hayabusa_ext/cmdline.rb +52 -0
- data/lib/hayabusa_ext/errors.rb +135 -0
- data/lib/hayabusa_ext/logging.rb +404 -0
- data/lib/hayabusa_ext/mailing.rb +158 -0
- data/lib/hayabusa_ext/sessions.rb +71 -0
- data/lib/hayabusa_ext/threadding.rb +96 -0
- data/lib/hayabusa_ext/threadding_timeout.rb +101 -0
- data/lib/hayabusa_ext/translations.rb +43 -0
- data/lib/hayabusa_ext/web.rb +190 -0
- data/lib/hayabusa_http_server.rb +102 -0
- data/lib/hayabusa_http_session.rb +361 -0
- data/lib/hayabusa_http_session_contentgroup.rb +176 -0
- data/lib/hayabusa_http_session_page_environment.rb +66 -0
- data/lib/hayabusa_http_session_post_multipart.rb +135 -0
- data/lib/hayabusa_http_session_request.rb +219 -0
- data/lib/hayabusa_http_session_response.rb +144 -0
- data/lib/hayabusa_models.rb +8 -0
- data/lib/kernel_ext/gettext_methods.rb +22 -0
- data/lib/kernel_ext/magic_methods.rb +61 -0
- data/lib/models/log.rb +130 -0
- data/lib/models/log_access.rb +88 -0
- data/lib/models/log_data.rb +27 -0
- data/lib/models/log_data_link.rb +3 -0
- data/lib/models/log_data_value.rb +21 -0
- data/lib/models/log_link.rb +65 -0
- data/lib/models/session.rb +35 -0
- data/pages/benchmark.rhtml +0 -0
- data/pages/benchmark_print.rhtml +14 -0
- data/pages/benchmark_simple.rhtml +3 -0
- data/pages/benchmark_threadded_content.rhtml +21 -0
- data/pages/debug_database_connections.rhtml +46 -0
- data/pages/debug_http_sessions.rhtml +40 -0
- data/pages/debug_memory_usage.rhtml +16 -0
- data/pages/error_notfound.rhtml +12 -0
- data/pages/logs_latest.rhtml +57 -0
- data/pages/logs_show.rhtml +32 -0
- data/pages/spec.rhtml +41 -0
- data/pages/spec_post.rhtml +3 -0
- data/pages/spec_test_multiple_clients.rhtml +3 -0
- data/pages/spec_thread_joins.rhtml +21 -0
- data/pages/spec_threadded_content.rhtml +40 -0
- data/pages/tests.rhtml +14 -0
- data/spec/cgi_spec.rb +47 -0
- data/spec/custom_urls_spec.rb +35 -0
- data/spec/fcgi_multiple_processes_spec.rb +32 -0
- data/spec/fcgi_spec.rb +69 -0
- data/spec/hayabusa_spec.rb +194 -0
- data/spec/spec_helper.rb +12 -0
- data/tests/cgi_test/config_cgi.rb +6 -0
- data/tests/cgi_test/threadded_content_test.rhtml +23 -0
- data/tests/cgi_test/vars_get_test.rhtml +4 -0
- data/tests/cgi_test/vars_header_test.rhtml +3 -0
- data/tests/cgi_test/vars_post_test.rhtml +4 -0
- data/tests/fcgi_test/config_fcgi.rb +6 -0
- data/tests/fcgi_test/index.rhtml +3 -0
- data/tests/fcgi_test/sleeper.rhtml +4 -0
- data/tests/fcgi_test/threadded_content_test.rhtml +23 -0
- data/tests/fcgi_test/vars_get_test.rhtml +4 -0
- data/tests/fcgi_test/vars_header_test.rhtml +3 -0
- data/tests/fcgi_test/vars_post_test.rhtml +4 -0
- metadata +257 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
#This object writes headers, trailing headers, status headers and more for HTTP-sessions.
|
4
|
+
class Hayabusa::Http_session::Response
|
5
|
+
attr_accessor :chunked, :cgroup, :nl, :status, :http_version, :headers, :headers_trailing, :headers_sent, :socket
|
6
|
+
|
7
|
+
STATUS_CODES = {
|
8
|
+
100 => "Continue",
|
9
|
+
200 => "OK",
|
10
|
+
201 => "Created",
|
11
|
+
202 => "Accepted",
|
12
|
+
204 => "No Content",
|
13
|
+
205 => "Reset Content",
|
14
|
+
206 => "Partial Content",
|
15
|
+
301 => "Moved Permanently",
|
16
|
+
302 => "Found",
|
17
|
+
303 => "See Other",
|
18
|
+
304 => "Not Modified",
|
19
|
+
307 => "Temporary Redirect",
|
20
|
+
400 => "Bad Request",
|
21
|
+
401 => "Unauthorized",
|
22
|
+
403 => "Forbidden",
|
23
|
+
404 => "Not Found",
|
24
|
+
500 => "Internal Server Error"
|
25
|
+
}
|
26
|
+
NL = "\r\n"
|
27
|
+
|
28
|
+
def initialize(args)
|
29
|
+
@chunked = false
|
30
|
+
@socket = args[:socket]
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset(args)
|
34
|
+
@status = 200
|
35
|
+
@http_version = args[:http_version]
|
36
|
+
@close = args[:close]
|
37
|
+
@fileobj = nil
|
38
|
+
@close = true if @http_version == "1.0"
|
39
|
+
@trailers = []
|
40
|
+
@skip_statuscode = true if args[:mode] == :cgi
|
41
|
+
|
42
|
+
@headers_sent = false
|
43
|
+
@headers_trailing = {}
|
44
|
+
|
45
|
+
@mode = args[:mode]
|
46
|
+
|
47
|
+
@headers = {
|
48
|
+
"date" => ["Date", Time.now.httpdate]
|
49
|
+
}
|
50
|
+
|
51
|
+
@headers_11 = {
|
52
|
+
"Connection" => "Keep-Alive"
|
53
|
+
}
|
54
|
+
if args[:mode] != :cgi and (!args.key?(:chunked) or args[:chunked])
|
55
|
+
@headers_11["Transfer-Encoding"] = "chunked"
|
56
|
+
end
|
57
|
+
|
58
|
+
#Socket-timeout is currently broken in JRuby.
|
59
|
+
if RUBY_ENGINE != "jruby"
|
60
|
+
@headers_11["Keep-Alive"] = "timeout=15, max=30"
|
61
|
+
end
|
62
|
+
|
63
|
+
@cookies = []
|
64
|
+
end
|
65
|
+
|
66
|
+
def header(key, val)
|
67
|
+
lines = val.to_s.count("\n") + 1
|
68
|
+
raise "Value contains more lines than 1 (#{lines})." if lines > 1
|
69
|
+
|
70
|
+
if !@headers_sent
|
71
|
+
@headers[key.to_s.downcase] = [key, val]
|
72
|
+
else
|
73
|
+
raise "Headers already sent and given header was not in trailing headers: '#{key}'." if @trailers.index(key) == nil
|
74
|
+
@headers_trailing[key.to_s.downcase] = [key, val]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def cookie(cookie)
|
79
|
+
@cookies << cookie
|
80
|
+
end
|
81
|
+
|
82
|
+
def header_str
|
83
|
+
if @skip_statuscode
|
84
|
+
res = ""
|
85
|
+
else
|
86
|
+
if @http_version == "1.0"
|
87
|
+
res = "HTTP/1.0 #{@status}"
|
88
|
+
else
|
89
|
+
res = "HTTP/1.1 #{@status}"
|
90
|
+
end
|
91
|
+
|
92
|
+
code = STATUS_CODES[@status]
|
93
|
+
res << " #{code}" if code
|
94
|
+
res << NL
|
95
|
+
end
|
96
|
+
|
97
|
+
@headers.each do |key, val|
|
98
|
+
res << "#{val[0]}: #{val[1]}#{NL}"
|
99
|
+
end
|
100
|
+
|
101
|
+
if @http_version == "1.1"
|
102
|
+
@headers_11.each do |key, val|
|
103
|
+
res << "#{key}: #{val}#{NL}"
|
104
|
+
end
|
105
|
+
|
106
|
+
@trailers.each do |trailer|
|
107
|
+
res << "Trailer: #{trailer}#{NL}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
@cookies.each do |cookie|
|
112
|
+
res << "Set-Cookie: #{Knj::Web.cookie_str(cookie)}#{NL}"
|
113
|
+
end
|
114
|
+
|
115
|
+
res << NL
|
116
|
+
|
117
|
+
return res
|
118
|
+
end
|
119
|
+
|
120
|
+
def write
|
121
|
+
@headers_sent = true
|
122
|
+
@socket.write(self.header_str)
|
123
|
+
|
124
|
+
if @status == 304
|
125
|
+
#do nothing.
|
126
|
+
else
|
127
|
+
if @chunked
|
128
|
+
@cgroup.write_to_socket
|
129
|
+
@socket.write("0#{NL}")
|
130
|
+
|
131
|
+
@headers_trailing.each do |header_id_str, header|
|
132
|
+
@socket.write("#{header[0]}: #{header[1]}#{NL}")
|
133
|
+
end
|
134
|
+
|
135
|
+
@socket.write(NL)
|
136
|
+
else
|
137
|
+
@cgroup.write_to_socket
|
138
|
+
@socket.write("#{NL}#{NL}") if @mode != :cgi
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
@socket.close if @close and @mode != :cgi
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Hayabusa::Models
|
2
|
+
#Autoloader for subclasses.
|
3
|
+
def self.const_missing(name)
|
4
|
+
require "#{File.dirname(__FILE__)}/models/#{name.to_s.downcase}.rb"
|
5
|
+
raise "Still not defined: '#{name}'." if !Hayabusa::Models.const_defined?(name)
|
6
|
+
return Hayabusa::Models.const_get(name)
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
def _(str)
|
4
|
+
hb = _hb
|
5
|
+
session = _session
|
6
|
+
locale = nil
|
7
|
+
|
8
|
+
if Thread.current[:locale].to_s.length > 0
|
9
|
+
locale = Thread.current[:locale]
|
10
|
+
elsif session and session[:locale].to_s.strip.length > 0
|
11
|
+
locale = session[:locale]
|
12
|
+
elsif hb and hb.config[:locale_default].to_s.strip.length > 0
|
13
|
+
session[:locale] = hb.config[:locale_default] if session
|
14
|
+
locale = hb.config[:locale_default]
|
15
|
+
elsif !session and !hb
|
16
|
+
return str
|
17
|
+
else
|
18
|
+
raise "No locale set for session and ':locale_default' not set in config."
|
19
|
+
end
|
20
|
+
|
21
|
+
return hb.gettext.trans(locale, str)
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
def _cookie
|
2
|
+
return Thread.current[:hayabusa][:cookie] if Thread.current[:hayabusa]
|
3
|
+
end
|
4
|
+
|
5
|
+
def _get
|
6
|
+
return Thread.current[:hayabusa][:get] if Thread.current[:hayabusa]
|
7
|
+
end
|
8
|
+
|
9
|
+
def _post
|
10
|
+
return Thread.current[:hayabusa][:post] if Thread.current[:hayabusa]
|
11
|
+
end
|
12
|
+
|
13
|
+
def _meta
|
14
|
+
return Thread.current[:hayabusa][:meta] if Thread.current[:hayabusa]
|
15
|
+
end
|
16
|
+
|
17
|
+
def _server
|
18
|
+
return Thread.current[:hayabusa][:meta] if Thread.current[:hayabusa]
|
19
|
+
end
|
20
|
+
|
21
|
+
def _session
|
22
|
+
return Thread.current[:hayabusa][:session].sess_data if Thread.current[:hayabusa] and Thread.current[:hayabusa][:session]
|
23
|
+
end
|
24
|
+
|
25
|
+
def _session_hash
|
26
|
+
return Thread.current[:hayabusa][:session].edata if Thread.current[:hayabusa] and Thread.current[:hayabusa][:session]
|
27
|
+
end
|
28
|
+
|
29
|
+
def _session_obj
|
30
|
+
return Thread.current[:hayabusa][:session] if Thread.current[:hayabusa] and Thread.current[:hayabusa][:session]
|
31
|
+
end
|
32
|
+
|
33
|
+
def _httpsession
|
34
|
+
return Thread.current[:hayabusa][:httpsession] if Thread.current[:hayabusa]
|
35
|
+
end
|
36
|
+
|
37
|
+
def _httpsession_var
|
38
|
+
return Thread.current[:hayabusa][:httpsession].httpsession_var if Thread.current[:hayabusa]
|
39
|
+
end
|
40
|
+
|
41
|
+
def _requestdata
|
42
|
+
return Thread.current[:hayabusa] if Thread.current[:hayabusa]
|
43
|
+
end
|
44
|
+
|
45
|
+
def _hb
|
46
|
+
return Thread.current[:hayabusa][:hb] if Thread.current[:hayabusa]
|
47
|
+
end
|
48
|
+
|
49
|
+
def _vars
|
50
|
+
return Thread.current[:hayabusa][:hb].vars if Thread.current[:hayabusa]
|
51
|
+
end
|
52
|
+
|
53
|
+
def _db
|
54
|
+
return Thread.current[:hayabusa][:db] if Thread.current[:hayabusa] and Thread.current[:hayabusa][:db] #This is the default use from a .rhtml-file.
|
55
|
+
return Thread.current[:hayabusa][:hb].db_handler if Thread.current[:hayabusa] and Thread.current[:hayabusa][:hb] #This is useually used when using autoload-argument for the appserver.
|
56
|
+
end
|
57
|
+
|
58
|
+
#This function makes it possible to define methods in ERubis-parsed files (else _buf-variable wouldnt be globally available).
|
59
|
+
def _buf
|
60
|
+
return $stdout
|
61
|
+
end
|
data/lib/models/log.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
class Hayabusa::Models::Log < Knj::Datarow
|
2
|
+
has_many [
|
3
|
+
{:class => :Log_link, :col => :log_id, :method => :links, :depends => true, :autodelete => true}
|
4
|
+
]
|
5
|
+
|
6
|
+
def self.list(d, &block)
|
7
|
+
sql = "SELECT #{table}.* FROM #{table}"
|
8
|
+
|
9
|
+
if d.args["object_lookup"]
|
10
|
+
data_val = d.ob.get_by(:Log_data_value, {"value" => d.args["object_lookup"].class.name})
|
11
|
+
return [] if !data_val #if this data-value cannot be found, nothing has been logged for the object. So just return empty array here and skip the rest.
|
12
|
+
|
13
|
+
sql << "
|
14
|
+
LEFT JOIN Log_link ON
|
15
|
+
Log_link.log_id = #{table}.id AND
|
16
|
+
Log_link.object_class_value_id = '#{d.db.esc(data_val.id)}' AND
|
17
|
+
Log_link.object_id = '#{d.db.esc(d.args["object_lookup"].id)}'
|
18
|
+
"
|
19
|
+
end
|
20
|
+
|
21
|
+
q_args = nil
|
22
|
+
return_sql = false
|
23
|
+
ret = self.list_helper(d)
|
24
|
+
|
25
|
+
sql << ret[:sql_joins]
|
26
|
+
sql << " WHERE 1=1"
|
27
|
+
|
28
|
+
d.args.each do |key, val|
|
29
|
+
case key
|
30
|
+
when "object_lookup"
|
31
|
+
sql << " AND Log_link.id IS NOT NULL"
|
32
|
+
when "return_sql"
|
33
|
+
return_sql = true
|
34
|
+
when "tag"
|
35
|
+
data_val = d.ob.get_by(:Log_data_value, {"value" => val})
|
36
|
+
if !data_val
|
37
|
+
sql << " AND false"
|
38
|
+
else
|
39
|
+
sql << " AND Log.tag_data_id = '#{d.db.esc(data_val.id)}'"
|
40
|
+
end
|
41
|
+
when :cloned_ubuf
|
42
|
+
q_args = {:cloned_ubuf => true}
|
43
|
+
else
|
44
|
+
raise "Invalid key: #{key}."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
sql << ret[:sql_where]
|
49
|
+
sql << ret[:sql_order]
|
50
|
+
sql << ret[:sql_limit]
|
51
|
+
|
52
|
+
return sql if return_sql
|
53
|
+
|
54
|
+
return d.ob.list_bysql(:Log, sql, q_args, &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.add(d)
|
58
|
+
d.data[:date_saved] = Time.now if !d.data.key?(:date_saved)
|
59
|
+
end
|
60
|
+
|
61
|
+
def text
|
62
|
+
return ob.get(:Log_data_value, self[:text_value_id])[:value]
|
63
|
+
end
|
64
|
+
|
65
|
+
def comment
|
66
|
+
return "" if self[:comment_data_id].to_i == 0
|
67
|
+
log_data = ob.get(:Log_data_value, self[:comment_data_id])
|
68
|
+
return "" if !log_data
|
69
|
+
return log_data[:value]
|
70
|
+
end
|
71
|
+
|
72
|
+
def tag
|
73
|
+
return "" if self[:tag_data_id].to_i == 0
|
74
|
+
log_data = ob.get(:Log_data_value, self[:tag_data_id])
|
75
|
+
return "" if !log_data
|
76
|
+
return log_data[:value]
|
77
|
+
end
|
78
|
+
|
79
|
+
def get
|
80
|
+
ob.args[:hayabusa].log_data_hash(self[:get_keys_data_id], self[:get_values_data_id])
|
81
|
+
end
|
82
|
+
|
83
|
+
def post
|
84
|
+
ob.args[:hayabusa].log_data_hash(self[:post_keys_data_id], self[:post_values_data_id])
|
85
|
+
end
|
86
|
+
|
87
|
+
def cookie
|
88
|
+
ob.args[:hayabusa].log_data_hash(self[:cookie_keys_data_id], self[:cookie_values_data_id])
|
89
|
+
end
|
90
|
+
|
91
|
+
def meta
|
92
|
+
ob.args[:hayabusa].log_data_hash(self[:meta_keys_data_id], self[:meta_values_data_id])
|
93
|
+
end
|
94
|
+
|
95
|
+
def session
|
96
|
+
ob.args[:hayabusa].log_data_hash(self[:session_keys_data_id], self[:session_values_data_id])
|
97
|
+
end
|
98
|
+
|
99
|
+
def ip
|
100
|
+
meta_d = self.meta
|
101
|
+
|
102
|
+
return meta_d[:HTTP_X_FORWARDED_FOR] if meta_d.has_key?(:HTTP_X_FORWARDED_FOR)
|
103
|
+
return meta_d[:REMOTE_ADDR] if meta_d.has_key?(:REMOTE_ADDR)
|
104
|
+
return "[no ip logged]"
|
105
|
+
end
|
106
|
+
|
107
|
+
def first_line
|
108
|
+
lines = self.text.to_s.split("\n").first.to_s
|
109
|
+
end
|
110
|
+
|
111
|
+
def objects_html(ob_use)
|
112
|
+
html = ""
|
113
|
+
first = true
|
114
|
+
|
115
|
+
self.links.each do |link|
|
116
|
+
obj = link.object(ob_use)
|
117
|
+
|
118
|
+
html << ", " if !first
|
119
|
+
first = false if first
|
120
|
+
|
121
|
+
if obj.respond_to?(:html)
|
122
|
+
html << obj.html
|
123
|
+
else
|
124
|
+
html << "#{obj.class.name}{#{obj.id}}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
return html
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class Hayabusa::Models::Log_access < Knj::Datarow
|
2
|
+
def get
|
3
|
+
return data_hash("get")
|
4
|
+
end
|
5
|
+
|
6
|
+
def post
|
7
|
+
return data_hash("post")
|
8
|
+
end
|
9
|
+
|
10
|
+
def meta
|
11
|
+
return data_hash("meta")
|
12
|
+
end
|
13
|
+
|
14
|
+
def cookie
|
15
|
+
return data_hash("cookie")
|
16
|
+
end
|
17
|
+
|
18
|
+
def ips
|
19
|
+
return data_array(self[:ip_data_id])
|
20
|
+
end
|
21
|
+
|
22
|
+
def data_array(data_id)
|
23
|
+
sql = "
|
24
|
+
SELECT
|
25
|
+
value_value.value AS value
|
26
|
+
|
27
|
+
FROM
|
28
|
+
Log_data_link AS value_links,
|
29
|
+
Log_data_value AS value_value
|
30
|
+
|
31
|
+
WHERE
|
32
|
+
value_links.data_id = '#{data_id}' AND
|
33
|
+
value_value.id = value_links.value_id
|
34
|
+
|
35
|
+
ORDER BY
|
36
|
+
key_links.no
|
37
|
+
"
|
38
|
+
|
39
|
+
arr = []
|
40
|
+
q_array = db.query(sql)
|
41
|
+
while d_array = q_array.fetch
|
42
|
+
arr << d_array[:value]
|
43
|
+
end
|
44
|
+
|
45
|
+
return arr
|
46
|
+
end
|
47
|
+
|
48
|
+
def data_hash(type)
|
49
|
+
col_keys_id = "#{type}_keys_data_id".to_sym
|
50
|
+
col_values_id = "#{type}_values_data_id".to_sym
|
51
|
+
|
52
|
+
keys_id = self[col_keys_id]
|
53
|
+
values_id = self[col_values_id]
|
54
|
+
|
55
|
+
keys_data_obj = ob.get(:Log_data, keys_id)
|
56
|
+
values_data_obj = ob.get(:Log_data, values_id)
|
57
|
+
|
58
|
+
sql = "
|
59
|
+
SELECT
|
60
|
+
key_value.value AS `key`,
|
61
|
+
value_value.value AS value
|
62
|
+
|
63
|
+
FROM
|
64
|
+
Log_data_link AS key_links,
|
65
|
+
Log_data_link AS value_links,
|
66
|
+
Log_data_value AS key_value,
|
67
|
+
Log_data_value AS value_value
|
68
|
+
|
69
|
+
WHERE
|
70
|
+
key_links.data_id = '#{keys_id}' AND
|
71
|
+
value_links.data_id = '#{values_id}' AND
|
72
|
+
key_links.no = value_links.no AND
|
73
|
+
key_value.id = key_links.value_id AND
|
74
|
+
value_value.id = value_links.value_id
|
75
|
+
|
76
|
+
ORDER BY
|
77
|
+
key_links.no
|
78
|
+
"
|
79
|
+
|
80
|
+
hash = {}
|
81
|
+
q_hash = db.query(sql)
|
82
|
+
while d_hash = q_hash.fetch
|
83
|
+
hash[d_hash[:key].to_s] = d_hash[:value]
|
84
|
+
end
|
85
|
+
|
86
|
+
return hash
|
87
|
+
end
|
88
|
+
end
|