excavator 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +7 -0
- data/README.md +280 -0
- data/lib/excavator.rb +71 -19
- data/lib/excavator/command.rb +59 -9
- data/lib/excavator/dsl.rb +97 -3
- data/lib/excavator/environment.rb +48 -1
- data/lib/excavator/namespace.rb +130 -1
- data/lib/excavator/param.rb +21 -1
- data/lib/excavator/param_parser.rb +88 -10
- data/lib/excavator/runner.rb +87 -33
- data/lib/excavator/table_view.rb +2 -0
- data/lib/excavator/version.rb +3 -1
- data/test/test_helper.rb +2 -0
- data/test/unit/command_test.rb +21 -0
- data/test/unit/namespace_test.rb +13 -6
- data/test/unit/param_parser_test.rb +1 -1
- metadata +11 -11
- data/bin/excavator +0 -3
data/lib/excavator/dsl.rb
CHANGED
@@ -1,21 +1,117 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
module Excavator
|
2
|
-
|
4
|
+
|
5
|
+
# Public: DSL is the primary interface for creating commands. When required,
|
6
|
+
# the global scope is extended with helpers to create namespaces, parameters,
|
7
|
+
# and commands.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# namespace :servers do
|
12
|
+
# desc "Create a new server"
|
13
|
+
# param :name, :desc => "Name of the server"
|
14
|
+
# param :region, :desc => "Region the server is created in"
|
15
|
+
# command :create do
|
16
|
+
# # ...
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
3
20
|
module DSL
|
21
|
+
|
22
|
+
# Public: Create a namespace to group commands. Namespaces can be nested.
|
23
|
+
# Namespace names are prefixed to the command name.
|
24
|
+
#
|
25
|
+
# This will create a new namespace if it doesn't exist. All commands defined
|
26
|
+
# within the passed in block are added to the namespace.
|
27
|
+
#
|
28
|
+
# name - A String or Symbol. This is used as a part of the command name.
|
29
|
+
# block -
|
30
|
+
#
|
31
|
+
# Examples
|
32
|
+
#
|
33
|
+
# # Creates the command "servers:create"
|
34
|
+
# namespace :servers do
|
35
|
+
# command :create do
|
36
|
+
# # ...
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Nesting namespaces
|
41
|
+
# namespace :public do
|
42
|
+
# namespace :instances do
|
43
|
+
# # ...
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# Returns nothing.
|
4
48
|
def namespace(name)
|
5
49
|
Excavator.runner.in_namespace(name) do
|
6
50
|
yield
|
7
51
|
end
|
8
52
|
end
|
9
53
|
|
54
|
+
# Public: Adds a description to the next command declared.
|
55
|
+
#
|
56
|
+
# description - A String describing the command.
|
57
|
+
#
|
58
|
+
# Examples
|
59
|
+
#
|
60
|
+
# desc "prints hello"
|
61
|
+
# command :print_hello { ... }
|
62
|
+
#
|
63
|
+
# Returns nothing.
|
10
64
|
def desc(description)
|
11
65
|
Excavator.runner.last_command.desc = description
|
12
66
|
end
|
13
67
|
|
68
|
+
# Public: Add a parameter to the next command declared. This passes all
|
69
|
+
# parameters directly into Param#initialize.
|
70
|
+
#
|
71
|
+
# See the Param and ParamParser class for more details.
|
72
|
+
#
|
73
|
+
# name - A String or Symbol of the parameter name.
|
74
|
+
# options - A Hash of Parameter options (default: {}).
|
75
|
+
# :desc - A String describing the parameter.
|
76
|
+
# :default - A default value for the parameter.
|
77
|
+
# :short - A String (normally one character) to use as the
|
78
|
+
# short switch for the parameter. For instance,
|
79
|
+
# "param :test, :short => 'c'" will allow
|
80
|
+
# the parameter to be used as "-c" on the command
|
81
|
+
# line.
|
82
|
+
# :optional - A Boolean specifying whether the paramter is
|
83
|
+
# optional.
|
84
|
+
#
|
85
|
+
# Examples
|
86
|
+
#
|
87
|
+
# # A required parameter with the long switch "--name"
|
88
|
+
# param :name
|
89
|
+
#
|
90
|
+
# # An optional parameter.
|
91
|
+
# param :name, :optional => true
|
92
|
+
#
|
93
|
+
# # Set a description.
|
94
|
+
# param :name, :desc => "A name"
|
95
|
+
#
|
96
|
+
# Returns nothing.
|
14
97
|
def param(name, options = {})
|
15
98
|
param = Excavator.param_class.new(name, options)
|
16
99
|
Excavator.runner.last_command.add_param(param)
|
17
100
|
end
|
18
101
|
|
102
|
+
# Public: Creates a command to add to Excavator.runner.
|
103
|
+
#
|
104
|
+
# name - A String or Symbole for the command.
|
105
|
+
# block - A block of code to execute when the command is called. This block
|
106
|
+
# is executed within an instance of Excavator::Environment.
|
107
|
+
#
|
108
|
+
# Examples
|
109
|
+
#
|
110
|
+
# command :hello do
|
111
|
+
# puts "hello"
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# Returns a Command.
|
19
115
|
def command(name, &block)
|
20
116
|
cmd = Excavator.runner.last_command
|
21
117
|
cmd.name = name
|
@@ -29,5 +125,3 @@ module Excavator
|
|
29
125
|
end # Excavator
|
30
126
|
|
31
127
|
self.extend Excavator::DSL
|
32
|
-
|
33
|
-
|
@@ -1,6 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
module Excavator
|
4
|
+
|
5
|
+
# Public: Environment instances are created for a Command's block to execute
|
6
|
+
# in. It is designed for modification and manipulation. Adding other methods
|
7
|
+
# into this class will allow all Commands to reference them.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# module Helpers
|
12
|
+
# def pretty_logger(msg)
|
13
|
+
# puts "*~*~*~*"
|
14
|
+
# puts msg
|
15
|
+
# puts "*~*~*~*"
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # Add Helpers to Environment
|
20
|
+
# Excavator::Environment.modify Helpers
|
21
|
+
#
|
22
|
+
# param :msg
|
23
|
+
# command :print do
|
24
|
+
# # #pretty_logger is now available
|
25
|
+
# pretty_logger params[:msg]
|
26
|
+
# end
|
27
|
+
#
|
2
28
|
class Environment
|
3
29
|
|
30
|
+
# Public: A convenience method to include other modules in this class.
|
4
31
|
def self.modify(*mods, &block)
|
5
32
|
mods.each { |m| include m }
|
6
33
|
instance_eval &block if block
|
@@ -15,7 +42,27 @@ module Excavator
|
|
15
42
|
@unparsed_params = options[:unparsed_params]
|
16
43
|
end
|
17
44
|
|
18
|
-
# Execute another command
|
45
|
+
# Public: Execute another command. This is a convenience method to execute
|
46
|
+
# other commands defined in Excavator. #execute will return the value
|
47
|
+
# of the executed command (useful for building reusable blocks of code).
|
48
|
+
#
|
49
|
+
# command - A String full name of a command. This should include namespaces.
|
50
|
+
# params - A Hash of Param name to values to pass to this command.
|
51
|
+
#
|
52
|
+
# Examples
|
53
|
+
#
|
54
|
+
# namespace :test do
|
55
|
+
# command :prepare do
|
56
|
+
# puts params[:foo]
|
57
|
+
# # => "bar"
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# command :run do
|
61
|
+
# execute "test:prepare", {:foo => "bar"}
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# Returns value from executed command.
|
19
66
|
def execute(command, params = {})
|
20
67
|
command = runner.find_command(command)
|
21
68
|
command.execute(params)
|
data/lib/excavator/namespace.rb
CHANGED
@@ -1,6 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
module Excavator
|
4
|
+
|
5
|
+
# Internal: Namespace is a container for other Namespaces and Commands.
|
6
|
+
#
|
7
|
+
# Examples
|
8
|
+
#
|
9
|
+
# ns = Namespace.new
|
10
|
+
#
|
11
|
+
# # Add a command
|
12
|
+
# ns << Command.new(:log)
|
13
|
+
#
|
14
|
+
# # Add a namespace
|
15
|
+
# ns << Namespace.new(:database)
|
16
|
+
#
|
17
|
+
# # Lookup a command
|
18
|
+
# ns.command(:log)
|
19
|
+
#
|
20
|
+
# # Lookup a namespace
|
21
|
+
# ns.namespace(:namespace)
|
22
|
+
#
|
2
23
|
class Namespace
|
24
|
+
|
25
|
+
# Public: A String/Symbol name for the namespace. This is used to build
|
26
|
+
# a Command's full name used on the command line.
|
3
27
|
attr_reader :name
|
28
|
+
|
29
|
+
# Internal: A pointer to the parent Namespace (if any).
|
4
30
|
attr_accessor :parent
|
5
31
|
|
6
32
|
def initialize(name = nil, options = {})
|
@@ -9,6 +35,27 @@ module Excavator
|
|
9
35
|
@commands = {}
|
10
36
|
end
|
11
37
|
|
38
|
+
# Public: Add a Namespace or Command instance to this namespace.
|
39
|
+
#
|
40
|
+
# When adding namespaces, it will automatically set #parent attribute
|
41
|
+
# on the new object.
|
42
|
+
#
|
43
|
+
# obj - A Command or Namespace to add to the namespace.
|
44
|
+
#
|
45
|
+
# Examples
|
46
|
+
#
|
47
|
+
# ns = Namespace.new(:test)
|
48
|
+
#
|
49
|
+
# # Add a namespace. The added namespace will have #parent set to 'ns'.
|
50
|
+
# recent = Namespace.new(:recent)
|
51
|
+
# ns << recent
|
52
|
+
# recent.parent
|
53
|
+
# # => ns
|
54
|
+
#
|
55
|
+
# # Add a command.
|
56
|
+
# ns << Command.new(:name)
|
57
|
+
#
|
58
|
+
# Returns itself (Namespace).
|
12
59
|
def <<(obj)
|
13
60
|
if self.class === obj
|
14
61
|
obj.parent = self
|
@@ -16,16 +63,74 @@ module Excavator
|
|
16
63
|
else
|
17
64
|
@commands[obj.name] = obj
|
18
65
|
end
|
66
|
+
|
67
|
+
self
|
19
68
|
end
|
20
69
|
|
70
|
+
# Public: Look up a command within this namespace. It does not look into
|
71
|
+
# child namespaces.
|
72
|
+
#
|
73
|
+
# name - A String or Symbol name of the command to look up.
|
74
|
+
#
|
75
|
+
# Examples
|
76
|
+
#
|
77
|
+
# ns = Namespace.new(:test)
|
78
|
+
# ns << Command.new(:run)
|
79
|
+
# ns.command("run")
|
80
|
+
# # => <Command :run>
|
81
|
+
#
|
82
|
+
# Returns a Command if the command exists in the namespace.
|
83
|
+
# Returns nil if the command does not exist.
|
21
84
|
def command(name)
|
22
85
|
@commands[name.to_sym]
|
23
86
|
end
|
24
87
|
|
88
|
+
# Public: Look up a child namespace within this namespace.
|
89
|
+
#
|
90
|
+
# name - A String or Symbol name of the namespace to look up.
|
91
|
+
#
|
92
|
+
# Examples
|
93
|
+
#
|
94
|
+
# ns = Namespace.new(:test)
|
95
|
+
# ns << Namespace.new(:recent)
|
96
|
+
# ns.namespace(:recent)
|
97
|
+
# # => <Namespace :recent>
|
98
|
+
#
|
99
|
+
# Returns a Namespace if it exists in the namespace.
|
100
|
+
# Returns nil if the namespace does not exist.
|
25
101
|
def namespace(name)
|
26
102
|
@namespaces[name.to_sym]
|
27
103
|
end
|
28
104
|
|
105
|
+
# Public: The full name of the namespace. This returns a string of the
|
106
|
+
# namespace name and it's ancestor's name joined by ":". A namespace with
|
107
|
+
# no parent will return an emptry String.
|
108
|
+
#
|
109
|
+
# command_name - An optional String or Symbol name of a command to append
|
110
|
+
# to the end of the namespace's full name.
|
111
|
+
#
|
112
|
+
# Examples
|
113
|
+
#
|
114
|
+
# ns_a = Namespace.new(:a)
|
115
|
+
# ns_b = Namespace.new(:b)
|
116
|
+
# ns_c = Namespace.new(:c)
|
117
|
+
#
|
118
|
+
# ns_a << ns_b
|
119
|
+
# ns_b << ns_c
|
120
|
+
#
|
121
|
+
# ns_a.full_name
|
122
|
+
# # => ""
|
123
|
+
#
|
124
|
+
# ns_b.full_name
|
125
|
+
# # => "a:b"
|
126
|
+
#
|
127
|
+
# ns_c.full_name
|
128
|
+
# # => "a:b:c"
|
129
|
+
#
|
130
|
+
# ns_c.full_name("zebra")
|
131
|
+
# # => "a:b:c:zebra"
|
132
|
+
#
|
133
|
+
# Returns a String.
|
29
134
|
def full_name(command_name = nil)
|
30
135
|
return "#{command_name}" if parent.nil?
|
31
136
|
|
@@ -37,11 +142,35 @@ module Excavator
|
|
37
142
|
parts.compact.join(":")
|
38
143
|
end
|
39
144
|
|
145
|
+
# Public: An array of full command names and their description. This will
|
146
|
+
# include all commands in child namespaces as well.
|
147
|
+
#
|
148
|
+
# Examples
|
149
|
+
#
|
150
|
+
# # Setup
|
151
|
+
# ns_a = Namespace.new(:a)
|
152
|
+
# ns_b = Namespace.new(:b)
|
153
|
+
# ns_c = Namespace.new(:c)
|
154
|
+
#
|
155
|
+
# # A > B > C
|
156
|
+
# ns_a << ns_b
|
157
|
+
# ns_b << ns_c
|
158
|
+
#
|
159
|
+
# ns_b << Command.new(:foo, :desc => "Foo Desc")
|
160
|
+
# ns_c << Command.new(:bar, :desc => "Bar Desc")
|
161
|
+
#
|
162
|
+
# ns_a.commands_and_descriptions
|
163
|
+
# # => [
|
164
|
+
# # ["a:b:foo", "Foo Desc"],
|
165
|
+
# # ["a:b:c:bar", "Bar Desc"
|
166
|
+
# # ]
|
167
|
+
#
|
168
|
+
# Returns an Array of two item Arrays.
|
40
169
|
def commands_and_descriptions
|
41
170
|
items = []
|
42
171
|
@commands.each do |cmd_name, cmd|
|
43
172
|
items << [
|
44
|
-
full_name
|
173
|
+
cmd.full_name,
|
45
174
|
cmd.desc
|
46
175
|
]
|
47
176
|
end
|
data/lib/excavator/param.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
module Excavator
|
4
|
+
|
5
|
+
# Internal: Param is an object to describe the attributes of a parameter.
|
6
|
+
# There's never a need to instantiate these directly - instead use DSL#param.
|
2
7
|
class Param
|
8
|
+
|
9
|
+
# Public: The String/Symbol name of the Param.
|
3
10
|
attr_reader :name
|
11
|
+
|
12
|
+
# Public: An optional default value for the Param.
|
4
13
|
attr_reader :default
|
14
|
+
|
15
|
+
# Public: An optional String description for the Param.
|
5
16
|
attr_reader :desc
|
6
|
-
|
17
|
+
|
18
|
+
# Public: An optional String (normally one character) to use as the short
|
19
|
+
# switch for the Param.
|
20
|
+
attr_accessor :short
|
7
21
|
|
8
22
|
def initialize(name, options = {})
|
9
23
|
@name = name
|
@@ -13,10 +27,16 @@ module Excavator
|
|
13
27
|
@short = options[:short]
|
14
28
|
end
|
15
29
|
|
30
|
+
# Public: Returns whether or not the Param is required.
|
31
|
+
#
|
32
|
+
# Returns a Boolean.
|
16
33
|
def required?
|
17
34
|
!optional?
|
18
35
|
end
|
19
36
|
|
37
|
+
# Public: Returns whether or not the Param is optional.
|
38
|
+
#
|
39
|
+
# Returns a Boolean.
|
20
40
|
def optional?
|
21
41
|
@default || (@default.nil? && @optional)
|
22
42
|
end
|
@@ -1,9 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
module Excavator
|
5
|
+
|
6
|
+
# Public: ParamParser is a strategy object to integrate Command, Param, and
|
7
|
+
# OptionParser to parse commandline arguments.
|
8
|
+
#
|
9
|
+
# ParamParser is the heart of Excavator. It takes Params and a Command and
|
10
|
+
# creates a command line parser that:
|
11
|
+
#
|
12
|
+
# * automatically assigns short and long switches for params
|
13
|
+
# (e.g. :name => --name or -n)
|
14
|
+
# * parsers arguments into named parameters
|
15
|
+
# * assigns default values to parameters if specified
|
16
|
+
# * creates a help message
|
17
|
+
# * verifies required params are passed - throws an error if they are not
|
18
|
+
#
|
19
|
+
# Examples
|
20
|
+
#
|
21
|
+
# parser = Excavator::ParamParser.new
|
22
|
+
# parser.build(
|
23
|
+
# :name => "command_name",
|
24
|
+
# :desc => "command description",
|
25
|
+
# :params => [ <Param>, <Param>, ... ]
|
26
|
+
# )
|
27
|
+
#
|
28
|
+
# parser.parse!(ARGV)
|
29
|
+
# # => { :param1 => "val", :param2 => "val2", ... }
|
30
|
+
#
|
3
31
|
class ParamParser
|
4
32
|
|
5
33
|
attr_accessor :name
|
6
|
-
attr_accessor :banner
|
7
34
|
|
8
35
|
def initialize
|
9
36
|
@parser = OptionParser.new
|
@@ -11,10 +38,29 @@ module Excavator
|
|
11
38
|
@params = []
|
12
39
|
end
|
13
40
|
|
41
|
+
# Public: Builds the parser up with the name, description and Params
|
42
|
+
# passed in.
|
43
|
+
#
|
44
|
+
# This builds an OptionParser object with a "-h" and "--help" option.
|
45
|
+
#
|
46
|
+
# options - A Hash of parameters needed to build the parser.
|
47
|
+
# :name - A String/Symbol name of the Command.
|
48
|
+
# :desc - A String description of the command.
|
49
|
+
# :params - An Array of Params.
|
50
|
+
#
|
51
|
+
# Examples
|
52
|
+
#
|
53
|
+
# ParamParser.new.build({
|
54
|
+
# :name => "test_command",
|
55
|
+
# :desc => "description",
|
56
|
+
# :params => [<Param>, <Param>]
|
57
|
+
# })
|
58
|
+
#
|
59
|
+
# Returns nothing.
|
14
60
|
def build(options = {})
|
15
|
-
@name = options[:name] if options[:name]
|
16
|
-
@params = options[:params] if options[:params]
|
17
|
-
@desc = options[:desc] if options[:desc]
|
61
|
+
@name = options[:name] #if options[:name]
|
62
|
+
@params = options[:params] #if options[:params]
|
63
|
+
@desc = options[:desc] #if options[:desc]
|
18
64
|
|
19
65
|
required_params = []
|
20
66
|
optional_params = []
|
@@ -26,7 +72,7 @@ module Excavator
|
|
26
72
|
opts = []
|
27
73
|
|
28
74
|
# Long option
|
29
|
-
opts << "--#{param.name}"
|
75
|
+
opts << "--#{param.name.to_s.gsub("_", "-")}"
|
30
76
|
|
31
77
|
# params require an argument (for now)
|
32
78
|
opts << "=#{param.name.to_s.upcase}"
|
@@ -69,6 +115,36 @@ module Excavator
|
|
69
115
|
end
|
70
116
|
end
|
71
117
|
|
118
|
+
# Public: Parses command line arguments. Any argument matching Params are
|
119
|
+
# removed from the argument list (destructive!) and returned in a hash.
|
120
|
+
#
|
121
|
+
# args - An Array of command line arguments. This is usually ARGV.
|
122
|
+
# If the last argument in the Array is a Hash, it is used as the
|
123
|
+
# default parameters. This is a convience method to pass arguments
|
124
|
+
# in a Hash form rather than an Array like ARGV.
|
125
|
+
#
|
126
|
+
# Examples
|
127
|
+
#
|
128
|
+
# parser = ParamParser.new
|
129
|
+
# parser.build({...})
|
130
|
+
#
|
131
|
+
# # Standard usage - assuming there is :name param.
|
132
|
+
# args = ["command", "--name", "hello"]
|
133
|
+
# parser.parse!(args)
|
134
|
+
# # => {:name => "hello"},
|
135
|
+
# # => args is now ["command"]
|
136
|
+
#
|
137
|
+
# # Same as above, but the hash is checked to see whether or not
|
138
|
+
# # the required params are met.
|
139
|
+
# parser.parse!([{:name => "hello"}])
|
140
|
+
# # => {:name => "hello"}
|
141
|
+
#
|
142
|
+
# # Assume :name is required
|
143
|
+
# parser.parse!(["command"])
|
144
|
+
# # => Raises MissingParamsError
|
145
|
+
#
|
146
|
+
# Returns a Hash of Symbol names to values.
|
147
|
+
# Raises Excavator::MissingParamsError if args is missing required params.
|
72
148
|
def parse!(args)
|
73
149
|
@parsed_params = args.last.is_a?(Hash) ? args.pop : {}
|
74
150
|
|
@@ -79,6 +155,13 @@ module Excavator
|
|
79
155
|
@parsed_params
|
80
156
|
end
|
81
157
|
|
158
|
+
# Public: Print out a help message for this parser.
|
159
|
+
def usage
|
160
|
+
@parser.to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
82
165
|
def detect_missing_params!
|
83
166
|
missing_params = []
|
84
167
|
@params.each do |p|
|
@@ -90,11 +173,6 @@ module Excavator
|
|
90
173
|
raise MissingParamsError.new(missing_params) if missing_params.size > 0
|
91
174
|
end
|
92
175
|
|
93
|
-
def usage
|
94
|
-
@parser.to_s
|
95
|
-
end
|
96
|
-
|
97
|
-
protected
|
98
176
|
|
99
177
|
def set_default_params
|
100
178
|
@params.each do |param|
|