warbler 1.2.1 → 1.3.0.beta1

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.
Files changed (65) hide show
  1. data/Gemfile +8 -5
  2. data/History.txt +5 -0
  3. data/Manifest.txt +43 -23
  4. data/README.txt +67 -26
  5. data/Rakefile +17 -6
  6. data/ext/JarMain.java +147 -0
  7. data/ext/{Main.java → WarMain.java} +4 -4
  8. data/ext/{WarblerWar.java → WarblerJar.java} +79 -13
  9. data/ext/{WarblerWarService.java → WarblerJarService.java} +2 -2
  10. data/lib/warbler/application.rb +9 -9
  11. data/lib/warbler/config.rb +58 -204
  12. data/lib/warbler/gems.rb +2 -2
  13. data/lib/warbler/jar.rb +247 -0
  14. data/lib/warbler/task.rb +28 -60
  15. data/lib/warbler/templates/config.erb +1 -1
  16. data/lib/warbler/templates/rack.erb +1 -1
  17. data/lib/warbler/templates/rails.erb +1 -1
  18. data/lib/warbler/traits/bundler.rb +58 -0
  19. data/lib/warbler/traits/gemspec.rb +64 -0
  20. data/lib/warbler/traits/jar.rb +53 -0
  21. data/lib/warbler/traits/merb.rb +34 -0
  22. data/lib/warbler/traits/nogemspec.rb +41 -0
  23. data/lib/warbler/traits/rack.rb +31 -0
  24. data/lib/warbler/traits/rails.rb +57 -0
  25. data/lib/warbler/traits/war.rb +191 -0
  26. data/lib/warbler/traits.rb +105 -0
  27. data/lib/warbler/version.rb +1 -1
  28. data/lib/warbler/war.rb +1 -247
  29. data/lib/warbler.rb +2 -5
  30. data/lib/warbler_jar.jar +0 -0
  31. data/spec/sample_jar/History.txt +6 -0
  32. data/spec/sample_jar/Manifest.txt +8 -0
  33. data/spec/sample_jar/README.txt +30 -0
  34. data/spec/sample_jar/lib/sample_jar.rb +6 -0
  35. data/spec/sample_jar/sample_jar.gemspec +41 -0
  36. data/spec/sample_jar/test/test_sample_jar.rb +8 -0
  37. data/spec/{sample → sample_war}/app/controllers/application.rb +0 -0
  38. data/spec/{sample → sample_war}/app/helpers/application_helper.rb +0 -0
  39. data/spec/{sample → sample_war}/config/boot.rb +0 -0
  40. data/spec/{sample → sample_war}/config/database.yml +0 -0
  41. data/spec/{sample → sample_war}/config/environment.rb +0 -0
  42. data/spec/{sample → sample_war}/config/environments/development.rb +0 -0
  43. data/spec/{sample → sample_war}/config/environments/production.rb +0 -0
  44. data/spec/{sample → sample_war}/config/environments/test.rb +0 -0
  45. data/spec/{sample → sample_war}/config/initializers/inflections.rb +0 -0
  46. data/spec/{sample → sample_war}/config/initializers/mime_types.rb +0 -0
  47. data/spec/{sample → sample_war}/config/initializers/new_rails_defaults.rb +0 -0
  48. data/spec/{sample → sample_war}/config/routes.rb +0 -0
  49. data/spec/{sample → sample_war}/lib/tasks/utils.rake +0 -0
  50. data/spec/{sample → sample_war}/public/404.html +0 -0
  51. data/spec/{sample → sample_war}/public/422.html +0 -0
  52. data/spec/{sample → sample_war}/public/500.html +0 -0
  53. data/spec/{sample → sample_war}/public/favicon.ico +0 -0
  54. data/spec/{sample → sample_war}/public/index.html +0 -0
  55. data/spec/{sample → sample_war}/public/robots.txt +0 -0
  56. data/spec/spec_helper.rb +40 -0
  57. data/spec/warbler/application_spec.rb +2 -9
  58. data/spec/warbler/config_spec.rb +101 -83
  59. data/spec/warbler/jar_spec.rb +763 -0
  60. data/spec/warbler/task_spec.rb +56 -41
  61. data/spec/warbler/traits_spec.rb +16 -0
  62. data/spec/warbler/war_spec.rb +2 -492
  63. data/warble.rb +36 -32
  64. metadata +57 -35
  65. data/lib/warbler_war.jar +0 -0
data/lib/warbler/task.rb CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  require 'rake'
9
9
  require 'rake/tasklib'
10
- require 'stringio'
11
- require 'zip/zip'
10
+ require 'warbler/config'
11
+ require 'warbler/jar'
12
12
 
13
13
  module Warbler
14
14
  # Warbler Rake task. Allows defining multiple configurations inside the same
@@ -18,11 +18,11 @@ module Warbler
18
18
  # code like the following in a Rakefile:
19
19
  #
20
20
  # Warbler::Task.new("war1", Warbler::Config.new do |config|
21
- # config.war_name = "war1"
21
+ # config.jar_name = "war1"
22
22
  # # ...
23
23
  # end
24
24
  # Warbler::Task.new("war2", Warbler::Config.new do |config|
25
- # config.war_name = "war2"
25
+ # config.jar_name = "war2"
26
26
  # # ...
27
27
  # end
28
28
  #
@@ -35,11 +35,10 @@ module Warbler
35
35
  # Warbler::Config
36
36
  attr_accessor :config
37
37
 
38
- # Warbler::War
39
- attr_accessor :war
38
+ # Warbler::Jar
39
+ attr_accessor :jar
40
40
 
41
- def initialize(name = :war, config = nil)
42
- @name = name
41
+ def initialize(name = nil, config = nil)
43
42
  @config = config
44
43
  if @config.nil? && File.exists?(Config::FILE)
45
44
  @config = eval(File.open(Config::FILE) {|f| f.read})
@@ -49,11 +48,15 @@ module Warbler
49
48
  warn "Warbler::Config not provided by override in initializer or #{Config::FILE}; using defaults"
50
49
  @config = Config.new
51
50
  end
52
- @war = Warbler::War.new
51
+ @name = name || @config.jar_extension
52
+ @jar = Warbler::Jar.new
53
53
  yield self if block_given?
54
54
  define_tasks
55
55
  end
56
56
 
57
+ # Deprecated: attr_accessor :war
58
+ alias war jar
59
+
57
60
  private
58
61
  def define_tasks
59
62
  define_main_task
@@ -72,7 +75,7 @@ module Warbler
72
75
  end
73
76
 
74
77
  def define_main_task
75
- desc "Create the project .war file"
78
+ desc "Create the project #{config.jar_extension} file"
76
79
  task @name do
77
80
  unless @config.features.empty?
78
81
  @config.features.each do |feature|
@@ -93,28 +96,31 @@ module Warbler
93
96
  end
94
97
 
95
98
  def define_clean_task
96
- desc "Remove the .war file"
99
+ desc "Remove the project #{config.jar_extension} file"
97
100
  task "clean" do
98
- rm_f "#{config.war_name}.war"
101
+ rm_f "#{config.jar_name}.#{config.jar_extension}"
99
102
  end
100
103
  task "clear" => "#{name}:clean"
101
104
  end
102
105
 
103
106
  def define_compiled_task
104
107
  task "compiled" do
105
- war.compile(config)
108
+ jar.compile(config)
109
+ task @name do
110
+ rm_f config.compiled_ruby_files.map {|f| f.sub(/\.rb$/, '.class') }
111
+ end
106
112
  end
107
113
  end
108
114
 
109
115
  def define_files_task
110
116
  task "files" do
111
- war.apply(config)
117
+ jar.apply(config)
112
118
  end
113
119
  end
114
120
 
115
121
  def define_jar_task
116
122
  task "jar" do
117
- war.create(config)
123
+ jar.create(config)
118
124
  end
119
125
  end
120
126
 
@@ -122,36 +128,22 @@ module Warbler
122
128
  desc "Dump diagnostic information"
123
129
  task "debug" => "files" do
124
130
  require 'yaml'
125
- puts YAML::dump(config)
126
- war.files.each {|k,v| puts "#{k} -> #{String === v ? v : '<blob>'}"}
131
+ puts config.dump
132
+ jar.files.each {|k,v| puts "#{k} -> #{String === v ? v : '<blob>'}"}
127
133
  end
128
134
  task "debug:includes" => "files" do
129
135
  puts "", "included files:"
130
- puts *war.webinf_filelist.include
136
+ puts *war.app_filelist.include
131
137
  end
132
138
  task "debug:excludes" => "files" do
133
139
  puts "", "excluded files:"
134
- puts *war.webinf_filelist.exclude
140
+ puts *war.app_filelist.exclude
135
141
  end
136
142
  end
137
143
 
138
144
  def define_gemjar_task
139
145
  task "gemjar" do
140
- task "#@name:jar" => "#@name:make_gemjar"
141
- end
142
-
143
- gem_jar = Warbler::War.new
144
- task "make_gemjar" => "files" do
145
- gem_path = Regexp::quote(config.relative_gem_path)
146
- gems = war.files.select{|k,v| k =~ %r{#{gem_path}/} }
147
- gems.each do |k,v|
148
- gem_jar.files[k.sub(%r{#{gem_path}/}, '')] = v
149
- end
150
- war.files["WEB-INF/lib/gems.jar"] = "tmp/gems.jar"
151
- war.files.reject!{|k,v| k =~ /#{gem_path}/ }
152
- mkdir_p "tmp"
153
- gem_jar.add_manifest
154
- gem_jar.create("tmp/gems.jar")
146
+ @config.features << "gemjar"
155
147
  end
156
148
  end
157
149
 
@@ -186,32 +178,8 @@ module Warbler
186
178
  end
187
179
 
188
180
  def define_executable_task
189
- winstone_type = ENV["WINSTONE"] || "winstone-lite"
190
- winstone_version = ENV["WINSTONE_VERSION"] || "0.9.10"
191
- winstone_path = "net/sourceforge/winstone/#{winstone_type}/#{winstone_version}/#{winstone_type}-#{winstone_version}.jar"
192
- winstone_jar = File.expand_path("~/.m2/repository/#{winstone_path}")
193
- file winstone_jar do |t|
194
- # Not always covered in tests as these lines may not get
195
- # executed every time if the jar is cached.
196
- puts "Downloading #{winstone_type}.jar" #:nocov:
197
- mkdir_p File.dirname(t.name) #:nocov:
198
- require 'open-uri' #:nocov:
199
- maven_repo = ENV["MAVEN_REPO"] || "http://repo2.maven.org/maven2" #:nocov:
200
- open("#{maven_repo}/#{winstone_path}") do |stream| #:nocov:
201
- File.open(t.name, "wb") do |f| #:nocov:
202
- while buf = stream.read(4096) #:nocov:
203
- f << buf #:nocov:
204
- end #:nocov:
205
- end #:nocov:
206
- end #:nocov:
207
- end
208
-
209
- task "executable" => winstone_jar do
210
- war.files['META-INF/MANIFEST.MF'] = StringIO.new(War::DEFAULT_MANIFEST.chomp + "Main-Class: Main\n")
211
- war.files['Main.class'] = Zip::ZipFile.open("#{WARBLER_HOME}/lib/warbler_war.jar") do |zf|
212
- zf.get_input_stream('Main.class') {|io| StringIO.new(io.read) }
213
- end
214
- war.files['WEB-INF/winstone.jar'] = winstone_jar
181
+ task "executable" do
182
+ @config.features << "executable"
215
183
  end
216
184
  end
217
185
 
@@ -1 +1 @@
1
- WARBLER_CONFIG = <%= webxml.context_params(false) .inspect %>
1
+ WARBLER_CONFIG = <%= config.webxml ? config.webxml.context_params(false).inspect : '{}' %>
@@ -1 +1 @@
1
- ENV['RACK_ENV'] = '<%= (params = webxml.context_params; params['rack.env'] || params['rails.env']) %>'
1
+ ENV['RACK_ENV'] = '<%= (params = config.webxml.context_params; params['rack.env'] || params['rails.env']) %>'
@@ -1 +1 @@
1
- ENV['RAILS_ENV'] = '<%= webxml.rails.env %>'
1
+ ENV['RAILS_ENV'] = '<%= config.webxml.rails.env %>'
@@ -0,0 +1,58 @@
1
+ #--
2
+ # Copyright (c) 2010 Engine Yard, Inc.
3
+ # This source code is available under the MIT license.
4
+ # See the file LICENSE.txt for details.
5
+ #++
6
+
7
+ module Warbler
8
+ module Traits
9
+ # The Bundler trait uses Bundler to determine gem dependencies to
10
+ # be added to the project.
11
+ class Bundler
12
+ include Trait
13
+
14
+ def self.detect?
15
+ File.exist?("Gemfile")
16
+ end
17
+
18
+ def self.requires?(trait)
19
+ trait == Traits::War || trait == Traits::Jar
20
+ end
21
+
22
+ def before_configure
23
+ config.bundler = true
24
+ config.bundle_without = ["development", "test"]
25
+ end
26
+
27
+ def after_configure
28
+ add_bundler_gems if config.bundler
29
+ end
30
+
31
+ def add_bundler_gems
32
+ config.gems.clear
33
+ config.gem_dependencies = false # Bundler takes care of these
34
+
35
+ require 'bundler'
36
+ gemfile = Pathname.new("Gemfile").expand_path
37
+ root = gemfile.dirname
38
+ lockfile = root.join('Gemfile.lock')
39
+ definition = ::Bundler::Definition.build(gemfile, lockfile, nil)
40
+ groups = definition.groups - config.bundle_without.map {|g| g.to_sym}
41
+ definition.specs_for(groups).each {|spec| config.gems << spec }
42
+ config.init_contents << StringIO.new("ENV['BUNDLE_WITHOUT'] = '#{config.bundle_without.join(':')}'\n")
43
+ end
44
+
45
+ def update_archive(jar)
46
+ add_bundler_files(jar) if config.bundler
47
+ end
48
+
49
+ # Add Bundler Gemfiles to the archive.
50
+ def add_bundler_files(jar)
51
+ jar.files[jar.apply_pathmaps(config, 'Gemfile', :application)] = 'Gemfile'
52
+ if File.exist?('Gemfile.lock')
53
+ jar.files[jar.apply_pathmaps(config, 'Gemfile.lock', :application)] = 'Gemfile.lock'
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,64 @@
1
+ #--
2
+ # Copyright (c) 2010 Engine Yard, Inc.
3
+ # This source code is available under the MIT license.
4
+ # See the file LICENSE.txt for details.
5
+ #++
6
+
7
+ module Warbler
8
+ module Traits
9
+ # The Gemspec trait reads a .gemspec file to determine the files,
10
+ # executables, require paths, and dependencies for a project.
11
+ class Gemspec
12
+ include Trait
13
+
14
+ def self.detect?
15
+ !Dir['*.gemspec'].empty?
16
+ end
17
+
18
+ def before_configure
19
+ @spec_file = Dir['*.gemspec'].first
20
+ require 'yaml'
21
+ @spec = File.open(@spec_file) {|f| Gem::Specification.from_yaml(f) } rescue Gem::Specification.load(@spec_file)
22
+ @spec.runtime_dependencies.each {|g| config.gems << g }
23
+ config.dirs = []
24
+ config.compiled_ruby_files = @spec.files.select {|f| f =~ /\.rb$/}
25
+ end
26
+
27
+ def after_configure
28
+ @spec.require_paths.each do |p|
29
+ add_init_load_path(config.pathmaps.application.inject(p) {|pm,x| pm.pathmap(x)})
30
+ end
31
+ end
32
+
33
+ def update_archive(jar)
34
+ @spec.files.each do |f|
35
+ unless File.exist?(f)
36
+ warn "update your gemspec; skipping missing file #{f}"
37
+ next
38
+ end
39
+ file_key = jar.apply_pathmaps(config, f, :application)
40
+ next if jar.files[file_key]
41
+ jar.files[file_key] = f
42
+ end
43
+ config.compiled_ruby_files.each do |f|
44
+ f = f.sub(/\.rb$/, '.class')
45
+ next unless File.exist?(f)
46
+ jar.files[jar.apply_pathmaps(config, f, :application)] = f
47
+ end
48
+ bin_path = jar.apply_pathmaps(config, default_executable, :application)
49
+ add_main_rb(jar, bin_path)
50
+ end
51
+
52
+ def default_executable
53
+ if @spec.default_executable
54
+ "bin/#{@spec.default_executable}"
55
+ else
56
+ exe = Dir['bin/*'].first
57
+ raise "No executable script found" unless exe
58
+ warn "No default executable found in #{@spec_file}, using #{exe}"
59
+ exe
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,53 @@
1
+ #--
2
+ # Copyright (c) 2010 Engine Yard, Inc.
3
+ # This source code is available under the MIT license.
4
+ # See the file LICENSE.txt for details.
5
+ #++
6
+
7
+ require 'stringio'
8
+ require 'ostruct'
9
+
10
+ module Warbler
11
+ module Traits
12
+ # The Jar trait sets up the archive layout for an executable jar
13
+ # project, and adds the JRuby jar files and a JarMain class to the
14
+ # archive.
15
+ class Jar
16
+ include Trait
17
+
18
+ def self.detect?
19
+ !War.detect?
20
+ end
21
+
22
+ def before_configure
23
+ config.pathmaps = default_pathmaps
24
+ config.java_libs = default_jar_files
25
+ config.manifest_file = 'MANIFEST.MF' if File.exist?('MANIFEST.MF')
26
+ end
27
+
28
+ def after_configure
29
+ config.init_contents << StringIO.new("require 'rubygems'\n")
30
+ end
31
+
32
+ def update_archive(jar)
33
+ jar.files['META-INF/MANIFEST.MF'] = StringIO.new(Warbler::Jar::DEFAULT_MANIFEST.chomp + "Main-Class: JarMain\n") unless config.manifest_file
34
+ jar.files['JarMain.class'] = jar.entry_in_jar("#{WARBLER_HOME}/lib/warbler_jar.jar", "JarMain.class")
35
+ end
36
+
37
+ def default_pathmaps
38
+ p = OpenStruct.new
39
+ p.java_libs = ["META-INF/lib/%f"]
40
+ p.java_classes = ["%p"]
41
+ p.application = ["#{config.jar_name}/%p"]
42
+ p.gemspecs = ["specifications/%f"]
43
+ p.gems = ["gems/%p"]
44
+ p
45
+ end
46
+
47
+ def default_jar_files
48
+ require 'jruby-jars'
49
+ FileList[JRubyJars.core_jar_path, JRubyJars.stdlib_jar_path]
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,34 @@
1
+ #--
2
+ # Copyright (c) 2010 Engine Yard, Inc.
3
+ # This source code is available under the MIT license.
4
+ # See the file LICENSE.txt for details.
5
+ #++
6
+
7
+ module Warbler
8
+ module Traits
9
+ # The Merb trait adds Merb::BootLoader gem dependencies to the project.
10
+ class Merb
11
+ include Trait
12
+
13
+ def self.detect?
14
+ File.exist?("config/init.rb")
15
+ end
16
+
17
+ def self.requires?(trait)
18
+ trait == Traits::War
19
+ end
20
+
21
+ def before_configure
22
+ return false unless task = Warbler.project_application.lookup("merb_env")
23
+ task.invoke rescue nil
24
+ return false unless defined?(::Merb)
25
+ config.webxml.booter = :merb
26
+ if defined?(::Merb::BootLoader::Dependencies.dependencies)
27
+ ::Merb::BootLoader::Dependencies.dependencies.each {|g| config.gems << g }
28
+ else
29
+ warn "unable to auto-detect Merb dependencies; upgrade to Merb 1.0 or greater"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,41 @@
1
+ #--
2
+ # Copyright (c) 2010 Engine Yard, Inc.
3
+ # This source code is available under the MIT license.
4
+ # See the file LICENSE.txt for details.
5
+ #++
6
+
7
+ module Warbler
8
+ module Traits
9
+ # The NoGemspec trait is used when no gemspec file is found for a
10
+ # jar project. It assumes a standard layout including +bin+ and
11
+ # +lib+ directories.
12
+ class NoGemspec
13
+ include Trait
14
+
15
+ def self.detect?
16
+ Jar.detect? && !Gemspec.detect?
17
+ end
18
+
19
+ def before_configure
20
+ config.dirs = ['.']
21
+ end
22
+
23
+ def after_configure
24
+ if File.directory?("lib")
25
+ add_init_load_path(config.pathmaps.application.inject("lib") {|pm,x| pm.pathmap(x)})
26
+ end
27
+ end
28
+
29
+ def update_archive(jar)
30
+ add_main_rb(jar, jar.apply_pathmaps(config, default_executable, :application))
31
+ end
32
+
33
+ def default_executable
34
+ exes = Dir['bin/*']
35
+ exe = exes.grep(/#{config.jar_name}/).first || exes.first
36
+ raise "No executable script found" unless exe
37
+ exe
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ #--
2
+ # Copyright (c) 2010 Engine Yard, Inc.
3
+ # This source code is available under the MIT license.
4
+ # See the file LICENSE.txt for details.
5
+ #++
6
+
7
+ module Warbler
8
+ module Traits
9
+ # The Rack trait adds config.ru to a Rack-based war project.
10
+ class Rack
11
+ include Trait
12
+
13
+ def self.detect?
14
+ !Rails.detect? && (File.exist?("config.ru") || !Dir['*/config.ru'].empty?)
15
+ end
16
+
17
+ def self.requires?(trait)
18
+ trait == Traits::War
19
+ end
20
+
21
+ def before_configure
22
+ config.webxml.booter = :rack
23
+ config.webinf_files += [FileList['config.ru', '*/config.ru'].detect {|f| File.exist?(f)}]
24
+ end
25
+
26
+ def after_configure
27
+ config.init_contents << "#{config.warbler_templates}/rack.erb"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,57 @@
1
+ #--
2
+ # Copyright (c) 2010 Engine Yard, Inc.
3
+ # This source code is available under the MIT license.
4
+ # See the file LICENSE.txt for details.
5
+ #++
6
+
7
+ module Warbler
8
+ module Traits
9
+ # The Rails trait invokes the Rake environment task and sets up Rails for a war-based project.
10
+ class Rails
11
+ include Trait
12
+
13
+ def self.detect?
14
+ File.exist?("config/environment.rb")
15
+ end
16
+
17
+ def self.requires?(trait)
18
+ trait == Traits::War
19
+ end
20
+
21
+ def before_configure
22
+ config.jar_name = default_app_name
23
+
24
+ return unless Warbler.framework_detection
25
+ return false unless task = Warbler.project_application.lookup("environment")
26
+ task.invoke rescue nil
27
+ return false unless defined?(::Rails)
28
+ config.dirs << "tmp" if File.directory?("tmp")
29
+ config.webxml.booter = :rails
30
+ unless (defined?(::Rails.vendor_rails?) && ::Rails.vendor_rails?) || File.directory?("vendor/rails")
31
+ config.gems["rails"] = ::Rails::VERSION::STRING
32
+ end
33
+ if defined?(::Rails.configuration.gems)
34
+ ::Rails.configuration.gems.each do |g|
35
+ config.gems << Gem::Dependency.new(g.name, g.requirement) if Dir["vendor/gems/#{g.name}*"].empty?
36
+ end
37
+ end
38
+ if defined?(::Rails.configuration.threadsafe!) &&
39
+ (defined?(::Rails.configuration.allow_concurrency) && # Rails 3
40
+ ::Rails.configuration.allow_concurrency && ::Rails.configuration.preload_frameworks) ||
41
+ (defined?(::Rails.configuration.action_controller.allow_concurrency) && # Rails 2
42
+ ::Rails.configuration.action_controller.allow_concurrency && ::Rails.configuration.action_controller.preload_frameworks)
43
+ config.webxml.jruby.max.runtimes = 1
44
+ end
45
+ end
46
+
47
+ def after_configure
48
+ config.init_contents << "#{config.warbler_templates}/rails.erb"
49
+ end
50
+
51
+
52
+ def default_app_name
53
+ File.basename(File.expand_path(defined?(::Rails.root) ? ::Rails.root : (defined?(RAILS_ROOT) ? RAILS_ROOT : Dir.getwd)))
54
+ end
55
+ end
56
+ end
57
+ end