hem 1.0.1.beta1

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 (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