verso 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/README.md +10 -22
- data/lib/verso.rb +10 -2
- data/lib/verso/base.rb +50 -0
- data/lib/verso/cluster.rb +55 -25
- data/lib/verso/cluster_list.rb +15 -10
- data/lib/verso/correlation_list.rb +30 -40
- data/lib/verso/course.rb +106 -43
- data/lib/verso/course_list.rb +57 -21
- data/lib/verso/credential.rb +94 -20
- data/lib/verso/credential_list.rb +30 -15
- data/lib/verso/duty_area.rb +23 -10
- data/lib/verso/edition_list.rb +18 -10
- data/lib/verso/emphasis.rb +26 -12
- data/lib/verso/emphasis_list.rb +18 -10
- data/lib/verso/examination_list.rb +28 -12
- data/lib/verso/extra.rb +34 -32
- data/lib/verso/extras_list.rb +28 -12
- data/lib/verso/frontmatter.rb +43 -11
- data/lib/verso/hash.rb +19 -0
- data/lib/verso/http_gettable.rb +31 -0
- data/lib/verso/occupation.rb +36 -14
- data/lib/verso/occupation_data.rb +35 -14
- data/lib/verso/occupation_list.rb +23 -20
- data/lib/verso/pathway.rb +32 -14
- data/lib/verso/program_area.rb +42 -21
- data/lib/verso/program_area_list.rb +15 -11
- data/lib/verso/standard.rb +45 -23
- data/lib/verso/standards_list.rb +41 -30
- data/lib/verso/task.rb +52 -17
- data/lib/verso/task_list.rb +40 -17
- data/lib/verso/version.rb +1 -1
- data/spec/cluster_list_spec.rb +78 -5
- data/spec/cluster_spec.rb +106 -9
- data/spec/correlation_list_spec.rb +108 -50
- data/spec/course_list_spec.rb +112 -23
- data/spec/course_spec.rb +321 -127
- data/spec/credential_list_spec.rb +83 -52
- data/spec/credential_spec.rb +358 -19
- data/spec/duty_area_spec.rb +47 -17
- data/spec/edition_list_spec.rb +90 -4
- data/spec/emphasis_list_spec.rb +75 -11
- data/spec/emphasis_spec.rb +37 -21
- data/spec/examination_list_spec.rb +146 -20
- data/spec/extra_spec.rb +61 -22
- data/spec/extras_list_spec.rb +80 -17
- data/spec/frontmatter_spec.rb +141 -6
- data/spec/hash_spec.rb +49 -0
- data/spec/occupation_data_spec.rb +31 -13
- data/spec/occupation_list_spec.rb +88 -15
- data/spec/occupation_spec.rb +72 -28
- data/spec/pathway_spec.rb +47 -27
- data/spec/program_area_list_spec.rb +78 -4
- data/spec/program_area_spec.rb +70 -22
- data/spec/standard_spec.rb +94 -36
- data/spec/standards_list_spec.rb +130 -36
- data/spec/task_list_spec.rb +160 -51
- data/spec/task_spec.rb +120 -33
- data/verso.gemspec +3 -1
- metadata +41 -17
- data/lib/verso/http_get.rb +0 -9
- data/lib/verso/sol_correlation_list.rb +0 -53
- data/spec/sol_correlation_list_spec.rb +0 -74
data/lib/verso/course_list.rb
CHANGED
@@ -1,36 +1,72 @@
|
|
1
1
|
module Verso
|
2
|
-
|
2
|
+
# Search Verso courses by :text, :cluster, :program_area, :code, :edition,
|
3
|
+
# or any combination and get back an Array-like collection of
|
4
|
+
# {Verso::Course} objects.
|
5
|
+
#
|
6
|
+
# @see http://api.cteresource.org/docs/courses
|
7
|
+
#
|
8
|
+
# @example Search by text
|
9
|
+
# courses = Verso::CourseList.new(:text => "golf") # => <Verso::CourseList:0x007fa5a10bbb68 @attrs={:text=>"golf"}>
|
10
|
+
# courses.first.title # => "Turf Grass Applications, Advanced"
|
11
|
+
#
|
12
|
+
# @example Search by cluster
|
13
|
+
# course = Verso::CourseList.new(:cluster => "Information Technology).first
|
14
|
+
# course.title # => "Computer Applications"
|
15
|
+
#
|
16
|
+
# @example Search by program area
|
17
|
+
# course = Verso::CourseList.new(:program_area => "Career Connections").last
|
18
|
+
# course.title # => "Career Investigation Phase I"
|
19
|
+
#
|
20
|
+
# @example Search by code
|
21
|
+
# courses = Verso::CourseList.new(:code => "6320") # => <Verso::CourseList:0x007fa5a1f27a08 @attrs={:code=>"6320"}>
|
22
|
+
# courses.first # => <Verso::Course:0x007fa5a1f3e5c8 @attrs={:duration=>36, :edition=>"2012", :code=>"6320" . . . }>
|
23
|
+
# courses.first.code # => "6320"
|
24
|
+
#
|
25
|
+
# @example Search by edition
|
26
|
+
# all_2012 = Verso::CourseList.new(:edition => "2012")
|
27
|
+
#
|
28
|
+
# @example Search by combination
|
29
|
+
# courses = Verso::CourseList.new(:text => "internet", :cluster => "Marketing") # => <Verso::CourseList:0x007fa5a19b6c90 @attrs={ . . . }>
|
30
|
+
# courses.count # => 1
|
31
|
+
#
|
32
|
+
# @example An empty search returns an empty list
|
33
|
+
# courses = Verso::CourseList.new
|
34
|
+
# courses.count # => 0
|
35
|
+
#
|
36
|
+
# @overload initialize(attrs={})
|
37
|
+
# @option attrs [String] :code Course code
|
38
|
+
# @option attrs [String] :edition Edition year
|
39
|
+
# @option attrs [String] :text Free text
|
40
|
+
# @option attrs [String] :cluster Cluster title
|
41
|
+
# @option attrs [String] :program_area Program Area title
|
42
|
+
class CourseList < Verso::Base
|
3
43
|
include Enumerable
|
4
|
-
include
|
44
|
+
include HTTPGettable
|
45
|
+
extend Forwardable
|
46
|
+
def_delegators :courses, :[], :each, :empty?, :last, :length
|
5
47
|
|
6
|
-
|
7
|
-
@q_uri = Addressable::URI.new(
|
8
|
-
:path => '/courses',
|
9
|
-
:query_values => raw_query.
|
10
|
-
select { |k, v| v }.
|
11
|
-
reject { |k, v| v.to_s.empty? }
|
12
|
-
)
|
13
|
-
end
|
48
|
+
private
|
14
49
|
|
15
50
|
def courses
|
16
|
-
@courses ||= if
|
17
|
-
|
18
|
-
collect { |c| Course.new(c) }
|
51
|
+
@courses ||= if q_uri.query_values.values.any?
|
52
|
+
get_attr(:courses).collect { |c| Course.new(c) }
|
19
53
|
else
|
20
54
|
[]
|
21
55
|
end
|
22
56
|
end
|
23
57
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
58
|
+
def q_uri
|
59
|
+
Addressable::URI.new(
|
60
|
+
:path => '/courses',
|
61
|
+
:query_values => attrs.
|
62
|
+
reject { |k, v| k == :courses }.
|
63
|
+
select { |k, v| v }.
|
64
|
+
reject { |k, v| v.to_s.empty? }
|
65
|
+
)
|
30
66
|
end
|
31
67
|
|
32
|
-
def
|
33
|
-
|
68
|
+
def path
|
69
|
+
q_uri.request_uri
|
34
70
|
end
|
35
71
|
end
|
36
72
|
end
|
data/lib/verso/credential.rb
CHANGED
@@ -1,34 +1,108 @@
|
|
1
1
|
module Verso
|
2
|
-
|
3
|
-
|
2
|
+
# Credential Resource
|
3
|
+
#
|
4
|
+
# The usual way to get a Credential would be to use {Verso::CredentialList}
|
5
|
+
# or to get one from a related object, such as a {Verso::Course} object.
|
6
|
+
#
|
7
|
+
# @see http://api.cteresource.org/docs/credentials/credential
|
8
|
+
#
|
9
|
+
# @!attribute [r] admin_notes
|
10
|
+
# @return [String] HTML-formatted test administration notes
|
11
|
+
# @!attribute [r] amt_seal
|
12
|
+
# @return [Booelean] Advanced Math and Technology seal
|
13
|
+
# @!attribute [r] contact_info
|
14
|
+
# @return [String] Contractor contact information
|
15
|
+
# @!attribute [r] cost
|
16
|
+
# @return [String] Cost
|
17
|
+
# @!attribute [r] cte_seal
|
18
|
+
# @return [Boolean] CTE seal
|
19
|
+
# @!attribute [r] description
|
20
|
+
# @return [String] HTML-formatted text describing the credential.
|
21
|
+
# @!attribute [r] has_ancestor
|
22
|
+
# @return [Boolean] Did this credential exist in an earlier edition?
|
23
|
+
# @!attribute [r] how_to_earn_it
|
24
|
+
# @return [String] HTML-formatted text about how to earn it.
|
25
|
+
# @!attribute [r] id
|
26
|
+
# @return [Fixnum] Credential id
|
27
|
+
# @!attribute [r] items
|
28
|
+
# @return [String] Number of test items
|
29
|
+
# @!attribute [r] passing_score
|
30
|
+
# @return [Sring] Passing score
|
31
|
+
# @!attribute [r] pretest
|
32
|
+
# @return [Boolean,nil] Is a pre-test, study guide, or blueprint available?
|
33
|
+
# @!attribute [r] proctor
|
34
|
+
# @return [String] Test examiner/proctor
|
35
|
+
# @!attribute [r] program_area
|
36
|
+
# @return [String] Title of associated program area
|
37
|
+
# @!attribute [r] retired
|
38
|
+
# @return [Boolean] Is this credential slated for deletion?
|
39
|
+
# @!attribute [r] site
|
40
|
+
# @return [String] Allowed testing site
|
41
|
+
# @!attribute [r] time
|
42
|
+
# @return [String] Time allowed
|
43
|
+
# @!attribute [r] title
|
44
|
+
# @return [String] Credential title
|
45
|
+
# @!attribute [r] type
|
46
|
+
# @return ['Certification','License'] Credential type
|
47
|
+
# @!attribute [r] verified_credit
|
48
|
+
# @return [Boolean] Verified credit
|
49
|
+
#
|
50
|
+
# @overload initialize(attrs={})
|
51
|
+
# @note Any attributes may be set upon instantiation, using Options Hash.
|
52
|
+
# The following are required:
|
53
|
+
# @option attrs [Fixnum] :id Credential id *Required*
|
54
|
+
class Credential < Verso::Base
|
55
|
+
include HTTPGettable
|
56
|
+
attr_reader :admin_notes, :amt_seal, :contact_info, :cost, :cte_seal,
|
57
|
+
:description, :has_ancestor, :how_to_earn_it, :id, :items, :passing_score,
|
58
|
+
:pretest, :proctor, :program_area, :retired, :site, :time, :title, :type,
|
59
|
+
:verified_credit
|
60
|
+
alias amt_seal? amt_seal
|
61
|
+
alias cte_seal? cte_seal
|
62
|
+
alias has_ancestor? has_ancestor
|
63
|
+
alias retired? retired
|
64
|
+
alias verified_credit? verified_credit
|
4
65
|
|
5
|
-
|
6
|
-
|
66
|
+
# VDOE contacts, each responding to #name, #email, and #phone, all Strings.
|
67
|
+
#
|
68
|
+
# @return [Array] VDOE contacts
|
69
|
+
def contacts
|
70
|
+
@contacts ||= get_attr(:contacts).collect { |c| OpenStruct.new(c) }
|
7
71
|
end
|
8
72
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
JSON.parse(http_get("/credentials/#{id}"))["credential"]
|
13
|
-
)
|
14
|
-
end
|
15
|
-
@raw_credential[mname.to_s]
|
73
|
+
# @return [String] Contractor name
|
74
|
+
def contractor_name
|
75
|
+
get_attr(:contractor_name).to_s
|
16
76
|
end
|
17
77
|
|
18
|
-
|
19
|
-
|
78
|
+
# @return [String] Details
|
79
|
+
def details
|
80
|
+
get_attr(:details).to_s # #to_s b/c API sometimes returns nil
|
20
81
|
end
|
21
82
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
83
|
+
# Source. Responds to #title, #url, and #contact_info. All are Strings or
|
84
|
+
# nil. The last is HTML-formatted.
|
85
|
+
#
|
86
|
+
# @return [OpenStruct] Source
|
87
|
+
def source
|
88
|
+
# force update if we only have part of source
|
89
|
+
attrs.merge!(fetch) unless attrs[:source] && attrs[:source].has_key?(:url)
|
90
|
+
OpenStruct.new(get_attr(:source))
|
28
91
|
end
|
29
92
|
|
93
|
+
# @return [Array] Collection of related {Verso::Course} objects
|
30
94
|
def related_courses
|
31
|
-
|
95
|
+
@courses ||= get_attr(:related_courses).collect { |rc| Course.new(rc) }
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def path
|
101
|
+
"/credentials/#{id}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def fetch
|
105
|
+
super[:credential]
|
32
106
|
end
|
33
107
|
end
|
34
108
|
end
|
@@ -1,28 +1,43 @@
|
|
1
1
|
module Verso
|
2
|
-
|
2
|
+
# Credential list resource
|
3
|
+
#
|
4
|
+
# Search for {Verso::Credential} objects using free text, or get back the
|
5
|
+
# list of all of them.
|
6
|
+
#
|
7
|
+
# @see http://api.cteresource.org/docs/credentials
|
8
|
+
#
|
9
|
+
# @example All
|
10
|
+
# creds = Verso::CredentialList.new # => everything
|
11
|
+
# creds.first # => <Verso::Credential:0x007fb1a39e4038 . . . >
|
12
|
+
#
|
13
|
+
# @example Search
|
14
|
+
# creds = Verso::CredentialList.new(:text => "nocti")
|
15
|
+
# creds.first.source.title # => "National Occupational Competency Testing Institute (NOCTI)"
|
16
|
+
#
|
17
|
+
# @overload initialize(attrs={})
|
18
|
+
# @option attrs [String] :text Free text
|
19
|
+
class CredentialList < Verso::Base
|
3
20
|
include Enumerable
|
4
|
-
include
|
21
|
+
include HTTPGettable
|
22
|
+
extend Forwardable
|
23
|
+
def_delegators :credentials, :[], :each, :empty?, :last, :length
|
5
24
|
|
6
|
-
|
25
|
+
private
|
7
26
|
|
8
|
-
def
|
9
|
-
@
|
10
|
-
@q_uri.query_values = opts unless opts[:text].to_s.empty?
|
11
|
-
@credentials = JSON.parse(http_get(@q_uri.request_uri))["credentials"].
|
27
|
+
def credentials
|
28
|
+
@credentials ||= get_attr(:credentials).
|
12
29
|
collect { |c| Credential.new(c) }.
|
13
30
|
sort_by { |c| c.title }
|
14
31
|
end
|
15
32
|
|
16
|
-
def
|
17
|
-
|
33
|
+
def path
|
34
|
+
q_uri ||= Addressable::URI.new(:path => '/credentials')
|
35
|
+
q_uri.query_values = { :text => text } unless text.empty?
|
36
|
+
q_uri.request_uri
|
18
37
|
end
|
19
38
|
|
20
|
-
def
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def empty?
|
25
|
-
credentials.empty?
|
39
|
+
def text
|
40
|
+
attrs[:text] ? attrs[:text] : ''
|
26
41
|
end
|
27
42
|
end
|
28
43
|
end
|
data/lib/verso/duty_area.rb
CHANGED
@@ -1,16 +1,29 @@
|
|
1
1
|
module Verso
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
2
|
+
# Duty Area
|
3
|
+
#
|
4
|
+
# A group of tasks in a {Verso::TaskList}.
|
5
|
+
#
|
6
|
+
# @see http://api.cteresource.org/docs/courses/course/tasks
|
7
|
+
#
|
8
|
+
# @!attribute [r] code
|
9
|
+
# @return [String] Course code
|
10
|
+
# @!attribute [r] edition
|
11
|
+
# @return [String] Course edition year
|
12
|
+
# @!attribute [r] title
|
13
|
+
# @return [String] Duty Area title
|
14
|
+
#
|
15
|
+
# @note A DutyArea is created for you by {Verso::TaskList}. It corresponds
|
16
|
+
# to a portion of the Task List resource. You should never need to
|
17
|
+
# instantiate one yourself.
|
18
|
+
class DutyArea < Verso::Base
|
19
|
+
attr_reader :code, :edition, :title
|
10
20
|
|
21
|
+
# Tasks within this Duty Area
|
22
|
+
#
|
23
|
+
# @return [Array] {Verso::Task} objects
|
11
24
|
def tasks
|
12
|
-
@tasks ||=
|
13
|
-
Task.new(t.merge(
|
25
|
+
@tasks ||= get_attr(:tasks).collect do |t|
|
26
|
+
Task.new(t.merge(:code => code, :edition => edition))
|
14
27
|
end
|
15
28
|
end
|
16
29
|
end
|
data/lib/verso/edition_list.rb
CHANGED
@@ -1,19 +1,27 @@
|
|
1
1
|
module Verso
|
2
|
-
|
2
|
+
# Edition List resource
|
3
|
+
#
|
4
|
+
# A collection of Edition proxies that respond to #year, returning a string
|
5
|
+
# year like '2012'.
|
6
|
+
#
|
7
|
+
# @example Get a list
|
8
|
+
# editions = Verso::EditionList.new
|
9
|
+
# editions.first.year => # "2012"
|
10
|
+
#
|
11
|
+
class EditionList < Verso::Base
|
3
12
|
include Enumerable
|
4
|
-
include
|
13
|
+
include HTTPGettable
|
14
|
+
extend Forwardable
|
15
|
+
def_delegators :editions, :[], :each, :empty?, :last, :length
|
5
16
|
|
6
|
-
|
7
|
-
@editions ||= JSON.parse(http_get('/editions/'))["editions"].
|
8
|
-
collect { |e| OpenStruct.new(e) }
|
9
|
-
end
|
17
|
+
private
|
10
18
|
|
11
|
-
def
|
12
|
-
editions.
|
19
|
+
def editions
|
20
|
+
@editions ||= get_attr(:editions).collect { |e| OpenStruct.new(e) }
|
13
21
|
end
|
14
22
|
|
15
|
-
def
|
16
|
-
editions
|
23
|
+
def path
|
24
|
+
"/editions/"
|
17
25
|
end
|
18
26
|
end
|
19
27
|
end
|
data/lib/verso/emphasis.rb
CHANGED
@@ -1,21 +1,35 @@
|
|
1
1
|
module Verso
|
2
|
-
|
3
|
-
|
2
|
+
# Academic Emphasis resource
|
3
|
+
#
|
4
|
+
# @see http://api.cteresource.org/docs/academics/emphasis
|
5
|
+
#
|
6
|
+
# @!attribute [r] id
|
7
|
+
# @return [Fixnum] Academic Emphasis id
|
8
|
+
# @!attribute [r] title
|
9
|
+
# @return Academic Emphasis title
|
10
|
+
#
|
11
|
+
# @overload initialize(attrs={})
|
12
|
+
# @note Any attributes may be set upon instantiation, using Options Hash.
|
13
|
+
# The following are required:
|
14
|
+
# @option attrs [Fixnum] :id Academic Emphasis id *Required*
|
15
|
+
class Emphasis < Verso::Base
|
16
|
+
include HTTPGettable
|
17
|
+
attr_reader :id, :title
|
4
18
|
|
5
|
-
|
6
|
-
|
19
|
+
# @return [Array] Collection of related {Verso::OccupationData} objects
|
20
|
+
def occupation_data
|
21
|
+
@occupation_data ||= get_attr(:occupation_data).
|
22
|
+
collect { |od| OccupationData.new(od) }
|
7
23
|
end
|
8
24
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@raw_emphasis[mname.to_s]
|
25
|
+
private
|
26
|
+
|
27
|
+
def fetch
|
28
|
+
super[:emphasis]
|
14
29
|
end
|
15
30
|
|
16
|
-
def
|
17
|
-
|
18
|
-
collect { |od| OccupationData.new(od) }
|
31
|
+
def path
|
32
|
+
"/academics/#{id}"
|
19
33
|
end
|
20
34
|
end
|
21
35
|
end
|
data/lib/verso/emphasis_list.rb
CHANGED
@@ -1,19 +1,27 @@
|
|
1
1
|
module Verso
|
2
|
-
|
2
|
+
# Academic Emphasis List resource
|
3
|
+
#
|
4
|
+
# A collection of all {Verso::Emphasis} objects.
|
5
|
+
#
|
6
|
+
# @see http://api.cteresource.org/docs/academics
|
7
|
+
#
|
8
|
+
# @example Get a list
|
9
|
+
# emphases = Verso::EmphasisList.new # => all of them
|
10
|
+
# emphases.first.title # => "Algebra"
|
11
|
+
class EmphasisList < Verso::Base
|
3
12
|
include Enumerable
|
4
|
-
include
|
13
|
+
include HTTPGettable
|
14
|
+
extend Forwardable
|
15
|
+
def_delegators :emphases, :[], :each, :empty?, :last, :length
|
5
16
|
|
6
|
-
|
7
|
-
@emphases ||= JSON.parse(http_get('/academics/'))["emphases"].
|
8
|
-
collect { |em| Emphasis.new(em) }
|
9
|
-
end
|
17
|
+
private
|
10
18
|
|
11
|
-
def
|
12
|
-
emphases.
|
19
|
+
def emphases
|
20
|
+
@emphases ||= get_attr(:emphases).collect { |em| Emphasis.new(em) }
|
13
21
|
end
|
14
22
|
|
15
|
-
def
|
16
|
-
|
23
|
+
def path
|
24
|
+
"/academics/"
|
17
25
|
end
|
18
26
|
end
|
19
27
|
end
|
@@ -1,21 +1,37 @@
|
|
1
1
|
module Verso
|
2
|
-
|
2
|
+
# Examination List resource
|
3
|
+
#
|
4
|
+
# A collection of Examination stand-in objects that respond to:
|
5
|
+
# * #amt_seal @return [Boolean] AMT Seal
|
6
|
+
# * #passing_score @return [String] Passing score
|
7
|
+
# * #retired @return [Boolean] Slated to be deleted?
|
8
|
+
# * #source @return [String] Title of exam source
|
9
|
+
# * #title @return [String] Exam title
|
10
|
+
# * #verified_credit @return [Booelean] Verified credit
|
11
|
+
#
|
12
|
+
# The attributes of the Examination stand-ins are similar to
|
13
|
+
# {Verso::Credential}.
|
14
|
+
#
|
15
|
+
# @example Get the list
|
16
|
+
# exams = Verso::ExaminationList.new # => everything
|
17
|
+
# exams.first.title # => "Advanced Placement Computer Science A"
|
18
|
+
#
|
19
|
+
# @see http://api.cteresource.org/docs/examinations
|
20
|
+
class ExaminationList < Verso::Base
|
3
21
|
include Enumerable
|
4
|
-
include
|
22
|
+
include HTTPGettable
|
23
|
+
extend Forwardable
|
24
|
+
def_delegators :examinations, :[], :each, :empty?, :last, :length
|
5
25
|
|
6
|
-
|
26
|
+
private
|
7
27
|
|
8
|
-
def
|
9
|
-
@examinations
|
10
|
-
|
28
|
+
def examinations
|
29
|
+
@examinations ||= get_attr(:examinations).
|
30
|
+
collect { |e| OpenStruct.new(e) }
|
11
31
|
end
|
12
32
|
|
13
|
-
def
|
14
|
-
examinations
|
15
|
-
end
|
16
|
-
|
17
|
-
def last
|
18
|
-
examinations[-1]
|
33
|
+
def path
|
34
|
+
"/examinations/"
|
19
35
|
end
|
20
36
|
end
|
21
37
|
end
|