gena 0.0.7 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9394c4d63919ceaf86b70b90e8338ab8927b7b41
4
- data.tar.gz: 7028e23fd9c244d528e07ff1a5bbf7c51b36a8b5
3
+ metadata.gz: d011b05b5a46e8082c31e35c5937526e60813077
4
+ data.tar.gz: 8faa576c158597b3a126c55c380aaf9e44e0b266
5
5
  SHA512:
6
- metadata.gz: 9d3135276f6d6b0fdb7dc0e322d60d5beca16fe3e94875352f98d285985957f0b299ebb736456050e4c6770328cfc92c9b2c7bf5c81ccbf051284320a4adf01a
7
- data.tar.gz: e48bde3e2dfe2b332c53e42951e7a7bd83e3c519c19733e66b25a739547cfdd7d81ae8a92453ad9faf755cb10052711522b4cb47cd7ab9793ca0ba03e4af2d46
6
+ metadata.gz: 2329244fb94233fc4a8c538401a1047470d080c86b2dda958c3f79860f79c5832026d0eb66dfa6900becb34b6bdd17cdec7f50b4dd6213607d7f7321f90ac69a
7
+ data.tar.gz: 240c13add3eb328b128c9cb5c5a2f4831379e656f3cbcc70f718e9943664c07e76944321d0dbe2018350a2ba9fcaa988c93abae8b927231bd8c0cc8d13dd1cdc
data/bin/gena CHANGED
@@ -1,71 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'gena'
3
+ # require 'gena'
4
+ require_relative '../lib/gena'
4
5
 
5
- unless system "which xc-resave >> /dev/null"
6
- raise "\nPlease install xc-resave utility first. Run: \n\nbrew install alexgarbarev/core/xc-resave\n\n"
7
- end
6
+ Gena::Application.new.check
8
7
 
9
- TEMPLATES_PROJECT_FOLDER = File.expand_path('Templates')
10
- TEMPLATES_SYSTEM_FOLDER = File.expand_path('~/.gena/templates')
8
+ Gena::Application.start(ARGV)
11
9
 
12
- # Include all template code files
13
10
 
14
- registered = []
15
- Dir["#{TEMPLATES_PROJECT_FOLDER}/**/*.rb", "#{TEMPLATES_SYSTEM_FOLDER}/**/*.rb"].each do |file|
16
- template_name = file.split(File::SEPARATOR)[-2]
17
- unless registered.include? template_name
18
- registered << template_name
19
- require file
20
- end
21
- end
22
-
23
- cli = GenerateCli.new
24
- options = cli.parse_arguments
25
-
26
- def cleanup
27
- ramba_adapter = RambaAdapter.new(nil, nil)
28
- ramba_adapter.delete_default_template
29
- ramba_adapter.delete_rambafile
30
- end
31
-
32
- if options[:cleanup]
33
- cleanup
34
- exit
35
- end
36
-
37
- at_exit do
38
- puts 'Cleaning up..'
39
- cleanup
40
- end
41
-
42
- config = Generate::Config.new
43
- config.load_plist_config
44
-
45
- if options[:fetch]
46
- FileUtils.rm_rf File.expand_path(TEMPLATES_SYSTEM_FOLDER)
47
- FileUtils.mkdir_p File.expand_path(TEMPLATES_SYSTEM_FOLDER)
48
- command = "git clone --depth 1 #{config.config['templates_url']} #{TEMPLATES_SYSTEM_FOLDER}"
49
- exec command
50
- end
51
-
52
- template = Generate::BaseTemplate.new_from_options(options, config)
53
-
54
- unless template.class.generamba?
55
- template.run
56
- exit
57
- end
58
-
59
- ramba_adapter = RambaAdapter.new(template, config)
60
- ramba_adapter.create_rambafile
61
- ramba_adapter.regenerate_default_template
62
-
63
- cli_command = ramba_adapter.generamba_gen_command(options)
64
- cli_command << " && #{__FILE__} --cleanup"
65
- cli_command << " && xc-resave #{config.xcode_project_path}"
66
-
67
- if options[:verbose]
68
- puts "#{cli_command}"
69
- end
70
-
71
- exec "#{cli_command}"
11
+ if system "which xc-resave >> /dev/null"
12
+ # exec "xc-resave #{$config.xcode_project_path}"
13
+ else
14
+ puts "\nWarning: xc-resave utility not installed. Run: \n\nbrew install alexgarbarev/core/xc-resave\n\n"
15
+ end
data/lib/cli/cli.rb ADDED
@@ -0,0 +1,138 @@
1
+ require 'thor'
2
+
3
+ module Gena
4
+
5
+ class Application < Thor
6
+
7
+ class << self
8
+
9
+ # class_for_command - hash to store custom classes (actually Plugin subclasses) for each
10
+ # command registered with gena
11
+ def class_for_command
12
+ @class_for_command ||= Hash.new
13
+ end
14
+
15
+ def class_for_command=(commands)
16
+ @class_for_command = commands
17
+ end
18
+
19
+ def plugin_classes
20
+ class_for_command.values.uniq
21
+ end
22
+
23
+ # Override help to forward
24
+
25
+ def help(shell, subcommand = false)
26
+
27
+ #List plugin commands separately from Gena general commands
28
+ plugins = []
29
+ class_for_command.each do |command, klass|
30
+ plugins += klass.printable_commands(false)
31
+ end
32
+
33
+ plugins.uniq!
34
+
35
+ list = printable_commands(true, subcommand)
36
+ Thor::Util.thor_classes_in(self).each do |klass|
37
+ list += klass.printable_commands(false)
38
+ end
39
+
40
+ list -= plugins
41
+
42
+ # Remove this line to disable alphabetical sorting
43
+ # list.sort! { |a, b| a[0] <=> b[0] }
44
+
45
+ # Add this line to remove the help-command itself from the output
46
+ # list.reject! {|l| l[0].split[1] == 'help'}
47
+
48
+ if defined?(@package_name) && @package_name
49
+ shell.say "#{@package_name} commands:"
50
+ else
51
+ shell.say "General commands:"
52
+ end
53
+
54
+ shell.print_table(list, :indent => 2, :truncate => true)
55
+ shell.say
56
+ class_options_help(shell)
57
+
58
+ shell.say "Plugins:"
59
+ shell.print_table(plugins, :indent => 2, :truncate => true)
60
+ end
61
+
62
+
63
+ # Override start to do custom dispatch (looking for plugin for unknown command)
64
+
65
+ def start(given_args = ARGV, config = {})
66
+
67
+ config[:shell] ||= Thor::Base.shell.new
68
+
69
+ command_name = normalize_command_name(retrieve_command_name(given_args.dup))
70
+ clazz = command_name ? class_for_command[command_name] : nil
71
+
72
+ if command_name && clazz
73
+ clazz.dispatch(nil, given_args.dup, nil, config)
74
+ else
75
+ dispatch(nil, given_args.dup, nil, config)
76
+ end
77
+
78
+ finish
79
+
80
+ rescue Thor::Error => e
81
+ config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
82
+ exit(1) if exit_on_failure?
83
+ rescue Errno::EPIPE
84
+ # This happens if a thor command is piped to something like `head`,
85
+ # which closes the pipe when it's done reading. This will also
86
+ # mean that if the pipe is closed, further unnecessary
87
+ # computation will not occur.
88
+ exit(0)
89
+ end
90
+
91
+ def finish
92
+ XcodeUtils.shared.save_project
93
+ Application.new.check_gena_version
94
+ end
95
+
96
+
97
+ end
98
+
99
+ no_tasks do
100
+
101
+
102
+ def check_gena_version
103
+ # Read cache
104
+ say "Checking for update.." if $verbose
105
+
106
+ version_path = File.expand_path "#{GENA_HOME}/version.plist"
107
+ plist = File.exists?(version_path) ? Plist::parse_xml(version_path) : Hash.new
108
+
109
+ data = nil
110
+
111
+ if !plist['timestamp'] || (DateTime.now.to_time.to_i - plist['timestamp'].to_i) > GENA_UPDATE_CHECK_INTERVAL
112
+ last_release_text = `curl https://api.github.com/repos/alexgarbarev/gena/releases/latest -s`
113
+ data = JSON.parse(last_release_text)
114
+
115
+ plist['data'] = data
116
+ plist['timestamp'] = DateTime.now.to_time.to_i
117
+ File.open(version_path, 'w') { |f| f.write plist.to_plist }
118
+ else
119
+ data = plist['data']
120
+ end
121
+
122
+ tag_name = data['tag_name']
123
+
124
+ if tag_name > VERSION
125
+ say "New update v#{tag_name} is available for gena.\nSee release notes: #{data['url']}", Color::YELLOW
126
+ say "Please update by:\n#{set_color('gem install gena', Color::GREEN)}", Color::YELLOW
127
+ end
128
+
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ end
135
+
136
+
137
+
138
+
data/lib/cli/init.rb ADDED
@@ -0,0 +1,273 @@
1
+ module Gena
2
+
3
+ class Application < Thor
4
+
5
+ desc 'init', 'Initialize gena.plist with default parameters'
6
+
7
+ def init
8
+
9
+ xcode_project_name = `find . -name *.xcodeproj`
10
+ xcode_project_name.strip!
11
+
12
+ xcode_project_name = ask_with_default("Enter path for #{set_color('project', Color::YELLOW)} or ENTER to continue (#{xcode_project_name}):", xcode_project_name)
13
+
14
+ xcode_project = Xcodeproj::Project.open(xcode_project_name)
15
+
16
+
17
+ main_target = nil
18
+ test_target = nil
19
+ xcode_project.native_targets.each do |target|
20
+ if target.product_type == 'com.apple.product-type.application'
21
+ main_target = target
22
+ elsif target.product_type == 'com.apple.product-type.bundle.unit-test'
23
+ test_target = target
24
+ end
25
+ end
26
+
27
+ sources_path = common_path_in_target(main_target, 'main.m')
28
+ tests_path = common_path_in_target(test_target, "#{sources_path}/")
29
+
30
+ sources_path = relative_to_current_dir(sources_path)
31
+ tests_path = relative_to_current_dir(tests_path)
32
+
33
+ hash = Hash.new
34
+ hash[:plugins_url] = [
35
+ 'https://github.com/alexgarbarev/gena-plugins.git'
36
+ ]
37
+
38
+ default_build_configuration = main_target.build_configuration_list.default_configuration_name || 'Debug'
39
+ info_plist_value = main_target.build_configuration_list.get_setting('INFOPLIST_FILE')[default_build_configuration]
40
+ if info_plist_value['$(SRCROOT)/']
41
+ info_plist_value['$(SRCROOT)/'] = ''
42
+ end
43
+
44
+ hash['company'] = xcode_project.root_object.attributes['ORGANIZATIONNAME'].to_s
45
+ hash['prefix'] = xcode_project.root_object.attributes['CLASSPREFIX'].to_s
46
+ hash['project_name'] = xcode_project.root_object.name
47
+ hash['project_target'] = main_target.name
48
+ hash['test_target'] = test_target.name
49
+ hash['info_plist'] = info_plist_value
50
+ hash['sources_dir'] = ask_with_default("Enter path for #{set_color('sources', Color::YELLOW)} or ENTER to continue (#{sources_path}):", sources_path)
51
+ hash['tests_dir'] = ask_with_default("Enter path for #{set_color('tests', Color::YELLOW)} or ENTER to continue (#{tests_path}):", tests_path)
52
+
53
+ say '===============================================================', Color::YELLOW
54
+ print_table(hash)
55
+ say '===============================================================', Color::YELLOW
56
+
57
+ File.open('gena.plist', 'w') { |f| f.write hash.to_plist }
58
+
59
+
60
+ say "'gena.plist' created..", Color::GREEN
61
+ end
62
+
63
+ no_tasks do
64
+
65
+ @downloaded_urls = Hash.new
66
+
67
+
68
+ def check
69
+
70
+ $verbose = (ARGV.include? '-v')
71
+ if $verbose
72
+ ARGV.reject! { |n| n == '-v' || n == '--verbose' }
73
+ end
74
+
75
+ unless Config.exists?
76
+ if ARGV == ['init']
77
+ init
78
+ ARGV.replace([])
79
+ else
80
+ if yes? "'gena.plist' is not exists. Do you want to create new one? (Y/n)", Color::YELLOW
81
+ init
82
+ end
83
+ end
84
+ end
85
+
86
+ $config = Gena::Config.new
87
+ $config.load_plist_config
88
+
89
+ unless $config.data
90
+ say '\'gena.plist\' is corrupted! Try recreating', Color::RED
91
+ abort
92
+ end
93
+
94
+ if !$config.data['plugins_url'] || $config.data['plugins_url'].count == 0
95
+ say "'plugins_url' key is missing inside 'gena.plist'", Color::RED
96
+ abort
97
+ end
98
+ download_plugins
99
+ load_plugins
100
+ save_plugin_configs
101
+ end
102
+
103
+ def download_plugins
104
+ load_downloaded_urls
105
+ $config.data['plugins_url'].each do |plugin_url|
106
+ if remote_url? plugin_url
107
+ is_old = old_url?(plugin_url)
108
+ if !downloaded_url?(plugin_url) || is_old
109
+ message = is_old ? 'not up to date' : 'not downloaded yet'
110
+ say "Plugin at '#{plugin_url}' #{message}", Color::YELLOW
111
+ download_plugin_at plugin_url
112
+ end
113
+ end
114
+ end
115
+ save_downloaded_urls
116
+ end
117
+
118
+ def remote_url?(url)
119
+ url =~ /(.+@)*([\w\d\.]+):(.*)/
120
+ end
121
+
122
+ def downloaded_url?(url)
123
+ File.exists? File.expand_path(download_path_for(url))
124
+ end
125
+
126
+ def old_url?(url)
127
+ entity = @downloaded_urls[key_for_url(url)]
128
+ return true unless entity
129
+ elapsed_seconds = DateTime.now.to_time.to_i - entity['timestamp'].to_i
130
+ if elapsed_seconds > GENA_UPDATE_CHECK_INTERVAL
131
+ current_hash = `git ls-remote #{url} refs/heads/master | cut -f 1`.strip
132
+ entity['timestamp'] = DateTime.now.to_time.to_i
133
+ return current_hash != entity['hash']
134
+ end
135
+ false
136
+ end
137
+
138
+ def load_downloaded_urls
139
+ plist_path = File.expand_path "#{GENA_HOME}/plugins.plist"
140
+ if File.exists? plist_path
141
+ @downloaded_urls = Plist::parse_xml(plist_path)
142
+ else
143
+ @downloaded_urls = Hash.new
144
+ end
145
+ end
146
+
147
+ def save_downloaded_urls
148
+ File.open(File.expand_path("#{GENA_HOME}/plugins.plist"), 'w') { |f| f.write @downloaded_urls.to_plist }
149
+ end
150
+
151
+ def key_for_url(url)
152
+ Digest::MD5.hexdigest url
153
+ end
154
+
155
+ def download_path_for(url)
156
+ hash = Digest::MD5.hexdigest url
157
+ File.expand_path "#{GENA_HOME}/plugins/#{hash}"
158
+ end
159
+
160
+ def download_plugin_at(url)
161
+ say "Downloading plugin from '#{url}'..", Color::GREEN, ' '
162
+ output_path = File.expand_path(download_path_for(url))
163
+ FileUtils.rmtree output_path
164
+ FileUtils.mkdir_p output_path
165
+ result = gena_system "git clone --depth 1 #{url} #{output_path}"
166
+ if result
167
+ say 'success!', Color::GREEN
168
+ @downloaded_urls[key_for_url(url)] = {
169
+ 'hash' => `git ls-remote #{url} refs/heads/master | cut -f 1`.strip,
170
+ 'timestamp' => DateTime.now.to_time.to_i
171
+ }
172
+
173
+ else
174
+ say 'Failed! Run with \'-v\' to debug', Color::RED
175
+ end
176
+ end
177
+
178
+ def load_plugins
179
+ registered = []
180
+ $config.data['plugins_url'].each do |plugin_url|
181
+ path = remote_url?(plugin_url) ? download_path_for(plugin_url) : plugin_url
182
+ Dir["#{File.expand_path(path)}/**/*.rb"].each do |file|
183
+ template_name = file.split(File::SEPARATOR)[-2]
184
+ unless registered.include? template_name
185
+ registered << template_name
186
+ say "Loading '#{file}'..", Color::YELLOW if $verbose
187
+ require file
188
+ end
189
+ end
190
+ end
191
+ Gena::Plugin.descendants.each do |clazz|
192
+ clazz.setup_thor_commands
193
+ end
194
+ end
195
+
196
+ def save_plugin_configs
197
+
198
+ data = $config.data[GENA_PLUGINS_CONFIG_KEY] || Hash.new
199
+
200
+ Application.plugin_classes.each do |klass|
201
+
202
+ defaults = klass.plugin_config_defaults
203
+
204
+ if defaults.count > 0
205
+ plugin_config_name = klass.plugin_config_name
206
+
207
+ if !data[plugin_config_name] || data[plugin_config_name].count == 0
208
+ say "Writing config defaults for plugin '#{plugin_config_name}'..", Color::GREEN
209
+ data[plugin_config_name] = defaults
210
+ elsif !data[plugin_config_name].keys.to_set.eql? defaults.keys.to_set
211
+ missing_keys = defaults.keys - data[plugin_config_name].keys
212
+ say "Adding missing config keys #{missing_keys} for plugin '#{plugin_config_name}.'", Color::GREEN
213
+ missing_keys.each { |key| data[plugin_config_name][key] = defaults[key] }
214
+ end
215
+
216
+ end
217
+
218
+ end
219
+
220
+ $config.data[GENA_PLUGINS_CONFIG_KEY] = data
221
+ $config.save!
222
+
223
+ end
224
+
225
+ private
226
+
227
+ def ask_with_default(message, default)
228
+ value = ask(message)
229
+ if value.empty?
230
+ value = default
231
+ end
232
+ value
233
+ end
234
+
235
+ def relative_to_current_dir(path)
236
+
237
+ if path.empty? || !path["#{Dir.pwd}/"]
238
+ path
239
+ else
240
+ result = path.dup
241
+ result["#{Dir.pwd}/"] = ''
242
+ result
243
+ end
244
+ end
245
+
246
+ def common_path_in_target(target, except_match)
247
+ common = ''
248
+ target.source_build_phase.files.each do |file|
249
+ unless file.file_ref.real_path.to_s[except_match]
250
+ path_components = file.file_ref.real_path.to_s #.split('/')
251
+ if common.empty?
252
+ common = path_components
253
+ else
254
+ common = common.path_intersection path_components
255
+ end
256
+ end
257
+ end
258
+
259
+ if File.file? common
260
+ common = common.delete_last_path_component
261
+ end
262
+ if common[-1] == '/'
263
+ common = common[0..-2]
264
+ end
265
+ common
266
+ end
267
+
268
+ end
269
+
270
+ end
271
+
272
+
273
+ end
@@ -0,0 +1,176 @@
1
+
2
+
3
+ module Gena
4
+
5
+ module Filetype
6
+ class Context
7
+ attr_accessor :target_key, :base_dir_key, :is_resource
8
+
9
+ def initialize(target, base_dir, is_resource)
10
+ @target_key = target
11
+ @base_dir_key = base_dir
12
+ @is_resource = is_resource
13
+ end
14
+ end
15
+ SOURCE = Context.new('project_target', 'sources_dir', false)
16
+ RESOURCE = Context.new('project_target', 'sources_dir', true)
17
+ TEST_SOURCE = Context.new('test_target', 'tests_dir', false)
18
+ TEST_RESOURCE = Context.new('test_target', 'tests_dir', true)
19
+ end
20
+
21
+ class Codegen < Thor
22
+
23
+ no_tasks do
24
+
25
+
26
+ def initialize(output_path, template_params)
27
+
28
+ @output_path = output_path
29
+ @template_params = template_params
30
+
31
+ end
32
+
33
+
34
+ def add_file(template_name, file_name, type, params = nil)
35
+
36
+ # Getting path for template
37
+ plugin_dir = File.dirname(caller.first.scan(/.*rb/).first)
38
+ template_path = absolute_path_for_template(template_name, plugin_dir)
39
+
40
+ # Output path
41
+ output_path = absolute_path_for_output(file_name, type)
42
+
43
+ # Params
44
+ template_params = @template_params.merge($config.data_without_plugins)
45
+ template_params = params.merge(template_params) if params
46
+
47
+ render_template_to_file(template_path, output_path, template_params)
48
+
49
+ add_file_to_project(output_path, type)
50
+
51
+ end
52
+
53
+ def render_template(template_name, params)
54
+
55
+ plugin_dir = File.dirname(caller.first.scan(/.*rb/).first)
56
+ template_path = absolute_path_for_template(template_name, plugin_dir)
57
+
58
+ render_template_from_path(template_path, params)
59
+
60
+ end
61
+
62
+ def render_template_to_file(template_name, output_path, params)
63
+
64
+ plugin_dir = File.dirname(caller.first.scan(/.*rb/).first)
65
+ template_path = absolute_path_for_template(template_name, plugin_dir)
66
+
67
+ render_template_from_path_to_file(template_path, output_path, params)
68
+ end
69
+
70
+ def add_file_to_project(output_path, type)
71
+
72
+ target_name = $config.data[type.target_key]
73
+
74
+ target = XcodeUtils.shared.obtain_target(target_name)
75
+
76
+ dirname = File.dirname(output_path)
77
+
78
+ group = XcodeUtils.shared.make_group(dirname, dirname)
79
+
80
+ XcodeUtils.shared.add_file(target, group, output_path, type.is_resource)
81
+ end
82
+
83
+ def remove_from_project(path)
84
+
85
+ XcodeUtils.shared.delete_path(path)
86
+
87
+ end
88
+
89
+ private
90
+
91
+
92
+ def render_template_from_path(template_path, params)
93
+
94
+ if $config.header_dir.empty?
95
+ say "No 'header' field inside 'gena.plist'. You can specify path to header's liquid template there. Using default header", Color::YELLOW if $verbose
96
+ Liquid::Template.file_system = GenaStaticHeader.new
97
+ else
98
+ Liquid::Template.file_system = Liquid::LocalFileSystem.new($config.header_dir, '%s.liquid')
99
+ end
100
+
101
+ file_source = IO.read(template_path)
102
+ template = Liquid::Template.parse(file_source)
103
+
104
+ template.render(params)
105
+ end
106
+
107
+ def render_template_from_path_to_file(template_path, output_path, params)
108
+
109
+ content = render_template(template_path, params)
110
+
111
+ FileUtils.mkpath(File.dirname(output_path))
112
+
113
+ say "Writing to file: #{output_path}", Color::GREEN
114
+ File.open(output_path, 'w+') do |f|
115
+ f.write(content)
116
+ end
117
+ end
118
+
119
+ def absolute_path_for_output(file_name, type)
120
+ # Output path
121
+ base_dir = $config.expand_to_project($config.data[type.base_dir_key])
122
+
123
+ if @output_path[0] == '/'
124
+ puts 'Output path is absolute' if $verbose
125
+ output_path = File.join(@output_path, file_name)
126
+ else
127
+ puts 'Output path is relative' if $verbose
128
+ output_path = File.join(base_dir, @output_path, file_name)
129
+ end
130
+ output_path
131
+ end
132
+
133
+ def absolute_path_for_template(template_path, plugin_dir)
134
+ result = ''
135
+
136
+ expanded = File.expand_path(template_path)
137
+ if File.exists? expanded
138
+ result = expanded
139
+ else
140
+ joined = File.join(plugin_dir, template_path)
141
+ if File.exists? joined
142
+ result = joined
143
+ end
144
+ end
145
+
146
+ if !result.empty?
147
+ say "Found template at path '#{result}'", Color::YELLOW if $verbose
148
+ else
149
+ say "Can't find path for template '#{template_path}'", Color::RED
150
+ abort
151
+ end
152
+ result
153
+ end
154
+
155
+ end
156
+
157
+ end
158
+
159
+ class GenaStaticHeader < Liquid::BlankFileSystem
160
+
161
+ def read_template_file(name)
162
+
163
+ if name == 'header'
164
+ '////////////////////////////////////////////////////////////////////////////////
165
+ //
166
+ // Generated by Gena.
167
+ //
168
+ ////////////////////////////////////////////////////////////////////////////////'
169
+ else
170
+ ''
171
+ end
172
+ end
173
+
174
+ end
175
+
176
+ end