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