backup 3.0.3.build.0 → 3.0.3

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 (126) hide show
  1. data/.gitignore +2 -0
  2. data/.infinity_test +7 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +17 -0
  5. data/Gemfile.lock +88 -0
  6. data/LICENSE.md +24 -0
  7. data/README.md +236 -0
  8. data/backup.gemspec +41 -0
  9. data/bin/backup +191 -12
  10. data/lib/backup.rb +162 -0
  11. data/lib/backup/archive.rb +54 -0
  12. data/lib/backup/cli.rb +50 -0
  13. data/lib/backup/compressor/base.rb +17 -0
  14. data/lib/backup/compressor/gzip.rb +61 -0
  15. data/lib/backup/configuration/base.rb +15 -0
  16. data/lib/backup/configuration/compressor/base.rb +10 -0
  17. data/lib/backup/configuration/compressor/gzip.rb +23 -0
  18. data/lib/backup/configuration/database/base.rb +18 -0
  19. data/lib/backup/configuration/database/mongodb.rb +37 -0
  20. data/lib/backup/configuration/database/mysql.rb +37 -0
  21. data/lib/backup/configuration/database/postgresql.rb +37 -0
  22. data/lib/backup/configuration/database/redis.rb +35 -0
  23. data/lib/backup/configuration/encryptor/base.rb +10 -0
  24. data/lib/backup/configuration/encryptor/gpg.rb +17 -0
  25. data/lib/backup/configuration/encryptor/open_ssl.rb +26 -0
  26. data/lib/backup/configuration/helpers.rb +54 -0
  27. data/lib/backup/configuration/notifier/base.rb +39 -0
  28. data/lib/backup/configuration/notifier/mail.rb +52 -0
  29. data/lib/backup/configuration/storage/base.rb +18 -0
  30. data/lib/backup/configuration/storage/cloudfiles.rb +21 -0
  31. data/lib/backup/configuration/storage/dropbox.rb +25 -0
  32. data/lib/backup/configuration/storage/ftp.rb +25 -0
  33. data/lib/backup/configuration/storage/rsync.rb +25 -0
  34. data/lib/backup/configuration/storage/s3.rb +25 -0
  35. data/lib/backup/configuration/storage/scp.rb +25 -0
  36. data/lib/backup/configuration/storage/sftp.rb +25 -0
  37. data/lib/backup/configuration/syncer/rsync.rb +45 -0
  38. data/lib/backup/database/base.rb +33 -0
  39. data/lib/backup/database/mongodb.rb +137 -0
  40. data/lib/backup/database/mysql.rb +104 -0
  41. data/lib/backup/database/postgresql.rb +111 -0
  42. data/lib/backup/database/redis.rb +105 -0
  43. data/lib/backup/encryptor/base.rb +17 -0
  44. data/lib/backup/encryptor/gpg.rb +78 -0
  45. data/lib/backup/encryptor/open_ssl.rb +67 -0
  46. data/lib/backup/finder.rb +39 -0
  47. data/lib/backup/logger.rb +86 -0
  48. data/lib/backup/model.rb +272 -0
  49. data/lib/backup/notifier/base.rb +29 -0
  50. data/lib/backup/notifier/binder.rb +32 -0
  51. data/lib/backup/notifier/mail.rb +141 -0
  52. data/lib/backup/notifier/templates/notify_failure.erb +31 -0
  53. data/lib/backup/notifier/templates/notify_success.erb +16 -0
  54. data/lib/backup/storage/base.rb +67 -0
  55. data/lib/backup/storage/cloudfiles.rb +95 -0
  56. data/lib/backup/storage/dropbox.rb +82 -0
  57. data/lib/backup/storage/ftp.rb +114 -0
  58. data/lib/backup/storage/object.rb +45 -0
  59. data/lib/backup/storage/rsync.rb +99 -0
  60. data/lib/backup/storage/s3.rb +108 -0
  61. data/lib/backup/storage/scp.rb +105 -0
  62. data/lib/backup/storage/sftp.rb +106 -0
  63. data/lib/backup/syncer/rsync.rb +119 -0
  64. data/lib/backup/version.rb +72 -0
  65. data/lib/templates/archive +4 -0
  66. data/lib/templates/compressor/gzip +4 -0
  67. data/lib/templates/database/mongodb +10 -0
  68. data/lib/templates/database/mysql +11 -0
  69. data/lib/templates/database/postgresql +11 -0
  70. data/lib/templates/database/redis +10 -0
  71. data/lib/templates/encryptor/gpg +9 -0
  72. data/lib/templates/encryptor/openssl +5 -0
  73. data/lib/templates/notifier/mail +14 -0
  74. data/lib/templates/readme +15 -0
  75. data/lib/templates/storage/cloudfiles +7 -0
  76. data/lib/templates/storage/dropbox +8 -0
  77. data/lib/templates/storage/ftp +8 -0
  78. data/lib/templates/storage/rsync +7 -0
  79. data/lib/templates/storage/s3 +8 -0
  80. data/lib/templates/storage/scp +8 -0
  81. data/lib/templates/storage/sftp +8 -0
  82. data/lib/templates/syncer/rsync +14 -0
  83. data/spec/archive_spec.rb +53 -0
  84. data/spec/backup_spec.rb +11 -0
  85. data/spec/compressor/gzip_spec.rb +59 -0
  86. data/spec/configuration/base_spec.rb +35 -0
  87. data/spec/configuration/compressor/gzip_spec.rb +28 -0
  88. data/spec/configuration/database/base_spec.rb +16 -0
  89. data/spec/configuration/database/mongodb_spec.rb +30 -0
  90. data/spec/configuration/database/mysql_spec.rb +32 -0
  91. data/spec/configuration/database/postgresql_spec.rb +32 -0
  92. data/spec/configuration/database/redis_spec.rb +30 -0
  93. data/spec/configuration/encryptor/gpg_spec.rb +25 -0
  94. data/spec/configuration/encryptor/open_ssl_spec.rb +31 -0
  95. data/spec/configuration/notifier/mail_spec.rb +32 -0
  96. data/spec/configuration/storage/cloudfiles_spec.rb +34 -0
  97. data/spec/configuration/storage/dropbox_spec.rb +40 -0
  98. data/spec/configuration/storage/ftp_spec.rb +40 -0
  99. data/spec/configuration/storage/rsync_spec.rb +37 -0
  100. data/spec/configuration/storage/s3_spec.rb +37 -0
  101. data/spec/configuration/storage/scp_spec.rb +40 -0
  102. data/spec/configuration/storage/sftp_spec.rb +40 -0
  103. data/spec/configuration/syncer/rsync_spec.rb +46 -0
  104. data/spec/database/base_spec.rb +30 -0
  105. data/spec/database/mongodb_spec.rb +144 -0
  106. data/spec/database/mysql_spec.rb +150 -0
  107. data/spec/database/postgresql_spec.rb +164 -0
  108. data/spec/database/redis_spec.rb +122 -0
  109. data/spec/encryptor/gpg_spec.rb +57 -0
  110. data/spec/encryptor/open_ssl_spec.rb +102 -0
  111. data/spec/logger_spec.rb +46 -0
  112. data/spec/model_spec.rb +236 -0
  113. data/spec/notifier/mail_spec.rb +97 -0
  114. data/spec/spec_helper.rb +21 -0
  115. data/spec/storage/base_spec.rb +33 -0
  116. data/spec/storage/cloudfiles_spec.rb +102 -0
  117. data/spec/storage/dropbox_spec.rb +89 -0
  118. data/spec/storage/ftp_spec.rb +133 -0
  119. data/spec/storage/object_spec.rb +74 -0
  120. data/spec/storage/rsync_spec.rb +115 -0
  121. data/spec/storage/s3_spec.rb +110 -0
  122. data/spec/storage/scp_spec.rb +129 -0
  123. data/spec/storage/sftp_spec.rb +125 -0
  124. data/spec/syncer/rsync_spec.rb +156 -0
  125. data/spec/version_spec.rb +32 -0
  126. metadata +195 -6
@@ -0,0 +1,162 @@
1
+ # encoding: utf-8
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ ##
7
+ # The Backup Ruby Gem
8
+ module Backup
9
+
10
+ ##
11
+ # List the available database, storage, compressor, encryptor and notifier constants.
12
+ # These are used to dynamically define these constants as classes inside Backup::Finder
13
+ # to provide a nicer configuration file DSL syntax to the users. Adding existing constants
14
+ # to the arrays below will enable the user to use a constant instead of a string.
15
+ # Example, instead of:
16
+ # database "MySQL" do |mysql|
17
+ # You can do:
18
+ # database MySQL do |mysql|
19
+ DATABASES = ['MySQL', 'PostgreSQL', 'MongoDB', 'Redis']
20
+ STORAGES = ['S3', 'CloudFiles', 'Dropbox', 'FTP', 'SFTP', 'SCP', 'RSync']
21
+ COMPRESSORS = ['Gzip']
22
+ ENCRYPTORS = ['OpenSSL', 'GPG']
23
+ NOTIFIERS = ['Mail']
24
+ SYNCERS = ['RSync']
25
+
26
+ ##
27
+ # Backup's internal paths
28
+ LIBRARY_PATH = File.join(File.dirname(__FILE__), 'backup')
29
+ CONFIGURATION_PATH = File.join(LIBRARY_PATH, 'configuration')
30
+ STORAGE_PATH = File.join(LIBRARY_PATH, 'storage')
31
+ DATABASE_PATH = File.join(LIBRARY_PATH, 'database')
32
+ COMPRESSOR_PATH = File.join(LIBRARY_PATH, 'compressor')
33
+ ENCRYPTOR_PATH = File.join(LIBRARY_PATH, 'encryptor')
34
+ NOTIFIER_PATH = File.join(LIBRARY_PATH, 'notifier')
35
+ SYNCER_PATH = File.join(LIBRARY_PATH, 'syncer')
36
+
37
+ ##
38
+ # Backup's Environment paths
39
+ PATH = File.join(ENV['HOME'], 'Backup')
40
+ DATA_PATH = File.join(ENV['HOME'], 'Backup', 'data')
41
+ CONFIG_FILE = File.join(ENV['HOME'], 'Backup', 'config.rb')
42
+ LOG_PATH = File.join(ENV['HOME'], 'Backup', 'log')
43
+ TMP_PATH = File.join(ENV['HOME'], 'Backup', '.tmp')
44
+
45
+ ##
46
+ # Autoload Backup base files
47
+ autoload :Model, File.join(LIBRARY_PATH, 'model')
48
+ autoload :Archive, File.join(LIBRARY_PATH, 'archive')
49
+ autoload :CLI, File.join(LIBRARY_PATH, 'cli')
50
+ autoload :Finder, File.join(LIBRARY_PATH, 'finder')
51
+ autoload :Logger, File.join(LIBRARY_PATH, 'logger')
52
+ autoload :Version, File.join(LIBRARY_PATH, 'version')
53
+
54
+ ##
55
+ # Autoload Backup configuration files
56
+ module Configuration
57
+ autoload :Base, File.join(CONFIGURATION_PATH, 'base')
58
+ autoload :Helpers, File.join(CONFIGURATION_PATH, 'helpers')
59
+
60
+ module Notifier
61
+ autoload :Base, File.join(CONFIGURATION_PATH, 'notifier', 'base')
62
+ autoload :Mail, File.join(CONFIGURATION_PATH, 'notifier', 'mail')
63
+ end
64
+
65
+ module Encryptor
66
+ autoload :Base, File.join(CONFIGURATION_PATH, 'encryptor', 'base')
67
+ autoload :OpenSSL, File.join(CONFIGURATION_PATH, 'encryptor', 'open_ssl')
68
+ autoload :GPG, File.join(CONFIGURATION_PATH, 'encryptor', 'gpg')
69
+ end
70
+
71
+ module Compressor
72
+ autoload :Base, File.join(CONFIGURATION_PATH, 'compressor', 'base')
73
+ autoload :Gzip, File.join(CONFIGURATION_PATH, 'compressor', 'gzip')
74
+ end
75
+
76
+ module Storage
77
+ autoload :Base, File.join(CONFIGURATION_PATH, 'storage', 'base')
78
+ autoload :S3, File.join(CONFIGURATION_PATH, 'storage', 's3')
79
+ autoload :CloudFiles, File.join(CONFIGURATION_PATH, 'storage', 'cloudfiles')
80
+ autoload :Dropbox, File.join(CONFIGURATION_PATH, 'storage', 'dropbox')
81
+ autoload :FTP, File.join(CONFIGURATION_PATH, 'storage', 'ftp')
82
+ autoload :SFTP, File.join(CONFIGURATION_PATH, 'storage', 'sftp')
83
+ autoload :SCP, File.join(CONFIGURATION_PATH, 'storage', 'scp')
84
+ autoload :RSync, File.join(CONFIGURATION_PATH, 'storage', 'rsync')
85
+ end
86
+
87
+ module Syncer
88
+ autoload :RSync, File.join(CONFIGURATION_PATH, 'syncer', 'rsync')
89
+ end
90
+
91
+ module Database
92
+ autoload :Base, File.join(CONFIGURATION_PATH, 'database', 'base')
93
+ autoload :MySQL, File.join(CONFIGURATION_PATH, 'database', 'mysql')
94
+ autoload :PostgreSQL, File.join(CONFIGURATION_PATH, 'database', 'postgresql')
95
+ autoload :MongoDB, File.join(CONFIGURATION_PATH, 'database', 'mongodb')
96
+ autoload :Redis, File.join(CONFIGURATION_PATH, 'database', 'redis')
97
+ end
98
+ end
99
+
100
+ ##
101
+ # Autoload Backup storage files
102
+ module Storage
103
+ autoload :Base, File.join(STORAGE_PATH, 'base')
104
+ autoload :Object, File.join(STORAGE_PATH, 'object')
105
+ autoload :S3, File.join(STORAGE_PATH, 's3')
106
+ autoload :CloudFiles, File.join(STORAGE_PATH, 'cloudfiles')
107
+ autoload :Dropbox, File.join(STORAGE_PATH, 'dropbox')
108
+ autoload :FTP, File.join(STORAGE_PATH, 'ftp')
109
+ autoload :SFTP, File.join(STORAGE_PATH, 'sftp')
110
+ autoload :SCP, File.join(STORAGE_PATH, 'scp')
111
+ autoload :RSync, File.join(STORAGE_PATH, 'rsync')
112
+ end
113
+
114
+ ##
115
+ # Autoload Backup syncer files
116
+ module Syncer
117
+ autoload :RSync, File.join(SYNCER_PATH, 'rsync')
118
+ end
119
+
120
+ ##
121
+ # Autoload Backup database files
122
+ module Database
123
+ autoload :Base, File.join(DATABASE_PATH, 'base')
124
+ autoload :MySQL, File.join(DATABASE_PATH, 'mysql')
125
+ autoload :PostgreSQL, File.join(DATABASE_PATH, 'postgresql')
126
+ autoload :MongoDB, File.join(DATABASE_PATH, 'mongodb')
127
+ autoload :Redis, File.join(DATABASE_PATH, 'redis')
128
+ end
129
+
130
+ ##
131
+ # Autoload compressor files
132
+ module Compressor
133
+ autoload :Base, File.join(COMPRESSOR_PATH, 'base')
134
+ autoload :Gzip, File.join(COMPRESSOR_PATH, 'gzip')
135
+ end
136
+
137
+ ##
138
+ # Autoload encryptor files
139
+ module Encryptor
140
+ autoload :Base, File.join(ENCRYPTOR_PATH, 'base')
141
+ autoload :OpenSSL, File.join(ENCRYPTOR_PATH, 'open_ssl')
142
+ autoload :GPG, File.join(ENCRYPTOR_PATH, 'gpg')
143
+ end
144
+
145
+ ##
146
+ # Autoload notification files
147
+ module Notifier
148
+ autoload :Base, File.join(NOTIFIER_PATH, 'base')
149
+ autoload :Binder, File.join(NOTIFIER_PATH, 'binder')
150
+ autoload :Mail, File.join(NOTIFIER_PATH, 'mail')
151
+ end
152
+
153
+ ##
154
+ # Dynamically defines all the available database, storage, compressor, encryptor and notifier
155
+ # classes inside Backup::Finder to improve the DSL for the configuration file
156
+ (DATABASES + STORAGES + COMPRESSORS + ENCRYPTORS + NOTIFIERS + SYNCERS).each do |constant|
157
+ unless Backup::Finder.const_defined?(constant)
158
+ Backup::Finder.const_set(constant, Class.new)
159
+ end
160
+ end
161
+
162
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Archive
5
+ include Backup::CLI
6
+
7
+ ##
8
+ # Stores the name of the archive
9
+ attr_accessor :name
10
+
11
+ ##
12
+ # Stores an array of different paths/files to store
13
+ attr_accessor :paths
14
+
15
+ ##
16
+ # Stores the path to the archive directory
17
+ attr_accessor :archive_path
18
+
19
+ ##
20
+ # Takes the name of the archive and the configuration block
21
+ def initialize(name, &block)
22
+ @name = name.to_sym
23
+ @paths = Array.new
24
+ @archive_path = File.join(TMP_PATH, TRIGGER, 'archive')
25
+
26
+ instance_eval(&block)
27
+ end
28
+
29
+ ##
30
+ # Adds new paths to the @paths instance variable array
31
+ def add(path)
32
+ @paths << path
33
+ end
34
+
35
+ ##
36
+ # Archives all the provided paths in to a single .tar file
37
+ # and places that .tar file in the folder which later will be packaged
38
+ def perform!
39
+ mkdir(archive_path)
40
+ Logger.message("#{ self.class } started packaging and archiving #{ paths.map { |path| "\"#{path}\""}.join(", ") }.")
41
+ run("#{ utility(:tar) } -c #{ paths_to_package } 1> '#{ File.join(archive_path, "#{name}.tar") }' 2> /dev/null")
42
+ end
43
+
44
+ private
45
+
46
+ ##
47
+ # Returns a "tar-ready" string of all the specified paths combined
48
+ def paths_to_package
49
+ paths.map do |path|
50
+ "'#{path}'"
51
+ end.join("\s")
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module CLI
5
+
6
+ ##
7
+ # Wrapper method for %x[] to run CL commands
8
+ # through a ruby method. This helps with test coverage and
9
+ # improves readability
10
+ def run(command)
11
+ %x[#{command}]
12
+ end
13
+
14
+ ##
15
+ # Wrapper method for FileUtils.mkdir_p to create directories
16
+ # through a ruby method. This helps with test coverage and
17
+ # improves readability
18
+ def mkdir(path)
19
+ FileUtils.mkdir_p(path)
20
+ end
21
+
22
+ ##
23
+ # Wrapper for the FileUtils.rm_rf to remove files and folders
24
+ # through a ruby method. This helps with test coverage and
25
+ # improves readability
26
+ def rm(path)
27
+ FileUtils.rm_rf(path)
28
+ end
29
+
30
+ ##
31
+ # Tries to find the full path of the specified utility. If the full
32
+ # path is found, it'll return that. Otherwise it'll just return the
33
+ # name of the utility. If the 'utility_path' is defined, it'll check
34
+ # to see if it isn't an empty string, and if it isn't, it'll go ahead and
35
+ # always use that path rather than auto-detecting it
36
+ def utility(name)
37
+ if respond_to?(:utility_path)
38
+ if utility_path.is_a?(String) and not utility_path.empty?
39
+ return utility_path
40
+ end
41
+ end
42
+
43
+ if path = %x[which #{name}].chomp and not path.empty?
44
+ return path
45
+ end
46
+ name
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Compressor
5
+ class Base
6
+ include Backup::CLI
7
+ include Backup::Configuration::Helpers
8
+
9
+ ##
10
+ # Logs a message to the console and log file to inform
11
+ # the client that Backup is compressing the archive
12
+ def log!
13
+ Backup::Logger.message "#{ self.class } started compressing the archive."
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Compressor
5
+ class Gzip < Base
6
+
7
+ ##
8
+ # Tells Backup::Compressor::Gzip to compress
9
+ # better rather than faster when set to true
10
+ attr_writer :best
11
+
12
+ ##
13
+ # Tells Backup::Compressor::Gzip to compress
14
+ # faster rather than better when set to true
15
+ attr_writer :fast
16
+
17
+ ##
18
+ # Creates a new instance of Backup::Compressor::Gzip and
19
+ # configures it to either compress faster or better
20
+ def initialize(&block)
21
+ load_defaults!
22
+
23
+ @best ||= false
24
+ @fast ||= false
25
+
26
+ instance_eval(&block) if block_given?
27
+ end
28
+
29
+ ##
30
+ # Performs the compression of the packages backup file
31
+ def perform!
32
+ log!
33
+ run("#{ utility(:gzip) } #{ options } '#{ Backup::Model.file }'")
34
+ Backup::Model.extension += '.gz'
35
+ end
36
+
37
+ private
38
+
39
+ ##
40
+ # Combines the provided options and returns a gzip options string
41
+ def options
42
+ (best + fast).join("\s")
43
+ end
44
+
45
+ ##
46
+ # Returns the gzip option syntax for compressing
47
+ # better when @best is set to true
48
+ def best
49
+ return ['--best'] if @best; []
50
+ end
51
+
52
+ ##
53
+ # Returns the gzip option syntax for compressing
54
+ # faster when @fast is set to true
55
+ def fast
56
+ return ['--fast'] if @fast; []
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Configuration
5
+ class Base
6
+ extend Backup::Configuration::Helpers
7
+
8
+ ##
9
+ # Allows for global configuration through block-notation
10
+ def self.defaults
11
+ yield self
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Configuration
5
+ module Compressor
6
+ class Base < Backup::Configuration::Base
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Configuration
5
+ module Compressor
6
+ class Gzip < Base
7
+ class << self
8
+
9
+ ##
10
+ # Tells Backup::Compressor::Gzip to compress
11
+ # better rather than faster when set to true
12
+ attr_accessor :best
13
+
14
+ ##
15
+ # Tells Backup::Compressor::Gzip to compress
16
+ # faster rather than better when set to true
17
+ attr_accessor :fast
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Configuration
5
+ module Database
6
+ class Base < Backup::Configuration::Base
7
+ class << self
8
+
9
+ ##
10
+ # Allows the user to specify the path to a "dump" utility
11
+ # in case it cannot be auto-detected by Backup
12
+ attr_accessor :utility_path
13
+
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Configuration
5
+ module Database
6
+ class MongoDB < Base
7
+ class << self
8
+
9
+ ##
10
+ # Name of the database that needs to get dumped
11
+ attr_accessor :name
12
+
13
+ ##
14
+ # Credentials for the specified database
15
+ attr_accessor :username, :password
16
+
17
+ ##
18
+ # Connectivity options
19
+ attr_accessor :host, :port
20
+
21
+ ##
22
+ # IPv6 support (disabled by default)
23
+ attr_accessor :ipv6
24
+
25
+ ##
26
+ # Collections to dump, collections that aren't specified won't get dumped
27
+ attr_accessor :only_collections
28
+
29
+ ##
30
+ # Additional "mongodump" options
31
+ attr_accessor :additional_options
32
+
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end