hobo-inviqa 0.0.2
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.
- data/Gemfile +4 -0
- data/Gemfile.lock +93 -0
- data/Guardfile +14 -0
- data/Hobofile +34 -0
- data/Rakefile +2 -0
- data/bin/hobo +23 -0
- data/features/deps.feature +43 -0
- data/features/hobo/basic.feature +30 -0
- data/features/hobo/help.feature +12 -0
- data/features/hobo/subcommands.feature +16 -0
- data/features/seed/plant.feature +64 -0
- data/features/step_definitions/seed.rb +11 -0
- data/features/support/env.rb +6 -0
- data/features/vm.feature +0 -0
- data/hobo.gemspec +37 -0
- data/lib/hobo.rb +44 -0
- data/lib/hobo/cli.rb +185 -0
- data/lib/hobo/config/file.rb +21 -0
- data/lib/hobo/error_handlers/debug.rb +9 -0
- data/lib/hobo/error_handlers/friendly.rb +52 -0
- data/lib/hobo/errors.rb +60 -0
- data/lib/hobo/help_formatter.rb +111 -0
- data/lib/hobo/helper/file_locator.rb +39 -0
- data/lib/hobo/helper/shell.rb +59 -0
- data/lib/hobo/lib/seed/project.rb +41 -0
- data/lib/hobo/lib/seed/replacer.rb +57 -0
- data/lib/hobo/lib/seed/seed.rb +43 -0
- data/lib/hobo/metadata.rb +28 -0
- data/lib/hobo/patches/rake.rb +56 -0
- data/lib/hobo/patches/slop.rb +22 -0
- data/lib/hobo/paths.rb +49 -0
- data/lib/hobo/tasks/debug.rb +22 -0
- data/lib/hobo/tasks/deps.rb +45 -0
- data/lib/hobo/tasks/seed.rb +43 -0
- data/lib/hobo/tasks/tools.rb +13 -0
- data/lib/hobo/tasks/vm.rb +49 -0
- data/lib/hobo/ui.rb +96 -0
- data/lib/hobo/util.rb +7 -0
- data/lib/hobo/version.rb +3 -0
- data/spec/hobo/cli_spec.rb +135 -0
- data/spec/hobo/config/file_spec.rb +48 -0
- data/spec/hobo/error_handlers/debug_spec.rb +10 -0
- data/spec/hobo/error_handlers/friendly_spec.rb +81 -0
- data/spec/hobo/error_spec.rb +0 -0
- data/spec/hobo/help_formatter_spec.rb +131 -0
- data/spec/hobo/helpers/file_locator_spec.rb +7 -0
- data/spec/hobo/helpers/shell_spec.rb +7 -0
- data/spec/hobo/lib/seed/project_spec.rb +83 -0
- data/spec/hobo/lib/seed/replacer_spec.rb +47 -0
- data/spec/hobo/lib/seed/seed_spec.rb +95 -0
- data/spec/hobo/metadata_spec.rb +46 -0
- data/spec/hobo/patches/rake_spec.rb +0 -0
- data/spec/hobo/paths_spec.rb +77 -0
- data/spec/hobo/ui_spec.rb +64 -0
- data/spec/spec_helper.rb +6 -0
- metadata +355 -0
data/lib/hobo/cli.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
module Hobo
|
2
|
+
|
3
|
+
class Halt < Error
|
4
|
+
end
|
5
|
+
|
6
|
+
class Cli
|
7
|
+
attr_accessor :slop, :help_formatter
|
8
|
+
|
9
|
+
def initialize opts = {}
|
10
|
+
@opts = opts
|
11
|
+
@slop = opts[:slop] || Slop.new
|
12
|
+
@help_formatter = opts[:help] || Hobo::HelpFormatter.new(@slop)
|
13
|
+
@help_opts = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def start argv = ARGV
|
17
|
+
load_hobofile
|
18
|
+
|
19
|
+
tasks = structure_tasks Hobo::Metadata.metadata.keys
|
20
|
+
args = fix_args_with_equals argv
|
21
|
+
define_global_opts @slop
|
22
|
+
|
23
|
+
begin
|
24
|
+
# Parse out global args first
|
25
|
+
@slop.parse! args
|
26
|
+
opts = @slop.to_hash
|
27
|
+
@help_opts[:all] = opts[:all]
|
28
|
+
Hobo.ui.interactive = !(opts[:'non-interactive'] == true)
|
29
|
+
|
30
|
+
@slop.add_callback :empty do
|
31
|
+
show_help
|
32
|
+
end
|
33
|
+
|
34
|
+
# Necessary to make command level help work
|
35
|
+
args.push "--help" if @slop.help?
|
36
|
+
|
37
|
+
@help_formatter.command_map = define_tasks(tasks, @slop)
|
38
|
+
|
39
|
+
remaining = @slop.parse! args
|
40
|
+
raise Hobo::InvalidCommandOrOpt.new remaining.join(" "), self if remaining.size > 0
|
41
|
+
|
42
|
+
show_help if @slop.help?
|
43
|
+
rescue Halt
|
44
|
+
# NOP
|
45
|
+
end
|
46
|
+
|
47
|
+
return 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def show_help(opts = {})
|
51
|
+
Hobo.ui.info @help_formatter.help(@help_opts.merge(opts))
|
52
|
+
halt
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def load_hobofile
|
58
|
+
if Hobo.in_project? && File.exists?(Hobo.hobofile_path)
|
59
|
+
load Hobo.hobofile_path
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def define_global_opts slop
|
64
|
+
slop.on '--debug', 'Enable debugging'
|
65
|
+
slop.on '-a', '--all', 'Show hidden commands'
|
66
|
+
slop.on '-h', '--help', 'Display help'
|
67
|
+
slop.on '--non-interactive', 'Run non-interactively. Defaults will be automaticall used where possible.'
|
68
|
+
|
69
|
+
slop.on '-v', '--version', 'Print version information' do
|
70
|
+
Hobo.ui.info "Hobo version #{Hobo::VERSION}"
|
71
|
+
halt
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def halt
|
76
|
+
raise Halt.new
|
77
|
+
end
|
78
|
+
|
79
|
+
def define_tasks structured_list, scope, stack = [], map = {}
|
80
|
+
structured_list.each do |k, v|
|
81
|
+
name = (stack + [k]).join(':')
|
82
|
+
new_stack = stack + [k]
|
83
|
+
map[name] = if v.size == 0
|
84
|
+
define_command(name, scope, new_stack)
|
85
|
+
else
|
86
|
+
define_namespace(name, scope, new_stack, v, map)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
return map
|
90
|
+
end
|
91
|
+
|
92
|
+
# Map rake namespace to a Slop command
|
93
|
+
def define_namespace name, scope, stack, subtasks, map
|
94
|
+
metadata = Hobo::Metadata.metadata[name]
|
95
|
+
hobo = self
|
96
|
+
new_scope = nil
|
97
|
+
|
98
|
+
scope.instance_eval do
|
99
|
+
new_scope = command stack.last do
|
100
|
+
|
101
|
+
description metadata[:desc]
|
102
|
+
long_description metadata[:long_desc]
|
103
|
+
hidden metadata[:hidden]
|
104
|
+
project_only metadata[:project_only]
|
105
|
+
|
106
|
+
# NOP; run runs help anyway
|
107
|
+
on '-h', '--help', 'Display help' do end
|
108
|
+
|
109
|
+
run do |opts, args|
|
110
|
+
hobo.show_help(target: name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
define_tasks subtasks, new_scope, stack, map
|
116
|
+
return new_scope
|
117
|
+
end
|
118
|
+
|
119
|
+
# Map rake task to a Slop command
|
120
|
+
def define_command name, scope, stack
|
121
|
+
metadata = Hobo::Metadata.metadata[name]
|
122
|
+
hobo = self
|
123
|
+
new_scope = nil
|
124
|
+
|
125
|
+
scope.instance_eval do
|
126
|
+
new_scope = command stack.last do
|
127
|
+
task = Rake::Task[name]
|
128
|
+
|
129
|
+
description metadata[:desc]
|
130
|
+
long_description metadata[:long_desc]
|
131
|
+
arg_list task.arg_names
|
132
|
+
hidden metadata[:hidden]
|
133
|
+
project_only metadata[:project_only]
|
134
|
+
|
135
|
+
metadata[:opts].each do |opt|
|
136
|
+
on *opt
|
137
|
+
end if metadata[:opts]
|
138
|
+
|
139
|
+
on '-h', '--help', 'Display help' do
|
140
|
+
hobo.show_help(target: name)
|
141
|
+
end
|
142
|
+
|
143
|
+
run do |opts, args|
|
144
|
+
raise ::Hobo::ProjectOnlyError.new if opts.project_only && !Hobo.in_project?
|
145
|
+
task.opts = opts.to_hash
|
146
|
+
raise ::Hobo::MissingArgumentsError.new(name, args, hobo) if args && task.arg_names.length > args.length
|
147
|
+
task.invoke *args
|
148
|
+
args.pop(task.arg_names.size)
|
149
|
+
task.opts = nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
return new_scope
|
155
|
+
end
|
156
|
+
|
157
|
+
# Expand flat task list in to hierarchy (non-recursive)
|
158
|
+
def structure_tasks list
|
159
|
+
out = {}
|
160
|
+
list.each do |name|
|
161
|
+
ref = out
|
162
|
+
name = name.split(":")
|
163
|
+
name.each do |n|
|
164
|
+
ref[n] ||= {}
|
165
|
+
ref = ref[n]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
out
|
169
|
+
end
|
170
|
+
|
171
|
+
# Slop badly handles assignment args passed as --arg=val
|
172
|
+
# This hack fixes that by making them --arg val
|
173
|
+
def fix_args_with_equals args
|
174
|
+
items = []
|
175
|
+
args.each do |item|
|
176
|
+
if item.match /^-.*\=/
|
177
|
+
item.split('=').each { |i| items.push i }
|
178
|
+
else
|
179
|
+
items.push item
|
180
|
+
end
|
181
|
+
end
|
182
|
+
return items
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Hobo
|
4
|
+
module Config
|
5
|
+
class File
|
6
|
+
def self.save(file, config)
|
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
|
+
config = ::File.exists?(file) ? YAML.load_file(file) : {}
|
16
|
+
raise "Invalid hobo configuration (#{file})" unless config
|
17
|
+
return config
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'tmpdir' # Needed for Dir.tmpdir
|
2
|
+
|
3
|
+
module Hobo
|
4
|
+
module ErrorHandlers
|
5
|
+
class Friendly
|
6
|
+
def handle error
|
7
|
+
log_file = File.join(Dir.tmpdir, 'hobo_error.log')
|
8
|
+
|
9
|
+
# Not possible to match Interrupt class unless we use class name as string for some reason!
|
10
|
+
case error.class.to_s
|
11
|
+
when "Interrupt"
|
12
|
+
Hobo.ui.warning "\n\nCaught Interrupt. Aborting\n"
|
13
|
+
return 1
|
14
|
+
when "Hobo::ExternalCommandError"
|
15
|
+
FileUtils.cp error.output.path, log_file
|
16
|
+
Hobo.ui.error <<-ERROR
|
17
|
+
|
18
|
+
The following external command appears to have failed (exit status #{error.exit_code}):
|
19
|
+
#{error.command}
|
20
|
+
|
21
|
+
The output of the command has been logged to #{log_file}
|
22
|
+
ERROR
|
23
|
+
return 3
|
24
|
+
when "Hobo::InvalidCommandOrOpt"
|
25
|
+
Hobo.ui.error "\n#{error.message}"
|
26
|
+
Hobo.ui.info error.cli.help_formatter.help if error.cli
|
27
|
+
return 4
|
28
|
+
when "Hobo::MissingArgumentsError"
|
29
|
+
Hobo.ui.error "\n#{error.message}"
|
30
|
+
Hobo.ui.info error.cli.help_formatter.help(target: error.command) if error.cli
|
31
|
+
return 5
|
32
|
+
when "Hobo::UserError"
|
33
|
+
Hobo.ui.error "\n#{error.message}\n"
|
34
|
+
return 6
|
35
|
+
when "Hobo::ProjectOnlyError"
|
36
|
+
Hobo.ui.error "\nHobo requires you to be in a project directory for this command!\n"
|
37
|
+
return 7
|
38
|
+
else
|
39
|
+
File.write(log_file, "(#{error.class}) #{error.message}\n\n#{error.backtrace.join("\n")}")
|
40
|
+
Hobo.ui.error <<-ERROR
|
41
|
+
|
42
|
+
An unexpected error has occured:
|
43
|
+
#{error.message}
|
44
|
+
|
45
|
+
The backtrace has been logged to #{log_file}
|
46
|
+
ERROR
|
47
|
+
return 128
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/hobo/errors.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Hobo
|
2
|
+
class Error < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class RubyVersionError < Error
|
6
|
+
def initialize
|
7
|
+
super("Ruby 1.9+ is required to run hobo")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class MissingDependencies < Error
|
12
|
+
def initialize deps
|
13
|
+
deps.map! { |dep| " - #{dep}"}
|
14
|
+
super("Hobo requires the following commands to be available on your path:\n\n" + deps.join("\n"))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvalidCommandOrOpt < Error
|
19
|
+
attr_accessor :command, :cli
|
20
|
+
def initialize command, cli = nil
|
21
|
+
@command = command
|
22
|
+
@cli = cli
|
23
|
+
super("Invalid command or option specified: '#{command}'")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MissingArgumentsError < Error
|
28
|
+
attr_accessor :command, :cli
|
29
|
+
def initialize command, args, cli = nil
|
30
|
+
@command = command
|
31
|
+
@args = args
|
32
|
+
@cli = cli
|
33
|
+
super("Not enough arguments for #{command}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ExternalCommandError < Error
|
38
|
+
attr_accessor :command, :exit_code, :output
|
39
|
+
|
40
|
+
def initialize command, exit_code, output
|
41
|
+
@command = command
|
42
|
+
@exit_code = exit_code
|
43
|
+
@output = output
|
44
|
+
super("'#{command}' returned exit code #{exit_code}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class UserError < Error
|
49
|
+
end
|
50
|
+
|
51
|
+
class ProjectOnlyError < Error
|
52
|
+
end
|
53
|
+
|
54
|
+
class NonInteractiveError < Error
|
55
|
+
def initialize question
|
56
|
+
@question = question
|
57
|
+
super("A task requested input from user but hobo is in non-interactive mode")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Hobo
|
2
|
+
class HelpFormatter
|
3
|
+
attr_accessor :command_map
|
4
|
+
ALIGN_PAD = 4
|
5
|
+
|
6
|
+
def initialize global
|
7
|
+
@global = global
|
8
|
+
@command_map = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def help opts = {}
|
12
|
+
target = opts[:target] || nil
|
13
|
+
output = [""]
|
14
|
+
|
15
|
+
command = @command_map[target]
|
16
|
+
|
17
|
+
if !command
|
18
|
+
command_opts = []
|
19
|
+
commands = commands_for(@global, opts)
|
20
|
+
command = @global
|
21
|
+
else
|
22
|
+
command_opts = options_for(command)
|
23
|
+
commands = commands_for(command, opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
global_opts = options_for(@global)
|
27
|
+
|
28
|
+
align_to = longest([global_opts, command_opts, commands]) + ALIGN_PAD
|
29
|
+
|
30
|
+
if command != @global
|
31
|
+
description = [ command.long_description, command.description ].compact.first
|
32
|
+
output.push Hobo.ui.color("#{description}\n", :description) if description
|
33
|
+
end
|
34
|
+
|
35
|
+
output.push section("Usage", [usage(command, target)])
|
36
|
+
output.push section("Global options", global_opts, align_to)
|
37
|
+
output.push section("Command options", command_opts, align_to)
|
38
|
+
output.push section("Commands", commands, align_to)
|
39
|
+
|
40
|
+
return output.compact.join("\n")
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def longest inputs
|
46
|
+
inputs.map do |input|
|
47
|
+
next unless input.is_a? Array
|
48
|
+
if input.size == 0
|
49
|
+
0
|
50
|
+
else
|
51
|
+
input.map(&:first).map(&:size).max
|
52
|
+
end
|
53
|
+
end.compact.max
|
54
|
+
end
|
55
|
+
|
56
|
+
def options_for source
|
57
|
+
heads = source.options.reject(&:tail?)
|
58
|
+
tails = (source.options - heads)
|
59
|
+
|
60
|
+
(heads + tails).map do |opt|
|
61
|
+
next if source != @global && opt.short == 'h'
|
62
|
+
line = padded(opt.short ? "-#{opt.short}," : "", 4)
|
63
|
+
|
64
|
+
if opt.long
|
65
|
+
value = opt.config[:argument] ? "#{opt.long.upcase}" : ""
|
66
|
+
value = "[#{value}]" if opt.accepts_optional_argument?
|
67
|
+
value = "=#{value}" unless value.empty?
|
68
|
+
|
69
|
+
line += "--#{opt.long}#{value}"
|
70
|
+
end
|
71
|
+
|
72
|
+
[Hobo.ui.color(line, :opt), opt.description]
|
73
|
+
end.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
def commands_for source, opts = {}
|
77
|
+
source.commands.map do |name, command|
|
78
|
+
next if command.hidden && !opts[:all]
|
79
|
+
next unless command.hidden == false || command.description || opts[:all]
|
80
|
+
[Hobo.ui.color(name, :command), command.description]
|
81
|
+
end.compact
|
82
|
+
end
|
83
|
+
|
84
|
+
def usage source, command = nil
|
85
|
+
banner = source.banner
|
86
|
+
if banner.nil?
|
87
|
+
arg_list = (source.arg_list || []).map do |arg|
|
88
|
+
"<#{arg}>"
|
89
|
+
end
|
90
|
+
|
91
|
+
banner = "#{File.basename($0, '.*')}"
|
92
|
+
banner << " [command]" if source.commands.any? && command.nil?
|
93
|
+
banner << " #{command.split(':').join(' ')}" if command
|
94
|
+
banner << " #{arg_list.join(' ')}" if arg_list.size > 0
|
95
|
+
banner << " [options]"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def section title, contents, align_to = false
|
100
|
+
return nil if contents.empty?
|
101
|
+
output = Hobo.ui.color("#{title}:\n", :help_title)
|
102
|
+
output += contents.map do |line|
|
103
|
+
line.is_a?(String) ? " #{line}" : " #{padded(line[0], align_to)}#{line[1]}"
|
104
|
+
end.join("\n") + "\n"
|
105
|
+
end
|
106
|
+
|
107
|
+
def padded str, target
|
108
|
+
str + (' ' * (target - str.size))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Hobo
|
4
|
+
module Helper
|
5
|
+
def locate(pattern, opts = {}, &block)
|
6
|
+
match = nil
|
7
|
+
|
8
|
+
Dir.chdir Hobo.project_path do
|
9
|
+
match = locate_git(pattern, false, &block)
|
10
|
+
match = locate_git(pattern, true, &block) if !match
|
11
|
+
end
|
12
|
+
|
13
|
+
return true if match
|
14
|
+
|
15
|
+
Hobo.ui.warning opts[:missing] if opts[:missing]
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def locate_git pattern, others, &block
|
22
|
+
args = [ 'git', 'ls-files', pattern ]
|
23
|
+
args.push '-o' if others
|
24
|
+
output = Hobo::Helper.shell *args, :capture => true
|
25
|
+
path = output.split("\n")[0]
|
26
|
+
|
27
|
+
unless path.nil?
|
28
|
+
path.strip!
|
29
|
+
Dir.chdir File.dirname(path) do
|
30
|
+
yield path
|
31
|
+
end
|
32
|
+
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
include Hobo::Helper
|