complicode 1.0.1 → 2.0.0

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: c7ea01c2b4b68843962e6b00d12ef8429fb906760f8f8248a94ac16d00d9bef0
4
- data.tar.gz: 9b7380b749c9086893fa6416687dcfb934a7ea4eded354aa2656f8d1df90e64b
3
+ metadata.gz: a12ea0604947b47e7077ce14b8d703361d18615d0ef28ebde58ecdd65a5782f5
4
+ data.tar.gz: 3362ee7edb677550cf553eb188f7d4cb5e25c8841d86f0a963813d2abd51adf9
5
5
  SHA512:
6
- metadata.gz: d66589838ad40670c5391578977e0498aaf4d520ded16a1d75001253c734a7176e24283b5b3942b5b9eb90e5e6a668c8f3042c47e9680f43c07249f6d6edf3fe
7
- data.tar.gz: dd4afb08004bf361ead2758be51c85e8aeb80928ca2aa26863ed53d5c5f75feaacd2f008b9295b8ec871ded050d4e7db7e9b8fbad4a283d2fe1e66e4f52b287d
6
+ metadata.gz: 20fdc2bca8fe7493291cd5fe2360ddeb9060718b5b2e1640a23efa093709e3294c9224a909056c35fa8271216dd272e7b35e968173195ac2fea3e6e9cf598c44
7
+ data.tar.gz: bbc7e21969a45a3ce0e82bed02c78ebe2354bb7c19ecfa90db3d528117acf5f2e223cc312410f4662462f1aae6b7401c835712262a33efa16ace879480541fd0
data/README.md CHANGED
@@ -1,17 +1,18 @@
1
1
  # Complicode
2
2
 
3
3
  [![Gem](https://img.shields.io/gem/v/complicode.svg?style=flat)](http://rubygems.org/gems/complicode)
4
- [![CircleCI](https://circleci.com/gh/pablocrivella/complicode.svg?style=svg)](https://circleci.com/gh/pablocrivella/complicode)
5
- [![Maintainability](https://api.codeclimate.com/v1/badges/935822c7c481aa464186/maintainability)](https://codeclimate.com/github/pablocrivella/complicode/maintainability)
6
- [![Test Coverage](https://api.codeclimate.com/v1/badges/935822c7c481aa464186/test_coverage)](https://codeclimate.com/github/pablocrivella/complicode/test_coverage)
4
+ [![Depfu](https://badges.depfu.com/badges/6f2f73672eae4d603d6ae923164435e2/overview.svg)](https://depfu.com/github/pablocrivella/statics?project=Bundler)
5
+ [![Inline docs](http://inch-ci.org/github/pablocrivella/complicode.svg?branch=master&style=shields)](http://inch-ci.org/github/pablocrivella/complicode)
6
+ [![Maintainability](https://api.codeclimate.com/v1/badges/d874a9673862541f247b/maintainability)](https://codeclimate.com/github/pablocrivella/complicode/maintainability)
7
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/d874a9673862541f247b/test_coverage)](https://codeclimate.com/github/pablocrivella/complicode/test_coverage)
7
8
 
8
- Control code generator for invoices inside the Bolivian national tax service..
9
+ Control code generator for invoices inside the Bolivian national tax service.
9
10
 
10
11
  Links:
11
12
 
12
- - [API Docs](https://www.rubydoc.info/gems/complicode)
13
- - [Contributing](https://github.com/pablocrivella/complicode/blob/master/CONTRIBUTING.md)
14
- - [Code of Conduct](https://github.com/pablocrivella/complicode/blob/master/CODE_OF_CONDUCT.md)
13
+ - [API Docs](https://www.rubydoc.info/gems/complicode)
14
+ - [Contributing](https://github.com/pablocrivella/complicode/blob/master/CONTRIBUTING.md)
15
+ - [Code of Conduct](https://github.com/pablocrivella/complicode/blob/master/CODE_OF_CONDUCT.md)
15
16
 
16
17
  ## Requirements
17
18
 
@@ -38,18 +39,17 @@ require "complicode"
38
39
 
39
40
  authorization_code = "29040011007"
40
41
  key = "9rCB7Sv4X29d)5k7N%3ab89p-3(5[A"
41
- Complicode::Generate.call(authorization_code, key, number: "1503", nit: "4189179011", issue_date: "20070702", amount: "2500")
42
+ invoice = Complicode::Invoice.new(number: 1503, nit: 4189179011, issue_date: Date.new(2007, 7, 2), amount: 2500.0)
43
+ generator = Complicode::Generator.new
44
+ generator.call(authorization_code: authorization_code, key: key, invoice: invoice)
42
45
  # => "6A-DC-53-05-14"
43
- # If ignored, "nit" defaults to "0"
44
- Complicode::Generate.call(authorization_code, key, number: "1503", issue_date: "20070702", amount: "2500")
45
- # => "9E-84-73-A4"
46
46
  ```
47
47
 
48
48
  ## Tests
49
49
 
50
50
  To test, run:
51
51
 
52
- ```
52
+ ```shell
53
53
  bundle exec rspec spec/
54
54
  ```
55
55
 
@@ -63,5 +63,5 @@ Read [Semantic Versioning](https://semver.org) for details. Briefly, it means:
63
63
 
64
64
  ## License
65
65
 
66
- Copyright 2018 [Pablo Crivella](https://pablocrivella.me).
67
- Read [LICENSE](LICENSE.md) for details.
66
+ Copyright 2020 [Pablo Crivella](https://pablocrivella.me).
67
+ Read [LICENSE](LICENSE) for details.
@@ -6,10 +6,5 @@ require "verhoeff"
6
6
  require "date"
7
7
  require "complicode/invoice"
8
8
  require "complicode/partial_key"
9
- require "complicode/generators/verification_digits"
10
- require "complicode/generators/partial_keys"
11
- require "complicode/generators/data"
12
- require "complicode/generators/ascii_sums"
13
- require "complicode/generators/base64_data"
14
- require "complicode/generate"
9
+ require "complicode/generator"
15
10
  require "complicode/version"
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Complicode
4
+ class Generator
5
+ BASE64 = %w[
6
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V
7
+ W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z + /
8
+ ].freeze
9
+
10
+ # @param authorization_code [String]
11
+ # @param key [String]
12
+ # @param invoice [Complicode::Invoice]
13
+ # @return [String]
14
+ def call(authorization_code:, key:, invoice:)
15
+ key.freeze
16
+
17
+ seeds = [
18
+ invoice.number.to_s,
19
+ invoice.nit.to_s,
20
+ invoice.issue_date.strftime("%Y%m%d"),
21
+ invoice.amount.round.to_s,
22
+ ]
23
+
24
+ seeds = append_verification_digits(seeds, 2)
25
+ digits = generate_verification_digits(seeds)
26
+ partial_keys = generate_partial_keys(key.dup, digits)
27
+
28
+ seeds.unshift(authorization_code)
29
+ seeds = append_partial_keys(seeds, partial_keys)
30
+
31
+ encryption_key = key + digits
32
+ encrypted_data = encrypt(seeds.join, encryption_key)
33
+
34
+ ascii_sums = generate_ascii_sums(encrypted_data, partial_keys.count)
35
+ base64_data = generate_base64_data(ascii_sums, partial_keys)
36
+
37
+ format(encrypt(base64_data, encryption_key))
38
+ end
39
+
40
+ def append_verification_digits(seed, count)
41
+ case seed
42
+ when Array
43
+ seed.map { |seed| append_verification_digits(seed, count) }
44
+ else
45
+ count.times do
46
+ seed = seed.to_s + Verhoeff.checksum_digit_of(seed).to_s
47
+ end
48
+
49
+ seed
50
+ end
51
+ end
52
+
53
+ # @param invoice [Array<String>]
54
+ # @return [String]
55
+ def generate_verification_digits(seeds)
56
+ sum = seeds.map(&:to_i).sum
57
+ sum = append_verification_digits(sum, 5)
58
+ sum.to_s[-5..-1]
59
+ end
60
+
61
+ # @param key [String]
62
+ # @param digits [String]
63
+ # @return [Array<Complicode::PartialKey>]
64
+ def generate_partial_keys(key, digits)
65
+ partial_key_sizes = digits.split("").map { |digit| digit.to_i + 1 }
66
+ partial_key_sizes.map { |index| PartialKey.new(value: key.slice!(0...index)) }
67
+ end
68
+
69
+ def append_partial_keys(seeds, partial_keys)
70
+ seeds.map.with_index { |seed, index| seed + partial_keys[index].value }
71
+ end
72
+
73
+ # @param encrypted_data [String]
74
+ # @param partials_count [Integer]
75
+ # @return [Struct]
76
+ def generate_ascii_sums(data, partials_count)
77
+ Struct.new(:total, :partials).new(0, Array.new(partials_count, 0)).tap do |sums|
78
+ data.each_byte.with_index do |byte, index|
79
+ sums.total += byte
80
+ sums.partials[index % partials_count] += byte
81
+ end
82
+ end
83
+ end
84
+
85
+ # @param ascii_sums [Struct]
86
+ # @param partial_keys [Array<String>]
87
+ # @return [String]
88
+ def generate_base64_data(ascii_sums, partial_keys)
89
+ ascii_sums.partials.each_with_index.inject(0) { |sum, (partial_sum, index)|
90
+ sum + ascii_sums.total * partial_sum / partial_keys[index].size
91
+ }.b(10).to_s(BASE64)
92
+ end
93
+
94
+ # @param data [String]
95
+ # @param encryption_key [String]
96
+ # @return [String]
97
+ def encrypt(data, encryption_key)
98
+ RC4.new(encryption_key).encrypt(data).unpack1("H*").upcase
99
+ end
100
+
101
+ # @param code [String]
102
+ # @return [String]
103
+ def format(code)
104
+ code.scan(/.{2}/).join("-")
105
+ end
106
+ end
107
+ end
@@ -2,39 +2,17 @@
2
2
 
3
3
  module Complicode
4
4
  class Invoice
5
- attr_accessor :nit, :amount, :issue_date, :number
5
+ attr_reader :nit, :amount, :issue_date, :number
6
6
 
7
- # @param [Hash] attributes of the invoice.
8
- # @option attributes [String] :nit
9
- # @option attributes [String] :number
10
- # @option attributes [String] :amount
11
- # @option attributes [String] :issue_date
12
- def initialize(attributes = {})
13
- @nit = attributes.fetch(:nit, "0").to_s
14
- @number = attributes.fetch(:number).to_s
15
- @amount = attributes.fetch(:amount).to_s.tr(",", ".").to_f.round.to_s
16
- @issue_date = Date.parse(attributes.fetch(:issue_date).to_s).strftime("%Y%m%d")
17
- end
18
-
19
- # @param attribute [Symbol]
20
- def append_checksum_digit_to(attribute)
21
- append_to(attribute, Verhoeff.checksum_digit_of(send(attribute)).to_s)
22
- end
23
-
24
- # @param attribute [Symbol]
25
- # @param value [String]
26
- def append_to(attribute, value)
27
- send("#{attribute}=", send(attribute) + value)
28
- end
29
-
30
- # @return [Integer]
31
- def sum
32
- [number, nit, issue_date, amount].map(&:to_i).inject(:+)
33
- end
34
-
35
- # @return [String]
36
- def concat
37
- [number, nit, issue_date, amount].inject(:+)
7
+ # @param [Integer] :nit
8
+ # @param [Integer] :number
9
+ # @param [Float] :amount
10
+ # @param [Date] :issue_date
11
+ def initialize(nit:, number:, issue_date:, amount:)
12
+ @amount = Float(amount)
13
+ @nit = Integer(nit)
14
+ @number = Integer(number)
15
+ @issue_date = Date.parse(issue_date.to_s)
38
16
  end
39
17
  end
40
18
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Complicode
4
- PartialKey = Struct.new(:value, keyword_init: true) do
4
+ PartialKey = Struct.new(:value, keyword_init: true) {
5
5
  def size
6
6
  value.size
7
7
  end
8
- end
8
+ }
9
9
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Complicode
4
- VERSION = "1.0.1"
4
+ VERSION = "2.0.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: complicode
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Crivella
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-04 00:00:00.000000000 Z
11
+ date: 2020-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: radix
@@ -58,56 +58,56 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.16'
61
+ version: '2.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '1.16'
68
+ version: '2.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: pry
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.11'
75
+ version: '0.12'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.11'
82
+ version: '0.12'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: pry-byebug
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '3.6'
89
+ version: '3.7'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '3.6'
96
+ version: '3.7'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '12.0'
103
+ version: '13.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '12.0'
110
+ version: '13.0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rspec
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -137,61 +137,61 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0.4'
139
139
  - !ruby/object:Gem::Dependency
140
- name: rubocop
140
+ name: rubocop-rspec
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '0.58'
145
+ version: '1.3'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '0.58'
152
+ version: '1.3'
153
153
  - !ruby/object:Gem::Dependency
154
- name: rubocop-rspec
154
+ name: simplecov
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: '1.29'
159
+ version: '0.16'
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: '1.29'
166
+ version: '0.16'
167
167
  - !ruby/object:Gem::Dependency
168
- name: simplecov
168
+ name: smarter_csv
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: '0.16'
173
+ version: '1.2'
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: '0.16'
180
+ version: '1.2'
181
181
  - !ruby/object:Gem::Dependency
182
- name: smarter_csv
182
+ name: standard
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: '1.2'
187
+ version: '0.1'
188
188
  type: :development
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: '1.2'
194
+ version: '0.1'
195
195
  description: Control code generator for invoices inside the Bolivian national tax
196
196
  service.
197
197
  email:
@@ -205,12 +205,7 @@ files:
205
205
  - LICENSE
206
206
  - README.md
207
207
  - lib/complicode.rb
208
- - lib/complicode/generate.rb
209
- - lib/complicode/generators/ascii_sums.rb
210
- - lib/complicode/generators/base64_data.rb
211
- - lib/complicode/generators/data.rb
212
- - lib/complicode/generators/partial_keys.rb
213
- - lib/complicode/generators/verification_digits.rb
208
+ - lib/complicode/generator.rb
214
209
  - lib/complicode/invoice.rb
215
210
  - lib/complicode/partial_key.rb
216
211
  - lib/complicode/version.rb
@@ -236,8 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
236
231
  - !ruby/object:Gem::Version
237
232
  version: '0'
238
233
  requirements: []
239
- rubyforge_project:
240
- rubygems_version: 2.7.6
234
+ rubygems_version: 3.0.3
241
235
  signing_key:
242
236
  specification_version: 4
243
237
  summary: Complicode! A needlessly complicated code generator!
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Complicode
4
- class Generate
5
- # @return [String]
6
- def self.call(*args)
7
- new(*args).send(:call)
8
- end
9
-
10
- # @param authorization_code [String]
11
- # @param key [String]
12
- # @param [Hash] invoice_attributes of the invoice.
13
- # @option invoice_attributes [String] :nit
14
- # @option invoice_attributes [String] :number
15
- # @option invoice_attributes [String] :amount
16
- # @option invoice_attributes [String] :issue_date
17
- def initialize(authorization_code, key, invoice_attributes = {})
18
- @invoice = Invoice.new(invoice_attributes)
19
- @authorization_code = authorization_code
20
- @key = key
21
- end
22
-
23
- private
24
-
25
- # @return [String]
26
- def call
27
- partial_keys = Generators::PartialKeys.call(@key.dup, verification_digits)
28
- data = Generators::Data.call(@authorization_code, @invoice, partial_keys)
29
- encrypted_data = encrypt(data, encryption_key)
30
- ascii_sums = Generators::AsciiSums.call(encrypted_data, partial_keys.count)
31
- base64_data = Generators::Base64Data.call(ascii_sums, partial_keys)
32
- format(encrypt(base64_data, encryption_key))
33
- end
34
-
35
- # @return [String]
36
- def encryption_key
37
- @encryption_key ||= @key + verification_digits
38
- end
39
-
40
- # @return [String]
41
- def verification_digits
42
- @verification_digits ||= Generators::VerificationDigits.call(@invoice)
43
- end
44
-
45
- # @param data [String]
46
- # @param encryption_key [String]
47
- # @return [String]
48
- def encrypt(data, encryption_key)
49
- RC4.new(encryption_key).encrypt(data).unpack1("H*").upcase
50
- end
51
-
52
- # @param code [String]
53
- # @return [String]
54
- def format(code)
55
- code.scan(/.{2}/).join("-")
56
- end
57
- end
58
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Complicode
4
- module Generators
5
- class AsciiSums
6
- # @param encrypted_data [String]
7
- # @param partials_count [Integer]
8
- # @return [Struct]
9
- def self.call(encrypted_data, partials_count)
10
- Struct.new(:total, :partials).new(0, Array.new(partials_count, 0)).tap do |sums|
11
- encrypted_data.each_byte.with_index do |byte, index|
12
- sums.total += byte
13
- sums.partials[index % partials_count] += byte
14
- end
15
- end
16
- end
17
- end
18
- end
19
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Complicode
4
- module Generators
5
- class Base64Data
6
- BASE64 = %w[
7
- 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V
8
- W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z + /
9
- ].freeze
10
-
11
- # @param ascii_sums [Struct]
12
- # @param partial_keys [Array<String>]
13
- # @return [String]
14
- def self.call(ascii_sums, partial_keys)
15
- ascii_sums.partials.each_with_index.inject(0) do |sum, (partial_sum, index)|
16
- sum + ascii_sums.total * partial_sum / partial_keys[index].size
17
- end.b(10).to_s(BASE64)
18
- end
19
- end
20
- end
21
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Complicode
4
- module Generators
5
- class Data
6
- # @param authorization_code [String]
7
- # @param invoice [Complicode::Invoice]
8
- # @param partial_keys [Array<String>]
9
- # @return [String]
10
- def self.call(authorization_code, invoice, partial_keys)
11
- authorization_code += partial_keys[0].value
12
-
13
- %i[number nit issue_date amount].each.with_index(1) do |attribute, index|
14
- invoice.append_to(attribute, partial_keys[index].value)
15
- end
16
-
17
- authorization_code + invoice.concat
18
- end
19
- end
20
- end
21
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Complicode
4
- module Generators
5
- class PartialKeys
6
- # @param key [String]
7
- # @param verification_digits [String]
8
- # @return [Array<String>]
9
- def self.call(key, verification_digits)
10
- partial_key_sizes = verification_digits.split("").map { |digit| digit.to_i + 1 }
11
- partial_key_sizes.map do |index|
12
- PartialKey.new(value: key.slice!(0...index))
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Complicode
4
- module Generators
5
- class VerificationDigits
6
- DIGITS_COUNT = 5
7
- ITERATIONS = 2
8
-
9
- # @param invoice [Complicode::Invoice]
10
- # @return [String]
11
- def self.call(invoice)
12
- ITERATIONS.times do
13
- %i[number nit issue_date amount]
14
- .each { |attribute| invoice.append_checksum_digit_to(attribute) }
15
- end
16
-
17
- sum = invoice.sum
18
- DIGITS_COUNT.times { sum = Verhoeff.checksum_of(sum) }
19
- sum.to_s[-DIGITS_COUNT..-1]
20
- end
21
- end
22
- end
23
- end