omnijack 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +15 -0
- data/NOTICE +5 -0
- data/README.md +119 -0
- data/Rakefile +23 -0
- data/features/list.feature +19 -0
- data/features/metadata.feature +43 -0
- data/features/platforms.feature +23 -0
- data/features/step_definitions/list.rb +12 -0
- data/features/step_definitions/metadata.rb +20 -0
- data/features/step_definitions/platforms.rb +8 -0
- data/features/step_definitions/project.rb +20 -0
- data/features/support/env.rb +4 -0
- data/lib/omnijack/config.rb +67 -0
- data/lib/omnijack/list.rb +94 -0
- data/lib/omnijack/metadata.rb +244 -0
- data/lib/omnijack/platforms.rb +96 -0
- data/lib/omnijack/project/metaprojects.rb +38 -0
- data/lib/omnijack/project.rb +63 -0
- data/lib/omnijack/version.rb +24 -0
- data/lib/omnijack.rb +54 -0
- data/omnijack.gemspec +37 -0
- data/spec/omnijack/config_spec.rb +55 -0
- data/spec/omnijack/list_spec.rb +133 -0
- data/spec/omnijack/metadata_spec.rb +577 -0
- data/spec/omnijack/platforms_spec.rb +132 -0
- data/spec/omnijack/project/angry_chef_spec.rb +55 -0
- data/spec/omnijack/project/chef_container_spec.rb +55 -0
- data/spec/omnijack/project/chef_dk_spec.rb +55 -0
- data/spec/omnijack/project/chef_server_spec.rb +55 -0
- data/spec/omnijack/project/chef_spec.rb +55 -0
- data/spec/omnijack/project_spec.rb +52 -0
- data/spec/omnijack_spec.rb +109 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/real_test_data.json +131 -0
- data/vendor/chef/LICENSE +201 -0
- data/vendor/chef/NOTICE +21 -0
- data/vendor/chef/lib/chef/exceptions.rb +353 -0
- data/vendor/chef/lib/chef/mixin/params_validate.rb +242 -0
- metadata +276 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5cadaf238bfb0ff5f33ed95b1329043182bd8151
|
4
|
+
data.tar.gz: 890034875797b786f1fcda49430aff51ad5e7abc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 703d6eca87963b31a99564b84c80f709621fde3440b9c8254ebe8d4260560c49ec7e6ded97d2151564c822fa9a0d24651f71c1bef7d828ca2d71c2e693bc1cf7
|
7
|
+
data.tar.gz: 1eed214f4e432ba6b1c70191a48cefd63a7ca1d1cddb369bf99eb11b4a6566ec693084d5f4e588b6b2df082f752d5faa997d19f11993b91ade3027eecf7d8cf3
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Author:: Jonathan Hartman (<j@p4nt5.com>)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Jonathan Hartman
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
data/NOTICE
ADDED
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
Omnijack
|
2
|
+
========
|
3
|
+
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/omnijack.png)][fury]
|
5
|
+
[![Build Status](http://img.shields.io/travis/RoboticCheese/omnijack-ruby.svg)][travis]
|
6
|
+
[![Code Climate](http://img.shields.io/codeclimate/github/kabisaict/flow.svg)][codeclimate]
|
7
|
+
[![Coverage Status](http://img.shields.io/coveralls/RoboticCheese/omnijack-ruby.svg)][coveralls]
|
8
|
+
[![Dependency Status](http://img.shields.io/gemnasium/RoboticCheese/omnijack.svg)][gemnasium]
|
9
|
+
|
10
|
+
[fury]: http://badge.fury.io/rb/omnijack
|
11
|
+
[travis]: http://travis-ci.org/RoboticCheese/omnijack-ruby
|
12
|
+
[codeclimate]: https://codeclimate.com/github/RoboticCheese/omnijack-ruby
|
13
|
+
[coveralls]: https://coveralls.io/r/RoboticCheese/omnijack-ruby
|
14
|
+
[gemnasium]: https://gemnasium.com/RoboticCheese/omnijack-ruby
|
15
|
+
|
16
|
+
A Ruby client interface to Chef's
|
17
|
+
[Omnitruck](https://github.com/opscode/opscode-omnitruck) API.
|
18
|
+
|
19
|
+
Installation
|
20
|
+
------------
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'omnijack'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install omnijack
|
35
|
+
|
36
|
+
Usage
|
37
|
+
-----
|
38
|
+
|
39
|
+
Getting Chef-DK package metadata from the official Chef API:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'omnijack'
|
43
|
+
|
44
|
+
chef_dk = Omnijack::Project::ChefDk.new
|
45
|
+
metadata = chef_dk.metadata
|
46
|
+
|
47
|
+
puts metadata
|
48
|
+
|
49
|
+
puts metadata.url
|
50
|
+
puts metadata.filename
|
51
|
+
puts metadata.md5
|
52
|
+
puts metadata.sha256
|
53
|
+
puts metadata.yolo
|
54
|
+
|
55
|
+
puts metadata[:url]
|
56
|
+
puts metadata[:filename]
|
57
|
+
puts metadata[:md5]
|
58
|
+
puts metadata[:sha256]
|
59
|
+
puts metadata[:yolo]
|
60
|
+
```
|
61
|
+
Getting Chef-DK project data from an unofficial Omnitruck API:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
Omnijack::Project::ChefDk.new(
|
65
|
+
base_url: 'https://some.custom.chef.api/endpoint'
|
66
|
+
)
|
67
|
+
```
|
68
|
+
|
69
|
+
Getting Chef-DK project data for a version other than the latest release:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
Omnijack::Project::ChefDk.new(
|
73
|
+
version: '1.2.3',
|
74
|
+
prerelease: true,
|
75
|
+
nightlies: true
|
76
|
+
)
|
77
|
+
```
|
78
|
+
|
79
|
+
Getting Chef-DK project data for a platform other than the one running:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
Omnijack::Project::ChefDk.new(
|
83
|
+
platform: 'ubuntu',
|
84
|
+
platform_version: '14.04',
|
85
|
+
machine_arch: 'x86_64'
|
86
|
+
)
|
87
|
+
```
|
88
|
+
|
89
|
+
Getting AngryChef project data:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
Omnijack::Project::AngryChef.new
|
93
|
+
```
|
94
|
+
Getting Chef project data:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
Omnijack::Project::Chef.new
|
98
|
+
```
|
99
|
+
|
100
|
+
Getting Chef-Container project data:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
Omnijack::Project::Container.new
|
104
|
+
```
|
105
|
+
|
106
|
+
Getting Chef Server project data:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
Omnijack::Project::Server.new
|
110
|
+
```
|
111
|
+
|
112
|
+
Contributing
|
113
|
+
------------
|
114
|
+
|
115
|
+
1. Fork it ( https://github.com/[my-github-username]/omnijack/fork )
|
116
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
117
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
118
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
119
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'rubocop/rake_task'
|
5
|
+
require 'cane/rake_task'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
require 'cucumber/rake/task'
|
8
|
+
|
9
|
+
Cane::RakeTask.new
|
10
|
+
|
11
|
+
RuboCop::RakeTask.new
|
12
|
+
|
13
|
+
desc 'Display LOC stats'
|
14
|
+
task :loc do
|
15
|
+
puts "\n## LOC Stats"
|
16
|
+
sh 'countloc -r lib/kitchen'
|
17
|
+
end
|
18
|
+
|
19
|
+
RSpec::Core::RakeTask.new(:spec)
|
20
|
+
|
21
|
+
Cucumber::Rake::Task.new(:features)
|
22
|
+
|
23
|
+
task default: [:cane, :rubocop, :loc, :spec, :features]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
Feature: Package lists
|
4
|
+
In order to see what all packages are available
|
5
|
+
As an Omnitruck API consumer
|
6
|
+
I want to access package list data
|
7
|
+
|
8
|
+
Scenario Outline: All default <Project>
|
9
|
+
Given no special arguments
|
10
|
+
When I create a <Project> project
|
11
|
+
Then the list has a section for Ubuntu 12.04 x86_64
|
12
|
+
Examples:
|
13
|
+
# Some endpoints aren't served by Chef's public Omnitruck API
|
14
|
+
| Project |
|
15
|
+
# | AngryChef |
|
16
|
+
| Chef |
|
17
|
+
# | ChefDk |
|
18
|
+
# | ChefContainer |
|
19
|
+
| ChefServer |
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
Feature: Metadata
|
4
|
+
In order to find out what package to download
|
5
|
+
As an Omnitruck API consumer
|
6
|
+
I want to access package metadata
|
7
|
+
|
8
|
+
Scenario Outline: All default <Project>
|
9
|
+
Given a Ubuntu 12.04 node
|
10
|
+
And no special arguments
|
11
|
+
When I create a <Project> project
|
12
|
+
Then the metadata has a url attribute
|
13
|
+
And the metadata has a filename attribute
|
14
|
+
And the metadata has an md5 attribute
|
15
|
+
And the metadata has a sha256 attribute
|
16
|
+
And the metadata doesn't have a yolo attribute
|
17
|
+
Examples:
|
18
|
+
| Project |
|
19
|
+
| AngryChef |
|
20
|
+
| Chef |
|
21
|
+
| ChefDk |
|
22
|
+
| ChefContainer |
|
23
|
+
| ChefServer |
|
24
|
+
|
25
|
+
Scenario Outline: <Project> with nightlies enabled
|
26
|
+
Given a Ubuntu 12.04 node
|
27
|
+
And nightlies enabled
|
28
|
+
When I create a <Project> project
|
29
|
+
Then the metadata has a url attribute
|
30
|
+
And the metadata has a filename attribute
|
31
|
+
And the metadata has an md5 attribute
|
32
|
+
And the metadata has a sha256 attribute
|
33
|
+
And the metadata has a yolo attribute
|
34
|
+
Examples:
|
35
|
+
| Project |
|
36
|
+
# | AngryChef | There are no nightly builds for AngryChef(?)
|
37
|
+
| Chef |
|
38
|
+
| ChefDk |
|
39
|
+
| ChefContainer |
|
40
|
+
| ChefServer |
|
41
|
+
|
42
|
+
# We can't quite be guaranteed of a prerelease version of anything existing
|
43
|
+
# Scenario Outline: <Project> with prerelease enabled
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
Feature: Package platforms
|
4
|
+
In order to see what all packages are available
|
5
|
+
As an Omnitruck API consumer
|
6
|
+
I want to access package list data
|
7
|
+
|
8
|
+
Scenario Outline: All default <Project>
|
9
|
+
Given no special arguments
|
10
|
+
When I create a <Project> project
|
11
|
+
Then the platforms maps the following items:
|
12
|
+
| Nickname | FullName |
|
13
|
+
| el | Enterprise Linux |
|
14
|
+
| debian | Debian |
|
15
|
+
| freebsd | FreeBSD |
|
16
|
+
Examples:
|
17
|
+
# Some endpoints aren't served by Chef's public Omnitruck API
|
18
|
+
| Project |
|
19
|
+
| AngryChef |
|
20
|
+
| Chef |
|
21
|
+
| ChefDk |
|
22
|
+
| ChefContainer |
|
23
|
+
| ChefServer |
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
Then(/^the list has a section for ([\w ]+) ([0-9\.]+) x86_64$/) do |p, v|
|
4
|
+
platform = p.split.join('_').downcase.to_sym
|
5
|
+
version = v.to_sym
|
6
|
+
[
|
7
|
+
@project.list.send(platform)[version][:x86_64],
|
8
|
+
@project.list[platform][version][:x86_64]
|
9
|
+
].each do |i|
|
10
|
+
expect(i).to be_an_instance_of(Hash)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
Then(/^the metadata (has|doesn't have) a(n)? (\w+) attribute$/) do |has, _, a|
|
4
|
+
case has
|
5
|
+
when 'has'
|
6
|
+
regex = case a
|
7
|
+
when 'url'
|
8
|
+
%r{^https://opscode-omnibus-packages\.s3\.amazonaws\.com.*$}
|
9
|
+
when 'filename'
|
10
|
+
/^[A-Za-z0-9_\.\-%]+\.(rpm|deb|pkg|msi)$/
|
11
|
+
when 'md5'
|
12
|
+
/^\w{32}$/
|
13
|
+
when 'sha256'
|
14
|
+
/^\w{64}$/
|
15
|
+
end
|
16
|
+
expect(@project.metadata.send(a)).to match(regex)
|
17
|
+
else
|
18
|
+
expect(@project.metadata.send(a)).to eq(nil)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
Then(/^the platforms maps the following items:$/) do |platforms|
|
4
|
+
puts platforms.hashes.each do |hsh|
|
5
|
+
expect(@project.platforms.send(hsh['Nickname'])).to eq(hsh['FullName'])
|
6
|
+
expect(@project.platforms[hsh['Nickname']]).to eq(hsh['FullName'])
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
Given(/^a(n)? ([\w ]+) ([0-9\.]+) node$/) do |_, platform, version|
|
4
|
+
node = { platform: platform.split.join('_').downcase,
|
5
|
+
platform_version: version,
|
6
|
+
kernel: { machine: 'x86_64' } }
|
7
|
+
allow_any_instance_of(Omnijack::Metadata).to receive(:node).and_return(node)
|
8
|
+
end
|
9
|
+
|
10
|
+
Given(/^no special arguments$/) do
|
11
|
+
@args = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
Given(/^nightlies enabled$/) do
|
15
|
+
@args = { nightlies: true }
|
16
|
+
end
|
17
|
+
|
18
|
+
When(/^I create a(n)? (\w+) project$/) do |_, project|
|
19
|
+
@project = Omnijack::Project.const_get(project).new(@args)
|
20
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author:: Jonathan Hartman (<j@p4nt5.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2014, Jonathan Hartman
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
require_relative '../../vendor/chef/lib/chef/exceptions'
|
20
|
+
require_relative '../../vendor/chef/lib/chef/mixin/params_validate'
|
21
|
+
|
22
|
+
class Omnijack
|
23
|
+
# Offer a config to base all the metaruby off of
|
24
|
+
#
|
25
|
+
# @author Jonathan Hartman <j@p4nt5.com>
|
26
|
+
module Config
|
27
|
+
DEFAULT_BASE_URL ||= 'https://www.getchef.com/chef'
|
28
|
+
OMNITRUCK_PROJECTS ||= {
|
29
|
+
angry_chef: {
|
30
|
+
endpoints: {
|
31
|
+
metadata: '/metadata-angrychef',
|
32
|
+
package_list: '/full_angrychef_list',
|
33
|
+
platform_names: '/angrychef_platform_names'
|
34
|
+
}
|
35
|
+
},
|
36
|
+
chef: {
|
37
|
+
endpoints: {
|
38
|
+
metadata: '/metadata',
|
39
|
+
package_list: '/full_client_list',
|
40
|
+
platform_names: '/chef_platform_names'
|
41
|
+
}
|
42
|
+
},
|
43
|
+
chef_dk: {
|
44
|
+
endpoints: {
|
45
|
+
metadata: '/metadata-chefdk',
|
46
|
+
package_list: '/full_chefdk_list',
|
47
|
+
platform_names: '/chefdk_platform_names'
|
48
|
+
}
|
49
|
+
},
|
50
|
+
chef_container: {
|
51
|
+
endpoints: {
|
52
|
+
metadata: '/metadata-container',
|
53
|
+
package_list: '/full_container_list',
|
54
|
+
platform_names: '/chef_container_platform_names'
|
55
|
+
}
|
56
|
+
},
|
57
|
+
chef_server: {
|
58
|
+
endpoints: {
|
59
|
+
metadata: '/metadata-server',
|
60
|
+
package_list: '/full_server_list',
|
61
|
+
platform_names: '/chef_server_platform_names'
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
METADATA_ATTRIBUTES ||= [:url, :md5, :sha256, :yolo]
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author:: Jonathan Hartman (<j@p4nt5.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2014, Jonathan Hartman
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
require 'json'
|
20
|
+
require 'open-uri'
|
21
|
+
require_relative 'config'
|
22
|
+
require_relative '../omnijack'
|
23
|
+
|
24
|
+
class Omnijack
|
25
|
+
# A class for representing an Omnitruck full list object
|
26
|
+
#
|
27
|
+
# @author Jonathan Hartman <j@p4nt5.com>
|
28
|
+
class List < Omnijack
|
29
|
+
include Config
|
30
|
+
|
31
|
+
#
|
32
|
+
# Make list items accessible via methods
|
33
|
+
#
|
34
|
+
# @param [Symbol] method_id
|
35
|
+
#
|
36
|
+
def method_missing(method_id, args = nil)
|
37
|
+
args.nil? && to_h[method_id] || super
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Make list items accessible via hash keys
|
42
|
+
#
|
43
|
+
# @param [Symbol] key
|
44
|
+
# @return [String, NilClass]
|
45
|
+
#
|
46
|
+
def [](key)
|
47
|
+
to_h[key]
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Offer a hash representation of the list
|
52
|
+
#
|
53
|
+
# @return [Hash]
|
54
|
+
#
|
55
|
+
def to_h
|
56
|
+
# TODO: Use a Mash -- some keys are better off addressed as strings
|
57
|
+
JSON.parse(raw_data, symbolize_names: true)
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Use the raw data string as a string representation
|
62
|
+
#
|
63
|
+
define_method(:to_s) { raw_data }
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
#
|
68
|
+
# Fetch the raw list from the configured URI
|
69
|
+
#
|
70
|
+
# @return [String]
|
71
|
+
#
|
72
|
+
def raw_data
|
73
|
+
@raw_data ||= api_url.open.read
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Construct the full API query URL from base + endpoint
|
78
|
+
#
|
79
|
+
# @return [URI::HTTP, URI::HTTPS]
|
80
|
+
#
|
81
|
+
def api_url
|
82
|
+
@api_url ||= URI.parse(::File.join(base_url, endpoint))
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Return the API endpoint for the package list of this project
|
87
|
+
#
|
88
|
+
# @return [String]
|
89
|
+
#
|
90
|
+
def endpoint
|
91
|
+
OMNITRUCK_PROJECTS[name][:endpoints][:package_list]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|