excavator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +10 -0
- data/bin/excavator +3 -0
- data/excavator.gemspec +25 -0
- data/lib/excavator.rb +80 -0
- data/lib/excavator/command.rb +79 -0
- data/lib/excavator/dsl.rb +33 -0
- data/lib/excavator/environment.rb +24 -0
- data/lib/excavator/namespace.rb +56 -0
- data/lib/excavator/param.rb +25 -0
- data/lib/excavator/param_parser.rb +135 -0
- data/lib/excavator/runner.rb +107 -0
- data/lib/excavator/table_view.rb +90 -0
- data/lib/excavator/version.rb +3 -0
- data/test/fixtures/commands/test.rb +18 -0
- data/test/test_helper.rb +32 -0
- data/test/unit/command_test.rb +75 -0
- data/test/unit/dsl_test.rb +49 -0
- data/test/unit/environment_test.rb +64 -0
- data/test/unit/namespace_test.rb +61 -0
- data/test/unit/param_parser_test.rb +128 -0
- data/test/unit/runner_test.rb +107 -0
- data/test/unit/table_view_test.rb +62 -0
- metadata +118 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'excavator'
|
2
|
+
module Excavator
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
# Runner specific vars
|
6
|
+
attr_accessor :command_paths
|
7
|
+
attr_accessor :commands
|
8
|
+
attr_reader :current_namespace
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@namespace = Namespace.new(:default)
|
12
|
+
@current_namespace = @namespace
|
13
|
+
end
|
14
|
+
|
15
|
+
def cwd
|
16
|
+
Excavator.cwd
|
17
|
+
end
|
18
|
+
|
19
|
+
def namespaces
|
20
|
+
@namespaces
|
21
|
+
end
|
22
|
+
|
23
|
+
def namespace
|
24
|
+
@namespace
|
25
|
+
end
|
26
|
+
|
27
|
+
def current_namespace
|
28
|
+
@current_namespace
|
29
|
+
end
|
30
|
+
|
31
|
+
def in_namespace(name)
|
32
|
+
ns = @current_namespace.namespace(name)
|
33
|
+
ns = @current_namespace << Excavator.namespace_class.new(name) unless ns
|
34
|
+
@current_namespace = ns
|
35
|
+
yield
|
36
|
+
@current_namespace = ns.parent
|
37
|
+
end
|
38
|
+
|
39
|
+
def clear_commands!
|
40
|
+
self.commands = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def run(*args)
|
44
|
+
args.flatten!
|
45
|
+
|
46
|
+
name = args.delete_at(0)
|
47
|
+
load_commands
|
48
|
+
|
49
|
+
if (name.nil? && args.size == 0) || display_help?(name)
|
50
|
+
display_help
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
command = find_command name
|
55
|
+
command.execute *args
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_command(cmd)
|
59
|
+
*namespaces, command_name = cmd.to_s.split(':').collect {|c| c.to_sym }
|
60
|
+
cur_namespace = current_namespace
|
61
|
+
namespaces.each do |n|
|
62
|
+
cur_namespace = cur_namespace.namespace(n)
|
63
|
+
end
|
64
|
+
|
65
|
+
cur_namespace.command(command_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def display_help?(command_name)
|
69
|
+
["-h", "-?", "--help", "help"].include?(command_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def display_help
|
73
|
+
table_view = Excavator::TableView.new do |t|
|
74
|
+
t.title "#{File.basename($0)} commands:\n"
|
75
|
+
t.header "Command"
|
76
|
+
t.header "Description"
|
77
|
+
t.divider "\t"
|
78
|
+
|
79
|
+
namespace.commands_and_descriptions.sort.each do |command|
|
80
|
+
t.record *command
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
puts table_view
|
85
|
+
end
|
86
|
+
|
87
|
+
def load_commands
|
88
|
+
command_paths.each do |path|
|
89
|
+
Dir["#{path.to_s}/**/*.rb"].each { |file| load file }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def last_command
|
94
|
+
@last_command ||= Excavator.command_class.new(
|
95
|
+
self, :namespace => current_namespace
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def clear_last_command!
|
100
|
+
@last_command = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def command_paths
|
104
|
+
@command_paths = Excavator.command_paths || [cwd.join("commands")]
|
105
|
+
end
|
106
|
+
end # Runner
|
107
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'excavator'
|
2
|
+
module Excavator
|
3
|
+
class TableView
|
4
|
+
class InvalidDataForHeaders < StandardError; end
|
5
|
+
|
6
|
+
DEFAULT_DIVIDER = " | "
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@options = options
|
10
|
+
@_title = ""
|
11
|
+
@_headers = []
|
12
|
+
@_records = []
|
13
|
+
@col_widths = Hash.new { |hash, key| hash[key] = 0 }
|
14
|
+
@divider = options[:divider] || DEFAULT_DIVIDER
|
15
|
+
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def title(t = nil)
|
20
|
+
if t.nil?
|
21
|
+
@_title = t
|
22
|
+
else
|
23
|
+
@_title = t
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def header(*headers)
|
28
|
+
headers.flatten!
|
29
|
+
|
30
|
+
headers.each do |h|
|
31
|
+
index = @_headers.size
|
32
|
+
@_headers << h
|
33
|
+
@col_widths[h] = h.to_s.size
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def divider(str)
|
38
|
+
@divider = str
|
39
|
+
end
|
40
|
+
|
41
|
+
def record(*args)
|
42
|
+
args.flatten!
|
43
|
+
if args.size != @_headers.size
|
44
|
+
raise InvalidDataForHeaders.new(<<-MSG)
|
45
|
+
Number of columns for record (#{args.size}) does not match number
|
46
|
+
of headers (#{@_headers.size})
|
47
|
+
MSG
|
48
|
+
end
|
49
|
+
update_column_widths Hash[*@_headers.zip(args).flatten]
|
50
|
+
@_records << args
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
out = ""
|
55
|
+
out << @_title << "\n" if !@_title.nil? && @_title != ""
|
56
|
+
|
57
|
+
print_headers out
|
58
|
+
|
59
|
+
@_records.each_with_index do |record|
|
60
|
+
record.each_with_index do |val, i|
|
61
|
+
out << column_formats[i] % val
|
62
|
+
out << @divider if i != (record.size - 1)
|
63
|
+
end
|
64
|
+
out << "\n"
|
65
|
+
end
|
66
|
+
out << "\n"
|
67
|
+
|
68
|
+
out
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_column_widths(hash)
|
72
|
+
hash.each do |key, val|
|
73
|
+
@col_widths[key] = val.size if val && val.size > @col_widths[key]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def print_headers(out)
|
78
|
+
@_headers.each_with_index do |h, i|
|
79
|
+
out << column_formats[i] % h
|
80
|
+
out << @divider if i != (@_headers.size - 1)
|
81
|
+
end
|
82
|
+
out << "\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
# Internal
|
86
|
+
def column_formats
|
87
|
+
@col_widths.collect { |h, width| "%-#{width}s" }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
command :test do
|
2
|
+
puts "test command"
|
3
|
+
end
|
4
|
+
|
5
|
+
desc "A command with an argument"
|
6
|
+
param :region, :default => "west"
|
7
|
+
command :command_with_arg do
|
8
|
+
puts params[:region]
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :first do
|
12
|
+
namespace :second do
|
13
|
+
command :third do
|
14
|
+
puts "third"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
basedir = Pathname.new(File.dirname(__FILE__)).join("..").expand_path
|
3
|
+
$LOAD_PATH.unshift(basedir.join("lib").to_s)
|
4
|
+
require 'excavator'
|
5
|
+
|
6
|
+
gem 'minitest'
|
7
|
+
require 'minitest/spec'
|
8
|
+
require 'minitest/autorun'
|
9
|
+
require 'minitest/mock'
|
10
|
+
|
11
|
+
require 'ruby-debug'
|
12
|
+
Debugger.start
|
13
|
+
|
14
|
+
module TestHelpers
|
15
|
+
def basedir
|
16
|
+
Pathname.new(__FILE__).join("..", "..").expand_path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# I like #context more than #describe
|
21
|
+
alias :context :describe
|
22
|
+
|
23
|
+
class MiniTest::Spec
|
24
|
+
include TestHelpers
|
25
|
+
class << self
|
26
|
+
# Oh Rails, you have a strangle hold on my conventions ...
|
27
|
+
alias :test :it
|
28
|
+
alias :setup :before
|
29
|
+
alias :teardown :after
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
context "Command" do
|
3
|
+
include Excavator
|
4
|
+
|
5
|
+
setup do
|
6
|
+
Excavator.reset!
|
7
|
+
@runner = Excavator.runner
|
8
|
+
end
|
9
|
+
|
10
|
+
test "execute a command" do
|
11
|
+
cmd = Command.new(@runner, :name => "test", :block => proc { puts "test"})
|
12
|
+
out, error = capture_io do
|
13
|
+
cmd.execute
|
14
|
+
end
|
15
|
+
assert_match /test/, out
|
16
|
+
end
|
17
|
+
|
18
|
+
test "command arguments available in #params" do
|
19
|
+
cmd = Command.new(
|
20
|
+
@runner,
|
21
|
+
:name => "test",
|
22
|
+
:param_definitions => [Param.new(:name)],
|
23
|
+
:block => proc { puts params[:name] }
|
24
|
+
)
|
25
|
+
out, error = capture_io do
|
26
|
+
cmd.execute("--name", "hello, world!")
|
27
|
+
end
|
28
|
+
assert_match /hello, world!/, out
|
29
|
+
end
|
30
|
+
|
31
|
+
test "passing in two named parameters" do
|
32
|
+
cmd = Command.new(
|
33
|
+
@runner,
|
34
|
+
:name => "test",
|
35
|
+
:param_definitions => [Param.new(:arg1), Param.new(:arg2)],
|
36
|
+
:block => proc { puts "#{params[:arg1]}#{params[:arg2]}" }
|
37
|
+
)
|
38
|
+
out, error = capture_io do
|
39
|
+
cmd.execute("--arg1", "1", "--arg2", "2")
|
40
|
+
end
|
41
|
+
assert_match /12/, out
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
test "#execute_with_params runs a command's block" do
|
46
|
+
cmd = Command.new(Excavator.runner).tap do |c|
|
47
|
+
c.name = "test"
|
48
|
+
c.add_param(Param.new(:name))
|
49
|
+
c.add_param(Param.new(:server))
|
50
|
+
c.block = Proc.new do
|
51
|
+
[params[:name], params[:server]]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
results = cmd.execute_with_params :name => "paydro", :server => "web"
|
56
|
+
assert_equal ["paydro", "web"], results
|
57
|
+
end
|
58
|
+
|
59
|
+
test "#execute_with_params throws error when missing param" do
|
60
|
+
cmd = Command.new(Excavator.runner).tap do |c|
|
61
|
+
c.name = "test"
|
62
|
+
c.add_param(Param.new(:name))
|
63
|
+
c.add_param(Param.new(:server))
|
64
|
+
c.block = Proc.new do
|
65
|
+
[params[:name], params[:server]]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
error = assert_raises MissingParamsError do
|
70
|
+
cmd.execute_with_params :name => "paydro"
|
71
|
+
end
|
72
|
+
assert error.params.include?(:server)
|
73
|
+
end
|
74
|
+
end # Command
|
75
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
context "DSL command creation and execution" do
|
4
|
+
include Excavator::DSL
|
5
|
+
|
6
|
+
setup do
|
7
|
+
Excavator.reset!
|
8
|
+
@runner = Excavator.runner
|
9
|
+
end
|
10
|
+
|
11
|
+
test "create a command" do
|
12
|
+
refute @runner.find_command("test")
|
13
|
+
cmd = command(:test) { puts "test" }
|
14
|
+
assert cmd = @runner.find_command("test")
|
15
|
+
end
|
16
|
+
|
17
|
+
test "creating command clears out #last_command for a new command" do
|
18
|
+
cmd1 = command(:first) {}
|
19
|
+
refute_equal cmd1.object_id, @runner.last_command.object_id
|
20
|
+
end
|
21
|
+
|
22
|
+
test "adding a description to a command" do
|
23
|
+
desc "test desc"
|
24
|
+
command(:test) {}
|
25
|
+
assert_equal "test desc", @runner.find_command("test").desc
|
26
|
+
end
|
27
|
+
|
28
|
+
test "create a namespaced command" do
|
29
|
+
cmd = nil
|
30
|
+
namespace :outer do
|
31
|
+
cmd = command(:test) { }
|
32
|
+
end
|
33
|
+
|
34
|
+
assert found_cmd = @runner.find_command("outer:test")
|
35
|
+
assert_equal cmd.object_id, found_cmd.object_id
|
36
|
+
end
|
37
|
+
|
38
|
+
test "command has access to it's namespace" do
|
39
|
+
cmd = nil
|
40
|
+
b = nil
|
41
|
+
namespace :a do
|
42
|
+
namespace :b do
|
43
|
+
cmd = command(:inside) {}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
assert_equal :inside, cmd.name
|
47
|
+
assert_equal :b, cmd.namespace.name
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
context "Environment" do
|
4
|
+
include Excavator
|
5
|
+
include Excavator::DSL
|
6
|
+
|
7
|
+
setup do
|
8
|
+
Excavator.reset!
|
9
|
+
Excavator.command_paths = [
|
10
|
+
basedir.join("test", "fixtures", "commands")
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
test "execute another command" do
|
15
|
+
cmd = command(:first) { execute :second }
|
16
|
+
command(:second) { "called from second" }
|
17
|
+
assert_equal "called from second", cmd.execute
|
18
|
+
end
|
19
|
+
|
20
|
+
test "execute command with params" do
|
21
|
+
cmd = command(:run_another_cmd) do
|
22
|
+
execute :my_name, :name => 'paydro'
|
23
|
+
end
|
24
|
+
|
25
|
+
param :name
|
26
|
+
command(:my_name) { params[:name] }
|
27
|
+
|
28
|
+
assert_equal "paydro", cmd.execute
|
29
|
+
end
|
30
|
+
|
31
|
+
end # Environment
|
32
|
+
|
33
|
+
context "Environment#modify" do
|
34
|
+
include Excavator
|
35
|
+
|
36
|
+
module OtherCommands
|
37
|
+
def hello
|
38
|
+
"world"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
setup do
|
43
|
+
env_class = Class.new(Excavator::Environment)
|
44
|
+
Excavator.environment_class = env_class
|
45
|
+
@class = env_class
|
46
|
+
end
|
47
|
+
|
48
|
+
teardown do
|
49
|
+
Excavator.environment_class = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
test "modify environment with modules" do
|
53
|
+
@class.modify OtherCommands
|
54
|
+
assert_equal "world", @class.new.hello
|
55
|
+
end
|
56
|
+
|
57
|
+
test "modify environment with a block" do
|
58
|
+
@class.modify do
|
59
|
+
include OtherCommands
|
60
|
+
end
|
61
|
+
|
62
|
+
assert_equal "world", @class.new.hello
|
63
|
+
end
|
64
|
+
end
|