bake-toolkit 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ require 'bake/model/metamodel'
2
+ require 'bake/model/language'
3
+ require 'bake/version'
4
+
5
+ require 'rgen/environment'
6
+ require 'rgen/fragment/dump_file_cache'
7
+ require 'rgen/fragment/fragmented_model'
8
+ require 'rgen/util/file_cache_map'
9
+
10
+ require 'rtext/default_loader'
11
+
12
+ require 'cxxproject/utils/exit_helper'
13
+ require 'cxxproject/utils/printer'
14
+ require 'bake/options'
15
+
16
+ module Cxxproject
17
+
18
+ class Loader
19
+
20
+ attr_reader :model
21
+
22
+ def initialize(options)
23
+ @env = RGen::Environment.new
24
+ @options = options
25
+
26
+ fcm = RGen::Util::FileCacheMap.new(".bake", ".cache")
27
+ fcm.version_info = Version.bake
28
+ @DumpFileCache = RGen::Fragment::DumpFileCache.new(fcm)
29
+ if @options.nocache
30
+ def @DumpFileCache.load(fragment)
31
+ :invalid
32
+ end
33
+ end
34
+
35
+ @model = RGen::Fragment::FragmentedModel.new(:env => @env)
36
+ @mainProjectName = File::basename(@options.main_dir)
37
+ end
38
+
39
+ def load(filename)
40
+
41
+ sumErrors = 0
42
+
43
+ if not File.exists?filename
44
+ Printer.printError "Error: #{filename} does not exist"
45
+ ExitHelper.exit(1)
46
+ end
47
+
48
+ loader = RText::DefaultLoader.new(
49
+ Cxxproject::Language,
50
+ @model,
51
+ :file_provider => proc { [filename] },
52
+ :cache => @DumpFileCache)
53
+ loader.load(:before_load => proc {|fragment, kind|
54
+ case kind
55
+ when :load_update_cache
56
+ if @options.verbose
57
+ puts "Loading and caching #{fragment.location}"
58
+ else
59
+ puts "Loading #{fragment.location}"
60
+ end
61
+ when :load_cached
62
+ if @options.verbose
63
+ puts "Loading cached #{fragment.location}"
64
+ else
65
+ puts "Loading #{fragment.location}"
66
+ end
67
+ when :load
68
+ puts "Loading #{fragment.location}"
69
+ else
70
+ Printer.printError "Error: Could not load #{fragment.location}"
71
+ ExitHelper.exit(1)
72
+ end
73
+ })
74
+
75
+ f = @model.fragments[0]
76
+ @model.remove_fragment(f)
77
+
78
+ f.data[:problems].each do |p|
79
+ Printer.printError "Error: "+p.file+"("+p.line.to_s+"): "+p.message
80
+ end
81
+
82
+ if f.data[:problems].length > 0
83
+ ExitHelper.exit(1)
84
+ end
85
+
86
+ return f
87
+
88
+ end
89
+
90
+
91
+ end
92
+ end
@@ -0,0 +1,46 @@
1
+ require 'bake/model/metamodel'
2
+
3
+ require 'rtext/language'
4
+
5
+
6
+
7
+ module Cxxproject
8
+
9
+ class Idp
10
+ def call(e,unused)
11
+ e.respond_to?(:ident) ? e.ident() : nil # IdentifierProvider.qualified_name(e)
12
+ end
13
+ end
14
+
15
+
16
+ Language =
17
+ RText::Language.new(Metamodel.ecore,
18
+ :feature_provider => proc {|c|
19
+ RGen::Serializer::OppositeReferenceFilter.call(c.eAllStructuralFeatures).reject {|f|
20
+ f.eAnnotations.any? {|a|
21
+ a.details.any? {|d| d.key == 'internal' && d.value == 'true'}
22
+ }
23
+ }
24
+ },
25
+ :unlabled_arguments => proc {|c|
26
+ if c.name == "Compiler" or c.name == "CompilerAdaptions"
27
+ ["ctype"]
28
+ elsif c.name == "Define"
29
+ ["str"]
30
+ elsif c.name == "Flags" or c.name == "LibPostfixFlags" or c.name == "LibPrefixFlags"
31
+ ["overwrite"]
32
+ elsif c.name == "UserLibrary"
33
+ ["lib"]
34
+ elsif c.name == "DefaultToolchain"
35
+ ["basedOn"]
36
+ else
37
+ ["name"]
38
+ end
39
+ },
40
+ :identifier_provider => Idp.new,
41
+ :line_number_attribute => "line_number",
42
+ :file_name_attribute => "file_name",
43
+ :fragment_ref_attribute => "fragment_ref"#,
44
+ )
45
+
46
+ end
@@ -0,0 +1,226 @@
1
+ require 'rgen/metamodel_builder'
2
+ require 'rgen/metamodel_builder/data_types'
3
+
4
+ module Cxxproject
5
+
6
+ module Metamodel
7
+ extend RGen::MetamodelBuilder::ModuleExtension
8
+
9
+ class ModelElement < RGen::MetamodelBuilder::MMBase
10
+ abstract
11
+ has_attr 'line_number', Integer do
12
+ annotation :details => {'internal' => 'true'}
13
+ end
14
+ has_attr 'file_name', String do
15
+ annotation :details => {'internal' => 'true'}
16
+ end
17
+ module ClassModule
18
+ attr_accessor :fragment_ref
19
+
20
+ def id
21
+ splitted = file_name.split("/")
22
+ splitted[splitted.length-2]
23
+ end
24
+
25
+ end
26
+ end
27
+
28
+ CompilerType = RGen::MetamodelBuilder::DataTypes::Enum.new([:CPP, :C, :ASM])
29
+
30
+ class Flags < ModelElement
31
+ has_attr 'overwrite', String, :defaultValueLiteral => ""
32
+ has_attr 'add', String, :defaultValueLiteral => ""
33
+ has_attr 'remove', String, :defaultValueLiteral => ""
34
+ end
35
+ class LibPrefixFlags < ModelElement
36
+ has_attr 'overwrite', String, :defaultValueLiteral => ""
37
+ has_attr 'add', String, :defaultValueLiteral => ""
38
+ has_attr 'remove', String, :defaultValueLiteral => ""
39
+ end
40
+ class LibPostfixFlags < ModelElement
41
+ has_attr 'overwrite', String, :defaultValueLiteral => ""
42
+ has_attr 'add', String, :defaultValueLiteral => ""
43
+ has_attr 'remove', String, :defaultValueLiteral => ""
44
+ end
45
+ class Define < ModelElement
46
+ has_attr 'str', String, :defaultValueLiteral => ""
47
+ end
48
+
49
+
50
+ class Archiver < ModelElement
51
+ has_attr 'command', String, :defaultValueLiteral => ""
52
+ contains_many 'flags', Flags, 'parent'
53
+ end
54
+
55
+ class Linker < ModelElement
56
+ has_attr 'command', String, :defaultValueLiteral => ""
57
+ contains_many 'flags', Flags, 'parent'
58
+ contains_many 'libprefixflags', LibPrefixFlags, 'parent'
59
+ contains_many 'libpostfixflags', LibPostfixFlags, 'parent'
60
+ end
61
+
62
+ class Compiler < ModelElement
63
+ has_attr 'ctype', CompilerType
64
+ has_attr 'command', String, :defaultValueLiteral => ""
65
+ contains_many 'define', Define, 'parent'
66
+ contains_many 'flags', Flags, 'parent'
67
+ end
68
+
69
+ class DefaultToolchain < ModelElement
70
+ has_attr 'basedOn', String, :defaultValueLiteral => ""
71
+ contains_many 'compiler', Compiler, 'parent'
72
+ contains_one 'archiver', Archiver, 'parent'
73
+ contains_one 'linker', Linker, 'parent'
74
+ end
75
+
76
+ class Toolchain < ModelElement
77
+ contains_many 'compiler', Compiler, 'parent'
78
+ contains_one 'archiver', Archiver, 'parent'
79
+ contains_one 'linker', Linker, 'parent'
80
+ end
81
+
82
+ class Person < ModelElement
83
+ has_attr 'name', String, :defaultValueLiteral => ""
84
+ has_attr 'email', String, :defaultValueLiteral => ""
85
+ end
86
+
87
+ class Responsible < ModelElement
88
+ contains_many "person", Person, 'parent'
89
+ end
90
+
91
+ class Files < ModelElement
92
+ has_attr 'name', String, :defaultValueLiteral => ""
93
+ contains_many 'define', Define, 'parent'
94
+ contains_many 'flags', Flags, 'parent'
95
+ end
96
+
97
+ class ExcludeFiles < ModelElement
98
+ has_attr 'name', String, :defaultValueLiteral => ""
99
+ end
100
+
101
+ class IncludeDir < ModelElement
102
+ has_attr 'name', String, :defaultValueLiteral => ""
103
+ end
104
+
105
+ class ExternalLibrary < ModelElement
106
+ has_attr 'name', String, :defaultValueLiteral => ""
107
+ has_attr 'search', Boolean, :defaultValueLiteral => "true"
108
+ end
109
+
110
+ class ExternalLibrarySearchPath < ModelElement
111
+ has_attr 'name', String, :defaultValueLiteral => ""
112
+ end
113
+
114
+ class Step < ModelElement
115
+ has_attr 'name', String, :defaultValueLiteral => ""
116
+ has_attr 'default', String, :defaultValueLiteral => "on"
117
+ has_attr 'filter', String, :defaultValueLiteral => ""
118
+ end
119
+
120
+ class Makefile < Step
121
+ has_attr 'lib', String, :defaultValueLiteral => ""
122
+ has_attr 'target', String, :defaultValueLiteral => ""
123
+ has_attr 'pathTo', String, :defaultValueLiteral => ""
124
+ has_attr 'flags', String, :defaultValueLiteral => ""
125
+ end
126
+
127
+ class CommandLine < Step
128
+ end
129
+
130
+ class PreSteps < ModelElement
131
+ contains_many 'step', Step, 'parent'
132
+ end
133
+
134
+ class PostSteps < ModelElement
135
+ contains_many 'step', Step, 'parent'
136
+ end
137
+
138
+ class UserLibrary < ModelElement
139
+ has_attr 'lib', String, :defaultValueLiteral => ""
140
+ end
141
+
142
+ class LinkerScript < ModelElement
143
+ has_attr 'name', String, :defaultValueLiteral => ""
144
+ end
145
+
146
+ class MapFile < ModelElement
147
+ has_attr 'name', String, :defaultValueLiteral => ""
148
+ end
149
+
150
+ class ArtifactName < ModelElement
151
+ has_attr 'name', String, :defaultValueLiteral => ""
152
+ end
153
+
154
+ class BaseConfig_INTERNAL < ModelElement
155
+ has_attr 'name', String, :defaultValueLiteral => ""
156
+ contains_one 'preSteps', PreSteps, 'parent'
157
+ contains_one 'postSteps', PostSteps, 'parent'
158
+ contains_many 'userLibrary', UserLibrary, 'parent'
159
+ contains_many 'exLib', ExternalLibrary, 'parent'
160
+ contains_many 'exLibSearchPath', ExternalLibrarySearchPath, 'parent'
161
+ contains_one 'defaultToolchain', DefaultToolchain, 'parent'
162
+
163
+ module ClassModule
164
+ def ident
165
+ s = file_name.split("/")
166
+ s[s.length-2] + "/" + name
167
+ end
168
+ end
169
+
170
+ end
171
+
172
+ class BuildConfig_INTERNAL < BaseConfig_INTERNAL
173
+ contains_many 'files', Files, 'parent'
174
+ contains_many 'excludeFiles', ExcludeFiles, 'parent'
175
+ contains_many 'includeDir', IncludeDir, 'parent'
176
+ contains_one 'toolchain', Toolchain, 'parent'
177
+
178
+ module ClassModule
179
+ def ident
180
+ s = file_name.split("/")
181
+ s[s.length-2] + "/" + name
182
+ end
183
+ end
184
+
185
+ end
186
+
187
+ class ExecutableConfig < BuildConfig_INTERNAL
188
+ contains_one 'linkerScript', LinkerScript, 'parent'
189
+ contains_one 'artifactName', ArtifactName, 'parent'
190
+ contains_one 'mapFile', MapFile, 'parent'
191
+ end
192
+
193
+ class LibraryConfig < BuildConfig_INTERNAL
194
+ end
195
+
196
+ class CustomConfig < BaseConfig_INTERNAL
197
+ contains_one 'step', Step, 'parent'
198
+ end
199
+
200
+ class Project < ModelElement
201
+ #has_attr 'name', String do
202
+ # annotation :details => {'internal' => 'true'}
203
+ #end
204
+ contains_one 'responsible', Responsible, 'parent'
205
+ contains_many 'config', BaseConfig_INTERNAL, 'parent'
206
+
207
+ module ClassModule
208
+ def name
209
+ splitted = file_name.split("/")
210
+ x = splitted[splitted.length-2]
211
+ x
212
+ end
213
+ end
214
+
215
+ end
216
+
217
+ class Dependency < ModelElement
218
+ has_attr 'name', String, :defaultValueLiteral => ""
219
+ has_attr 'config', String, :defaultValueLiteral => ""
220
+ end
221
+
222
+ BaseConfig_INTERNAL.contains_many 'dependency', Dependency, 'parent'
223
+
224
+ end
225
+
226
+ end
@@ -0,0 +1,15 @@
1
+ require 'bake/model/metamodel'
2
+ require 'cxxproject/ext/file'
3
+
4
+ module Cxxproject
5
+ module Metamodel
6
+
7
+ module Project::ClassModule
8
+ def get_project_dir
9
+ # todo: no. must be set from outside
10
+ ::File.dirname(file_name)
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,288 @@
1
+ require 'cxxproject/ext/rake'
2
+ require 'cxxproject/utils/printer'
3
+ require "cxxproject/toolchain/colorizing_formatter"
4
+ require "option/parser"
5
+
6
+ module Cxxproject
7
+
8
+ class Options < Parser
9
+ attr_reader :build_config, :main_dir, :project, :filename, :eclipse_version, :alias_filename # String
10
+ attr_reader :roots, :include_filter, :exclude_filter # String List
11
+ attr_reader :clean, :rebuild, :single, :verbose, :nocache, :color, :show_includes, :linkOnly, :check_uninc, :printLess # Boolean
12
+ attr_reader :threads, :socket # Fixnum
13
+
14
+ def initialize(argv)
15
+ super(argv)
16
+
17
+ @build_config = ""
18
+ @main_dir = nil
19
+ @project = nil
20
+ @filename = nil
21
+ @single = false
22
+ @clean = false
23
+ @rebuild = false
24
+ @verbose = false
25
+ @nocache = false
26
+ @check_uninc = false
27
+ @color = false
28
+ @show_includes = false
29
+ @linkOnly = false
30
+ @printLess = false
31
+ @threads = 8
32
+ @roots = []
33
+ @socket = 0
34
+ @include_filter = []
35
+ @exclude_filter = []
36
+ @def_root = nil
37
+ @eclipse_version = ""
38
+ @alias_filename = ""
39
+
40
+ add_option(Option.new("-m",true) { |x| set_main_dir(x) })
41
+ add_option(Option.new("-b",true) { |x| set_build_config(x) })
42
+ add_option(Option.new("-p",true) { |x| set_project(x); set_single })
43
+ add_option(Option.new("-f",true) { |x| set_filename(x) })
44
+ add_option(Option.new("-c",false) { set_clean })
45
+ add_option(Option.new("-a",true) { |x| set_color(x) })
46
+ add_option(Option.new("-w",true) { |x| set_root(x) })
47
+ add_option(Option.new("-v",false) { set_verbose })
48
+ add_option(Option.new("-r",false) { set_error })
49
+ add_option(Option.new("--rebuild",false) { set_rebuild })
50
+ add_option(Option.new("--prepro",false) { set_prepro })
51
+ add_option(Option.new("--link_only",false) { set_linkOnly })
52
+ add_option(Option.new("--print_less",false) { set_printLess })
53
+ add_option(Option.new("--ignore_cache",false) { set_nocache })
54
+ add_option(Option.new("--threads",true) { |x| set_threads(x) })
55
+ add_option(Option.new("--socket",true) { |x| set_socket(x) })
56
+ add_option(Option.new("--toolchain_info",true) { |x| print_toolchain(x) })
57
+ add_option(Option.new("--available_toolchain",false) { print_toolchains })
58
+ add_option(Option.new("--include_filter",true) { |x| set_include_filter(x) })
59
+ add_option(Option.new("--exclude_filter",true) { |x| set_exclude_filter(x) })
60
+ add_option(Option.new("--show_abs_paths",false) { set_show_fullnames })
61
+ add_option(Option.new("-h",false) { usage; ExitHelper.exit(0) })
62
+ add_option(Option.new("--help",false) { usage; ExitHelper.exit(0) })
63
+ add_option(Option.new("--show_include_paths",false) { set_show_inc })
64
+ add_option(Option.new("--eclipse_version",true) { |x| set_eclipse_version(x) })
65
+ add_option(Option.new("--show_license",false) { show_license })
66
+ add_option(Option.new("--check_uninc",false) { set_check_uninc })
67
+ add_option(Option.new("--alias",true) { |x| set_alias_filename(x) })
68
+
69
+ end
70
+
71
+ def usage
72
+ puts "\nUsage: bake [options]"
73
+ puts " -m <dir> Directory of main project (default is current directory)."
74
+ puts " -b <name> Config name of main project"
75
+ puts " -p <dir> Project to build/clean (default is main project)"
76
+ puts " -f <name> Build/Clean this file only."
77
+ puts " -c Clean the file/project."
78
+ puts " -a <scheme> Use ansi color sequences (console must support it). Possible values are 'white' and 'black'."
79
+ puts " -v Verbose output."
80
+ puts " -r Stop on first error."
81
+ puts " -w <root> Add a workspace root (can be used multiple times)."
82
+ puts " If no root is specified, the parent directory of the main project is added automatically."
83
+ puts " --rebuild Clean before build."
84
+ puts " --prepro Stop after preprocessor."
85
+ puts " --link_only Only link executable - doesn't update objects and archives or start PreSteps and PostSteps"
86
+ puts " --print_less Some progression logs will be suppressed"
87
+ puts " --ignore_cache Rereads the original meta files - usefull if workspace structure has been changed."
88
+ puts " --check_uninc Checks for unnecessary includes (only done for successful project builds)."
89
+ puts " --threads <num> Set NUMBER of parallel compiled files (default is 8)."
90
+ puts " --socket <num> Set SOCKET for sending errors, receiving commands, etc. - used by e.g. Eclipse."
91
+ puts " --toolchain_info <name> Prints default values of a toolchain."
92
+ puts " --toolchain_names Prints available toolchains."
93
+ puts " --include_filter <name> Includes steps with this filter name (can be used multiple times)."
94
+ puts " 'PRE' or 'POST' includes all PreSteps respectively PostSteps."
95
+ puts " --exclude_filter <name> Excludes steps with this filter name (can be used multiple times)."
96
+ puts " 'PRE' or 'POST' excludes all PreSteps respectively PostSteps."
97
+ puts " --show_abs_paths Compiler prints absolute filename paths instead of relative paths."
98
+ puts ""
99
+ puts " -h, --help Print this help."
100
+ puts " --show_license Print the license."
101
+
102
+ end
103
+
104
+ def parse_options()
105
+ parse_internal(false)
106
+ set_main_dir(Dir.pwd) if @main_dir.nil?
107
+ set_project(File.basename(@main_dir)) if @project.nil?
108
+ @roots << @def_root if @roots.length == 0
109
+ Rake::application.max_parallel_tasks = @threads
110
+
111
+ if @linkOnly
112
+ if @rebuild
113
+ Printer.printError "Error: --link_only and --rebuild not allowed at the same time"
114
+ ExitHelper.exit(1)
115
+ end
116
+ if @clean
117
+ Printer.printError "Error: --link_only and -c not allowed at the same time"
118
+ ExitHelper.exit(1)
119
+ end
120
+ end
121
+ end
122
+
123
+ def check_valid_dir(dir)
124
+ if not File.exists?(dir)
125
+ Printer.printError "Error: Directory #{dir} does not exist"
126
+ ExitHelper.exit(1)
127
+ end
128
+ if not File.directory?(dir)
129
+ Printer.printError "Error: #{dir} is not a directory"
130
+ ExitHelper.exit(1)
131
+ end
132
+ end
133
+
134
+ def set_include_filter(x)
135
+ @include_filter << x
136
+ end
137
+
138
+ def set_exclude_filter(x)
139
+ @exclude_filter << x
140
+ end
141
+
142
+ def set_build_config(config)
143
+ @build_config = config
144
+ end
145
+
146
+ def set_main_dir(dir)
147
+ check_valid_dir(dir)
148
+ @main_dir = File.expand_path(dir.gsub(/[\\]/,'/'))
149
+ @def_root = File.dirname(@main_dir)
150
+ end
151
+
152
+ def set_project(name)
153
+ @project = name
154
+ end
155
+
156
+ def set_filename(filename)
157
+ @filename = filename.gsub(/[\\]/,'/')
158
+ end
159
+
160
+ def set_single()
161
+ @single = true
162
+ end
163
+ def set_clean()
164
+ @clean = true
165
+ end
166
+ def set_rebuild()
167
+ @clean = true
168
+ @rebuild = true
169
+ end
170
+ def set_verbose()
171
+ @verbose = true
172
+ end
173
+ def set_nocache()
174
+ @nocache = true
175
+ end
176
+ def set_check_uninc()
177
+ @check_uninc = true
178
+ end
179
+ def set_prepro()
180
+ Rake::application.preproFlags = true
181
+ end
182
+ def set_linkOnly()
183
+ @linkOnly = true
184
+ set_single()
185
+ end
186
+ def set_printLess()
187
+ @printLess = true
188
+ end
189
+ def set_color(x)
190
+ if (x != "black" and x != "white")
191
+ Printer.printError "Error: color scheme must be 'black' or 'white'"
192
+ ExitHelper.exit(1)
193
+ end
194
+ begin
195
+ ColorizingFormatter::setColorScheme(x.to_sym)
196
+ @color = true
197
+ ColorizingFormatter.enabled = true
198
+ rescue Exception => e
199
+ Printer.printError "Error: colored gem not installed (#{e.message})"
200
+ puts e.backtrace if @verbose
201
+ ExitHelper.exit(1)
202
+ end
203
+ end
204
+ def set_error()
205
+ Rake::Task.bail_on_first_error = true
206
+ end
207
+
208
+ def set_show_inc
209
+ @show_includes = true
210
+ end
211
+
212
+ def set_show_fullnames
213
+ Rake::application.consoleOutput_fullnames = true
214
+ end
215
+
216
+ def set_root(dir)
217
+ check_valid_dir(dir)
218
+ r = File.expand_path(dir.gsub(/[\\]/,'/'))
219
+ @roots << r if not @roots.include?r
220
+ end
221
+
222
+ def set_threads(num)
223
+ @threads = String === num ? num.to_i : num
224
+ if @threads <= 0
225
+ Printer.printError "Error: number of threads must be > 0"
226
+ ExitHelper.exit(1)
227
+ end
228
+ end
229
+ def set_socket(num)
230
+ @socket = String === num ? num.to_i : num
231
+ end
232
+
233
+ def printHash(x, level)
234
+ x.each do |k,v|
235
+ if Hash === v
236
+ if level > 0
237
+ level.times {print " "}
238
+ else
239
+ print "\n"
240
+ end
241
+ puts k
242
+ printHash(v,level+1)
243
+ elsif Array === v or String === v
244
+ level.times {print " "}
245
+ puts "#{k} = #{v}"
246
+ end
247
+ end
248
+ end
249
+
250
+ def print_toolchain(x)
251
+ tcs = Cxxproject::Toolchain::Provider[x]
252
+ if tcs.nil?
253
+ puts "Toolchain not available"
254
+ else
255
+ printHash(tcs, 0)
256
+ end
257
+ ExitHelper.exit(0)
258
+ end
259
+
260
+ def print_toolchains()
261
+ puts "Available toolchains:"
262
+ Cxxproject::Toolchain::Provider.list.keys.each { |c| puts "* #{c}" }
263
+ ExitHelper.exit(0)
264
+ end
265
+
266
+ def show_license()
267
+ licenseFile = File.join(File.dirname(File.dirname(File.dirname(__FILE__))), "license.txt")
268
+ file = File.new(licenseFile, "r")
269
+ while (line = file.gets)
270
+ puts "#{line}"
271
+ end
272
+ file.close
273
+ ExitHelper.exit(0)
274
+ end
275
+
276
+ def set_eclipse_version(x)
277
+ @eclipse_version = x
278
+ end
279
+
280
+ def set_alias_filename(x)
281
+ @alias_filename = x
282
+ end
283
+
284
+ end
285
+
286
+ end
287
+
288
+