rdm 0.2.0 → 0.3.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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/Gemfile.lock +80 -0
  4. data/bin/rdm +78 -10
  5. data/example/.rdm/helpers/render_helper.rb +12 -0
  6. data/example/.rdm/templates/package/.gitignore +1 -0
  7. data/example/.rdm/templates/package/.rspec +2 -0
  8. data/example/.rdm/templates/package/<%=package_subdir_name%>/<%=package_name%>.rb +3 -0
  9. data/example/.rdm/templates/package/<%=package_subdir_name%>/<%=package_name%>/.gitkeep +0 -0
  10. data/{lib/rdm/templates/package/package.rb.erb → example/.rdm/templates/package/Package.rb} +0 -0
  11. data/{lib/rdm/templates/package/bin/console_irb → example/.rdm/templates/package/bin/console} +0 -0
  12. data/example/.rdm/templates/package/spec/spec_helper.rb +10 -0
  13. data/example/.rdm/templates/repository/dao/<%=name%>_dao.rb +4 -0
  14. data/example/.rdm/templates/repository/mapper/<%=name%>_mapper.rb +2 -0
  15. data/example/.rdm/templates/repository/repository/<%=name%>_repository.rb +2 -0
  16. data/example/.rdm/templates/repository/views/users.html.erb +2 -0
  17. data/example/Gemfile.lock +50 -0
  18. data/example/Rdm.packages +1 -0
  19. data/example/tests/diff_run +0 -0
  20. data/example/tests/run +0 -0
  21. data/lib/rdm.rb +40 -8
  22. data/lib/rdm/cli/compile_package.rb +56 -0
  23. data/lib/rdm/cli/dependencies_controller.rb +30 -0
  24. data/lib/rdm/cli/diff_package.rb +21 -0
  25. data/lib/rdm/cli/gen_package.rb +24 -32
  26. data/lib/rdm/cli/init.rb +20 -27
  27. data/lib/rdm/cli/template_generator.rb +38 -0
  28. data/lib/rdm/errors.rb +37 -0
  29. data/lib/rdm/gen/init.rb +29 -44
  30. data/lib/rdm/gen/package.rb +24 -78
  31. data/lib/rdm/git/diff_command.rb +13 -0
  32. data/lib/rdm/git/diff_manager.rb +30 -0
  33. data/lib/rdm/git/repository_locator.rb +23 -0
  34. data/lib/rdm/handlers/dependencies_handler.rb +110 -0
  35. data/lib/rdm/handlers/diff_package_handler.rb +48 -0
  36. data/lib/rdm/handlers/template_handler.rb +118 -0
  37. data/lib/rdm/helpers/path_helper.rb +15 -0
  38. data/lib/rdm/package.rb +6 -0
  39. data/lib/rdm/package_importer.rb +1 -1
  40. data/lib/rdm/packages/compiler_service.rb +78 -0
  41. data/lib/rdm/packages/locator.rb +28 -0
  42. data/lib/rdm/settings.rb +14 -1
  43. data/lib/rdm/spec_runner.rb +5 -0
  44. data/lib/rdm/spec_runner/command_generator.rb +28 -0
  45. data/lib/rdm/spec_runner/command_params.rb +3 -0
  46. data/lib/rdm/spec_runner/package_fetcher.rb +13 -0
  47. data/lib/rdm/spec_runner/runner.rb +122 -0
  48. data/lib/rdm/spec_runner/view.rb +20 -0
  49. data/lib/rdm/templates/init/.rdm/helpers/render_helper.rb +12 -0
  50. data/lib/rdm/templates/init/{Gemfile.erb → Gemfile} +0 -0
  51. data/lib/rdm/templates/init/{Rdm.packages.erb → Rdm.packages} +0 -0
  52. data/lib/rdm/templates/init/{Readme.md.erb → Readme.md} +0 -0
  53. data/lib/rdm/templates/init/tests/diff_run +29 -0
  54. data/lib/rdm/templates/init/tests/run +7 -210
  55. data/lib/rdm/templates/package/<%=package_subdir_name%>/<%=package_name%>.rb +3 -0
  56. data/lib/rdm/templates/package/<%=package_subdir_name%>/<%=package_name%>/.gitkeep +0 -0
  57. data/lib/rdm/templates/package/Package.rb +8 -0
  58. data/lib/rdm/templates/package/bin/console +16 -0
  59. data/lib/rdm/templates/template_detector.rb +32 -0
  60. data/lib/rdm/templates/template_renderer.rb +49 -0
  61. data/lib/rdm/utils/file_utils.rb +20 -0
  62. data/lib/rdm/utils/render_util.rb +24 -0
  63. data/lib/rdm/utils/string_utils.rb +16 -0
  64. data/lib/rdm/version.rb +1 -1
  65. data/rdm.gemspec +1 -1
  66. data/spec/helpers/example_project_helper.rb +217 -0
  67. data/spec/helpers/git_commands_helper.rb +13 -0
  68. data/spec/rdm/cli/compile_package_spec.rb +114 -0
  69. data/spec/rdm/cli/dependencies_controller_spec.rb +50 -0
  70. data/spec/rdm/cli/diff_package_spec.rb +5 -0
  71. data/spec/rdm/cli/gen_package_spec.rb +60 -86
  72. data/spec/rdm/cli/init_spec.rb +53 -70
  73. data/spec/rdm/gen/init_spec.rb +21 -38
  74. data/spec/rdm/gen/package_spec.rb +70 -51
  75. data/spec/rdm/git/diff_manager_spec.rb +81 -0
  76. data/spec/rdm/git/repository_locator_spec.rb +31 -0
  77. data/spec/rdm/handlers/dependencies_handler_spec.rb +84 -0
  78. data/spec/rdm/handlers/diff_package_handler_spec.rb +78 -0
  79. data/spec/rdm/handlers/template_handler_spec.rb +94 -0
  80. data/spec/rdm/helpers/path_helper_spec.rb +52 -0
  81. data/spec/rdm/package/compiler_service_spec.rb +124 -0
  82. data/spec/rdm/package/locator_spec.rb +31 -0
  83. data/spec/rdm/rdm_spec.rb +2 -2
  84. data/spec/rdm/spec_runner/runner_spec.rb +12 -0
  85. data/spec/rdm/templates/template_detector_spec.rb +39 -0
  86. data/spec/rdm/templates/template_renderer_spec.rb +42 -0
  87. data/spec/spec_helper.rb +31 -25
  88. metadata +84 -17
  89. data/lib/rdm/gen/concerns/template_handling.rb +0 -81
  90. data/lib/rdm/support/colorize.rb +0 -106
  91. data/lib/rdm/support/render.rb +0 -17
  92. data/lib/rdm/support/template.rb +0 -30
  93. data/lib/rdm/templates/package/main_module_file.rb.erb +0 -3
  94. data/spec/rdm/support/colorize_spec.rb +0 -24
  95. data/spec/rdm/support/template_spec.rb +0 -20
@@ -0,0 +1,3 @@
1
+ module <%= camelize(package_name) %>
2
+
3
+ end
@@ -0,0 +1,8 @@
1
+ package do
2
+ name '<%= package_name %>'
3
+ version '1.0.0'
4
+ end
5
+
6
+ dependency do
7
+ # import 'utils'
8
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ require 'rdm'
4
+
5
+ ENV['RUBY_ENV'] ||= 'development'
6
+ Rdm.init(File.expand_path('../../', __FILE__))
7
+
8
+ # You can add fixtures and/or initialization code here to make experimenting
9
+ # with your gem easier. You can also use a different console, if you like.
10
+
11
+ # (If you use this, don't forget to add pry to your Gemfile!)
12
+ # require "pry"
13
+ # Pry.start
14
+
15
+ require 'irb'
16
+ IRB.start
@@ -0,0 +1,32 @@
1
+ module Rdm
2
+ module Templates
3
+ class TemplateDetector
4
+ DEFAULT_TEMPLATES_DIRECTORY = File.expand_path(__dir__)
5
+
6
+ def initialize(project_path)
7
+ @all_templates_directory ||= File.join(project_path, ".rdm", "templates")
8
+ end
9
+
10
+
11
+ def detect_template_folder(template_name)
12
+ template_path = [@all_templates_directory, DEFAULT_TEMPLATES_DIRECTORY]
13
+ .map {|dir| File.join(dir, template_name.to_s)}
14
+ .detect {|dir| Dir.exist?(dir)}
15
+
16
+ raise Rdm::Errors::TemplateDoesNotExist if template_path.nil?
17
+
18
+ template_path
19
+ end
20
+
21
+ def template_file_path(template_name, relative_path)
22
+ file_path = [detect_template_folder(template_name), DEFAULT_TEMPLATES_DIRECTORY]
23
+ .map {|folder| File.join(folder, relative_path)}
24
+ .detect {|file| File.exists?(file)}
25
+
26
+ raise Rdm::Errors::TemplateFileDoesNotExists if file_path.nil?
27
+
28
+ file_path
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,49 @@
1
+ module Rdm
2
+ module Templates
3
+ class TemplateRenderer
4
+ TEMPLATE_VARIABLE = /<%=\s*([\w\d-]+)\s*%>/i
5
+
6
+ class << self
7
+ def handle(template, locals = {})
8
+ Rdm::Templates::TemplateRenderer.new(template, locals).handle
9
+ end
10
+
11
+ def get_undefined_variables(template, locals = {})
12
+ Rdm::Templates::TemplateRenderer.new(template, locals).get_undefined_variables
13
+ end
14
+ end
15
+
16
+ def initialize(template, locals)
17
+ @template = template
18
+ @locals = locals
19
+ end
20
+
21
+ def handle
22
+ raise Rdm::Errors::TemplateVariableNotDefined, get_undefined_variables.map(&:to_s).join(';') if get_undefined_variables.any?
23
+
24
+ Rdm::Utils::RenderUtil.render(@template, @locals)
25
+ end
26
+
27
+ def get_undefined_variables
28
+ get_template_variables - get_passed_variables
29
+ end
30
+
31
+ private
32
+
33
+ def get_template_variables
34
+ @template
35
+ .scan(TEMPLATE_VARIABLE)
36
+ .flatten
37
+ .map(&:intern)
38
+ .uniq
39
+ end
40
+
41
+ def get_passed_variables
42
+ @locals
43
+ .keys
44
+ .uniq
45
+ .map(&:intern)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ class Rdm::Utils::FileUtils
2
+ class << self
3
+ def change_file(original_file, &block)
4
+ return unless block_given?
5
+
6
+ file_name = File.basename(original_file)
7
+ tmp_file = "/tmp/#{file_name}"
8
+
9
+ File.open(tmp_file, 'w') do |file|
10
+ File.foreach(original_file) do |line|
11
+ new_value = yield line || line
12
+ file.puts new_value
13
+ end
14
+ end
15
+
16
+ FileUtils.cp(tmp_file, original_file)
17
+ FileUtils.rm(tmp_file)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ require 'erb'
2
+ require 'byebug'
3
+
4
+ module Rdm
5
+ module Utils
6
+ class RenderUtil
7
+ def self.render(template, locals)
8
+ include Rdm::RenderHelper if defined?(Rdm::RenderHelper)
9
+ new(locals).render(template)
10
+ end
11
+
12
+ def initialize(locals = {})
13
+ @render_binding = binding
14
+ @locals = locals
15
+ end
16
+
17
+ def render(template)
18
+ @locals.each { |variable, value| @render_binding.local_variable_set(variable, value) }
19
+
20
+ ERB.new(template).result(@render_binding)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ module Rdm
2
+ module Utils
3
+ class StringUtils
4
+ class << self
5
+ def camelize(string, uppercase_first_letter = true)
6
+ if uppercase_first_letter
7
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
8
+ else
9
+ string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
10
+ end
11
+ string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module Rdm
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
@@ -23,4 +23,4 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "codecov"
24
24
  spec.add_dependency "activesupport"
25
25
  spec.add_dependency "commander", "~> 4.4"
26
- end
26
+ end
@@ -0,0 +1,217 @@
1
+ require "fileutils"
2
+ require "pathname"
3
+
4
+ module ExampleProjectHelper
5
+ RDM_EXAMPLE_PROJECT_PATH = File.expand_path(File.join(__dir__, '../../example'))
6
+
7
+ INIT_TEMPLATES_PATH = File.expand_path(File.join(__dir__, '../../lib/rdm/templates/init'))
8
+ INIT_GENERATED_FILES = Dir[File.join(INIT_TEMPLATES_PATH, '**/*')]
9
+
10
+ attr_reader :example_project_path, :rdm_source_file
11
+
12
+ def reset_example_project
13
+ FileUtils.rm_rf(@example_project_path)
14
+ end
15
+
16
+ def initialize_example_project(path: '/tmp/example', skip_rdm_init: false)
17
+ @example_project_path = path
18
+ @rdm_source_file = File.join(@example_project_path, Rdm::SOURCE_FILENAME)
19
+
20
+ FileUtils.cp_r(RDM_EXAMPLE_PROJECT_PATH, @example_project_path)
21
+
22
+ if skip_rdm_init
23
+ INIT_GENERATED_FILES.each do |f|
24
+ rel_path = Pathname.new(f).relative_path_from(Pathname.new(INIT_TEMPLATES_PATH))
25
+
26
+ FileUtils.rm_rf(File.join(@example_project_path, rel_path))
27
+ end
28
+
29
+ FileUtils.rm_rf(File.join(@example_project_path, '.rdm'))
30
+ end
31
+
32
+ # FileUtils.mkdir_p(File.join(path, 'configs'))
33
+ # FileUtils.mkdir_p(File.join(path, 'application/web/package/web'))
34
+ # FileUtils.mkdir_p(File.join(path, 'domain/core/package/core'))
35
+ # FileUtils.mkdir_p(File.join(path, 'subsystems/mailing_system/package/mailing_system'))
36
+ # FileUtils.mkdir_p(File.join(path, 'subsystems/api/package/api'))
37
+ # FileUtils.mkdir_p(File.join(path, 'tests'))
38
+
39
+ # if package_template
40
+ # FileUtils.mkdir_p(File.join(path, '.rdm/templates/package'))
41
+ # File.open(File.join(path, '.rdm/templates/package/Package.rb'), 'w') do |f|
42
+ # f.write <<~EOF
43
+ # package do
44
+ # name '<%= package_name %>'
45
+ # version "1.0"
46
+ # end
47
+
48
+ # dependency do
49
+ # # import "core"
50
+ # end
51
+ # EOF
52
+ # end
53
+ # end
54
+
55
+ # File.open(File.join(path, 'tests/run'), 'w') do |f|
56
+ # f.write <<~EOF
57
+ # # how to start tests for your project with rdm
58
+ # EOF
59
+ # end
60
+
61
+ # File.open(File.join(path, 'Readme.md'), 'w') do |f|
62
+ # f.write <<~EOF
63
+ # Some content about your project
64
+ # EOF
65
+ # end
66
+
67
+ # File.open(File.join(path, 'Gemfile'), 'w') do |f|
68
+ # f.write <<~EOF
69
+ # gem 'rdm'
70
+ # EOF
71
+ # end
72
+
73
+ # File.open(File.join(path, 'Gemfile.lock'), 'w') do |f|
74
+ # f.write <<~EOF
75
+ # gem 'rdm'
76
+ # EOF
77
+ # end
78
+
79
+ # File.open(File.join(path, 'Gemfile.lock'), 'w') do |f|
80
+ # f.write <<~EOF
81
+ # gem 'rdm'
82
+ # EOF
83
+ # end
84
+
85
+ # File.open(File.join(path, Rdm::SOURCE_FILENAME), 'w') do |f|
86
+ # f.write <<~EOF
87
+ # setup do
88
+ # role "production"
89
+ # configs_dir "configs"
90
+ # config_path ":configs_dir/:config_name/default.yml"
91
+ # role_config_path ":configs_dir/:config_name/:role.yml"
92
+ # package_subdir_name "package"
93
+ # compile_path "/tmp/example_compile"
94
+ # compile_ignore_files [
95
+ # '.gitignore',
96
+ # '.byebug_history',
97
+ # '.irbrc',
98
+ # '.rspec',
99
+ # '*_spec.rb',
100
+ # '*.log'
101
+ # ]
102
+ # compile_add_files [
103
+ # 'Gemfile',
104
+ # 'Gemfile.lock'
105
+ # ]
106
+ # end
107
+
108
+ # package "application/web"
109
+ # package "domain/core"
110
+ # package "subsystems/api"
111
+ # EOF
112
+ # end
113
+
114
+ # File.open(File.join(path, "application/web", Rdm::PACKAGE_FILENAME), 'w') do |f|
115
+ # f.write <<~EOF
116
+ # package do
117
+ # name "web"
118
+ # version "1.0"
119
+ # end
120
+
121
+ # dependency do
122
+ # import "core"
123
+ # end
124
+ # EOF
125
+ # end
126
+
127
+ # File.open(File.join(path, "domain/core", Rdm::PACKAGE_FILENAME), 'w') do |f|
128
+ # f.write <<~EOF
129
+ # package do
130
+ # name "core"
131
+ # version "1.0"
132
+ # end
133
+
134
+ # dependency do
135
+ # end
136
+ # EOF
137
+ # end
138
+
139
+ # File.open(File.join(path, "subsystems/api", Rdm::PACKAGE_FILENAME), 'w') do |f|
140
+ # f.write <<~EOF
141
+ # package do
142
+ # name "api"
143
+ # version "1.0"
144
+ # end
145
+
146
+ # dependency do
147
+ # import "web"
148
+ # end
149
+ # EOF
150
+ # end
151
+
152
+ # File.open(File.join(path, "application/web/package/web/", "sample_controller.rb"), 'w') do |f|
153
+ # f.write <<~EOF
154
+ # class Web::SampleController
155
+ # def perform
156
+ # sample_service.perform
157
+ # end
158
+
159
+ # def sample_service
160
+ # Core::SampleService.new
161
+ # end
162
+ # end
163
+ # EOF
164
+ # end
165
+
166
+ # File.open(File.join(path, "application/web/package/", "web.rb"), 'w') do |f|
167
+ # f.write <<~EOF
168
+ # require 'active_support'
169
+ # require 'active_support/dependencies'
170
+ # require 'active_support/core_ext'
171
+
172
+ # ActiveSupport::Dependencies.autoload_paths << Pathname.new(__FILE__).parent.to_s
173
+
174
+ # module Web
175
+ # end
176
+ # EOF
177
+ # end
178
+
179
+ # File.open(File.join(path, "domain/core/package/core/", "sample_service.rb"), 'w') do |f|
180
+ # f.write <<~EOF
181
+ # class Core::SampleService
182
+ # def perform
183
+ # puts "Core::SampleService called..."
184
+ # end
185
+ # end
186
+ # EOF
187
+ # end
188
+
189
+ # File.open(File.join(path, "domain/core/package/", "core.rb"), 'w') do |f|
190
+ # f.write <<~EOF
191
+ # require 'active_support'
192
+ # require 'active_support/dependencies'
193
+ # require 'active_support/core_ext'
194
+
195
+ # ActiveSupport::Dependencies.autoload_paths << Pathname.new(__FILE__).parent.to_s
196
+
197
+ # module Core
198
+ # end
199
+ # EOF
200
+ # end
201
+
202
+ # File.open(File.join(path, "subsystems/api/package", "api.rb"), 'w') do |f|
203
+ # f.write <<~EOF
204
+ # require 'active_support'
205
+ # require 'active_support/dependencies'
206
+ # require 'active_support/core_ext'
207
+
208
+ # ActiveSupport::Dependencies.autoload_paths << Pathname.new(__FILE__).parent.to_s
209
+
210
+ # module Api
211
+ # end
212
+ # EOF
213
+ # end
214
+
215
+ # return path
216
+ end
217
+ end
@@ -0,0 +1,13 @@
1
+ module GitCommandsHelper
2
+ def git_initialize_repository(path)
3
+ %x( cd #{path} && git init )
4
+ end
5
+
6
+ def git_commit_changes(path)
7
+ %x( cd #{path} && git add . && git commit -m 'Some commit' )
8
+ end
9
+
10
+ def git_index_changes(path)
11
+ %x( cd #{path} && git add . )
12
+ end
13
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rdm::CLI::CompilePackage do
4
+ include ExampleProjectHelper
5
+
6
+ subject { described_class }
7
+ let(:default_compile_path) { '/tmp/rdm_compiled' }
8
+ let(:new_compile_path) { '/tmp/new_rdm_compiled' }
9
+
10
+ describe "::compile" do
11
+ before :each do
12
+ initialize_example_project
13
+ end
14
+
15
+ after :each do
16
+ reset_example_project
17
+ FileUtils.rm_rf(default_compile_path)
18
+ FileUtils.rm_rf(new_compile_path)
19
+ end
20
+
21
+ context "setup compile_path at Rdm.packages" do
22
+ it "use default value for compile_path" do
23
+ expect {
24
+ subject.compile(
25
+ project_path: example_project_path,
26
+ package_name: 'core',
27
+ overwrite_directory: ->() { true }
28
+ )
29
+ }.to output(
30
+ <<~EOF
31
+ The following packages were copied:
32
+ Core
33
+ Repository
34
+ EOF
35
+ ).to_stdout
36
+ end
37
+ end
38
+
39
+ context "with invalid params" do
40
+ context "project_path" do
41
+ it "raises error if rdm is not initialized" do
42
+ expect {
43
+ subject.compile(
44
+ project_path: File.dirname(example_project_path),
45
+ compile_path: new_compile_path,
46
+ package_name: 'core'
47
+ )
48
+ }.to output(
49
+ "Rdm.packages was not found. Run 'rdm init' to create it\n"
50
+ ).to_stdout
51
+ end
52
+ end
53
+
54
+ context "compile_path" do
55
+ it "raises error if empty" do
56
+ Rdm::Utils::FileUtils.change_file(rdm_source_file) do |line|
57
+ next if line.include?('compile_path')
58
+ end
59
+
60
+ expect {
61
+ subject.compile(
62
+ project_path: example_project_path,
63
+ compile_path: '',
64
+ package_name: 'core'
65
+ )
66
+ }.to output(
67
+ "Destination path was not specified. Ex: rdm compile.package package_name --path FOLDER_PATH\n"
68
+ ).to_stdout
69
+ end
70
+ end
71
+
72
+ context "package name" do
73
+ it "raises error if empty" do
74
+ expect {
75
+ subject.compile(
76
+ project_path: example_project_path,
77
+ compile_path: new_compile_path,
78
+ package_name: ''
79
+ )
80
+ }.to output(
81
+ "Package name was not specified. Ex: rdm compile.package PACKAGE_NAME\n"
82
+ ).to_stdout
83
+ end
84
+ end
85
+
86
+ context "with valid params" do
87
+ context "if directory exists" do
88
+ before do
89
+ FileUtils.mkdir_p(new_compile_path)
90
+ File.open(File.join(new_compile_path, 'old_file.txt'), 'w') {|f| f.write("Old File")}
91
+ end
92
+
93
+ it "ask to overwriting the directory" do
94
+ expect {
95
+ subject.compile(
96
+ project_path: example_project_path,
97
+ compile_path: new_compile_path,
98
+ package_name: 'core',
99
+ overwrite_directory: ->() { true }
100
+ )
101
+ }.to output(
102
+ <<~EOF
103
+ Destination directory exists. Overwrite it? (y/n)
104
+ The following packages were copied:
105
+ Core
106
+ Repository
107
+ EOF
108
+ ).to_stdout
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end