ruby-lzws 1.4.0 → 1.4.3

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: 68bdc9a93f0fec663f2bf0824e1e5838e7d86f2f2e83ca3f3239564410aa54dd
4
- data.tar.gz: ae80e7b822bbe11b2d1d3ebe3bcd1093f0f428a29b556c95e1e1af97d41b00bc
3
+ metadata.gz: 0125a2586e06a3882d077fc746873c1376eeafb7393de58ee28e84eace30763b
4
+ data.tar.gz: a68ceee328f8ea19195c7c75cc5cfb72893d536bb3e14859f4021faa7c934391
5
5
  SHA512:
6
- metadata.gz: e49fe4b9d0a2f319bf71e4098c3fc6b82c238d119eaffd76a76e4a6f5b480e6f950580983cef5ab71980843b18126c825c6846ac0030868127bc05db089da82c
7
- data.tar.gz: 8093294842cdb734cf7962dcde85c7cb530ffbb15d873681fc7c81bbe23d5d5d5af9e0ad6b66d934e86f9e99b68bafbf2d4b2ab68a02da7aa05db8aa2a389b7b
6
+ metadata.gz: 849991f7d18e0e8449e7f1e6b96ca66a4c5545db67b918c3c6f59d5071113e035122b6a3b2620c81e7ed32a5a7adaa682654df0456141e20e941c8757d727c27
7
+ data.tar.gz: acdcb9d6175e7285b0deb2324925cc9a8cdd1a59c51385c311b4c1a0c794fc3a010f588644a8986fe51e2e4188e6e32c83d5dfe0f8a48d22b32c4398fd9f668c
data/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # Ruby bindings for lzws library
2
2
 
3
- | AppVeyor | Circle | Github actions | Codecov | Gem |
4
- | :------: | :----: | :------------: | :-----: | :--: |
5
- | [![AppVeyor test status](https://ci.appveyor.com/api/projects/status/github/andrew-aladev/ruby-lzws?branch=master&svg=true)](https://ci.appveyor.com/project/andrew-aladev/ruby-lzws/branch/master) | [![Circle test status](https://circleci.com/gh/andrew-aladev/ruby-lzws/tree/master.svg?style=shield)](https://circleci.com/gh/andrew-aladev/ruby-lzws/tree/master) | [![Github Actions test status](https://github.com/andrew-aladev/ruby-lzws/workflows/test/badge.svg?branch=master)](https://github.com/andrew-aladev/ruby-lzws/actions) | [![Codecov](https://codecov.io/gh/andrew-aladev/ruby-lzws/branch/master/graph/badge.svg)](https://codecov.io/gh/andrew-aladev/ruby-lzws) | [![Gem](https://img.shields.io/gem/v/ruby-lzws.svg)](https://rubygems.org/gems/ruby-lzws) |
3
+ | AppVeyor | Jenkins | Github actions | Codecov | Gem |
4
+ | :------: | :-----: | :------------: | :-----: | :--: |
5
+ | [![AppVeyor test status](https://ci.appveyor.com/api/projects/status/github/andrew-aladev/ruby-lzws?branch=master&svg=true)](https://ci.appveyor.com/project/andrew-aladev/ruby-lzws/branch/master) | [![Jenkins test status](http://37.187.122.190:58182/buildStatus/icon?job=ruby-lzws)](http://37.187.122.190:58182/job/ruby-lzws) | [![Github Actions test status](https://github.com/andrew-aladev/ruby-lzws/workflows/test/badge.svg?branch=master)](https://github.com/andrew-aladev/ruby-lzws/actions) | [![Codecov](https://codecov.io/gh/andrew-aladev/ruby-lzws/branch/master/graph/badge.svg)](https://codecov.io/gh/andrew-aladev/ruby-lzws) | [![Gem](https://img.shields.io/gem/v/ruby-lzws.svg)](https://rubygems.org/gems/ruby-lzws) |
6
6
 
7
7
  See [lzws library](https://github.com/andrew-aladev/lzws).
8
8
 
9
9
  ## Installation
10
10
 
11
- Please install lzws library first, use latest 1.4.0+ version.
11
+ Operating systems: GNU/Linux, FreeBSD, OSX.
12
+
13
+ Dependencies: [lzws](https://github.com/andrew-aladev/lzws) 1.4.0+ version.
12
14
 
13
15
  ```sh
14
16
  gem install ruby-lzws
@@ -108,6 +110,10 @@ Parallel.each large_datas do |large_data|
108
110
  end
109
111
  ```
110
112
 
113
+ # Docs
114
+
115
+ Please review [rdoc generated docs](https://andrew-aladev.github.io/ruby-lzws).
116
+
111
117
  ## Options
112
118
 
113
119
  | Option | Values | Default | Description |
@@ -253,14 +259,11 @@ Special asynchronous methods missing in `Zlib::GzipWriter`.
253
259
  So it is possible to have asynchronous variants for these synchronous methods.
254
260
  Behaviour is the same as `IO#write_nonblock` method.
255
261
 
256
- All nonblock operations for file will raise `EBADF` error on Windows.
257
- Setting file into nonblocking mode is [not available on Windows](https://github.com/ruby/ruby/blob/master/win32/win32.c#L4388).
258
-
259
262
  ```
260
263
  #<<(object)
261
264
  #print(*objects)
262
265
  #printf(*args)
263
- #putc(object, encoding: ::Encoding::BINARY)
266
+ #putc(object, :encoding => 'ASCII-8BIT')
264
267
  #puts(*objects)
265
268
  ```
266
269
 
@@ -354,10 +357,6 @@ You should lock all shared data between threads.
354
357
  For example: you should not use same compressor/decompressor inside multiple threads.
355
358
  Please verify that you are using each processor inside single thread at the same time.
356
359
 
357
- ## Operating systems
358
-
359
- GNU/Linux, FreeBSD, OSX, Windows (MinGW).
360
-
361
360
  ## CI
362
361
 
363
362
  Please visit [scripts/test-images](scripts/test-images).
data/ext/lzws_ext/gvl.h CHANGED
@@ -4,7 +4,7 @@
4
4
  #if !defined(LZWS_EXT_GVL_H)
5
5
  #define LZWS_EXT_GVL_H
6
6
 
7
- #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
7
+ #if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
8
8
 
9
9
  #include "ruby/thread.h"
10
10
 
@@ -19,6 +19,6 @@
19
19
 
20
20
  #define LZWS_EXT_GVL_WRAP(_gvl, function, data) function((void*) data);
21
21
 
22
- #endif
22
+ #endif // HAVE_RB_THREAD_CALL_WITHOUT_GVL
23
23
 
24
24
  #endif // LZWS_EXT_GVL_H
data/ext/lzws_ext/macro.h CHANGED
@@ -8,6 +8,6 @@
8
8
  #define LZWS_EXT_UNUSED(x) x __attribute__((__unused__))
9
9
  #else
10
10
  #define LZWS_EXT_UNUSED(x) x
11
- #endif
11
+ #endif // __GNUC__
12
12
 
13
13
  #endif // LZWS_EXT_MACRO_H
data/lib/lzws/error.rb CHANGED
@@ -1,21 +1,26 @@
1
1
  # Ruby bindings for lzws library.
2
2
  # Copyright (c) 2019 AUTHORS, MIT License.
3
3
 
4
+ require "adsp"
5
+
4
6
  module LZWS
5
7
  class BaseError < ::StandardError; end
6
8
 
7
9
  class AllocateError < BaseError; end
8
- class ValidateError < BaseError; end
9
10
 
10
- class UsedAfterCloseError < BaseError; end
11
11
  class NotEnoughSourceBufferError < BaseError; end
12
12
  class NotEnoughDestinationBufferError < BaseError; end
13
- class NotEnoughDestinationError < BaseError; end
14
13
  class DecompressorCorruptedSourceError < BaseError; end
15
14
 
16
15
  class AccessIOError < BaseError; end
17
16
  class ReadIOError < BaseError; end
18
17
  class WriteIOError < BaseError; end
19
18
 
20
- class UnexpectedError < BaseError; end
19
+ ValidateError = ADSP::ValidateError
20
+
21
+ NotEnoughDestinationError = ADSP::NotEnoughDestinationError
22
+ UsedAfterCloseError = ADSP::UsedAfterCloseError
23
+
24
+ NotImplementedError = ADSP::NotImplementedError
25
+ UnexpectedError = ADSP::UnexpectedError
21
26
  end
data/lib/lzws/file.rb CHANGED
@@ -1,48 +1,25 @@
1
1
  # Ruby bindings for lzws library.
2
2
  # Copyright (c) 2019 AUTHORS, MIT License.
3
3
 
4
+ require "adsp/file"
4
5
  require "lzws_ext"
5
6
 
6
- require_relative "error"
7
7
  require_relative "option"
8
- require_relative "validation"
9
8
 
10
9
  module LZWS
11
- module File
12
- BUFFER_LENGTH_NAMES = %i[source_buffer_length destination_buffer_length].freeze
13
-
14
- def self.compress(source, destination, options = {})
15
- Validation.validate_string source
16
- Validation.validate_string destination
17
-
18
- options = Option.get_compressor_options options, BUFFER_LENGTH_NAMES
19
-
20
- open_files source, destination do |source_io, destination_io|
21
- LZWS._native_compress_io source_io, destination_io, options
22
- end
23
-
24
- nil
25
- end
26
-
27
- def self.decompress(source, destination, options = {})
28
- Validation.validate_string source
29
- Validation.validate_string destination
30
-
31
- options = Option.get_decompressor_options options, BUFFER_LENGTH_NAMES
32
-
33
- open_files source, destination do |source_io, destination_io|
34
- LZWS._native_decompress_io source_io, destination_io, options
35
- end
36
-
37
- nil
10
+ # LZWS::File class.
11
+ class File < ADSP::File
12
+ # Current option class.
13
+ Option = LZWS::Option
14
+
15
+ # Bypass native compress.
16
+ def self.native_compress_io(*args)
17
+ LZWS._native_compress_io(*args)
38
18
  end
39
19
 
40
- private_class_method def self.open_files(source, destination, &_block)
41
- ::File.open source, "rb" do |source_io|
42
- ::File.open destination, "wb" do |destination_io|
43
- yield source_io, destination_io
44
- end
45
- end
20
+ # Bypass native decompress.
21
+ def self.native_decompress_io(*args)
22
+ LZWS._native_decompress_io(*args)
46
23
  end
47
24
  end
48
25
  end
data/lib/lzws/option.rb CHANGED
@@ -7,29 +7,56 @@ require_relative "error"
7
7
  require_relative "validation"
8
8
 
9
9
  module LZWS
10
+ # LZWS::Option module.
10
11
  module Option
12
+ # Current default buffer length.
11
13
  DEFAULT_BUFFER_LENGTH = 0
12
14
 
15
+ # Current compressor defaults.
13
16
  COMPRESSOR_DEFAULTS = {
17
+ # Enables global VM lock where possible.
14
18
  :gvl => false,
19
+ # Max code bit length.
15
20
  :max_code_bit_length => nil,
21
+ # Disables magic header.
16
22
  :without_magic_header => nil,
23
+ # Enables block mode.
17
24
  :block_mode => nil,
25
+ # Enables most significant bit mode.
18
26
  :msb => nil,
27
+ # Enables unaligned bit groups.
19
28
  :unaligned_bit_groups => nil,
29
+ # Disables lzws library logging.
20
30
  :quiet => nil
21
31
  }
22
32
  .freeze
23
33
 
34
+ # Current decompressor defaults.
24
35
  DECOMPRESSOR_DEFAULTS = {
36
+ # Enables global VM lock where possible.
25
37
  :gvl => false,
38
+ # Disables magic header.
26
39
  :without_magic_header => nil,
40
+ # Enables most significant bit mode.
27
41
  :msb => nil,
42
+ # Enables unaligned bit groups.
28
43
  :unaligned_bit_groups => nil,
44
+ # Disables lzws library logging.
29
45
  :quiet => nil
30
46
  }
31
47
  .freeze
32
48
 
49
+ # Processes compressor +options+ and +buffer_length_names+.
50
+ # Option: +:source_buffer_length+ source buffer length.
51
+ # Option: +:destination_buffer_length+ destination buffer length.
52
+ # Option: +:gvl+ enables global VM lock where possible.
53
+ # Option: +:max_code_bit_length+ max code bit length.
54
+ # Option: +:block_mode+ enables block mode.
55
+ # Option: +:without_magic_header+ disables magic header.
56
+ # Option: +:msb+ enables most significant bit mode.
57
+ # Option: +:unaligned_bit_groups+ enables unaligned bit groups.
58
+ # Option: +:quiet+ disables lzws library logging.
59
+ # Returns processed compressor options.
33
60
  def self.get_compressor_options(options, buffer_length_names)
34
61
  Validation.validate_hash options
35
62
 
@@ -68,6 +95,15 @@ module LZWS
68
95
  options
69
96
  end
70
97
 
98
+ # Processes decompressor +options+ and +buffer_length_names+.
99
+ # Option: +:source_buffer_length+ source buffer length.
100
+ # Option: +:destination_buffer_length+ destination buffer length.
101
+ # Option: +:gvl+ enables global VM lock where possible.
102
+ # Option: +:without_magic_header+ disables magic header.
103
+ # Option: +:msb+ enables most significant bit mode.
104
+ # Option: +:unaligned_bit_groups+ enables unaligned bit groups.
105
+ # Option: +:quiet+ disables lzws library logging.
106
+ # Returns processed decompressor options.
71
107
  def self.get_decompressor_options(options, buffer_length_names)
72
108
  Validation.validate_hash options
73
109
 
@@ -1,74 +1,32 @@
1
1
  # Ruby bindings for lzws library.
2
2
  # Copyright (c) 2019 AUTHORS, MIT License.
3
3
 
4
+ require "adsp/stream/raw/compressor"
4
5
  require "lzws_ext"
5
6
 
6
- require_relative "abstract"
7
- require_relative "../../error"
8
7
  require_relative "../../option"
9
8
  require_relative "../../validation"
10
9
 
11
10
  module LZWS
12
11
  module Stream
13
12
  module Raw
14
- class Compressor < Abstract
15
- BUFFER_LENGTH_NAMES = %i[destination_buffer_length].freeze
13
+ # LZWS::Stream::Raw::Compressor class.
14
+ class Compressor < ADSP::Stream::Raw::Compressor
15
+ # Current native compressor class.
16
+ NativeCompressor = Stream::NativeCompressor
16
17
 
17
- def initialize(options = {})
18
- options = Option.get_compressor_options options, BUFFER_LENGTH_NAMES
19
- native_stream = NativeCompressor.new options
18
+ # Current option class.
19
+ Option = LZWS::Option
20
20
 
21
- super native_stream
22
- end
23
-
24
- def write(source, &writer)
21
+ # Flushes raw stream and writes next result using +writer+ proc.
22
+ def flush(&writer)
25
23
  do_not_use_after_close
26
24
 
27
- Validation.validate_string source
28
- Validation.validate_proc writer
29
-
30
- total_bytes_written = 0
31
-
32
- loop do
33
- bytes_written, need_more_destination = @native_stream.write source
34
- total_bytes_written += bytes_written
35
-
36
- if need_more_destination
37
- source = source.byteslice bytes_written, source.bytesize - bytes_written
38
- more_destination(&writer)
39
- next
40
- end
41
-
42
- unless bytes_written == source.bytesize
43
- # :nocov:
44
- # Compressor write should eat all provided "source" without remainder.
45
- raise UnexpectedError, "unexpected error"
46
- # :nocov:
47
- end
48
-
49
- break
50
- end
51
-
52
- total_bytes_written
53
- end
54
-
55
- def close(&writer)
56
- return nil if closed?
57
-
58
25
  Validation.validate_proc writer
59
26
 
60
- loop do
61
- need_more_destination = @native_stream.finish
62
-
63
- if need_more_destination
64
- more_destination(&writer)
65
- next
66
- end
67
-
68
- break
69
- end
27
+ write_result(&writer)
70
28
 
71
- super
29
+ nil
72
30
  end
73
31
  end
74
32
  end
@@ -1,56 +1,32 @@
1
1
  # Ruby bindings for lzws library.
2
2
  # Copyright (c) 2019 AUTHORS, MIT License.
3
3
 
4
+ require "adsp/stream/raw/decompressor"
4
5
  require "lzws_ext"
5
6
 
6
- require_relative "abstract"
7
7
  require_relative "../../option"
8
8
  require_relative "../../validation"
9
9
 
10
10
  module LZWS
11
11
  module Stream
12
12
  module Raw
13
- class Decompressor < Abstract
14
- BUFFER_LENGTH_NAMES = %i[destination_buffer_length].freeze
13
+ # LZWS::Stream::Raw::Decompressor class.
14
+ class Decompressor < ADSP::Stream::Raw::Decompressor
15
+ # Current native decompressor class.
16
+ NativeDecompressor = Stream::NativeDecompressor
15
17
 
16
- def initialize(options = {})
17
- options = Option.get_decompressor_options options, BUFFER_LENGTH_NAMES
18
- native_stream = NativeDecompressor.new options
18
+ # Current option class.
19
+ Option = LZWS::Option
19
20
 
20
- super native_stream
21
- end
22
-
23
- def read(source, &writer)
21
+ # Flushes raw stream and writes next result using +writer+ proc.
22
+ def flush(&writer)
24
23
  do_not_use_after_close
25
24
 
26
- Validation.validate_string source
27
25
  Validation.validate_proc writer
28
26
 
29
- total_bytes_read = 0
30
-
31
- loop do
32
- bytes_read, need_more_destination = @native_stream.read source
33
- total_bytes_read += bytes_read
34
-
35
- if need_more_destination
36
- source = source.byteslice bytes_read, source.bytesize - bytes_read
37
- more_destination(&writer)
38
- next
39
- end
40
-
41
- break
42
- end
43
-
44
- # Please remember that "total_bytes_read" can not be equal to "source.bytesize".
45
- total_bytes_read
46
- end
47
-
48
- def close(&writer)
49
- return nil if closed?
50
-
51
- Validation.validate_proc writer
27
+ write_result(&writer)
52
28
 
53
- super
29
+ nil
54
30
  end
55
31
  end
56
32
  end
@@ -1,186 +1,16 @@
1
1
  # Ruby bindings for lzws library.
2
2
  # Copyright (c) 2019 AUTHORS, MIT License.
3
3
 
4
- require_relative "abstract"
4
+ require "adsp/stream/reader"
5
+
5
6
  require_relative "raw/decompressor"
6
- require_relative "reader_helpers"
7
- require_relative "../validation"
8
7
 
9
8
  module LZWS
10
9
  module Stream
11
- class Reader < Abstract
12
- include ReaderHelpers
13
-
14
- attr_accessor :lineno
15
-
16
- def initialize(source_io, options = {}, *args)
17
- @options = options
18
-
19
- super source_io, *args
20
-
21
- initialize_source_buffer_length
22
- reset_io_remainder
23
- reset_need_to_flush
24
-
25
- @lineno = 0
26
- end
27
-
28
- protected def create_raw_stream
29
- Raw::Decompressor.new @options
30
- end
31
-
32
- protected def initialize_source_buffer_length
33
- source_buffer_length = @options[:source_buffer_length]
34
- Validation.validate_not_negative_integer source_buffer_length unless source_buffer_length.nil?
35
-
36
- if source_buffer_length.nil? || source_buffer_length.zero?
37
- source_buffer_length = Buffer::DEFAULT_SOURCE_BUFFER_LENGTH_FOR_DECOMPRESSOR
38
- end
39
-
40
- @source_buffer_length = source_buffer_length
41
- end
42
-
43
- protected def reset_io_remainder
44
- @io_remainder = ::String.new :encoding => ::Encoding::BINARY
45
- end
46
-
47
- protected def reset_need_to_flush
48
- @need_to_flush = false
49
- end
50
-
51
- # -- synchronous --
52
-
53
- def read(bytes_to_read = nil, out_buffer = nil)
54
- Validation.validate_not_negative_integer bytes_to_read unless bytes_to_read.nil?
55
- Validation.validate_string out_buffer unless out_buffer.nil?
56
-
57
- unless bytes_to_read.nil?
58
- return ::String.new :encoding => ::Encoding::BINARY if bytes_to_read.zero?
59
- return nil if eof?
60
-
61
- append_io_data @io.read(@source_buffer_length) while @buffer.bytesize < bytes_to_read && !@io.eof?
62
- flush_io_data if @buffer.bytesize < bytes_to_read
63
-
64
- return read_bytes_from_buffer bytes_to_read, out_buffer
65
- end
66
-
67
- append_io_data @io.read(@source_buffer_length) until @io.eof?
68
- flush_io_data
69
-
70
- read_buffer out_buffer
71
- end
72
-
73
- def rewind
74
- raw_wrapper :close
75
-
76
- reset_io_remainder
77
- reset_need_to_flush
78
-
79
- super
80
- end
81
-
82
- def close
83
- raw_wrapper :close
84
-
85
- super
86
- end
87
-
88
- def eof?
89
- empty? && @io.eof?
90
- end
91
-
92
- # -- asynchronous --
93
-
94
- def readpartial(bytes_to_read, out_buffer = nil)
95
- read_more_nonblock(bytes_to_read, out_buffer) { @io.readpartial @source_buffer_length }
96
- end
97
-
98
- def read_nonblock(bytes_to_read, out_buffer = nil, *options)
99
- read_more_nonblock(bytes_to_read, out_buffer) { @io.read_nonblock(@source_buffer_length, *options) }
100
- end
101
-
102
- protected def read_more_nonblock(bytes_to_read, out_buffer, &_block)
103
- Validation.validate_not_negative_integer bytes_to_read
104
- Validation.validate_string out_buffer unless out_buffer.nil?
105
-
106
- return ::String.new :encoding => ::Encoding::BINARY if bytes_to_read.zero?
107
-
108
- io_provided_eof_error = false
109
-
110
- if @buffer.bytesize < bytes_to_read
111
- begin
112
- append_io_data yield
113
- rescue ::EOFError
114
- io_provided_eof_error = true
115
- end
116
- end
117
-
118
- flush_io_data if @buffer.bytesize < bytes_to_read
119
- raise ::EOFError if empty? && io_provided_eof_error
120
-
121
- read_bytes_from_buffer bytes_to_read, out_buffer
122
- end
123
-
124
- # -- common --
125
-
126
- protected def append_io_data(io_data)
127
- io_portion = @io_remainder + io_data
128
- bytes_read = raw_wrapper :read, io_portion
129
- @io_remainder = io_portion.byteslice bytes_read, io_portion.bytesize - bytes_read
130
-
131
- # Even empty io data may require flush.
132
- @need_to_flush = true
133
- end
134
-
135
- protected def flush_io_data
136
- raw_wrapper :flush
137
-
138
- @need_to_flush = false
139
- end
140
-
141
- protected def empty?
142
- !@need_to_flush && @buffer.bytesize.zero?
143
- end
144
-
145
- protected def read_bytes_from_buffer(bytes_to_read, out_buffer)
146
- bytes_read = [@buffer.bytesize, bytes_to_read].min
147
-
148
- # Result uses buffer binary encoding.
149
- result = @buffer.byteslice 0, bytes_read
150
- @buffer = @buffer.byteslice bytes_read, @buffer.bytesize - bytes_read
151
- @pos += bytes_read
152
-
153
- result = out_buffer.replace result unless out_buffer.nil?
154
- result
155
- end
156
-
157
- protected def read_buffer(out_buffer)
158
- result = @buffer
159
- reset_buffer
160
- @pos += result.bytesize
161
-
162
- result.force_encoding @external_encoding unless @external_encoding.nil?
163
- result = transcode_to_internal result
164
-
165
- result = out_buffer.replace result unless out_buffer.nil?
166
- result
167
- end
168
-
169
- protected def transcode_to_internal(data)
170
- data = data.encode @internal_encoding, **@transcode_options unless @internal_encoding.nil?
171
- data
172
- end
173
-
174
- # We should be able to return data back to buffer.
175
- # We won't use any transcode options because transcoded data should be backward compatible.
176
- protected def transcode_to_external(data)
177
- data = data.encode @external_encoding unless @external_encoding.nil?
178
- data
179
- end
180
-
181
- protected def raw_wrapper(method_name, *args)
182
- @raw_stream.send(method_name, *args) { |portion| @buffer << portion }
183
- end
10
+ # LZWS::Stream::Reader class.
11
+ class Reader < ADSP::Stream::Reader
12
+ # Current raw stream class.
13
+ RawDecompressor = Raw::Decompressor
184
14
  end
185
15
  end
186
16
  end