github_api 0.11.2 → 0.11.3
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/README.md +139 -78
- data/Rakefile +13 -1
- data/features/cassettes/{auths/list.yml → repos/pages/get.yml} +14 -12
- data/features/cassettes/repos/pages/list_builds.yml +63 -0
- data/features/cassettes/repos/pages/list_latest_builds.yml +62 -0
- data/features/repos/pages.feature +24 -0
- data/features/settings.yml +7 -0
- data/lib/github_api.rb +0 -1
- data/lib/github_api/authorizations.rb +55 -41
- data/lib/github_api/authorizations/app.rb +77 -0
- data/lib/github_api/core_ext/array.rb +13 -5
- data/lib/github_api/core_ext/hash.rb +49 -30
- data/lib/github_api/repos.rb +6 -0
- data/lib/github_api/repos/deployments.rb +8 -0
- data/lib/github_api/repos/pages.rb +49 -0
- data/lib/github_api/validations/required.rb +1 -1
- data/lib/github_api/version.rb +1 -1
- data/spec/github/authorizations/app/create_spec.rb +44 -0
- data/spec/github/authorizations/app/delete_spec.rb +59 -0
- data/spec/github/authorizations/create_spec.rb +13 -13
- data/spec/github/authorizations/delete_spec.rb +5 -4
- data/spec/github/core_ext/hash_spec.rb +5 -5
- data/spec/github/normalizer_spec.rb +2 -2
- data/spec/github/parameter_filter_spec.rb +5 -5
- metadata +49 -43
- data/features/authorizations.feature +0 -12
- data/lib/github_api/core_ext/deep_merge.rb +0 -13
- data/lib/github_api/ssl_certs/cacert.pem +0 -41
@@ -0,0 +1,63 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://<BASIC_AUTH>@api.github.com/repos/<USER>/github_api_test/pages/builds?access_token=<TOKEN>
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ""
|
9
|
+
headers:
|
10
|
+
Accept:
|
11
|
+
- application/vnd.github.v3+json,application/vnd.github.beta+json;q=0.5,application/json;q=0.1
|
12
|
+
Accept-Charset:
|
13
|
+
- utf-8
|
14
|
+
User-Agent:
|
15
|
+
- Github Ruby Gem 0.11.2
|
16
|
+
Accept-Encoding:
|
17
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
18
|
+
response:
|
19
|
+
status:
|
20
|
+
code: 200
|
21
|
+
message: OK
|
22
|
+
headers:
|
23
|
+
Server:
|
24
|
+
- GitHub.com
|
25
|
+
Date:
|
26
|
+
- Sun, 16 Feb 2014 19:13:30 GMT
|
27
|
+
Content-Type:
|
28
|
+
- application/json; charset=utf-8
|
29
|
+
Status:
|
30
|
+
- 200 OK
|
31
|
+
X-Ratelimit-Limit:
|
32
|
+
- "5000"
|
33
|
+
X-Ratelimit-Remaining:
|
34
|
+
- "4987"
|
35
|
+
X-Ratelimit-Reset:
|
36
|
+
- "1392579836"
|
37
|
+
Cache-Control:
|
38
|
+
- private, max-age=60, s-maxage=60
|
39
|
+
Etag:
|
40
|
+
- "\"be2b47a6aa91c255a63bbfbd08983b13\""
|
41
|
+
Vary:
|
42
|
+
- Accept, Authorization, Cookie, X-GitHub-OTP
|
43
|
+
- Accept-Encoding
|
44
|
+
X-Github-Media-Type:
|
45
|
+
- github.v3; format=json
|
46
|
+
X-Content-Type-Options:
|
47
|
+
- nosniff
|
48
|
+
Content-Length:
|
49
|
+
- "2"
|
50
|
+
Access-Control-Allow-Credentials:
|
51
|
+
- "true"
|
52
|
+
Access-Control-Expose-Headers:
|
53
|
+
- ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
|
54
|
+
Access-Control-Allow-Origin:
|
55
|
+
- "*"
|
56
|
+
X-Github-Request-Id:
|
57
|
+
- 4D649864:435B:232290A:53010DDA
|
58
|
+
body:
|
59
|
+
encoding: US-ASCII
|
60
|
+
string: "[]"
|
61
|
+
http_version:
|
62
|
+
recorded_at: Sun, 16 Feb 2014 19:13:30 GMT
|
63
|
+
recorded_with: VCR 2.6.0
|
@@ -0,0 +1,62 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://<BASIC_AUTH>@api.github.com/repos/<USER>/github_api_test/pages/builds/latest?access_token=<TOKEN>
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ""
|
9
|
+
headers:
|
10
|
+
Accept:
|
11
|
+
- application/vnd.github.v3+json,application/vnd.github.beta+json;q=0.5,application/json;q=0.1
|
12
|
+
Accept-Charset:
|
13
|
+
- utf-8
|
14
|
+
User-Agent:
|
15
|
+
- Github Ruby Gem 0.11.2
|
16
|
+
Accept-Encoding:
|
17
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
18
|
+
response:
|
19
|
+
status:
|
20
|
+
code: 404
|
21
|
+
message: Not Found
|
22
|
+
headers:
|
23
|
+
Server:
|
24
|
+
- GitHub.com
|
25
|
+
Date:
|
26
|
+
- Sun, 16 Feb 2014 19:21:04 GMT
|
27
|
+
Content-Type:
|
28
|
+
- application/json; charset=utf-8
|
29
|
+
Transfer-Encoding:
|
30
|
+
- chunked
|
31
|
+
Status:
|
32
|
+
- 404 Not Found
|
33
|
+
X-Ratelimit-Limit:
|
34
|
+
- "5000"
|
35
|
+
X-Ratelimit-Remaining:
|
36
|
+
- "4983"
|
37
|
+
X-Ratelimit-Reset:
|
38
|
+
- "1392579836"
|
39
|
+
X-Github-Media-Type:
|
40
|
+
- github.v3; format=json
|
41
|
+
X-Content-Type-Options:
|
42
|
+
- nosniff
|
43
|
+
Access-Control-Allow-Credentials:
|
44
|
+
- "true"
|
45
|
+
Access-Control-Expose-Headers:
|
46
|
+
- ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
|
47
|
+
Access-Control-Allow-Origin:
|
48
|
+
- "*"
|
49
|
+
X-Github-Request-Id:
|
50
|
+
- 4D649864:435B:233DE61:53010FA0
|
51
|
+
Content-Encoding:
|
52
|
+
- gzip
|
53
|
+
body:
|
54
|
+
encoding: ASCII-8BIT
|
55
|
+
string: !binary |
|
56
|
+
H4sIAAAAAAAAA6tWyk0tLk5MT1WyUvLLL1Fwyy/NS1HSUUrJTy7NTc0rSSzJ
|
57
|
+
zM+LLy3KAcpnlJQUWOnrp6SWpebkF6QW6aVnlmSUJukl5+fqlxkr1QIARorx
|
58
|
+
tEwAAAA=
|
59
|
+
|
60
|
+
http_version:
|
61
|
+
recorded_at: Sun, 16 Feb 2014 19:21:04 GMT
|
62
|
+
recorded_with: VCR 2.6.0
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Feature: Pages API
|
2
|
+
|
3
|
+
Background:
|
4
|
+
Given I have "Github::Repos::Pages" instance
|
5
|
+
|
6
|
+
Scenario: List builds
|
7
|
+
|
8
|
+
Given I want to list resources with the following params:
|
9
|
+
| owner | repo |
|
10
|
+
| murek | github_api_test |
|
11
|
+
When I make request within a cassette named "repos/pages/list_builds"
|
12
|
+
Then the response status should be 200
|
13
|
+
And the response type should be JSON
|
14
|
+
And the response should be empty
|
15
|
+
|
16
|
+
Scenario: Get
|
17
|
+
|
18
|
+
Given I want to get resource with the following params:
|
19
|
+
| owner | repo |
|
20
|
+
| murek | github_api_test |
|
21
|
+
When I make request within a cassette named "repos/pages/get"
|
22
|
+
Then the response status should be 200
|
23
|
+
And the response type should be JSON
|
24
|
+
And the response should not be empty
|
data/lib/github_api.rb
CHANGED
@@ -5,6 +5,8 @@ module Github
|
|
5
5
|
# OAuth Authorizations API
|
6
6
|
class Authorizations < API
|
7
7
|
|
8
|
+
Github::require_all 'github_api/authorizations', 'app'
|
9
|
+
|
8
10
|
VALID_AUTH_PARAM_NAMES = %w[
|
9
11
|
scopes
|
10
12
|
add_scopes
|
@@ -15,15 +17,21 @@ module Github
|
|
15
17
|
client_secret
|
16
18
|
].freeze
|
17
19
|
|
20
|
+
# Access to Authorizations::App API
|
21
|
+
def app(options={}, &block)
|
22
|
+
@app ||= ApiFactory.new('Authorizations::App', current_options.merge(options), &block)
|
23
|
+
end
|
24
|
+
|
18
25
|
# List authorizations
|
19
26
|
#
|
20
|
-
#
|
21
|
-
# github = Github.new :
|
27
|
+
# @example
|
28
|
+
# github = Github.new basic_auth: 'login:password'
|
22
29
|
# github.oauth.list
|
23
30
|
# github.oauth.list { |auth| ... }
|
24
31
|
#
|
32
|
+
# @api public
|
25
33
|
def list(*args)
|
26
|
-
|
34
|
+
raise_authentication_error unless authenticated?
|
27
35
|
arguments(args)
|
28
36
|
|
29
37
|
response = get_request('/authorizations', arguments.params)
|
@@ -34,12 +42,15 @@ module Github
|
|
34
42
|
|
35
43
|
# Get a single authorization
|
36
44
|
#
|
37
|
-
#
|
38
|
-
# github = Github.new :
|
45
|
+
# @example
|
46
|
+
# github = Github.new basic_auth: 'login:password'
|
39
47
|
# github.oauth.get 'authorization-id'
|
40
48
|
#
|
49
|
+
# @return [ResponseWrapper]
|
50
|
+
#
|
51
|
+
# @api public
|
41
52
|
def get(*args)
|
42
|
-
|
53
|
+
raise_authentication_error unless authenticated?
|
43
54
|
arguments(args, required: [:authorization_id])
|
44
55
|
|
45
56
|
get_request("/authorizations/#{authorization_id}", arguments.params)
|
@@ -48,18 +59,26 @@ module Github
|
|
48
59
|
|
49
60
|
# Create a new authorization
|
50
61
|
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
62
|
+
# @param [Hash] params
|
63
|
+
# @option params [Array[String]] :scopes
|
64
|
+
# A list of scopes that this authorization is in.
|
65
|
+
# @option params [String] :note
|
66
|
+
# A note to remind you what the OAuth token is for.
|
67
|
+
# @option params [String] :note_url
|
68
|
+
# A URL to remind you what the OAuth token is for.
|
69
|
+
# @option params [String] :client_id
|
70
|
+
# The 20 character OAuth app client key for which to create the token.
|
71
|
+
# @option params [String] :client_secret
|
72
|
+
# The 40 character OAuth app client secret for which to create the token.
|
55
73
|
#
|
56
|
-
#
|
57
|
-
# github = Github.new :
|
74
|
+
# @example
|
75
|
+
# github = Github.new basic_auth: 'login:password'
|
58
76
|
# github.oauth.create
|
59
77
|
# "scopes" => ["public_repo"]
|
60
78
|
#
|
79
|
+
# @api public
|
61
80
|
def create(*args)
|
62
|
-
|
81
|
+
raise_authentication_error unless authenticated?
|
63
82
|
arguments(args) do
|
64
83
|
sift VALID_AUTH_PARAM_NAMES
|
65
84
|
end
|
@@ -69,19 +88,25 @@ module Github
|
|
69
88
|
|
70
89
|
# Update an existing authorization
|
71
90
|
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
91
|
+
# @param [Hash] inputs
|
92
|
+
# @option inputs [Array] :scopes
|
93
|
+
# Optional array - A list of scopes that this authorization is in.
|
94
|
+
# @option inputs [Array] :add_scopes
|
95
|
+
# Optional array - A list of scopes to add to this authorization.
|
96
|
+
# @option inputs [Array] :remove_scopes
|
97
|
+
# Optional array - A list of scopes to remove from this authorization.
|
98
|
+
# @option inputs [String] :note
|
99
|
+
# Optional string - A note to remind you what the OAuth token is for.
|
100
|
+
# @optoin inputs [String] :note_url
|
101
|
+
# Optional string - A URL to remind you what the OAuth token is for.
|
78
102
|
#
|
79
|
-
#
|
80
|
-
# github = Github.new :
|
81
|
-
# github.oauth.update "authorization-id",
|
103
|
+
# @example
|
104
|
+
# github = Github.new basic_auth: 'login:password'
|
105
|
+
# github.oauth.update "authorization-id", add_scopes: ["repo"]
|
82
106
|
#
|
107
|
+
# @api public
|
83
108
|
def update(*args)
|
84
|
-
|
109
|
+
raise_authentication_error unless authenticated?
|
85
110
|
arguments(args, required: [:authorization_id]) do
|
86
111
|
sift VALID_AUTH_PARAM_NAMES
|
87
112
|
end
|
@@ -92,34 +117,23 @@ module Github
|
|
92
117
|
|
93
118
|
# Delete an authorization
|
94
119
|
#
|
95
|
-
#
|
120
|
+
# @example
|
96
121
|
# github.oauth.delete 'authorization-id'
|
97
122
|
#
|
123
|
+
# @api public
|
98
124
|
def delete(*args)
|
99
|
-
|
125
|
+
raise_authentication_error unless authenticated?
|
100
126
|
arguments(args, required: [:authorization_id])
|
101
127
|
|
102
128
|
delete_request("/authorizations/#{authorization_id}", arguments.params)
|
103
129
|
end
|
104
130
|
alias :remove :delete
|
105
131
|
|
106
|
-
|
107
|
-
#
|
108
|
-
# = Examples
|
109
|
-
# github = Github.new basic_auth: "client_id:client_secret"
|
110
|
-
# github.oauth.revoke 'client-id'
|
111
|
-
#
|
112
|
-
#
|
113
|
-
def revoke(*args)
|
114
|
-
|
115
|
-
end
|
116
|
-
|
117
|
-
private
|
132
|
+
protected
|
118
133
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
end
|
134
|
+
def raise_authentication_error
|
135
|
+
raise ArgumentError, 'You can only access your own tokens' +
|
136
|
+
' via Basic Authentication'
|
123
137
|
end
|
124
138
|
|
125
139
|
end # Authorizations
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Github
|
4
|
+
|
5
|
+
class Authorizations::App < Authorizations
|
6
|
+
|
7
|
+
# Get-or-create an authorization for a specific app
|
8
|
+
#
|
9
|
+
# @param [Hash] params
|
10
|
+
# @option params [String] client_secret
|
11
|
+
# The 40 character OAuth app client secret associated with the client
|
12
|
+
# ID specified in the URL.
|
13
|
+
# @option params [Array] :scopes
|
14
|
+
# Optional array - A list of scopes that this authorization is in.
|
15
|
+
# @option params [String] :note
|
16
|
+
# Optional string - A note to remind you what the OAuth token is for.
|
17
|
+
# @option params [String] :note_url
|
18
|
+
# Optional string - A URL to remind you what the OAuth token is for.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# github = Github.new
|
22
|
+
# github.oauth.app.create 'client-id', client_secret: '...'
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def create(*args)
|
26
|
+
raise_authentication_error unless authenticated?
|
27
|
+
arguments(args, required: [:client_id]) do
|
28
|
+
sift Authorizations::VALID_AUTH_PARAM_NAMES
|
29
|
+
end
|
30
|
+
|
31
|
+
if client_id
|
32
|
+
put_request("/authorizations/clients/#{client_id}", arguments.params)
|
33
|
+
else
|
34
|
+
raise raise_app_authentication_error
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Revoke all authorizations for an application
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# github = Github.new basic_auth: "client_id:client_secret"
|
42
|
+
# github.oauth.app.delete 'client-id'
|
43
|
+
#
|
44
|
+
# Revoke an authorization for an application
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
#
|
48
|
+
# github = Github.new basic_auth: "client_id:client_secret"
|
49
|
+
# github.oauth.app.delete 'client-id', 'access-token'
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
def delete(*args)
|
53
|
+
raise_authentication_error unless authenticated?
|
54
|
+
params = arguments(args, required: [:client_id]).params
|
55
|
+
|
56
|
+
if client_id
|
57
|
+
if access_token = (params.delete('access_token') || args[1])
|
58
|
+
delete_request("/applications/#{client_id}/tokens/#{access_token}", params)
|
59
|
+
else
|
60
|
+
# Revokes all tokens
|
61
|
+
delete_request("/applications/#{client_id}/tokens", params)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
raise raise_app_authentication_error
|
65
|
+
end
|
66
|
+
end
|
67
|
+
alias :remove :delete
|
68
|
+
alias :revoke :delete
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def raise_app_authentication_error
|
73
|
+
raise ArgumentError, 'To create authorization for the app, ' +
|
74
|
+
'you need to provide client_id argument and client_secret parameter'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -1,17 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
class Array # :nodoc:
|
2
4
|
|
3
|
-
|
5
|
+
# Returns a new arrray with keys removed
|
6
|
+
#
|
7
|
+
def except(*keys)
|
4
8
|
self.dup.except!(*keys)
|
5
|
-
end unless method_defined?
|
9
|
+
end unless method_defined? :except
|
6
10
|
|
7
|
-
|
11
|
+
# Similar to except but modifies self
|
12
|
+
#
|
13
|
+
def except!(*items)
|
8
14
|
copy = self.dup
|
9
15
|
copy.reject! { |item| items.include? item }
|
10
16
|
copy
|
11
|
-
end unless method_defined?
|
17
|
+
end unless method_defined? :except!
|
12
18
|
|
19
|
+
# Selects a hash from the arguments list
|
20
|
+
#
|
13
21
|
def extract_options!
|
14
22
|
last.is_a?(::Hash) ? pop : {}
|
15
|
-
end
|
23
|
+
end unless method_defined? :extract_options!
|
16
24
|
|
17
25
|
end # Array
|
@@ -1,23 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
class Hash # :nodoc:
|
2
4
|
|
3
|
-
|
5
|
+
# Returns a new hash with keys removed
|
6
|
+
#
|
7
|
+
def except(*items)
|
4
8
|
self.dup.except!(*items)
|
5
|
-
end unless method_defined?
|
9
|
+
end unless method_defined? :except
|
6
10
|
|
7
|
-
|
11
|
+
# Similar to except but modifies self
|
12
|
+
#
|
13
|
+
def except!(*keys)
|
8
14
|
copy = self.dup
|
9
15
|
keys.each { |key| copy.delete!(key) }
|
10
16
|
copy
|
11
|
-
end unless method_defined?
|
17
|
+
end unless method_defined? :except!
|
12
18
|
|
13
|
-
|
19
|
+
# Returns a new hash with all the keys converted to symbols
|
20
|
+
#
|
21
|
+
def symbolize_keys
|
14
22
|
inject({}) do |hash, (key, value)|
|
15
23
|
hash[(key.to_sym rescue key) || key] = value
|
16
24
|
hash
|
17
25
|
end
|
18
|
-
end unless method_defined?
|
26
|
+
end unless method_defined? :symbolize_keys
|
19
27
|
|
20
|
-
|
28
|
+
# Similar to symbolize_keys but modifies self
|
29
|
+
#
|
30
|
+
def symbolize_keys!
|
21
31
|
hash = symbolize_keys
|
22
32
|
hash.each do |key, val|
|
23
33
|
hash[key] = case val
|
@@ -32,42 +42,51 @@ class Hash # :nodoc:
|
|
32
42
|
end
|
33
43
|
end
|
34
44
|
return hash
|
35
|
-
end unless method_defined?
|
45
|
+
end unless method_defined? :symbolize_keys!
|
36
46
|
|
37
|
-
|
47
|
+
# Returns hash collapsed into a query string
|
48
|
+
#
|
49
|
+
def serialize
|
38
50
|
self.map { |key, val| [key, val].join("=") }.join("&")
|
39
|
-
end unless method_defined?
|
51
|
+
end unless method_defined? :serialize
|
40
52
|
|
41
|
-
|
53
|
+
# Searches for all deeply nested keys
|
54
|
+
#
|
55
|
+
def deep_keys
|
42
56
|
keys = self.keys
|
43
57
|
keys.each do |key|
|
44
58
|
if self[key].is_a?(Hash)
|
45
|
-
keys << self[key].
|
59
|
+
keys << self[key].deep_keys.compact.flatten
|
46
60
|
next
|
47
61
|
end
|
48
62
|
end
|
49
63
|
keys.flatten
|
50
|
-
end unless method_defined?
|
64
|
+
end unless method_defined? :deep_keys
|
51
65
|
|
52
|
-
|
53
|
-
|
54
|
-
|
66
|
+
# Returns true if the given key is present inside deeply nested hash
|
67
|
+
#
|
68
|
+
def deep_key?(key)
|
69
|
+
self.deep_keys.include? key
|
70
|
+
end unless method_defined? :deep_key?
|
55
71
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
72
|
+
# Recursively merges self with other hash and returns new hash.
|
73
|
+
#
|
74
|
+
def deep_merge(other, &block)
|
75
|
+
dup.deep_merge!(other, &block)
|
76
|
+
end unless method_defined? :deep_merge
|
77
|
+
|
78
|
+
# Similar as deep_merge but modifies self
|
79
|
+
#
|
80
|
+
def deep_merge!(other, &block)
|
81
|
+
other.each_pair do |key, val|
|
82
|
+
tval = self[key]
|
83
|
+
if tval.is_a?(Hash) && val.is_a?(Hash)
|
84
|
+
self[key] = tval.deep_merge(val)
|
85
|
+
else
|
86
|
+
self[key] = block && tval ? block.call(k, tval, val) : val
|
68
87
|
end
|
69
88
|
end
|
70
|
-
|
71
|
-
end
|
89
|
+
self
|
90
|
+
end unless method_defined? :deep_merge!
|
72
91
|
|
73
92
|
end # Hash
|