cmdlet 0.2.1 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbd35f78dbd459eac65f0df1fd2f6aefc16b560b3083140a9ea092dc6cf359ec
4
- data.tar.gz: 27fff2ef08e9df32b43b5bf06d72755893e3a701af8057d55c0379f6ebe82226
3
+ metadata.gz: e35da0dc6c2ae3fd9ccab71c7a964d8e5e3c66e703e51f5710b6cea7867554b5
4
+ data.tar.gz: 37d37aa86e3b75dc60d7f17fa34cd652571698b5dbe7fbeb4baf431c8a70d2e6
5
5
  SHA512:
6
- metadata.gz: cf6ea403db6da89cf2f82957773fe648f7eb8883acd112524d44149fb4f8067409b3d17e8240d13b6ab1059883d86ecbadc4efb61bbbec4722cd0bc6620baee6
7
- data.tar.gz: 91153e6f0c1aec787f555ff44afe2469328208bd11f8d8857a05d4da18a7f54cdc00dd12e2a6c774c1d2ec2cd2870a5e23723bc08d9783709fd75fa526d40a35
6
+ metadata.gz: bee6e91c0894c7f902826d62572a94d88a5b361bb7d438c159ff0c5e548f21f1ccb693f4d09796bfe8ea1ffb9f87fa86828092c7bd6de271ddba68864a87aaba
7
+ data.tar.gz: 570220da94644b05c3c8fb685dfe034da565c520d9c7e20e52756861d6ea18508d69098c1cc8c09715cbee14bc5955bd7d5e9eaa01abb2c167ddc4cd9ae7a3f8
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Handlebarsjs
4
+ module Helpers
5
+ # {{cmdlet.category_description}}
6
+ module {{camel cmdlet.category}}
7
+ # {{camel cmdlet.name}}: {{cmdlet.description}}
8
+ class {{camel cmdlet.name}} < Handlebarsjs::BaseHelper
9
+ register_cmdlet(Cmdlet::{{camel cmdlet.category}}::{{camel cmdlet.name}})
10
+
11
+ def to_proc
12
+ ->({{#each cmdlet.parameters}}{{#if ./splat}}{{./splat}}{{/if}}{{./name}}{{#if ./default}} = {{{./default}}}{{/if}}{{#if @last}}{{^}}, {{/if}}{{/each}}, _opts) { wrapper(cmdlet.call({{#each cmdlet.parameters}}{{#if ./splat}}{{./splat}}{{/if}}{{./name}}{{#if @last}}{{^}}, {{/if}}{{/each}})) }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ {{#each cmdlets}}
4
+ require_relative 'handlebarsjs/helpers/{{category}}/{{name}}'
5
+ {{/each}}
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # {{camel cmdlet.name}}: {{cmdlet.description}}
4
+ RSpec.describe Handlebarsjs::Helpers::{{camel cmdlet.category}}::{{camel cmdlet.name}} do
5
+ let(:helper_name) { :{{cmdlet.name}} }
6
+ let(:helper) { described_class.new }
7
+ let(:handlebars) { Handlebarsjs::Handlebars.new }
8
+
9
+ before { handlebars.handlebars_snapshot.add_helper(helper_name, helper) }
10
+
11
+ describe '#process_template' do
12
+ subject { handlebars.process_template(template, data).squish }
13
+
14
+ let(:lhs) { nil }
15
+ let(:rhs) { nil }
16
+ let(:data) { { lhs: lhs, rhs: rhs } }
17
+
18
+ let(:template) do
19
+ <<~TEXT.chomp
20
+ {{#if (and lhs rhs)}}
21
+ {{^}}
22
+ {{/if}}
23
+ TEXT
24
+ end
25
+ end
26
+ end
data/.builders/boot.rb CHANGED
@@ -46,6 +46,7 @@ KConfig.configure(CONFIG_KEY) do |config|
46
46
  builder_folder = Dir.pwd
47
47
  base_folder = File.expand_path('../', builder_folder)
48
48
  global_template = File.expand_path('~/dev/kgems/k_templates/templates')
49
+ handlebars_folder = File.expand_path('~/dev/kgems/handlebarsjs/')
49
50
 
50
51
  config.template_folders.add(:global_template , global_template)
51
52
  config.template_folders.add(:template , File.expand_path('.templates', Dir.pwd))
@@ -55,6 +56,9 @@ KConfig.configure(CONFIG_KEY) do |config|
55
56
  config.target_folders.add(:spec , :app, 'spec/cmdlet')
56
57
  config.target_folders.add(:builder , builder_folder)
57
58
  config.target_folders.add(:builder_data , :builder, 'data')
59
+ config.target_folders.add(:handlebars , handlebars_folder)
60
+ config.target_folders.add(:handlebars_lib , :handlebars, 'lib')
61
+ config.target_folders.add(:handlebars_spec , :handlebars, 'spec')
58
62
  end
59
63
 
60
64
  KConfig.configuration(CONFIG_KEY).debug
@@ -199,28 +199,6 @@
199
199
  ],
200
200
  "ruby": " return '' if value.nil?\n\n value.downcase\n"
201
201
  },
202
- {
203
- "name": "sentence",
204
- "description": "Sentence case the characters in the given 'string'",
205
- "result": "value converted to sentence case",
206
- "category": "case",
207
- "category_description": "Tokenize and apply case and/or separator",
208
- "base_class_require": null,
209
- "base_class": null,
210
- "parameters": [
211
- {
212
- "name": "value",
213
- "description": "value - to be converted",
214
- "splat": null,
215
- "default": null,
216
- "param_type": "String|Int"
217
- }
218
- ],
219
- "examples": [
220
-
221
- ],
222
- "ruby": " tokenizer.parse(value,\n separator: ' ',\n preserve_case: true,\n compress_prefix_numerals: false,\n compress_suffix_numerals: false).titleize\n"
223
- },
224
202
  {
225
203
  "name": "slash",
226
204
  "description": "Slash case the characters in the given 'string'",
@@ -31,7 +31,12 @@ class CategoryDirector < KDirector::Directors::BaseDirector
31
31
  def generate_require_all_cmdlets
32
32
  cd(:lib)
33
33
  add('_.rb',
34
- template_file: 'require_all_cmdlets.rb',
34
+ template_file: 'cmdlets_require_all.rb',
35
+ cmdlets: data_access.cmdlet.all_cmdlets)
36
+
37
+ cd(:handlebars_lib)
38
+ add('_.rb',
39
+ template_file: 'handlebars_helper_require_all.rb',
35
40
  cmdlets: data_access.cmdlet.all_cmdlets)
36
41
 
37
42
  end
@@ -32,12 +32,20 @@ class CmdletDirector < KDirector::Directors::BaseDirector
32
32
  builder.cmdlets.each do |cmdlet|
33
33
  cmdlet_file = "#{cmdlet[:category]}/#{cmdlet[:name]}.rb"
34
34
  cmdlet_spec_file = "#{cmdlet[:category]}/#{cmdlet[:name]}_spec.rb"
35
+ handlebars_file = "handlebarsjs/helpers/#{cmdlet[:category]}/#{cmdlet[:name]}.rb"
36
+ handlebars_spec_file = "handlebarsjs/helpers/#{cmdlet[:category]}/#{cmdlet[:name]}_spec.rb"
35
37
 
36
38
  cd(:lib)
37
39
  add(cmdlet_file, template_file: 'cmdlet.rb', cmdlet: cmdlet)
38
40
 
39
41
  cd(:spec)
40
42
  add(cmdlet_spec_file, template_file: 'cmdlet_spec.rb', cmdlet: cmdlet, on_exist: :skip)
43
+
44
+ cd(:handlebars_lib)
45
+ add(handlebars_file, template_file: 'handlebars_helper.rb', cmdlet: cmdlet)
46
+
47
+ cd(:handlebars_spec)
48
+ add(handlebars_spec_file, template_file: 'handlebars_helper_spec.rb', cmdlet: cmdlet, on_exist: :skip)
41
49
  end
42
50
 
43
51
  self
@@ -48,6 +56,10 @@ class CmdletDirector < KDirector::Directors::BaseDirector
48
56
  k_builder.run_cop('**/*.rb', fix_unsafe: true)
49
57
  end
50
58
 
59
+ Dir.chdir(k_builder.target_folders.get(:handlebars)) do
60
+ k_builder.run_cop('**/*.rb', fix_unsafe: true)
61
+ end
62
+
51
63
  self
52
64
  end
53
65
 
@@ -109,21 +109,6 @@ KManager.action :case_commands do
109
109
  value.downcase
110
110
  RUBY
111
111
  end
112
- .cmdlet do
113
- name :sentence
114
- description "Sentence case the characters in the given 'string'"
115
- result 'value converted to sentence case'
116
-
117
- parameter :value, 'value - to be converted', param_type: 'String|Int'
118
-
119
- ruby <<-'RUBY'
120
- tokenizer.parse(value,
121
- separator: ' ',
122
- preserve_case: true,
123
- compress_prefix_numerals: false,
124
- compress_suffix_numerals: false).titleize
125
- RUBY
126
- end
127
112
  .cmdlet do
128
113
  name :slash
129
114
  description "Slash case the characters in the given 'string'"
@@ -11,7 +11,7 @@ KManager.action :comparison_commands do
11
11
 
12
12
  parameter :values, 'list of values (via *splat) to be checked via AND condition', splat: '*', param_type: 'Object'
13
13
 
14
- ruby <<-RUBY
14
+ ruby <<-RUBY
15
15
  values.all? { |value| value }
16
16
  RUBY
17
17
  end
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ # [0.4.0](https://github.com/klueless-io/cmdlet/compare/v0.3.0...v0.4.0) (2022-07-12)
2
+
3
+
4
+ ### Features
5
+
6
+ * remove sentence ([20961b8](https://github.com/klueless-io/cmdlet/commit/20961b89efae5f9738e24959010318eb284e2af4))
7
+
8
+ # [0.3.0](https://github.com/klueless-io/cmdlet/compare/v0.2.1...v0.3.0) (2022-07-11)
9
+
10
+
11
+ ### Features
12
+
13
+ * update handlebars templates ([599246d](https://github.com/klueless-io/cmdlet/commit/599246d9521381106524c263ccc1c157f093389d))
14
+
15
+ ## [0.2.1](https://github.com/klueless-io/cmdlet/compare/v0.2.0...v0.2.1) (2022-07-10)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * add case commandlets ([b269f2a](https://github.com/klueless-io/cmdlet/commit/b269f2a770dd698119f360d47756b47bd5b57516))
21
+
1
22
  # [0.2.0](https://github.com/klueless-io/cmdlet/compare/v0.1.2...v0.2.0) (2022-07-10)
2
23
 
3
24
 
data/lib/cmdlet/_.rb CHANGED
@@ -12,7 +12,6 @@ require_relative 'case/double_colon'
12
12
  require_relative 'case/human'
13
13
  require_relative 'case/lamel'
14
14
  require_relative 'case/lower'
15
- require_relative 'case/sentence'
16
15
  require_relative 'case/slash'
17
16
  require_relative 'case/snake'
18
17
  require_relative 'case/title'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cmdlet
4
- VERSION = '0.2.1'
4
+ VERSION = '0.4.1'
5
5
  end
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "cmdlet",
3
- "version": "0.2.1",
3
+ "version": "0.4.1",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "cmdlet",
9
- "version": "0.2.1",
9
+ "version": "0.4.1",
10
10
  "devDependencies": {
11
11
  "@klueless-js/semantic-release-rubygem": "github:klueless-js/semantic-release-rubygem",
12
12
  "@semantic-release/changelog": "^6.0.1",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cmdlet",
3
- "version": "0.2.1",
3
+ "version": "0.4.1",
4
4
  "description": "Cmdlet provides a set of functions (wrapped in the command pattern) that perform simple actions",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmdlet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-10 00:00:00.000000000 Z
11
+ date: 2022-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -49,7 +49,10 @@ files:
49
49
  - ".builders/.templates/FUNCTIONS.MD"
50
50
  - ".builders/.templates/cmdlet.rb"
51
51
  - ".builders/.templates/cmdlet_spec.rb"
52
- - ".builders/.templates/require_all_cmdlets.rb"
52
+ - ".builders/.templates/cmdlets_require_all.rb"
53
+ - ".builders/.templates/handlebars_helper.rb"
54
+ - ".builders/.templates/handlebars_helper_require_all.rb"
55
+ - ".builders/.templates/handlebars_helper_spec.rb"
53
56
  - ".builders/_.rb"
54
57
  - ".builders/boot.rb"
55
58
  - ".builders/data/categories.json"
@@ -65,7 +68,6 @@ files:
65
68
  - ".builders/director/cmdlet_dao.rb"
66
69
  - ".builders/director/cmdlet_director.rb"
67
70
  - ".builders/director/dao.rb"
68
- - ".builders/documents/commands.rb"
69
71
  - ".builders/documents/use_cases.rb"
70
72
  - ".builders/documents/x_functions.rb"
71
73
  - ".builders/generators/01-bootstrap.rb"
@@ -103,7 +105,6 @@ files:
103
105
  - lib/cmdlet/case/human.rb
104
106
  - lib/cmdlet/case/lamel.rb
105
107
  - lib/cmdlet/case/lower.rb
106
- - lib/cmdlet/case/sentence.rb
107
108
  - lib/cmdlet/case/slash.rb
108
109
  - lib/cmdlet/case/snake.rb
109
110
  - lib/cmdlet/case/title.rb
@@ -1,225 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- def commands
4
- return @commands unless @commands.nil?
5
-
6
- result = KDoc.model :document do
7
- table :rows do
8
- fields :category, :name, :alias, :description, :ruby # , f(name: :params, default: [:value])
9
-
10
- # context 'when array of string' do
11
- # let(:value) { %w[the quick fox] }
12
-
13
- # it { is_expected.to eq('the,quick,fox') }
14
- # end
15
-
16
- # context 'when array of numbers' do
17
- # let(:value) { [1, 2, 3] }
18
-
19
- # it { is_expected.to eq('1,2,3') }
20
- # end
21
-
22
- # context 'when array of symbol' do
23
- # let(:value) { %i[the quick fox] }
24
-
25
- # it { is_expected.to eq('the,quick,fox') }
26
- # end
27
-
28
- row :array , :join , [] , 'join an array of values with separator as a string', <<-'RUBY'
29
- return '' if value.nil? || !value.is_a?(Array)
30
- values = value.reject(&:blank?)
31
- return '' if value.length.zero?
32
-
33
- separator = ','
34
- values.join(separator)
35
- RUBY
36
-
37
- row :array , :join_pre , [] , 'join an array of values with separator as a string and using the separator at the beginning of string', <<-'RUBY'
38
- return '' if value.nil? || !value.is_a?(Array)
39
- values = value.reject(&:blank?)
40
- return '' if value.length.zero?
41
-
42
- separator = ','
43
- "#{separator}#{value.join(separator)}"
44
- RUBY
45
-
46
- # row :array , :join_post , [] , "join an array of values with separator as a string and using the separator at the end of string", <<-'RUBY'
47
- # return '' if value.nil? || !value.is_a?(Array)
48
- # values = value.reject(&:blank?)
49
- # return '' if value.length.zero?
50
-
51
- # separator = ','
52
- # "#{value.join(separator)}#{separator}"
53
- # RUBY
54
-
55
- # row :a_transform , :backslash , [:back_slash] , "convert to back slash notation", <<-'RUBY'
56
- # tokenizer.parse(value, preserve_case: true, separator: '\\')
57
- # RUBY
58
-
59
- # row :a_transform , :camel , [:upper_camel, :pascal] , "convert to camel notation", <<-'RUBY'
60
- # tokenizer.parse(value).underscore.camelize
61
- # RUBY
62
-
63
- # row :a_transform , :constant , [:constantize] , "", <<-'RUBY'
64
- # tokenizer.parse(value, separator: '_').upcase
65
- # RUBY
66
-
67
- # row :a_transform , :dash , [:dasherize] , "convert to dash notation", <<-'RUBY'
68
- # tokenizer.parse(value)
69
- # RUBY
70
-
71
- # row :a_transform , :dot , [:dotirize] , "", <<-'RUBY'
72
- # tokenizer.parse(value, separator: '.')
73
- # RUBY
74
-
75
- # row :a_transform , :double_colon , [] , "", <<-'RUBY'
76
- # tokenizer.parse(value, preserve_case: true, separator: '::')
77
- # RUBY
78
-
79
- # row :a_transform , :lower , [:lowercase, :downcase] , "", <<-'RUBY'
80
- # return '' if value.nil?
81
-
82
- # value.downcase
83
- # RUBY
84
-
85
- # # row :a_transform , :format_as , [] , "", <<-'RUBY'
86
-
87
- # # RUBY
88
-
89
- # row :a_transform , :proper , [] , "Proper case capitalizes the first letter of ALL words in a string", <<-'RUBY'
90
- # tokenizer.parse(value,
91
- # separator: ' ',
92
- # preserve_case: true,
93
- # compress_prefix_numerals: false,
94
- # compress_suffix_numerals: false)
95
- # .titleize
96
- # RUBY
97
-
98
- # row :a_transform , :sentence , [:human, :humanize] , "Upper case for first letter only. Numbers will maintain their spacing", <<-'RUBY'
99
- # tokenizer.parse(value,
100
- # separator: ' ',
101
- # preserve_case: true,
102
- # compress_prefix_numerals: false,
103
- # compress_suffix_numerals: false)
104
- # .humanize
105
- # RUBY
106
-
107
- # row :a_transform , :lamel , [:lower_camel] , "", <<-'RUBY'
108
- # tokenizer.parse(value, separator: '_').camelize(:lower)
109
- # RUBY
110
-
111
- # row :a_transform , :slash , [:forwardslash, :forward_slash] , "", <<-'RUBY'
112
- # tokenizer.parse(value, preserve_case: true, separator: '/')
113
- # RUBY
114
-
115
- # row :a_transform , :snake , [:snake] , "", <<-'RUBY'
116
- # tokenizer.parse(value, separator: '_', forced_separator: true)
117
- # RUBY
118
-
119
- # row :a_transform , :title , [:titleize] , "", <<-'RUBY'
120
- # tokenizer.parse(value,
121
- # separator: ' ',
122
- # preserve_case: true,
123
- # compress_prefix_numerals: false,
124
- # compress_suffix_numerals: false)
125
- # .titleize
126
- # RUBY
127
-
128
- # row :a_transform , :upper , [:upper_case, :upcase] , "", <<-'RUBY'
129
- # return '' if value.nil?
130
-
131
- # value.upcase
132
- # RUBY
133
-
134
- # row :a_comparison , :and , [:all] , "", <<-'RUBY'
135
- # values.all? { |value| value }
136
- # RUBY
137
-
138
- # # DEFAULT does not make sense in comparison
139
- # row :a_comparison , :default , [:fallback] , "", <<-'RUBY'
140
- # default_value = values[-1]
141
-
142
- # find_value = values[0..-2].find { |value| !value.nil? }
143
-
144
- # find_value || default_value
145
- # RUBY
146
-
147
- # row :a_comparison , :eq , [:equal] , "", <<-'RUBY'
148
- # lhs = lhs.to_s if lhs.is_a?(Symbol)
149
- # rhs = rhs.to_s if rhs.is_a?(Symbol)
150
-
151
- # lhs == rhs
152
- # RUBY
153
-
154
- # row :a_comparison , :gt , [] , "", <<-'RUBY'
155
- # lhs > rhs
156
- # RUBY
157
-
158
- # row :a_comparison , :gte , [] , "", <<-'RUBY'
159
- # lhs >= rhs
160
- # RUBY
161
-
162
- # row :a_comparison , :lt , [:less_than] , "# Lt: (less than) Block helper that renders a block if `a` is **less than** `b`. If an inverse block is specified it will be rendered when falsy.", <<-'RUBY'
163
- # lhs < rhs
164
- # RUBY
165
-
166
- # row :a_comparison , :lte , [:less_than_or_equal_to] , "# Lte: (less than or equal to) Block helper that renders a block if `a` is **less than or equal to** `b`. If an inverse block is specified it will be rendered when falsy.", <<-'RUBY'
167
- # lhs <= rhs
168
- # RUBY
169
-
170
- # row :a_comparison , :ne , [:not_equal] , "# Ne: (not equal) Block helper that renders a block if `a` is **not equal to** `b`. If an inverse block is specified it will be rendered when falsy.", <<-'RUBY'
171
- # lhs = lhs.to_s if lhs.is_a?(Symbol)
172
- # rhs = rhs.to_s if rhs.is_a?(Symbol)
173
-
174
- # lhs != rhs
175
- # RUBY
176
-
177
- # row :a_comparison , :or , [:any] , "", <<-'RUBY'
178
- # values.any? { |value| value }
179
- # RUBY
180
-
181
- # row :a_inflection , :ordinal , [] , "The suffix that should be added to a number to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th", <<-RUBY
182
- # return '' if value.nil?
183
-
184
- # value = value.to_i if value.is_a? String
185
-
186
- # value.ordinal
187
- # RUBY
188
-
189
- # row :a_inflection , :ordinalize , [] , "Turns a number into an ordinal string used to denote the position in an ordered sequence such as 1st, 2nd, 3rd, 4th.", <<-RUBY
190
- # return '' if value.nil?
191
-
192
- # value = value.to_i if value.is_a? String
193
-
194
- # value.ordinalize
195
- # RUBY
196
-
197
- # row :a_inflection , :pluralize , [] , "Returns the plural form of the word in the string", <<-RUBY
198
- # return '' if value.nil?
199
-
200
- # value = value.to_s if value.is_a?(Symbol)
201
-
202
- # value.pluralize
203
- # RUBY
204
-
205
- # row :a_inflection , :pluralize_by_number , [] , "Uses both a word and number to decide if the plural or singular form should be used.", <<-'RUBY'
206
- # return '' if value.nil?
207
-
208
- # count = count.to_i if count.is_a? String
209
- # format = :word if format.nil?
210
-
211
- # case format.to_sym
212
- # when :number_word, :number_and_word
213
- # "#{count} #{value.pluralize(count)}"
214
- # else # aka :word
215
- # value.pluralize(count)
216
- # end
217
- # RUBY
218
-
219
- # row :a_inflection , :singularize , [] , ""
220
- end
221
- end
222
-
223
- @commands = result.raw_data_struct.rows
224
- end
225
- @commands = nil
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Cmdlet
4
- # Tokenize and apply case and/or separator
5
- module Case
6
- # Sentence: Sentence case the characters in the given &#x27;string&#x27;
7
- class Sentence < Cmdlet::BaseCmdlet
8
- #
9
- # @param [String|Int] value - value - to be converted
10
- # @return [String] value converted to sentence case
11
- def call(value)
12
- tokenizer.parse(value,
13
- separator: ' ',
14
- preserve_case: true,
15
- compress_prefix_numerals: false,
16
- compress_suffix_numerals: false).titleize
17
- end
18
- end
19
- end
20
- end