greenhouse 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/bin/greenhouse +9 -0
  3. data/lib/greenhouse/cli.rb +88 -0
  4. data/lib/greenhouse/commands/add.rb +32 -0
  5. data/lib/greenhouse/commands/command.rb +88 -0
  6. data/lib/greenhouse/commands/configure.rb +9 -0
  7. data/lib/greenhouse/commands/help.rb +45 -0
  8. data/lib/greenhouse/commands/init.rb +93 -0
  9. data/lib/greenhouse/commands/launch.rb +113 -0
  10. data/lib/greenhouse/commands/new.rb +42 -0
  11. data/lib/greenhouse/commands/pull.rb +45 -0
  12. data/lib/greenhouse/commands/purge.rb +91 -0
  13. data/lib/greenhouse/commands/push.rb +45 -0
  14. data/lib/greenhouse/commands/remove.rb +32 -0
  15. data/lib/greenhouse/commands/start.rb +21 -0
  16. data/lib/greenhouse/commands/status.rb +50 -0
  17. data/lib/greenhouse/commands/sync.rb +42 -0
  18. data/lib/greenhouse/commands.rb +32 -0
  19. data/lib/greenhouse/projects/application.rb +19 -0
  20. data/lib/greenhouse/projects/collection.rb +23 -0
  21. data/lib/greenhouse/projects/engine.rb +6 -0
  22. data/lib/greenhouse/projects/gem.rb +6 -0
  23. data/lib/greenhouse/projects/project.rb +77 -0
  24. data/lib/greenhouse/projects/repository.rb +197 -0
  25. data/lib/greenhouse/projects.rb +86 -0
  26. data/lib/greenhouse/resources/dotenv_file.rb +69 -0
  27. data/lib/greenhouse/resources/file_resource.rb +50 -0
  28. data/lib/greenhouse/resources/ignore_file.rb +115 -0
  29. data/lib/greenhouse/resources/procfile.rb +144 -0
  30. data/lib/greenhouse/resources/projects_file.rb +44 -0
  31. data/lib/greenhouse/resources.rb +10 -0
  32. data/lib/greenhouse/scripts/argument.rb +36 -0
  33. data/lib/greenhouse/scripts/arguments.rb +28 -0
  34. data/lib/greenhouse/scripts/invalid_argument.rb +6 -0
  35. data/lib/greenhouse/scripts/script.rb +109 -0
  36. data/lib/greenhouse/scripts.rb +4 -0
  37. data/lib/greenhouse/tasks/add_project.rb +57 -0
  38. data/lib/greenhouse/tasks/generate_procfile.rb +22 -0
  39. data/lib/greenhouse/tasks/project_status.rb +37 -0
  40. data/lib/greenhouse/tasks/project_task.rb +363 -0
  41. data/lib/greenhouse/tasks/pull_project.rb +14 -0
  42. data/lib/greenhouse/tasks/purge_project.rb +13 -0
  43. data/lib/greenhouse/tasks/push_project.rb +14 -0
  44. data/lib/greenhouse/tasks/remove_greenhouse_files.rb +58 -0
  45. data/lib/greenhouse/tasks/remove_project.rb +15 -0
  46. data/lib/greenhouse/tasks/sync_project.rb +19 -0
  47. data/lib/greenhouse/tasks/task.rb +25 -0
  48. data/lib/greenhouse/tasks.rb +20 -0
  49. data/lib/greenhouse/version.rb +20 -0
  50. data/lib/greenhouse.rb +12 -0
  51. metadata +165 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 275b120e3a88469a7c59a1ef80c34e8e53684599
4
+ data.tar.gz: 3e5d0d6545c76c9d127252c20616277ccd338c9d
5
+ SHA512:
6
+ metadata.gz: b821db4b17049bc511b15af85f48a7810d22b45b8578aeddb47baf89b30ea54f99f88e4de727875639a322f40640477399cb6e58d107bb22fbf1c852070aca60
7
+ data.tar.gz: 15da3ce7567ce8ac67789d9f839dd202649bbef426c6d3a29571ebadf4286d4e60d27c02bc7d227e81b7a94d7efd8b490c9621e30cee25dec56ab8881395e05f
data/bin/greenhouse ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Greenhouse
4
+ #
5
+ $:.unshift File.expand_path("../../lib", __FILE__)
6
+
7
+ require 'greenhouse'
8
+
9
+ Greenhouse::CLI::start
@@ -0,0 +1,88 @@
1
+ module Greenhouse
2
+ class CLI
3
+ include Scripts::Script
4
+
5
+ valid_arguments("-v")
6
+
7
+ def self.verbose
8
+ @verbose || false
9
+ end
10
+
11
+ def self.verbose?
12
+ verbose == true
13
+ end
14
+
15
+ def self.verbose=(v)
16
+ @verbose = v
17
+ @verbose
18
+ end
19
+
20
+ def self.exec(cmd)
21
+ if verbose?
22
+ system cmd
23
+ else
24
+ `#{cmd}`
25
+ end
26
+ end
27
+
28
+ def self.arguments(*args)
29
+ keep = []
30
+ args.each_with_index do |arg,a|
31
+ if Commands::exists?(arg)
32
+ begin
33
+ @command = Commands::command(arg)
34
+ @command.arguments(*args.slice(a+1,args.length-a))
35
+ rescue Scripts::InvalidArgument => e
36
+ puts e.message
37
+ Commands::command(arg).usage
38
+ exit 1
39
+ end
40
+ break
41
+ end
42
+ keep << arg
43
+ end
44
+ super(*keep)
45
+ end
46
+
47
+ def self.binary=(bin)
48
+ @binary = bin
49
+ end
50
+
51
+ def self.binary
52
+ @binary ||= "greenhouse"
53
+ end
54
+
55
+ def self.command_name
56
+ binary
57
+ end
58
+
59
+ def self.usage
60
+ puts <<USAGE
61
+ usage: #{command_name} #{valid_arguments.to_s} <command> [<args>]
62
+
63
+ The available greenhouse commands are:
64
+ USAGE
65
+
66
+ Commands::commands.each do |cmd|
67
+ print " %-#{Commands::commands.sort { |a,b| a.command_name.length <=> b.command_name.length }.last.command_name.length + 2}s" % cmd.command_name
68
+ puts cmd.command_summary
69
+ end
70
+
71
+ puts
72
+ puts "See `#{command_name} help <command>` for more information on a specific command."
73
+ end
74
+
75
+ def self.start
76
+ arguments(*ARGV)
77
+ verbose = arguments.map(&:key).include?("-v")
78
+
79
+ if @command.nil?
80
+ usage
81
+ exit 1
82
+ end
83
+
84
+ @command.run
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,32 @@
1
+ module Greenhouse
2
+ module Commands
3
+ class Add
4
+ include Command
5
+ command_summary "Add a project to the ecosystem"
6
+
7
+ def add_another?
8
+ another = nil
9
+ while !['', 'n','no'].include?(another) do
10
+ puts "The following projects are configured in your ecosystem: "
11
+ #Projects::projects.each do |project|
12
+ # puts
13
+ # Tasks::ProjectStatus.perform(project)
14
+ #end
15
+ Projects.projects.each do |project|
16
+ puts " \e[36m#{project.title}\e[0m"
17
+ end
18
+ puts
19
+ print "Would you like to add another project? ([y]es/[N]o): "
20
+ another = STDIN.gets.chomp.downcase
21
+ Tasks::AddProject.perform if ['y','yes'].include?(another)
22
+ end
23
+ end
24
+
25
+ def run
26
+ Tasks::AddProject.perform
27
+
28
+ add_another?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,88 @@
1
+ module Greenhouse
2
+ module Commands
3
+ module Command
4
+ def self.included(base)
5
+ Commands::commands << base # Keep track of all commands
6
+
7
+ base.send :include, Scripts::Script
8
+ base.send :extend, ClassMethods
9
+ base.send :include, InstanceMethods
10
+ end
11
+
12
+ module ClassMethods
13
+ def command_name(n=nil)
14
+ @command_name = n unless n.nil?
15
+ return @command_name unless @command_name.nil?
16
+ self.name.underscore.split("/").last
17
+ end
18
+
19
+ def command_summary(summary=nil)
20
+ @command_summary = summary unless summary.nil?
21
+ @command_summary
22
+ end
23
+
24
+ def usage
25
+ puts "usage: #{::Greenhouse::CLI.command_name} #{command_name} #{valid_arguments.to_s}"
26
+ end
27
+
28
+ def run
29
+ begin
30
+ @command ||= new
31
+ before_hooks.each { |block| @command.instance_eval(&block) }
32
+ @command.run
33
+ after_hooks.each { |block| @command.instance_eval(&block) }
34
+ rescue Exception => e
35
+ puts e.message
36
+ @command.usage if e.is_a?(Scripts::InvalidArgument)
37
+ exit 1
38
+ end
39
+ end
40
+
41
+ def append_after_hook(&block)
42
+ @after_hooks ||= []
43
+ @after_hooks << block
44
+ end
45
+ alias_method :after_hook, :append_after_hook
46
+
47
+ def prepend_after_hook(&block)
48
+ @after_hooks ||= []
49
+ @after_hooks.unshift block
50
+ end
51
+
52
+ def after_hooks
53
+ @after_hooks ||= []
54
+ @after_hooks
55
+ end
56
+
57
+ def append_before_hook(&block)
58
+ @before_hooks ||= []
59
+ @before_hooks << block
60
+ end
61
+ alias_method :before_hook, :append_before_hook
62
+
63
+ def prepend_before_hook(&block)
64
+ @before_hooks ||= []
65
+ @before_hooks.unshift block
66
+ end
67
+
68
+ def before_hooks
69
+ @before_hooks ||= []
70
+ @before_hooks
71
+ end
72
+
73
+ def to_arg
74
+ Scripts::Argument.new(command_name, :summary => command_summary)
75
+ end
76
+ end
77
+
78
+ module InstanceMethods
79
+ %w(command_name command_summary usage).each do |method|
80
+ define_method method do |*args|
81
+ self.class.send method, *args
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,9 @@
1
+ module Greenhouse
2
+ module Commands
3
+ class Configure
4
+ include Command
5
+ command_summary "Configure your applications with .env files"
6
+ # exists as a placeholder for now, so forth rail can override it
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ module Greenhouse
2
+ module Commands
3
+ class Help
4
+ include Command
5
+
6
+ command_summary "Provides help for greenhouse usage"
7
+
8
+ valid_arguments *Commands::commands.map(&:to_arg)
9
+ validate_arguments false
10
+
11
+ def self.usage
12
+ puts <<USAGE
13
+ usage: #{::Greenhouse::CLI.command_name} #{command_name} <command>
14
+
15
+ The available greenhouse commands are:
16
+ USAGE
17
+
18
+ Commands::commands.select { |cmd| cmd != self}.each do |cmd|
19
+ print " %-#{Commands::commands.sort { |a,b| a.command_name.length <=> b.command_name.length }.last.command_name.length + 2}s" % cmd.command_name
20
+ puts "# #{cmd.command_summary}"
21
+ end
22
+ end
23
+
24
+ def run
25
+ if arguments.empty?
26
+ usage
27
+ return
28
+ end
29
+
30
+ begin
31
+ cmd = Commands::commands.select { |cmd| cmd.command_name == arguments.first.key.underscore }.first
32
+ raise if cmd.nil?
33
+
34
+ puts cmd.command_summary
35
+ puts
36
+ print " "
37
+ cmd.usage
38
+ rescue
39
+ puts "Invalid Command: #{arguments.first.key.underscore}"
40
+ usage
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,93 @@
1
+ require 'greenhouse/tasks/sync_project'
2
+
3
+ module Greenhouse
4
+ module Commands
5
+ class Init
6
+ include Command
7
+ command_summary "Initialize the current directory as a Greenhouse projects directory"
8
+
9
+ # Make sure .ignore defaults are set before init
10
+ before_hook do
11
+ unless Projects::ignore_file.exists?
12
+ Projects::ignore_file.open('w') do |f|
13
+ f.write <<IGNORE
14
+ # Add any files here that are checked into your git repository, but are commonly edited
15
+ # or changed when working in a dev environment.
16
+ #
17
+ # Files will be ignored across all projects with `git --assume-unchanged FILE` and all
18
+ # paths are relative to the project directory.
19
+ #
20
+ # If you wish to commit changes to a file, you may run `git --no-assume-unchanged FILE`
21
+ # to unignore it temporarily, then run `git --assume-unchanged FILE` again when you're
22
+ # done.
23
+ #
24
+ # You may also create a .ignore file within each project directory if you'd like, and
25
+ # the files listed there will only be ignored for that project. Of course anything
26
+ # defined in a project .gitignore file is permanently ignored by git and does not
27
+ # need to be accounted for here.
28
+
29
+ # Ignore Gemfile so you can point to local ecosystem paths during development without
30
+ # worry of inadvertently committing your Gemfile.
31
+ Gemfile
32
+
33
+ # Ignore Gemfile.lock (mainly for applications) so that you don't inadvertently commit
34
+ # any development/local gems to the bundle.
35
+ Gemfile.lock
36
+
37
+ # Ignore your application database config since different devs may be using different
38
+ # setups.
39
+ config/database.yml
40
+
41
+ # Ignore tmp dir just in case
42
+ tmp/
43
+ IGNORE
44
+ end
45
+ Projects::ignore_file.reload
46
+ end
47
+ end
48
+
49
+ after_hook do
50
+ puts
51
+ puts "New ecosystem initialized in \e[36m#{Projects::path}\e[0m"
52
+ end
53
+
54
+ class << self
55
+ def usage
56
+ puts "usage: #{::Greenhouse::CLI.command_name} #{command_name} #{valid_arguments.to_s}"
57
+ end
58
+ end
59
+
60
+ def add_another?
61
+ another = nil
62
+ while !['', 'n','no'].include?(another) do
63
+ puts "The following projects will be initialized in your ecosystem:"
64
+ Projects.projects.each do |project|
65
+ puts " \e[36m#{project.title}\e[0m"
66
+ end
67
+ puts
68
+ print "Would you like to add another project before initializing? ([y]es/[N]o): "
69
+ another = STDIN.gets.chomp.downcase
70
+ Tasks::AddProject.perform if ['y','yes'].include?(another)
71
+ end
72
+ end
73
+
74
+ def run
75
+ while Projects.projects.empty? do
76
+ puts "You must define at least one project for your ecosystem.\n"
77
+ Tasks::AddProject.perform
78
+ end
79
+
80
+ # Prompt to add another project
81
+ add_another?
82
+
83
+ # Sync all projects
84
+ Projects.projects.each do |project|
85
+ Tasks::SyncProject.perform(project)
86
+ end
87
+
88
+ # Create a Procfile that uses `greenhouse launch` to launch the app's processes
89
+ Tasks::GenerateProcfile.perform
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,113 @@
1
+ require 'greenhouse/tasks/sync_project'
2
+
3
+ module Greenhouse
4
+ module Commands
5
+ class Launch
6
+ include Command
7
+ command_summary "Launch an application's processes using it's Procfile"
8
+ project_arguments *Projects::applications.map(&:to_arg)
9
+ valid_arguments *Projects::applications.map { |app| app.procfile.processes.select { |key,process| process.enabled? }.keys }.flatten
10
+
11
+ class << self
12
+ def usage
13
+ puts <<USAGE
14
+ usage: #{::Greenhouse::CLI.command_name} #{command_name} <application> [<process> [<process> [...]]]
15
+
16
+ Applications:
17
+ #{project_arguments.to_help}
18
+ USAGE
19
+ end
20
+ end
21
+
22
+ def run
23
+ if arguments.empty?
24
+ puts "Please provide the application you'd like to launch."
25
+ puts
26
+ usage
27
+ return
28
+ end
29
+
30
+ appname = arguments.slice!(0).key
31
+ app = Projects.applications.select { |app| app.name == appname }.first
32
+
33
+ if app.nil?
34
+ if Projects.projects.select { |proj| proj.name == appname }.first.nil?
35
+ puts "The application '#{appname}' does not exist."
36
+ else
37
+ puts "The project '#{appname}' is not defined as an application."
38
+ end
39
+ puts
40
+ usage
41
+ return
42
+ end
43
+
44
+ if !app.exists?
45
+ puts "The application directory #{app.path} does not exist. Are you sure you've initialized your ecosystem?"
46
+ puts
47
+ usage
48
+ return
49
+ end
50
+
51
+ Signal.trap("HUP") { |signo| quit(signo) }
52
+ Signal.trap("INT") { |signo| quit(signo) }
53
+ Signal.trap("KILL") { |signo| quit(signo) }
54
+ Signal.trap("TERM") { |signo| quit(signo) }
55
+
56
+ begin
57
+ @procs = {}
58
+ Bundler.with_clean_env do
59
+ app.chdir do
60
+ if app.dotenv.exists?
61
+ begin
62
+ # We have to unset these because foreman sets them by default, and Dotenv won't override a set value
63
+ %w(PORT RACK_ENV RAILS_ENV).each { |key| ENV[key] = nil }
64
+ require 'dotenv'
65
+ Dotenv.load!
66
+ rescue
67
+ puts "\e[31mError parsing .env file for #{app.title}.\e[0m"
68
+ exit 1
69
+ end
70
+ else
71
+ puts "\e[33mWarning: No .env file found in #{app.title}!\e[0m"
72
+ puts "Your application may not behave as expected without a .env file."
73
+ end
74
+
75
+ unless app.procfile.exists?
76
+ puts "\e[31mNo Procfile found for #{app.title}\e[0m"
77
+ exit 1
78
+ end
79
+
80
+ app.procfile.enabled.each do |key,process|
81
+ next if arguments.length > 0 && !arguments.map(&:key).include?(key)
82
+ @procs[key] = process
83
+ end
84
+
85
+ arguments.map(&:key).each { |key| puts "Skipping process not found in Procfile: #{key}" unless @procs.keys.include?(key) }
86
+ @procs.each do |key,process|
87
+ @procs[key] = fork do
88
+ exec process.command
89
+ end
90
+ end
91
+
92
+ Process.wait
93
+ end
94
+ end
95
+ rescue
96
+ quit 1
97
+ end
98
+
99
+ end
100
+
101
+ def quit(signo)
102
+ @procs.each do |key,pid|
103
+ begin
104
+ Process.kill("KILL", pid)
105
+ rescue # make sure to kill all children
106
+ end
107
+ end
108
+ exit signo
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,42 @@
1
+ module Greenhouse
2
+ module Commands
3
+ class New
4
+ include Command
5
+
6
+ command_summary "Setup a new Greenhouse projects directory"
7
+ validate_arguments false
8
+
9
+ class << self
10
+ def usage
11
+ puts "usage: #{::Greenhouse::CLI.command_name} #{command_name} <name> #{valid_arguments.to_s}"
12
+ end
13
+ end
14
+
15
+ def run
16
+ if arguments.length == 0
17
+ usage
18
+ exit 1
19
+ end
20
+
21
+ projects_directory = arguments.first.key
22
+ if File.exists?(projects_directory)
23
+ STDERR.puts "Directory already exists: #{projects_directory}"
24
+ STDERR.puts "You can try running `#{::Greenhouse::CLI.command_name} init` from inside the directory."
25
+ exit 1
26
+ end
27
+
28
+ begin
29
+ FileUtils.mkdir(projects_directory)
30
+ rescue
31
+ STDERR.puts "Unable to create projects directory: #{projects_directory}"
32
+ exit 1
33
+ end
34
+
35
+ exec "cd #{projects_directory}; greenhouse init"
36
+ #Dir.chdir(projects_directory) do
37
+ # Init.run
38
+ #end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ module Greenhouse
2
+ module Commands
3
+ class Pull
4
+ include Command
5
+ command_summary "Pull and merge remote branches for all projects"
6
+ project_arguments *Projects::projects.map(&:to_arg)
7
+
8
+ class << self
9
+ def usage
10
+ puts <<USAGE
11
+ usage: #{::Greenhouse::CLI.command_name} #{command_name} [<project>] #{valid_arguments.to_s}
12
+
13
+ Projects:
14
+ #{project_arguments.to_help}
15
+ USAGE
16
+ end
17
+ end
18
+
19
+ def pull_all
20
+ if Projects.projects.empty?
21
+ puts "No projects defined."
22
+ return
23
+ end
24
+
25
+ Projects.projects.each do |project|
26
+ Tasks::PullProject.perform(project)
27
+ end
28
+ end
29
+
30
+ def pull_project(project)
31
+ unless project.exists?
32
+ puts "Project \e[36m#{project.title}\e[0m does not exist. Try initializing it with `greenhouse init`"
33
+ return
34
+ end
35
+
36
+ Tasks::PullProject.perform(project)
37
+ end
38
+
39
+ def run
40
+ project = Projects::projects.select { |project| arguments.map(&:key).include?(project.name) }.first
41
+ project.nil? ? pull_all : pull_project(project)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,91 @@
1
+ module Greenhouse
2
+ module Commands
3
+ class Purge
4
+ include ::Greenhouse::Commands::Command
5
+ command_summary "Purge project directories from your ecosystem allowing you to start from scratch"
6
+ valid_argument Scripts::Argument.new("-a --all", :summary => "Remove your ecosystem configuration files in addition to project directories (will NOT prompt when combined with -f)")
7
+ valid_argument Scripts::Argument.new(["-f", "--force"], :summary => "Force the purge, will still prompt if you have local changes or unpushed local branches")
8
+ project_arguments *Projects::projects.map(&:to_arg)
9
+
10
+ class << self
11
+ def usage
12
+ puts <<USAGE
13
+ usage: #{::Greenhouse::CLI.command_name} #{command_name} [<project>] #{valid_arguments.to_s}
14
+
15
+ Arguments:
16
+ #{valid_arguments.to_help}
17
+
18
+ Projects:
19
+ #{project_arguments.to_help}
20
+ USAGE
21
+ end
22
+ end
23
+
24
+ def clean?
25
+ return false if Projects::projects_file.exists?
26
+ return false if Projects::ignore_file.exists?
27
+ return false if Projects::procfile.exists?
28
+ return false if Projects::projects.any?(&:exists?)
29
+ true
30
+ end
31
+
32
+ def force?
33
+ arguments.map(&:key).include?("-f")
34
+ end
35
+
36
+ def purge_all?
37
+ arguments.map(&:key).include?("-a")
38
+ end
39
+
40
+ def run
41
+ app = Projects::projects.select { |project| arguments.map(&:key).include?(project.name) }.first
42
+ if app.nil?
43
+ if clean?
44
+ puts "Nothing to do."
45
+ return
46
+ end
47
+ #return unless force? || prompt
48
+
49
+ purge
50
+ else
51
+ Tasks::PurgeProject.perform(app, force?)
52
+ end
53
+
54
+ puts "\e[33mDone\e[0m"
55
+ return
56
+ end
57
+
58
+ def purge
59
+ Projects.projects.each do |project|
60
+ Tasks::PurgeProject.perform(project, force?)
61
+ end
62
+
63
+ Tasks::RemoveGreenhouseFiles.perform(force?) if purge_all?
64
+ end
65
+
66
+ def prompt
67
+ puts <<PUTS
68
+ \e[31mThis will completely clean out the ecosystem, removing all projects and
69
+ optionally dropping the database and wiping configuration files.\e[0m
70
+
71
+ PUTS
72
+ puts <<PUTS
73
+ You will be prompted if you have any uncommitted local changes or unpushed branches
74
+ in any of your projects and may choose to either take action or skip that project
75
+ to avoid losing any work.
76
+
77
+ PUTS
78
+ puts <<PUTS
79
+ You will be prompted separately to confirm whether you'd like to drop or keep your
80
+ databases.
81
+ PUTS
82
+
83
+ puts
84
+ print "Are you sure you want to continue? ([y]es/[N]o): "
85
+ continue = STDIN.gets.chomp.downcase
86
+ ["y","yes"].include?(continue)
87
+ end
88
+
89
+ end
90
+ end
91
+ end