yandex-webmaster 0.1.1
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/.document +5 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +20 -0
- data/README.md +259 -0
- data/Rakefile +46 -0
- data/lib/yandex-webmaster.rb +80 -0
- data/lib/yandex-webmaster/api/attributes.rb +23 -0
- data/lib/yandex-webmaster/api/attributes/accessor_builder.rb +59 -0
- data/lib/yandex-webmaster/api/attributes/reader_builder.rb +35 -0
- data/lib/yandex-webmaster/api/attributes/types/base.rb +27 -0
- data/lib/yandex-webmaster/api/attributes/types/boolean.rb +22 -0
- data/lib/yandex-webmaster/api/attributes/types/date.rb +17 -0
- data/lib/yandex-webmaster/api/attributes/types/date_time.rb +17 -0
- data/lib/yandex-webmaster/api/attributes/types/float.rb +17 -0
- data/lib/yandex-webmaster/api/attributes/types/integer.rb +17 -0
- data/lib/yandex-webmaster/api/attributes/types/symbol.rb +17 -0
- data/lib/yandex-webmaster/api/attributes/types/time.rb +17 -0
- data/lib/yandex-webmaster/api/attributes/writer_builder.rb +36 -0
- data/lib/yandex-webmaster/api/attributes_builder.rb +65 -0
- data/lib/yandex-webmaster/api_factory.rb +32 -0
- data/lib/yandex-webmaster/authorization.rb +59 -0
- data/lib/yandex-webmaster/base.rb +120 -0
- data/lib/yandex-webmaster/client.rb +16 -0
- data/lib/yandex-webmaster/configuration.rb +99 -0
- data/lib/yandex-webmaster/connection.rb +87 -0
- data/lib/yandex-webmaster/core_ext/array.rb +23 -0
- data/lib/yandex-webmaster/core_ext/class.rb +55 -0
- data/lib/yandex-webmaster/core_ext/date.rb +7 -0
- data/lib/yandex-webmaster/core_ext/date_time.rb +7 -0
- data/lib/yandex-webmaster/core_ext/hash.rb +92 -0
- data/lib/yandex-webmaster/core_ext/nil_class.rb +7 -0
- data/lib/yandex-webmaster/core_ext/object.rb +19 -0
- data/lib/yandex-webmaster/core_ext/string.rb +23 -0
- data/lib/yandex-webmaster/errors.rb +23 -0
- data/lib/yandex-webmaster/host.rb +220 -0
- data/lib/yandex-webmaster/hosts/crawling.rb +26 -0
- data/lib/yandex-webmaster/hosts/sitemap.rb +73 -0
- data/lib/yandex-webmaster/hosts/sitemap_info.rb +55 -0
- data/lib/yandex-webmaster/hosts/top_info.rb +19 -0
- data/lib/yandex-webmaster/hosts/verification.rb +81 -0
- data/lib/yandex-webmaster/request.rb +62 -0
- data/lib/yandex-webmaster/request/oauth2.rb +28 -0
- data/lib/yandex-webmaster/response/hashify.rb +59 -0
- data/lib/yandex-webmaster/version.rb +14 -0
- data/test/helper.rb +18 -0
- data/test/test_webmaster.rb +7 -0
- data/yandex-webmaster.gemspec +167 -0
- metadata +546 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'yandex-webmaster/configuration'
|
4
|
+
|
5
|
+
require 'yandex-webmaster/authorization'
|
6
|
+
require 'yandex-webmaster/connection'
|
7
|
+
require 'yandex-webmaster/request'
|
8
|
+
|
9
|
+
module Yandex
|
10
|
+
module Webmaster
|
11
|
+
class Base
|
12
|
+
include Authorization
|
13
|
+
include Connection
|
14
|
+
include Request
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def define_attributes(options = {}, &block)
|
18
|
+
options[:as] ||= :attributes
|
19
|
+
|
20
|
+
cattr_accessor options[:as].to_sym
|
21
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
22
|
+
@@#{options[:as].to_s} ||= {}
|
23
|
+
EOS
|
24
|
+
|
25
|
+
unless options[:inspect] == false
|
26
|
+
define_method(:inspect) do
|
27
|
+
inspection = self.send(options[:as].to_s).map { |key, value|
|
28
|
+
self.inspect_attribute(key, value[:instance_variable_name])
|
29
|
+
}.compact.join(', ')
|
30
|
+
|
31
|
+
"#<#{self.class} #{inspection}>"
|
32
|
+
end
|
33
|
+
|
34
|
+
define_method(:inspect_attribute) do |attribute_name, instance_variable_name|
|
35
|
+
value = instance_variable_get(instance_variable_name.to_s)
|
36
|
+
|
37
|
+
if value.is_a?(String) && value.length > 50
|
38
|
+
"#{attribute_name.to_s}[#{value.size}]: " + "#{value[0..50]}...".inspect
|
39
|
+
elsif value.is_a?(Array) && value.length > 5
|
40
|
+
"#{attribute_name.to_s}[#{value.size}]: " + "#{value[0..5]}...".inspect
|
41
|
+
else
|
42
|
+
"#{attribute_name.to_s}: " + value.inspect
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
Yandex::Webmaster::Api::AttributesBuilder.new(self, options, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def const_missing(name)
|
52
|
+
Yandex::Webmaster::Api::AttributesBuilder.cast_type(name) || super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_accessor :configuration
|
57
|
+
|
58
|
+
def initialize(attributes = {})
|
59
|
+
self.attributes = attributes
|
60
|
+
end
|
61
|
+
|
62
|
+
def attributes=(attributes = {})
|
63
|
+
attributes.each do |attr,value|
|
64
|
+
self.send("#{attr}=", value) if self.respond_to?("#{attr}=")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def configuration=(value)
|
69
|
+
value = Yandex::Webmaster::Configuration.new(value) if !value.is_a?(Yandex::Webmaster::Configuration)
|
70
|
+
@configuration = value
|
71
|
+
end
|
72
|
+
|
73
|
+
# Responds to attribute query or attribute clear
|
74
|
+
def method_missing(method, *args, &block) # :nodoc:
|
75
|
+
case method.to_s
|
76
|
+
when /^(.*)\?$/
|
77
|
+
return !!self.send($1.to_s)
|
78
|
+
when /^clear_(.*)$/
|
79
|
+
self.send("#{$1.to_s}=", nil)
|
80
|
+
else
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Acts as setter and getter for api requests arguments parsing.
|
86
|
+
#
|
87
|
+
# Returns Arguments instance.
|
88
|
+
#
|
89
|
+
# def arguments(args=(not_set = true), options={}, &block)
|
90
|
+
# if not_set
|
91
|
+
# @arguments
|
92
|
+
# else
|
93
|
+
# @arguments = Arguments.new(self, options).parse(*args, &block)
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def objects_from_response(klass, response, prefix)
|
100
|
+
self.objects_from_array(klass, self.fetch_value(response, prefix))
|
101
|
+
end
|
102
|
+
|
103
|
+
def fetch_value(response, prefix)
|
104
|
+
response.body[prefix]
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param klass [Class]
|
108
|
+
# @param array [Array]
|
109
|
+
# @return [Array<Class>]
|
110
|
+
def objects_from_array(klass, array)
|
111
|
+
array.map do |attributes|
|
112
|
+
instance = klass.new
|
113
|
+
instance.configuration = self.configuration
|
114
|
+
instance.attributes = attributes
|
115
|
+
instance
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Yandex
|
4
|
+
module Webmaster
|
5
|
+
class Client < Base
|
6
|
+
def hosts(reload = false)
|
7
|
+
@hosts = nil if reload
|
8
|
+
@hosts ||= Yandex::Webmaster::ApiFactory.new({
|
9
|
+
:klass => Yandex::Webmaster::Host,
|
10
|
+
:url => '/hosts',
|
11
|
+
:configuration => self.configuration
|
12
|
+
})
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Yandex
|
6
|
+
module Webmaster
|
7
|
+
class Configuration
|
8
|
+
VALID_OPTIONS_KEYS = [
|
9
|
+
:adapter,
|
10
|
+
:app_id,
|
11
|
+
:app_password,
|
12
|
+
:oauth_token,
|
13
|
+
:endpoint,
|
14
|
+
:site,
|
15
|
+
:ssl,
|
16
|
+
:user_agent,
|
17
|
+
:connection_options
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
# Other adapters are :typhoeus, :patron, :em_synchrony, :excon, :test
|
21
|
+
DEFAULT_ADAPTER = :net_http
|
22
|
+
|
23
|
+
# By default, don't set an application id
|
24
|
+
DEFAULT_APP_ID = nil
|
25
|
+
|
26
|
+
# By default, don't set an application password
|
27
|
+
DEFAULT_APP_PASSWORD = nil
|
28
|
+
|
29
|
+
# By default, don't set a user oauth access token
|
30
|
+
DEFAULT_OAUTH_TOKEN = nil
|
31
|
+
|
32
|
+
# The api endpoint used to connect to Yandex.Webmaster if none is set
|
33
|
+
DEFAULT_ENDPOINT = 'https://webmaster.yandex.ru/api/v2/'.freeze
|
34
|
+
|
35
|
+
# The web endpoint used to connect to Yandex.Webmaster if none is set
|
36
|
+
DEFAULT_SITE = 'https://oauth.yandex.ru/'.freeze
|
37
|
+
|
38
|
+
# The default SSL configuration
|
39
|
+
DEFAULT_SSL = {}
|
40
|
+
|
41
|
+
# The value sent in the http header for 'User-Agent' if none is set
|
42
|
+
DEFAULT_USER_AGENT = "Webmaster Ruby Gem #{Yandex::Webmaster::Version::STRING}".freeze
|
43
|
+
|
44
|
+
# By default uses the Faraday connection options if none is set
|
45
|
+
DEFAULT_CONNECTION_OPTIONS = {}
|
46
|
+
|
47
|
+
attr_accessor *VALID_OPTIONS_KEYS
|
48
|
+
|
49
|
+
def initialize(options = {})
|
50
|
+
raise ArgumentError if (options.symbolize_keys!.keys - VALID_OPTIONS_KEYS).any?
|
51
|
+
|
52
|
+
self.reset!
|
53
|
+
options.each { |k,v| send("#{k}=", v) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def keys
|
57
|
+
VALID_OPTIONS_KEYS
|
58
|
+
end
|
59
|
+
|
60
|
+
def current
|
61
|
+
VALID_OPTIONS_KEYS.inject({}) { |h, k| h[k] = send(k); h }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Reset configuration options to their defaults
|
65
|
+
#
|
66
|
+
def reset!
|
67
|
+
self.adapter = DEFAULT_ADAPTER
|
68
|
+
self.app_id = DEFAULT_APP_ID
|
69
|
+
self.app_password = DEFAULT_APP_PASSWORD
|
70
|
+
self.oauth_token = DEFAULT_OAUTH_TOKEN
|
71
|
+
self.endpoint = DEFAULT_ENDPOINT
|
72
|
+
self.site = DEFAULT_SITE
|
73
|
+
self.ssl = DEFAULT_SSL
|
74
|
+
self.user_agent = DEFAULT_USER_AGENT
|
75
|
+
self.connection_options = DEFAULT_CONNECTION_OPTIONS
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
# Convenience method to allow for global setting of configuration options
|
80
|
+
#
|
81
|
+
def configure
|
82
|
+
yield self
|
83
|
+
end
|
84
|
+
|
85
|
+
# Responds to attribute query or attribute clear
|
86
|
+
#
|
87
|
+
def method_missing(method, *args, &block) # :nodoc:
|
88
|
+
case method.to_s
|
89
|
+
when /^(.*)\?$/
|
90
|
+
return !!self.send($1.to_s)
|
91
|
+
when /^clear_(.*)$/
|
92
|
+
self.send("#{$1.to_s}=", nil)
|
93
|
+
else
|
94
|
+
super
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
|
5
|
+
module Yandex
|
6
|
+
module Webmaster
|
7
|
+
module Connection
|
8
|
+
extend self
|
9
|
+
|
10
|
+
USER_AGENT = 'User-Agent'.freeze
|
11
|
+
|
12
|
+
ACCEPT = 'Accept'.freeze
|
13
|
+
|
14
|
+
ACCEPT_CHARSET = 'Accept-Charset'.freeze
|
15
|
+
|
16
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
17
|
+
|
18
|
+
ALLOWED_OPTIONS = [
|
19
|
+
:headers,
|
20
|
+
:url,
|
21
|
+
:params,
|
22
|
+
:request,
|
23
|
+
:ssl
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
# Returns a Fraday::Connection object
|
27
|
+
#
|
28
|
+
def connection(options = {})
|
29
|
+
|
30
|
+
options = self.connection_options(options)
|
31
|
+
|
32
|
+
if @connection_options != options
|
33
|
+
@connection = nil
|
34
|
+
@connection_options = options
|
35
|
+
end
|
36
|
+
|
37
|
+
@connection ||= Faraday.new(@connection_options.merge(:builder => self.stack))
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def connection_options(options = {})
|
43
|
+
options.slice!(*ALLOWED_OPTIONS)
|
44
|
+
|
45
|
+
{
|
46
|
+
:headers => {
|
47
|
+
ACCEPT_CHARSET => "utf-8",
|
48
|
+
USER_AGENT => self.configuration.user_agent,
|
49
|
+
# Due to error in Yandex.Webmaster API I had to change this header
|
50
|
+
# http://clubs.ya.ru/webmaster-api/replies.xml?item_no=150
|
51
|
+
# CONTENT_TYPE => 'application/xml'
|
52
|
+
CONTENT_TYPE => 'application/x-www-form-urlencoded'
|
53
|
+
},
|
54
|
+
:ssl => options.fetch(:ssl) { self.configuration.ssl },
|
55
|
+
:url => options.fetch(:endpoint) { self.configuration.endpoint }
|
56
|
+
}.merge(options)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Exposes middleware builder to facilitate custom stacks and easy
|
60
|
+
# addition of new extensions such as cache adapter.
|
61
|
+
#
|
62
|
+
def stack(&block)
|
63
|
+
@stack ||= begin
|
64
|
+
block_given? ? Faraday::Builder.new(&block) : Faraday::Builder.new(&default_middleware)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Default middleware stack that uses default adapter as specified at
|
69
|
+
# configuration stage.
|
70
|
+
#
|
71
|
+
def default_middleware
|
72
|
+
Proc.new do |builder|
|
73
|
+
builder.use Faraday::Request::Multipart
|
74
|
+
builder.use Faraday::Request::UrlEncoded
|
75
|
+
builder.use Yandex::Webmaster::Request::OAuth2, self.configuration.oauth_token
|
76
|
+
|
77
|
+
builder.use Faraday::Response::Logger if ENV['DEBUG']
|
78
|
+
builder.use Yandex::Webmaster::Response::Hashify
|
79
|
+
# builder.use Yandex::Webmaster::Response::RaiseError
|
80
|
+
|
81
|
+
builder.adapter self.configuration.adapter
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Array # :nodoc:
|
4
|
+
def extract_options!
|
5
|
+
if last.is_a?(Hash) && last.extractable_options?
|
6
|
+
pop
|
7
|
+
else
|
8
|
+
{}
|
9
|
+
end
|
10
|
+
end unless method_defined?(:extract_options!)
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def wrap(object)
|
14
|
+
if object.nil?
|
15
|
+
[]
|
16
|
+
elsif object.respond_to?(:to_ary)
|
17
|
+
object.to_ary || [object]
|
18
|
+
else
|
19
|
+
[object]
|
20
|
+
end
|
21
|
+
end unless method_defined?(:wrap)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Class
|
4
|
+
def cattr_reader(*syms)
|
5
|
+
options = syms.extract_options!
|
6
|
+
syms.each do |sym|
|
7
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
8
|
+
unless defined? @@#{sym}
|
9
|
+
@@#{sym} = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.#{sym}
|
13
|
+
@@#{sym}
|
14
|
+
end
|
15
|
+
EOS
|
16
|
+
|
17
|
+
unless options[:instance_reader] == false || options[:instance_accessor] == false
|
18
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
19
|
+
def #{sym}
|
20
|
+
@@#{sym}
|
21
|
+
end
|
22
|
+
EOS
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end unless method_defined?(:cattr_reader)
|
26
|
+
|
27
|
+
def cattr_writer(*syms)
|
28
|
+
options = syms.extract_options!
|
29
|
+
syms.each do |sym|
|
30
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
31
|
+
unless defined? @@#{sym}
|
32
|
+
@@#{sym} = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.#{sym}=(obj)
|
36
|
+
@@#{sym} = obj
|
37
|
+
end
|
38
|
+
EOS
|
39
|
+
|
40
|
+
unless options[:instance_writer] == false || options[:instance_accessor] == false
|
41
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
42
|
+
def #{sym}=(obj)
|
43
|
+
@@#{sym} = obj
|
44
|
+
end
|
45
|
+
EOS
|
46
|
+
end
|
47
|
+
self.send("#{sym}=", yield) if block_given?
|
48
|
+
end
|
49
|
+
end unless method_defined?(:cattr_writer)
|
50
|
+
|
51
|
+
def cattr_accessor(*syms, &blk)
|
52
|
+
cattr_reader(*syms)
|
53
|
+
cattr_writer(*syms, &blk)
|
54
|
+
end unless method_defined?(:cattr_accessor)
|
55
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Hash # :nodoc:
|
4
|
+
def except(*items) # :nodoc:
|
5
|
+
self.dup.except!(*items)
|
6
|
+
end unless method_defined?(:except)
|
7
|
+
|
8
|
+
def except!(*keys) # :nodoc:
|
9
|
+
copy = self.dup
|
10
|
+
keys.each { |key| copy.delete!(key) }
|
11
|
+
copy
|
12
|
+
end unless method_defined?(:except!)
|
13
|
+
|
14
|
+
def slice(*keys)
|
15
|
+
keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
|
16
|
+
hash = self.class.new
|
17
|
+
keys.each { |k| hash[k] = self[k] if has_key?(k) }
|
18
|
+
hash
|
19
|
+
end unless method_defined?(:slice)
|
20
|
+
|
21
|
+
def slice!(*keys)
|
22
|
+
keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
|
23
|
+
omit = slice(*self.keys - keys)
|
24
|
+
hash = slice(*keys)
|
25
|
+
replace(hash)
|
26
|
+
omit
|
27
|
+
end unless method_defined?(:slice!)
|
28
|
+
|
29
|
+
def symbolize_keys # :nodoc:
|
30
|
+
inject({}) do |hash, (key, value)|
|
31
|
+
hash[(key.to_sym rescue key) || key] = value
|
32
|
+
hash
|
33
|
+
end
|
34
|
+
end unless method_defined?(:symbolize_keys)
|
35
|
+
|
36
|
+
def symbolize_keys! # :nodoc:
|
37
|
+
hash = symbolize_keys
|
38
|
+
hash.each do |key, val|
|
39
|
+
hash[key] = case val
|
40
|
+
when Hash
|
41
|
+
val.symbolize_keys!
|
42
|
+
when Array
|
43
|
+
val.map do |item|
|
44
|
+
item.is_a?(Hash) ? item.symbolize_keys! : item
|
45
|
+
end
|
46
|
+
else
|
47
|
+
val
|
48
|
+
end
|
49
|
+
end
|
50
|
+
return hash
|
51
|
+
end unless method_defined?(:symbolize_keys!)
|
52
|
+
|
53
|
+
def extractable_options?
|
54
|
+
instance_of?(Hash)
|
55
|
+
end unless method_defined?(:extractable_options?)
|
56
|
+
|
57
|
+
def serialize # :nodoc:
|
58
|
+
self.map { |key, val| [key, val].join("=") }.join("&")
|
59
|
+
end unless method_defined?(:serialize)
|
60
|
+
|
61
|
+
def all_keys # :nodoc:
|
62
|
+
keys = self.keys
|
63
|
+
keys.each do |key|
|
64
|
+
if self[key].is_a?(Hash)
|
65
|
+
keys << self[key].all_keys.compact.flatten
|
66
|
+
next
|
67
|
+
end
|
68
|
+
end
|
69
|
+
keys.flatten
|
70
|
+
end unless method_defined?(:all_keys)
|
71
|
+
|
72
|
+
def has_deep_key?(key)
|
73
|
+
self.all_keys.include? key
|
74
|
+
end unless method_defined?(:has_deep_key?)
|
75
|
+
|
76
|
+
def self.hash_traverse(hash, &block)
|
77
|
+
hash.each do |key, val|
|
78
|
+
block.call(key)
|
79
|
+
case val
|
80
|
+
when Hash
|
81
|
+
val.keys.each do |k|
|
82
|
+
_hash_traverse(val, &block)
|
83
|
+
end
|
84
|
+
when Array
|
85
|
+
val.each do |item|
|
86
|
+
_hash_traverse(item, &block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
return hash
|
91
|
+
end
|
92
|
+
end # Hash
|