redbreast 0.1.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.DS_Store +0 -0
- data/.rubocop.yml +5 -0
- data/README.md +102 -4
- data/Rakefile +3 -3
- data/bin/console +3 -3
- data/exe/redbreast +9 -7
- data/lib/.DS_Store +0 -0
- data/lib/redbreast.rb +25 -18
- data/lib/redbreast/.DS_Store +0 -0
- data/lib/redbreast/commands/color_generator.rb +54 -0
- data/lib/redbreast/commands/color_test_generator.rb +57 -0
- data/lib/redbreast/commands/configuration_installer.rb +29 -29
- data/lib/redbreast/commands/image_generator.rb +49 -47
- data/lib/redbreast/commands/image_test_generator.rb +57 -0
- data/lib/redbreast/commands/setup.rb +139 -92
- data/lib/redbreast/crawlers/color_crawler.rb +38 -0
- data/lib/redbreast/crawlers/image_crawler.rb +30 -10
- data/lib/redbreast/error_handler.rb +7 -6
- data/lib/redbreast/helpers/general.rb +73 -63
- data/lib/redbreast/helpers/hash.rb +8 -7
- data/lib/redbreast/helpers/terminal.rb +2 -3
- data/lib/redbreast/io/config.rb +14 -15
- data/lib/redbreast/serializers/objc_serializer.rb +66 -19
- data/lib/redbreast/serializers/serializer.rb +17 -16
- data/lib/redbreast/serializers/swift_serializer.rb +93 -2
- data/lib/redbreast/template_generators/.DS_Store +0 -0
- data/lib/redbreast/template_generators/colors/objc_colors_template_generator.rb +35 -0
- data/lib/redbreast/template_generators/colors/swift_colors_template_generator.rb +22 -0
- data/lib/redbreast/template_generators/images/objc_images_template_generator.rb +27 -41
- data/lib/redbreast/template_generators/images/swift_images_template_generator.rb +16 -21
- data/lib/redbreast/template_generators/objc_template_generator.rb +19 -19
- data/lib/redbreast/template_generators/swift_template_generator.rb +9 -10
- data/lib/redbreast/template_generators/tests/colors/objc_colors_tests_template_generator.rb +35 -0
- data/lib/redbreast/template_generators/tests/colors/swift_colors_tests_template_generator.rb +27 -0
- data/lib/redbreast/template_generators/tests/images/objc_images_tests_template_generator.rb +36 -0
- data/lib/redbreast/template_generators/tests/images/swift_images_tests_template_generator.rb +27 -0
- data/lib/redbreast/version.rb +1 -1
- data/redbreast.gemspec +21 -22
- metadata +35 -11
- data/lib/redbreast/commands/test_generator.rb +0 -56
- data/lib/redbreast/template_generators/tests/objc_tests_template_generator.rb +0 -39
- data/lib/redbreast/template_generators/tests/swift_tests_template_generator.rb +0 -31
@@ -1,18 +1,38 @@
|
|
1
1
|
module Redbreast
|
2
2
|
module Crawler
|
3
|
+
# Class for finding images
|
3
4
|
class Image
|
4
|
-
|
5
5
|
def self.image_names_uniq(assets_search_path)
|
6
|
-
Dir.glob(assets_search_path).flat_map
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
Dir.glob(assets_search_path).flat_map do |asset_folder|
|
7
|
+
Dir.glob("#{asset_folder}/**/*.imageset").map do |image_name|
|
8
|
+
name_to_split = image_name
|
9
|
+
split_name = name_to_split.split('.xcassets/')
|
10
|
+
current_image_name = split_name[0] + '.xcassets/'
|
11
|
+
current_iterating_name = split_name[0] + '.xcassets/'
|
12
|
+
|
13
|
+
split_name[1].split('/').each do |folder|
|
14
|
+
if folder.include? '.imageset'
|
15
|
+
current_image_name += folder
|
16
|
+
next
|
17
|
+
end
|
18
|
+
|
19
|
+
current_iterating_name += folder + '/'
|
13
20
|
|
21
|
+
Dir.glob("#{current_iterating_name}*.json").map do |path_name|
|
22
|
+
File.open path_name do |file|
|
23
|
+
unless file.find { |line| line =~ /provides/ }.nil?
|
24
|
+
current_image_name += folder + '/'
|
25
|
+
next
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
current_image_name.split('.xcassets/')[-1].chomp('.imageset')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
.uniq
|
35
|
+
end
|
14
36
|
end
|
15
37
|
end
|
16
38
|
end
|
17
|
-
|
18
|
-
|
@@ -1,24 +1,25 @@
|
|
1
1
|
module Redbreast
|
2
|
+
# Class for handling errors that occurr
|
2
3
|
class ErrorHandler
|
3
4
|
extend Helper::Terminal
|
4
5
|
|
5
6
|
class << self
|
6
7
|
def rescuable
|
7
8
|
yield
|
8
|
-
rescue => e
|
9
|
+
rescue StandardError => e
|
9
10
|
handle(e)
|
10
11
|
end
|
11
12
|
|
12
|
-
def handle(
|
13
|
+
def handle(error)
|
13
14
|
prompt.error(
|
14
|
-
case
|
15
|
+
case error
|
15
16
|
when Errno::ENOENT
|
16
|
-
"We could not find a file that we need:\n\n#{
|
17
|
+
"We could not find a file that we need:\n\n#{error.message}"
|
17
18
|
else
|
18
|
-
"An error happened. This might help:\n\n#{
|
19
|
+
"An error happened. This might help:\n\n#{error.message}"
|
19
20
|
end
|
20
21
|
)
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
24
|
-
end
|
25
|
+
end
|
@@ -1,67 +1,77 @@
|
|
1
1
|
module Redbreast
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
2
|
+
module Helper
|
3
|
+
# Module with general metods used in creating files
|
4
|
+
module General
|
5
|
+
ESCAPE_KEYWORDS = ['associatedtype', 'class', 'deinit', 'enum', 'extension',
|
6
|
+
'fileprivate', 'func', 'import', 'init', 'inout',
|
7
|
+
'internal', 'let', 'operator', 'private', 'protocol',
|
8
|
+
'public', 'static', 'struct', 'subscript', 'typealias',
|
9
|
+
'var', 'break', 'case', 'continue', 'default',
|
10
|
+
'defer', 'do', 'else', 'fallthrough', 'for',
|
11
|
+
'guard', 'if', 'in', 'repeat', 'return',
|
12
|
+
'switch', 'where', 'while', 'as', 'Any',
|
13
|
+
'catch', 'false', 'is', 'nil', 'rethrows',
|
14
|
+
'super', 'self', 'Self', 'throw', 'throws',
|
15
|
+
'true', 'try', '_'].freeze
|
16
|
+
def config
|
17
|
+
@config ||= Redbreast::IO::Config.read
|
18
|
+
end
|
19
|
+
|
20
|
+
def programming_language
|
21
|
+
@programming_language ||= config[:language]
|
22
|
+
end
|
23
|
+
|
24
|
+
def bundles
|
25
|
+
@bundles ||= config[:bundles]
|
26
|
+
end
|
27
|
+
|
28
|
+
def app_name
|
29
|
+
@app_name ||= config[:app_name]
|
30
|
+
end
|
31
|
+
|
32
|
+
def indent(level = 0, initial = '')
|
33
|
+
(1..level)
|
34
|
+
.to_a.reduce('') { |result, _| result + ' ' }
|
35
|
+
.concat(initial)
|
36
|
+
end
|
37
|
+
|
38
|
+
def clean_enum_name(name)
|
39
|
+
clean_name = name
|
40
|
+
.split(/[^0-9a-zA-Z]/)
|
41
|
+
.reject(&:empty?)
|
42
|
+
.map(&:capitalize)
|
43
|
+
.join
|
44
|
+
|
45
|
+
escape_with_underscore_if_needed(clean_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def clean_case_name(name)
|
49
|
+
clean_variable_name(name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def clean_variable_name(name)
|
53
|
+
clean_name = name
|
54
|
+
.split(/[^0-9a-zA-Z]/)
|
55
|
+
.reject(&:empty?)
|
56
|
+
.each_with_index
|
57
|
+
.map { |v, i| i.zero? ? v.tap { |char| char[0] = char[0].downcase } : v.capitalize }
|
58
|
+
.join
|
59
|
+
|
60
|
+
escaped_underscore = escape_with_underscore_if_needed(clean_name)
|
61
|
+
escape_keyword_if_needed(escaped_underscore)
|
62
|
+
end
|
63
|
+
|
64
|
+
def escape_with_underscore_if_needed(name)
|
65
|
+
return name if name.match(/^[A-Za-z_]/)
|
66
|
+
|
67
|
+
'_' + name
|
68
|
+
end
|
69
|
+
|
70
|
+
def escape_keyword_if_needed(name)
|
71
|
+
return name unless ESCAPE_KEYWORDS.include? name
|
18
72
|
|
19
|
-
|
20
|
-
@bundles ||= config[:bundles]
|
21
|
-
end
|
22
|
-
|
23
|
-
def indent(level = 0, initial = "")
|
24
|
-
(1..level)
|
25
|
-
.to_a.reduce("") { |result, value| result + " " }
|
26
|
-
.concat(initial)
|
27
|
-
end
|
28
|
-
|
29
|
-
def clean_enum_name(name)
|
30
|
-
clean_name = name
|
31
|
-
.split(/[^0-9a-zA-Z]/)
|
32
|
-
.reject { |c| c.empty? }
|
33
|
-
.map { |value| value.capitalize }
|
34
|
-
.join
|
35
|
-
|
36
|
-
escape_with_underscore_if_needed(clean_name)
|
37
|
-
end
|
38
|
-
|
39
|
-
def clean_case_name(name)
|
40
|
-
clean_variable_name(name)
|
41
|
-
end
|
42
|
-
|
43
|
-
def clean_variable_name(name)
|
44
|
-
clean_name = name
|
45
|
-
.split(/[^0-9a-zA-Z]/)
|
46
|
-
.reject { |c| c.empty? }
|
47
|
-
.each_with_index
|
48
|
-
.map { |value, index| index == 0 ? value.tap { |char| char[0] = char[0].downcase } : value.capitalize }
|
49
|
-
.join
|
50
|
-
|
51
|
-
escaped_underscore = escape_with_underscore_if_needed(clean_name)
|
52
|
-
escape_keyword_if_needed(escaped_underscore)
|
53
|
-
end
|
54
|
-
|
55
|
-
def escape_with_underscore_if_needed(name)
|
56
|
-
return name if name.match(/^[A-Za-z_]/)
|
57
|
-
"_" + name
|
58
|
-
end
|
59
|
-
|
60
|
-
def escape_keyword_if_needed(name)
|
61
|
-
return name if !ESCAPE_KEYWORDS.include? name
|
62
|
-
"`#{name}`"
|
63
|
-
end
|
64
|
-
|
73
|
+
"`#{name}`"
|
65
74
|
end
|
66
75
|
end
|
67
|
-
end
|
76
|
+
end
|
77
|
+
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module Redbreast
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
module Helper
|
3
|
+
# Module used for creating compact dictionaries
|
4
|
+
module HashHelper
|
5
|
+
def compact(dictionary)
|
6
|
+
dictionary.delete_if { |_k, v| v.nil? }
|
7
|
+
end
|
8
8
|
end
|
9
|
-
end
|
9
|
+
end
|
10
|
+
end
|
@@ -2,8 +2,8 @@ require 'tty-prompt'
|
|
2
2
|
|
3
3
|
module Redbreast
|
4
4
|
module Helper
|
5
|
+
# Module used for communicatin with user via terminal
|
5
6
|
module Terminal
|
6
|
-
|
7
7
|
def success(message = 'Success!')
|
8
8
|
prompt.ok(message)
|
9
9
|
end
|
@@ -11,7 +11,6 @@ module Redbreast
|
|
11
11
|
def prompt
|
12
12
|
@prompt ||= TTY::Prompt.new(interrupt: :exit)
|
13
13
|
end
|
14
|
-
|
15
14
|
end
|
16
15
|
end
|
17
|
-
end
|
16
|
+
end
|
data/lib/redbreast/io/config.rb
CHANGED
@@ -1,19 +1,18 @@
|
|
1
1
|
module Redbreast
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
2
|
+
module IO
|
3
|
+
# Used for reading and writing to config file
|
4
|
+
class Config
|
5
|
+
CONFIG_FILE_PATH = "#{Dir.pwd}/.redbrest.yml"
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def write(data)
|
9
|
+
File.open(CONFIG_FILE_PATH, 'w') { |file| YAML.dump(data, file) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def read
|
13
|
+
YAML.load_file(CONFIG_FILE_PATH)
|
15
14
|
end
|
16
|
-
|
17
15
|
end
|
18
16
|
end
|
19
|
-
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,27 +1,74 @@
|
|
1
|
-
require_relative 'serializer'
|
1
|
+
require_relative 'serializer'
|
2
2
|
|
3
3
|
module Redbreast
|
4
4
|
module Serializer
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
5
|
+
# Used for saving and creating ObjC files
|
6
|
+
class ObjC < Base
|
7
|
+
include Helper::General
|
8
|
+
|
9
|
+
def save(output_source_path:, template_generator:, generate_colors:)
|
10
|
+
FileUtils.mkdir_p output_source_path unless File.exist? output_source_path
|
11
|
+
|
12
|
+
file_base_name = generate_colors ? 'UIColor' : 'UIImage'
|
13
|
+
name = app_name.nil? ? 'Common' : app_name
|
14
|
+
|
15
|
+
if template_generator.h_template
|
16
|
+
write(output_source_path, template_generator.h_template, "#{file_base_name}+#{name}.h")
|
17
|
+
end
|
18
|
+
|
19
|
+
return unless template_generator.m_template
|
20
|
+
|
21
|
+
write(output_source_path, template_generator.m_template, "#{file_base_name}+#{name}.m")
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(output_source_path, template, file_name)
|
25
|
+
file = ERB.new(template, nil, '-').result(binding)
|
26
|
+
File.write(File.join(output_source_path, file_name), file)
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_objc_test_cases(names:, variable:)
|
30
|
+
text = ''
|
31
|
+
names.each do |name|
|
32
|
+
temp_array = name.split('/')
|
33
|
+
variable_name = temp_array.length == 1 ? clean_variable_name(name) : temp_array.unshift(temp_array.shift.downcase).join('')
|
34
|
+
text += variable % variable_name
|
23
35
|
end
|
36
|
+
text
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_m_file_objc(names:, variable_declaration:, variable_implementation:, bundle_name:)
|
40
|
+
text = ''
|
41
|
+
|
42
|
+
names.each do |name|
|
43
|
+
temp_arr = name.split('/')
|
44
|
+
|
45
|
+
variable_name = temp_arr.length == 1 ? clean_variable_name(name) : temp_arr.unshift(temp_arr.shift.downcase).join('')
|
46
|
+
text += variable_declaration % variable_name + variable_implementation % [name, bundle_name[:reference]]
|
47
|
+
text += name == names.last ? '' : "\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
text
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_h_file_objc(names:, variable:)
|
54
|
+
text = ''
|
55
|
+
|
56
|
+
names.each do |name|
|
57
|
+
temp_arr = name.split('/')
|
58
|
+
variable_name = temp_arr.length == 1 ? clean_variable_name(name) : temp_arr.unshift(temp_arr.shift.downcase).join('')
|
59
|
+
text += variable % variable_name
|
60
|
+
end
|
61
|
+
|
62
|
+
text
|
63
|
+
end
|
64
|
+
|
65
|
+
def generate_category(type, class_name, app_name)
|
66
|
+
text = "@#{type} #{class_name} ("
|
67
|
+
|
68
|
+
return text += 'Common)\n' if app_name.nil? || app_name.empty?
|
24
69
|
|
70
|
+
text + app_name + ")\n"
|
71
|
+
end
|
25
72
|
end
|
26
73
|
end
|
27
74
|
end
|
@@ -1,18 +1,19 @@
|
|
1
1
|
module Redbreast
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
2
|
+
module Serializer
|
3
|
+
# Used for initializing a serializer which will save files for respective languages
|
4
|
+
class Base
|
5
|
+
include ERB::Util
|
6
|
+
attr_accessor :asset_names, :bundle
|
7
|
+
|
8
|
+
def initialize(asset_names, bundle, app_name)
|
9
|
+
@asset_names = asset_names
|
10
|
+
@bundle = bundle
|
11
|
+
@app_name = app_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def save(*)
|
15
|
+
raise NotImplementedError, 'Abstract Method'
|
16
|
+
end
|
17
17
|
end
|
18
|
-
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,11 +1,12 @@
|
|
1
|
-
require_relative 'serializer'
|
1
|
+
require_relative 'serializer'
|
2
2
|
|
3
3
|
module Redbreast
|
4
4
|
module Serializer
|
5
|
+
# Used to save swift files
|
5
6
|
class Swift < Base
|
6
7
|
include Helper::General
|
7
8
|
|
8
|
-
def save(output_source_path
|
9
|
+
def save(output_source_path:, template_generator:, generate_colors:)
|
9
10
|
directory = File.dirname(output_source_path)
|
10
11
|
FileUtils.mkdir_p directory unless File.exist? directory
|
11
12
|
|
@@ -13,6 +14,96 @@ module Redbreast
|
|
13
14
|
File.write(output_source_path, file)
|
14
15
|
end
|
15
16
|
|
17
|
+
def generate_file_swift(names:, spacing: "\t", indentation: '', variable:, bundle:)
|
18
|
+
return if names.empty?
|
19
|
+
|
20
|
+
text = ''
|
21
|
+
arr = []
|
22
|
+
|
23
|
+
text, arr = generate_variables(names: names, spacing: spacing, indentation: indentation, variable: variable, bundle: bundle, text: text, array: arr)
|
24
|
+
|
25
|
+
arr = arr.uniq
|
26
|
+
text += indentation.empty? && text.empty? ? "\n" : ''
|
27
|
+
arr.each do |enum_name|
|
28
|
+
names_new = []
|
29
|
+
names_new_enum = []
|
30
|
+
new_enum_name = enum_name
|
31
|
+
|
32
|
+
text += "\n" + spacing + 'enum ' + enum_name + ' {'
|
33
|
+
names_new, names_new_enum = separate_variables_from_folders(names: names, enum_name: enum_name, new_enum_name: new_enum_name, names_new_enum: names_new_enum, names_new: names_new)
|
34
|
+
|
35
|
+
if !names_new_enum.empty? && new_enum_name == enum_name
|
36
|
+
indentation += indentation.empty? || indentation[-1] == '/' ? '' : '/'
|
37
|
+
text += "\n" + generate_file_swift(names: names_new_enum, spacing: spacing + "\t", indentation: indentation + enum_name, variable: variable, bundle: bundle)
|
38
|
+
end
|
39
|
+
|
40
|
+
unless names_new.empty?
|
41
|
+
|
42
|
+
indentation += indentation.empty? || indentation[-1] == '/' ? '' : '/'
|
43
|
+
text += "\n" + generate_file_swift(names: names_new, spacing: spacing + "\t", indentation: indentation + enum_name, variable: variable, bundle: bundle)
|
44
|
+
end
|
45
|
+
|
46
|
+
text += "\n" + spacing + '}' + "\n"
|
47
|
+
end
|
48
|
+
text
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate_extension(extended_class, app_name)
|
52
|
+
text = 'extension ' + extended_class + " {\n"
|
53
|
+
|
54
|
+
return text if app_name.nil? || app_name.empty?
|
55
|
+
|
56
|
+
text + "\tenum " + app_name + " {}\n}\n\nextension " + extended_class + '.' + app_name + " {\n"
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_swift_test_cases(names:, declaration:, app_name:)
|
60
|
+
text = ''
|
61
|
+
app_name_text = app_name.nil? || app_name.empty? ? '' : app_name + '.'
|
62
|
+
|
63
|
+
names.each do |name|
|
64
|
+
temp_array = name.split('/')
|
65
|
+
variable = temp_array.pop
|
66
|
+
additional_text = temp_array.count.zero? ? '' : '.'
|
67
|
+
text += "\t\t" + declaration + app_name_text + temp_array.join('.') + additional_text + clean_variable_name(variable)
|
68
|
+
text += name == names.last ? '' : "\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
text
|
72
|
+
end
|
73
|
+
|
74
|
+
def generate_variables(names:, spacing:, indentation:, bundle:, variable:, text:, array:)
|
75
|
+
names.each do |name|
|
76
|
+
temp_arr = name.split('/')
|
77
|
+
|
78
|
+
if temp_arr.length != 1
|
79
|
+
array.push(temp_arr.first)
|
80
|
+
else
|
81
|
+
name_prefix = indentation.empty? ? '' : '/'
|
82
|
+
text += spacing + variable % [clean_variable_name(name), indentation + name_prefix + name, bundle[:reference]]
|
83
|
+
#text += spacing + declaration + clean_variable_name(name) + type + indentation + name_prefix + name + var_end + bundle[:reference] + line_end
|
84
|
+
text += name == names.last ? '' : "\n"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
[text, array]
|
89
|
+
end
|
90
|
+
|
91
|
+
def separate_variables_from_folders(names:, enum_name:, new_enum_name:, names_new_enum:, names_new:)
|
92
|
+
names.each do |name|
|
93
|
+
temp_arr = name.split('/')
|
94
|
+
|
95
|
+
next if temp_arr.length == 1
|
96
|
+
|
97
|
+
if temp_arr.length > 2
|
98
|
+
names_new_enum.push(temp_arr.drop(1).join('/')) if temp_arr.first == new_enum_name
|
99
|
+
next
|
100
|
+
end
|
101
|
+
|
102
|
+
names_new.push(temp_arr.drop(1).join('/')) if temp_arr[0] == enum_name
|
103
|
+
end
|
104
|
+
|
105
|
+
[names_new, names_new_enum]
|
106
|
+
end
|
16
107
|
end
|
17
108
|
end
|
18
109
|
end
|