hem 1.0.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +10 -0
  3. data/.gitignore +3 -0
  4. data/.rspec +2 -0
  5. data/CHANGELOG.md +125 -0
  6. data/DoD.md +5 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +71 -0
  9. data/Guardfile +14 -0
  10. data/Hemfile +43 -0
  11. data/LICENSE +21 -0
  12. data/README.md +42 -0
  13. data/Rakefile +23 -0
  14. data/bin/hem +64 -0
  15. data/features/deps.feature +43 -0
  16. data/features/hem/basic.feature +43 -0
  17. data/features/hem/help.feature +16 -0
  18. data/features/hem/subcommands.feature +15 -0
  19. data/features/seed/plant.feature +64 -0
  20. data/features/step_definitions/env.rb +6 -0
  21. data/features/step_definitions/seed.rb +11 -0
  22. data/features/support/env.rb +6 -0
  23. data/hem.gemspec +47 -0
  24. data/lib/hem/asset_applicator.rb +33 -0
  25. data/lib/hem/asset_applicators/files.rb +5 -0
  26. data/lib/hem/asset_applicators/sqldump.rb +38 -0
  27. data/lib/hem/cli.rb +252 -0
  28. data/lib/hem/config/file.rb +22 -0
  29. data/lib/hem/config.rb +5 -0
  30. data/lib/hem/error_handlers/debug.rb +12 -0
  31. data/lib/hem/error_handlers/exit_code_map.rb +17 -0
  32. data/lib/hem/error_handlers/friendly.rb +58 -0
  33. data/lib/hem/errors.rb +89 -0
  34. data/lib/hem/help_formatter.rb +118 -0
  35. data/lib/hem/helper/file_locator.rb +44 -0
  36. data/lib/hem/helper/github.rb +10 -0
  37. data/lib/hem/helper/http_download.rb +41 -0
  38. data/lib/hem/helper/shell.rb +101 -0
  39. data/lib/hem/helper/vm_command.rb +30 -0
  40. data/lib/hem/lib/github/api.rb +48 -0
  41. data/lib/hem/lib/github/client.rb +52 -0
  42. data/lib/hem/lib/host_check/deps.rb +39 -0
  43. data/lib/hem/lib/host_check/git.rb +76 -0
  44. data/lib/hem/lib/host_check/ruby.rb +53 -0
  45. data/lib/hem/lib/host_check/vagrant.rb +45 -0
  46. data/lib/hem/lib/host_check.rb +34 -0
  47. data/lib/hem/lib/s3/local/file.rb +40 -0
  48. data/lib/hem/lib/s3/local/iohandler.rb +36 -0
  49. data/lib/hem/lib/s3/remote/file.rb +57 -0
  50. data/lib/hem/lib/s3/remote/iohandler.rb +38 -0
  51. data/lib/hem/lib/s3/sync.rb +134 -0
  52. data/lib/hem/lib/seed/project.rb +71 -0
  53. data/lib/hem/lib/seed/replacer.rb +56 -0
  54. data/lib/hem/lib/seed/seed.rb +111 -0
  55. data/lib/hem/lib/self_signed_cert_generator.rb +38 -0
  56. data/lib/hem/lib/vm/command.rb +131 -0
  57. data/lib/hem/lib/vm/inspector.rb +73 -0
  58. data/lib/hem/logging.rb +20 -0
  59. data/lib/hem/metadata.rb +42 -0
  60. data/lib/hem/null.rb +31 -0
  61. data/lib/hem/patches/deepstruct.rb +21 -0
  62. data/lib/hem/patches/rake.rb +101 -0
  63. data/lib/hem/patches/rubygems.rb +6 -0
  64. data/lib/hem/patches/slop.rb +69 -0
  65. data/lib/hem/paths.rb +96 -0
  66. data/lib/hem/tasks/assets.rb +92 -0
  67. data/lib/hem/tasks/config.rb +15 -0
  68. data/lib/hem/tasks/deps.rb +103 -0
  69. data/lib/hem/tasks/exec.rb +3 -0
  70. data/lib/hem/tasks/magento.rb +281 -0
  71. data/lib/hem/tasks/ops.rb +6 -0
  72. data/lib/hem/tasks/pr.rb +45 -0
  73. data/lib/hem/tasks/seed.rb +61 -0
  74. data/lib/hem/tasks/self.rb +45 -0
  75. data/lib/hem/tasks/shell_init.rb +25 -0
  76. data/lib/hem/tasks/system/completions.rb +76 -0
  77. data/lib/hem/tasks/system.rb +18 -0
  78. data/lib/hem/tasks/tools.rb +17 -0
  79. data/lib/hem/tasks/vm.rb +140 -0
  80. data/lib/hem/ui.rb +182 -0
  81. data/lib/hem/util.rb +76 -0
  82. data/lib/hem/version.rb +3 -0
  83. data/lib/hem.rb +72 -0
  84. data/lib/hobo/tasks/magento.rb +3 -0
  85. data/spec/hem/asset_applicator_spec.rb +30 -0
  86. data/spec/hem/cli_spec.rb +166 -0
  87. data/spec/hem/config/file_spec.rb +55 -0
  88. data/spec/hem/error_handlers/debug_spec.rb +43 -0
  89. data/spec/hem/error_handlers/friendly_spec.rb +97 -0
  90. data/spec/hem/error_spec.rb +0 -0
  91. data/spec/hem/help_formatter_spec.rb +162 -0
  92. data/spec/hem/helpers/file_locator_spec.rb +11 -0
  93. data/spec/hem/helpers/github_spec.rb +31 -0
  94. data/spec/hem/helpers/shell_spec.rb +22 -0
  95. data/spec/hem/helpers/vm_command_spec.rb +96 -0
  96. data/spec/hem/lib/github/api_spec.rb +92 -0
  97. data/spec/hem/lib/s3/sync_spec.rb +16 -0
  98. data/spec/hem/lib/seed/project_spec.rb +80 -0
  99. data/spec/hem/lib/seed/replacer_spec.rb +45 -0
  100. data/spec/hem/lib/seed/seed_spec.rb +127 -0
  101. data/spec/hem/logging_spec.rb +27 -0
  102. data/spec/hem/metadata_spec.rb +55 -0
  103. data/spec/hem/null_spec.rb +30 -0
  104. data/spec/hem/patches/rake_spec.rb +230 -0
  105. data/spec/hem/paths_spec.rb +75 -0
  106. data/spec/hem/ui_spec.rb +189 -0
  107. data/spec/hem/util_spec.rb +74 -0
  108. data/spec/spec_helper.rb +12 -0
  109. data/ssl/ca-bundle-s3.crt +3554 -0
  110. data/test_files/vagrant_fail/vagrant +2 -0
  111. metadata +339 -0
data/hem.gemspec ADDED
@@ -0,0 +1,47 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'hem/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "hem"
9
+ spec.version = Hem::VERSION.gsub('-', '.')
10
+ spec.authors = ["Mike Simons"]
11
+ spec.email = ["msimons@inviqa.com"]
12
+ spec.description = %q{Inviqan toolbelt}
13
+ spec.summary = %q{Inviqan toolbelt}
14
+ spec.homepage = ""
15
+
16
+ # This file will get interpretted at runtime due to Bundler.setup
17
+ # Without the $HEM_ARGV check (set in bin/hem) fatal: not a git repository errors show up
18
+ spec.files = `git ls-files`.split($/) if ENV['HEM_BUILD'] == '1'
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "slop", "~> 3.4.7"
24
+ spec.add_dependency "highline", "~> 1.6.20"
25
+ spec.add_dependency "rake", "~> 10.1.1"
26
+ spec.add_dependency "deepstruct", "~> 0.0.5"
27
+ spec.add_dependency "semantic", "~> 1.3.0"
28
+ spec.add_dependency "aws-sdk", "~> 2.0.24"
29
+ spec.add_dependency "ruby-progressbar", "~> 1.4.1"
30
+ spec.add_dependency "teerb", "~> 0.0.1"
31
+ spec.add_dependency "net-ssh-simple", "~> 1.6.3"
32
+ spec.add_dependency "pry", "~> 0.9.12"
33
+ spec.add_dependency "octokit", "~> 3.0"
34
+
35
+ # This prevents Bundler.setup from complaining that rubygems did not install dev deps
36
+ # If you want to run dev deps you need to ensure HEM_ENV=dev is set for bundle install & bundle exec
37
+ if ENV['HEM_ENV'] == 'dev'
38
+ spec.add_development_dependency "aruba", "~> 0.5.4"
39
+ spec.add_development_dependency "rspec", "~> 2.14.1"
40
+ spec.add_development_dependency "fakefs", "~> 0.5.0"
41
+ spec.add_development_dependency "rr", "~> 1.1.2"
42
+ spec.add_development_dependency "guard", "~> 2.2.5"
43
+ spec.add_development_dependency "guard-rspec", "~> 4.2.4"
44
+ spec.add_development_dependency "guard-cucumber", "~> 1.4.1"
45
+ spec.add_development_dependency "simplecov", "~> 0.7.1"
46
+ end
47
+ end
@@ -0,0 +1,33 @@
1
+ module Hem
2
+ class << self
3
+ attr_accessor :asset_applicators
4
+
5
+ # Utility method to access (with initialization) the asset applicator registry.
6
+ # This allows you to register new asset applicator methods on a per-project basis.
7
+ # For example:
8
+ #
9
+ # Hem.asset_applicators.register /.*\.zip/ do |file|
10
+ # Dir.chdir File.dirname(file) do
11
+ # shell "unzip", file
12
+ # end
13
+ # end
14
+ #
15
+ # @see Hem::AssetApplicatorRegistry
16
+ # @return [Hem::AssetApplicatorRegistry] Applicator registry cotnainer
17
+ def asset_applicators
18
+ @asset_applicators ||= AssetApplicatorRegistry.new
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # Thin wrapper over a hash to provide a means to "register" asset applicators
25
+ class AssetApplicatorRegistry < Hash
26
+ # Register a new asset applicator
27
+ # @param [Regexp] Pattern to match against asset filename.
28
+ # @yield The block to be executed when an asset matches the pattern.
29
+ def register pattern, &block
30
+ self[pattern] = block
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ # Built in applicators
2
+ Hem.asset_applicators.register /.*\.files\.(tgz|tar\.gz|tar\.bz2)/ do |file|
3
+ Hem.ui.title "Applying file dump (#{file})"
4
+ vm_shell "tar -xvf #{file.shellescape}"
5
+ end
@@ -0,0 +1,38 @@
1
+ # Built in applicators
2
+ Hem.asset_applicators.register /.*\.sql\.gz/ do |file|
3
+ matches = file.match(/^([^\.]+).*\.sql\.gz/)
4
+ db = File.basename(matches[1])
5
+
6
+ status = {
7
+ :db_exists => false,
8
+ :db_has_tables => false
9
+ }
10
+
11
+ begin
12
+ result = shell(vm_mysql(:db => db).pipe('SHOW TABLES; SELECT FOUND_ROWS();', :on => :vm), :capture => true)
13
+ status[:db_exists] = true
14
+ status[:db_has_tables] = !(result.split("\n").last.strip == '0')
15
+ rescue Hem::ExternalCommandError
16
+ # This will fail later with a more useful error message
17
+ end
18
+
19
+ if status[:db_exists] && status[:db_has_tables]
20
+ Hem.ui.warning "Already applied (#{file})"
21
+ next
22
+ end
23
+
24
+ if status[:db_exists] && !status[:db_has_tables]
25
+ # Db exists but is empty
26
+ shell(vm_mysql(:mysql => 'mysqladmin', :append => " --force drop #{db.shellescape}"))
27
+ end
28
+
29
+ begin
30
+ Hem.ui.title "Applying mysqldump (#{file})"
31
+ shell(vm_mysql(:mysql => 'mysqladmin', :append => " create #{db.shellescape}"))
32
+ shell(vm_mysql(:auto_echo => false, :db => db) < "zcat #{file.shellescape}")
33
+ rescue Hem::ExternalCommandError => exception
34
+ Hem.ui.error "Could not apply #{file} due to the following error:\n"
35
+ Hem.ui.error File.read(exception.output.path).strip
36
+ raise exception
37
+ end
38
+ end
data/lib/hem/cli.rb ADDED
@@ -0,0 +1,252 @@
1
+ module Hem
2
+
3
+ class << self
4
+ attr_accessor :cli
5
+ end
6
+
7
+ # Utility error to shortcut exit routine within actions
8
+ class Halt < Error
9
+ end
10
+
11
+ # Main application class
12
+ class Cli
13
+ include Hem::Logging
14
+
15
+ attr_accessor :slop, :help_formatter
16
+
17
+ # @param [Hash] Initialization accepts several options in a hash:
18
+ # - :slop - Slop instance
19
+ # - :help - Help formatter instance
20
+ # - :host_check - Host check invocation class
21
+ #
22
+ # :help and :host_check are only used by tests.
23
+ # :slop is used to ensure low-level args parsed in bin/hem are propagated to the application
24
+ def initialize opts = {}
25
+ @opts = opts
26
+ @slop = opts[:slop] || Slop.new
27
+ @help_formatter = opts[:help] || Hem::HelpFormatter.new(@slop)
28
+ @help_opts = {}
29
+ @host_check = opts[:host_check] || Hem::Lib::HostCheck
30
+ end
31
+
32
+ # entry point for application
33
+ # @param [Array] Arguments from ARGV. Defaults to ARGV
34
+ def start args = ARGV
35
+ load_user_config
36
+ load_builtin_tasks
37
+ load_hemfiles
38
+ load_project_config
39
+ Hem.chefdk_compat
40
+
41
+ tasks = structure_tasks Hem::Metadata.metadata.keys
42
+ define_global_opts @slop
43
+
44
+ begin
45
+ # Parse out global args first
46
+ @slop.parse! args
47
+ opts = @slop.to_hash
48
+
49
+ perform_host_checks unless opts[:'skip-host-checks']
50
+
51
+ @help_opts[:all] = opts[:all]
52
+
53
+ @slop.add_callback :empty do
54
+ show_help
55
+ end
56
+
57
+ # Necessary to make command level help work
58
+ args.push "--help" if @slop.help?
59
+
60
+ @help_formatter.command_map = define_tasks(tasks, @slop)
61
+
62
+ remaining = @slop.parse! args
63
+ raise Hem::InvalidCommandOrOpt.new remaining.join(" "), self if remaining.size > 0
64
+
65
+ show_help if @slop.help?
66
+ rescue Halt
67
+ # NOP
68
+ end
69
+
70
+ return 0
71
+ end
72
+
73
+ # Display help and exit
74
+ # @param [Hash] Options to apss to help formatter.
75
+ # Options are mostly used for filtering
76
+ def show_help(opts = {})
77
+ Hem.ui.info @help_formatter.help(@help_opts.merge(opts))
78
+ halt
79
+ end
80
+
81
+ private
82
+
83
+ def load_builtin_tasks
84
+ require 'hem/tasks/assets'
85
+ require 'hem/tasks/config'
86
+ require 'hem/tasks/deps'
87
+ require 'hem/tasks/exec'
88
+ require 'hem/tasks/ops'
89
+ require 'hem/tasks/pr'
90
+ require 'hem/tasks/seed'
91
+ require 'hem/tasks/self'
92
+ require 'hem/tasks/shell_init'
93
+ require 'hem/tasks/system'
94
+ require 'hem/tasks/system/completions'
95
+ require 'hem/tasks/tools'
96
+ require 'hem/tasks/vm'
97
+ end
98
+
99
+ def load_user_config
100
+ Hem.user_config = Hem::Config::File.load Hem.user_config_file
101
+ end
102
+
103
+ def load_project_config
104
+ if Hem.in_project?
105
+ Hem.project_config = Hem::Config::File.load Hem.project_config_file
106
+ else
107
+ Hem.project_config = DeepStruct.wrap({})
108
+ end
109
+ end
110
+
111
+ def load_hemfiles
112
+ if Hem.in_project? && File.exists?(Hem.hemfile_path)
113
+ logger.debug("cli: Loading hemfile @ #{Hem.hemfile_path}")
114
+ eval(File.read(Hem.hemfile_path), TOPLEVEL_BINDING, Hem.hemfile_path)
115
+ end
116
+
117
+ if File.exists?(Hem.user_hemfile_path)
118
+ logger.debug("cli: Loading hemfile @ #{Hem.user_hemfile_path}")
119
+ eval(File.read(Hem.user_hemfile_path), TOPLEVEL_BINDING, Hem.user_hemfile_path)
120
+ end
121
+ end
122
+
123
+ def perform_host_checks
124
+ checks = [
125
+ 'vagrant.*',
126
+ 'ssh_present',
127
+ 'git_present'
128
+ ]
129
+
130
+ @host_check.check(
131
+ :filter => /#{checks.join('|')}/,
132
+ :raise => true
133
+ )
134
+ end
135
+
136
+ def define_global_opts slop
137
+ slop.on '-a', '--all', 'Show hidden commands'
138
+ slop.on '-h', '--help', 'Display help'
139
+
140
+ slop.on '-v', '--version', 'Print version information' do
141
+ Hem.ui.info "Hem version #{Hem::VERSION}"
142
+ halt
143
+ end
144
+ end
145
+
146
+ def halt
147
+ raise Halt.new
148
+ end
149
+
150
+ # Takes a nested hash of commands and creates nested Slop instances populated with metadata.
151
+ #
152
+ # @param [Hash] A nested hash of namespaces & commands.
153
+ # @param [Slop] A slop instance on which to define tasks.
154
+ # @param [Array] Stack of string names of parental namespaces.
155
+ # @param [Hash] Hash of "namespace:command" => Slop instances.
156
+ # @return [Hash] Hash of "namespace:command" => Slop instances.
157
+ def define_tasks structured_list, scope, stack = [], map = {}
158
+ structured_list.each do |k, v|
159
+ name = (stack + [k]).join(':')
160
+ new_stack = stack + [k]
161
+ logger.debug("cli: Defined #{name}")
162
+ map[name] = if v.size == 0
163
+ define_command(name, scope, new_stack)
164
+ else
165
+ define_namespace(name, scope, new_stack, v, map)
166
+ end
167
+ end
168
+ return map
169
+ end
170
+
171
+ # Map rake namespace to a Slop command
172
+ def define_namespace name, scope, stack, subtasks, map
173
+ metadata = Hem::Metadata.metadata[name]
174
+ hem = self
175
+ new_scope = nil
176
+
177
+ scope.instance_eval do
178
+ new_scope = command stack.last do
179
+
180
+ description metadata[:desc]
181
+ long_description metadata[:long_desc]
182
+ hidden metadata[:hidden]
183
+ project_only metadata[:project_only]
184
+
185
+ # NOP; run runs help anyway
186
+ on '-h', '--help', 'Display help' do end
187
+
188
+ run do |opts, args|
189
+ hem.show_help(target: name)
190
+ end
191
+ end
192
+ end
193
+
194
+ define_tasks subtasks, new_scope, stack, map
195
+ return new_scope
196
+ end
197
+
198
+ # Map rake task to a Slop command
199
+ def define_command name, scope, stack
200
+ metadata = Hem::Metadata.metadata[name]
201
+ hem = self
202
+ new_scope = nil
203
+
204
+ scope.instance_eval do
205
+ new_scope = command stack.last do
206
+ task = Rake::Task[name]
207
+
208
+ description metadata[:desc]
209
+ long_description metadata[:long_desc]
210
+ arg_list task.arg_names
211
+ hidden metadata[:hidden]
212
+ project_only metadata[:project_only]
213
+
214
+ metadata[:opts].each do |opt|
215
+ on *opt
216
+ end if metadata[:opts]
217
+
218
+ on '-h', '--help', 'Display help' do
219
+ hem.show_help(target: name)
220
+ end
221
+
222
+ run do |opts, args|
223
+ Dir.chdir Hem.project_path if Hem.in_project?
224
+ raise ::Hem::ProjectOnlyError.new if opts.project_only && !Hem.in_project?
225
+ task.opts = opts.to_hash.merge({:_unparsed => hem.slop.unparsed})
226
+ raise ::Hem::MissingArgumentsError.new(name, args, hem) if args && task.arg_names.length > args.length
227
+ task.invoke *args
228
+ args.pop(task.arg_names.size)
229
+ task.opts = nil
230
+ end
231
+ end
232
+ end
233
+
234
+ return new_scope
235
+ end
236
+
237
+ # Expand flat task list in to hierarchy (non-recursive)
238
+ # @param [Array] List of strings with entries of the form "namespace1:namespace2:task"
239
+ def structure_tasks list
240
+ out = {}
241
+ list.each do |name|
242
+ ref = out
243
+ name = name.split(":")
244
+ name.each do |n|
245
+ ref[n] ||= {}
246
+ ref = ref[n]
247
+ end
248
+ end
249
+ out
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,22 @@
1
+ module Hem
2
+ module Config
3
+ class File
4
+ def self.save(file, config)
5
+ require 'yaml'
6
+ config = config.unwrap if config.public_methods.include? :unwrap
7
+ dir = ::File.dirname file
8
+ FileUtils.mkdir_p dir unless ::File.exists? dir
9
+ ::File.open(file, 'w+') do |f|
10
+ f.puts config.to_yaml
11
+ end
12
+ end
13
+
14
+ def self.load(file)
15
+ require 'yaml'
16
+ config = ::File.exists?(file) ? YAML.load_file(file) : {}
17
+ raise "Invalid hem configuration (#{file})" unless config
18
+ return DeepStruct.wrap(config)
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/hem/config.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Hem
2
+ class << self
3
+ attr_accessor :project_config, :user_config
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module Hem
2
+ module ErrorHandlers
3
+ class Debug
4
+ include Hem::ErrorHandlers::ExitCodeMap
5
+
6
+ def handle error
7
+ Hem.ui.error "\n(#{error.class}) #{error.message}\n\n#{(error.backtrace || []).join("\n")}"
8
+ return EXIT_CODES[error.class.to_s] || DEFAULT_EXIT_CODE
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module Hem
2
+ module ErrorHandlers
3
+ module ExitCodeMap
4
+ DEFAULT_EXIT_CODE = 128
5
+ EXIT_CODES = {
6
+ 'Interrupt' => 1,
7
+ 'Hem::ExternalCommandError' => 3,
8
+ 'Hem::InvalidCommandOrOpt' => 4,
9
+ 'Hem::MissingArgumentsError' => 5,
10
+ 'Hem::UserError' => 6,
11
+ 'Hem::ProjectOnlyError' => 7,
12
+ 'Hem::HostCheckError' => 8,
13
+ 'Hem::Error' => 9
14
+ }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ module Hem
2
+ module ErrorHandlers
3
+ class Friendly
4
+ include Hem::ErrorHandlers::ExitCodeMap
5
+
6
+ def handle error
7
+ require 'tmpdir'
8
+ log_file = File.join(Dir.tmpdir, 'hem_error.log')
9
+
10
+ # Not possible to match Interrupt class unless we use class name as string for some reason!
11
+ case error.class.to_s
12
+ when "Interrupt"
13
+ Hem.ui.warning "\n\nCaught Interrupt. Aborting\n"
14
+ when "Hem::ExternalCommandError"
15
+ FileUtils.cp error.output.path, log_file
16
+
17
+ File.open(log_file, "a") do |file|
18
+ file.write "\n(#{error.class}) #{error.message}\n\n#{error.backtrace.join("\n")}"
19
+ end
20
+
21
+ Hem.ui.error <<-ERROR
22
+
23
+ The following external command appears to have failed (exit status #{error.exit_code}):
24
+ #{error.command}
25
+
26
+ The output of the command has been logged to #{log_file}
27
+ ERROR
28
+ when "Hem::InvalidCommandOrOpt"
29
+ Hem.ui.error "\n#{error.message}"
30
+ Hem.ui.info error.cli.help_formatter.help if error.cli
31
+ when "Hem::MissingArgumentsError"
32
+ Hem.ui.error "\n#{error.message}"
33
+ Hem.ui.info error.cli.help_formatter.help(target: error.command) if error.cli
34
+ when "Hem::UserError"
35
+ Hem.ui.error "\n#{error.message}\n"
36
+ when "Hem::ProjectOnlyError"
37
+ Hem.ui.error "\nHem requires you to be in a project directory for this command!\n"
38
+ when "Hem::HostCheckError"
39
+ Hem.ui.error "\nHem has detected a problem with your system configuration:\n"
40
+ Hem.ui.warning error.advice.gsub(/^/, ' ')
41
+ when "Hem::Error"
42
+ Hem.ui.error "\n#{error.message}\n"
43
+ else
44
+ File.write(log_file, "(#{error.class}) #{error.message}\n\n#{error.backtrace.join("\n")}")
45
+ Hem.ui.error <<-ERROR
46
+
47
+ An unexpected error has occured:
48
+ #{error.message}
49
+
50
+ The backtrace has been logged to #{log_file}
51
+ ERROR
52
+ end
53
+
54
+ return EXIT_CODES[error.class.to_s] || DEFAULT_EXIT_CODE
55
+ end
56
+ end
57
+ end
58
+ end
data/lib/hem/errors.rb ADDED
@@ -0,0 +1,89 @@
1
+ module Hem
2
+ class Error < StandardError
3
+ attr_reader :exit_code
4
+ end
5
+
6
+ class RubyVersionError < Error
7
+ def initialize
8
+ super("Ruby 1.9+ is required to run hem")
9
+ end
10
+ end
11
+
12
+ class MissingDependencies < Error
13
+ def initialize deps
14
+ deps.map! { |dep| " - #{dep}"}
15
+ super("Hem requires the following commands to be available on your path:\n\n" + deps.join("\n"))
16
+ end
17
+ end
18
+
19
+ class InvalidCommandOrOpt < Error
20
+ attr_accessor :command, :cli
21
+ def initialize command, cli = nil
22
+ @command = command
23
+ @cli = cli
24
+ super("Invalid command or option specified: '#{command}'")
25
+ end
26
+ end
27
+
28
+ class MissingArgumentsError < Error
29
+ attr_accessor :command, :cli
30
+ def initialize command, args, cli = nil
31
+ @command = command
32
+ @args = args
33
+ @cli = cli
34
+ super("Not enough arguments for #{command}")
35
+ end
36
+ end
37
+
38
+ class ExternalCommandError < Error
39
+ attr_accessor :command, :exit_code, :output
40
+
41
+ def initialize command, exit_code, output
42
+ @command = command
43
+ @exit_code = exit_code
44
+ @output = output
45
+ super("'#{command}' returned exit code #{exit_code}")
46
+ end
47
+ end
48
+
49
+ class UserError < Error
50
+ end
51
+
52
+ class ProjectOnlyError < Error
53
+ end
54
+
55
+ class NonInteractiveError < Error
56
+ def initialize question
57
+ @question = question
58
+ super("A task requested input from the user but hem is in non-interactive mode")
59
+ end
60
+ end
61
+
62
+ class MissingDependency < Error
63
+ def initialize dep
64
+ @dependency = dep
65
+ super("A tool that hem depends on could not be detected (#{dep})")
66
+ end
67
+ end
68
+
69
+ class HostCheckError < Error
70
+ attr_accessor :summary, :advice
71
+ def initialize summary, advice
72
+ @summary = summary
73
+ @advice = advice
74
+ super("Host check failed: #{summary}")
75
+ end
76
+ end
77
+
78
+ class UndefinedEditorError < Error
79
+ def initialize
80
+ super('You need to define a preferred editor, either in your hem config or with the EDITOR environment variable')
81
+ end
82
+ end
83
+
84
+ class GithubAuthenticationError < Error
85
+ end
86
+
87
+ class GithubApiError < Error
88
+ end
89
+ end