hem 1.0.1.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +10 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +125 -0
- data/DoD.md +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +71 -0
- data/Guardfile +14 -0
- data/Hemfile +43 -0
- data/LICENSE +21 -0
- data/README.md +42 -0
- data/Rakefile +23 -0
- data/bin/hem +64 -0
- data/features/deps.feature +43 -0
- data/features/hem/basic.feature +43 -0
- data/features/hem/help.feature +16 -0
- data/features/hem/subcommands.feature +15 -0
- data/features/seed/plant.feature +64 -0
- data/features/step_definitions/env.rb +6 -0
- data/features/step_definitions/seed.rb +11 -0
- data/features/support/env.rb +6 -0
- data/hem.gemspec +47 -0
- data/lib/hem/asset_applicator.rb +33 -0
- data/lib/hem/asset_applicators/files.rb +5 -0
- data/lib/hem/asset_applicators/sqldump.rb +38 -0
- data/lib/hem/cli.rb +252 -0
- data/lib/hem/config/file.rb +22 -0
- data/lib/hem/config.rb +5 -0
- data/lib/hem/error_handlers/debug.rb +12 -0
- data/lib/hem/error_handlers/exit_code_map.rb +17 -0
- data/lib/hem/error_handlers/friendly.rb +58 -0
- data/lib/hem/errors.rb +89 -0
- data/lib/hem/help_formatter.rb +118 -0
- data/lib/hem/helper/file_locator.rb +44 -0
- data/lib/hem/helper/github.rb +10 -0
- data/lib/hem/helper/http_download.rb +41 -0
- data/lib/hem/helper/shell.rb +101 -0
- data/lib/hem/helper/vm_command.rb +30 -0
- data/lib/hem/lib/github/api.rb +48 -0
- data/lib/hem/lib/github/client.rb +52 -0
- data/lib/hem/lib/host_check/deps.rb +39 -0
- data/lib/hem/lib/host_check/git.rb +76 -0
- data/lib/hem/lib/host_check/ruby.rb +53 -0
- data/lib/hem/lib/host_check/vagrant.rb +45 -0
- data/lib/hem/lib/host_check.rb +34 -0
- data/lib/hem/lib/s3/local/file.rb +40 -0
- data/lib/hem/lib/s3/local/iohandler.rb +36 -0
- data/lib/hem/lib/s3/remote/file.rb +57 -0
- data/lib/hem/lib/s3/remote/iohandler.rb +38 -0
- data/lib/hem/lib/s3/sync.rb +134 -0
- data/lib/hem/lib/seed/project.rb +71 -0
- data/lib/hem/lib/seed/replacer.rb +56 -0
- data/lib/hem/lib/seed/seed.rb +111 -0
- data/lib/hem/lib/self_signed_cert_generator.rb +38 -0
- data/lib/hem/lib/vm/command.rb +131 -0
- data/lib/hem/lib/vm/inspector.rb +73 -0
- data/lib/hem/logging.rb +20 -0
- data/lib/hem/metadata.rb +42 -0
- data/lib/hem/null.rb +31 -0
- data/lib/hem/patches/deepstruct.rb +21 -0
- data/lib/hem/patches/rake.rb +101 -0
- data/lib/hem/patches/rubygems.rb +6 -0
- data/lib/hem/patches/slop.rb +69 -0
- data/lib/hem/paths.rb +96 -0
- data/lib/hem/tasks/assets.rb +92 -0
- data/lib/hem/tasks/config.rb +15 -0
- data/lib/hem/tasks/deps.rb +103 -0
- data/lib/hem/tasks/exec.rb +3 -0
- data/lib/hem/tasks/magento.rb +281 -0
- data/lib/hem/tasks/ops.rb +6 -0
- data/lib/hem/tasks/pr.rb +45 -0
- data/lib/hem/tasks/seed.rb +61 -0
- data/lib/hem/tasks/self.rb +45 -0
- data/lib/hem/tasks/shell_init.rb +25 -0
- data/lib/hem/tasks/system/completions.rb +76 -0
- data/lib/hem/tasks/system.rb +18 -0
- data/lib/hem/tasks/tools.rb +17 -0
- data/lib/hem/tasks/vm.rb +140 -0
- data/lib/hem/ui.rb +182 -0
- data/lib/hem/util.rb +76 -0
- data/lib/hem/version.rb +3 -0
- data/lib/hem.rb +72 -0
- data/lib/hobo/tasks/magento.rb +3 -0
- data/spec/hem/asset_applicator_spec.rb +30 -0
- data/spec/hem/cli_spec.rb +166 -0
- data/spec/hem/config/file_spec.rb +55 -0
- data/spec/hem/error_handlers/debug_spec.rb +43 -0
- data/spec/hem/error_handlers/friendly_spec.rb +97 -0
- data/spec/hem/error_spec.rb +0 -0
- data/spec/hem/help_formatter_spec.rb +162 -0
- data/spec/hem/helpers/file_locator_spec.rb +11 -0
- data/spec/hem/helpers/github_spec.rb +31 -0
- data/spec/hem/helpers/shell_spec.rb +22 -0
- data/spec/hem/helpers/vm_command_spec.rb +96 -0
- data/spec/hem/lib/github/api_spec.rb +92 -0
- data/spec/hem/lib/s3/sync_spec.rb +16 -0
- data/spec/hem/lib/seed/project_spec.rb +80 -0
- data/spec/hem/lib/seed/replacer_spec.rb +45 -0
- data/spec/hem/lib/seed/seed_spec.rb +127 -0
- data/spec/hem/logging_spec.rb +27 -0
- data/spec/hem/metadata_spec.rb +55 -0
- data/spec/hem/null_spec.rb +30 -0
- data/spec/hem/patches/rake_spec.rb +230 -0
- data/spec/hem/paths_spec.rb +75 -0
- data/spec/hem/ui_spec.rb +189 -0
- data/spec/hem/util_spec.rb +74 -0
- data/spec/spec_helper.rb +12 -0
- data/ssl/ca-bundle-s3.crt +3554 -0
- data/test_files/vagrant_fail/vagrant +2 -0
- 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,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,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
|