greenhouse 0.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 (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