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/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2012 Peter Bui
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
# Excavator
|
2
|
+
|
3
|
+
Excavator is a commandline scripting framework. It takes care of parameter
|
4
|
+
parsing, command namespacing and command loading so that you can focus on
|
5
|
+
writing your scripts.
|
6
|
+
|
7
|
+
Excavator is like a stripped down version of rake but uses optparse for
|
8
|
+
parameter parsing.
|
9
|
+
|
10
|
+
Here's a simple, albeit contrived, example of what you can do with Excavator.
|
11
|
+
|
12
|
+
require 'excavator'
|
13
|
+
|
14
|
+
param :name
|
15
|
+
command :hello do
|
16
|
+
puts "Hello, #{params[:name]}!"
|
17
|
+
end
|
18
|
+
|
19
|
+
Excavator.run(ARGV)
|
20
|
+
|
21
|
+
Place the previous contents in a file named `say` and then run it:
|
22
|
+
|
23
|
+
$ chmod a+x say
|
24
|
+
$ say hello --name paydro
|
25
|
+
Hello, paydro!
|
26
|
+
$
|
27
|
+
|
28
|
+
|
29
|
+
# Installation
|
30
|
+
|
31
|
+
gem install excavator
|
32
|
+
|
33
|
+
# Usage
|
34
|
+
|
35
|
+
Excavator does not come with a commandline tool. The way to use Excavator is to first create an executable file, then add commands in the file.
|
36
|
+
|
37
|
+
Creating the file:
|
38
|
+
|
39
|
+
$ touch my_commands
|
40
|
+
$ chmod a+x my_commands
|
41
|
+
|
42
|
+
Edit `my_commands` and add the following:
|
43
|
+
|
44
|
+
#!/usr/bin/env ruby
|
45
|
+
require 'excavator'
|
46
|
+
|
47
|
+
namespace :happy_quotes do
|
48
|
+
commands :smile do
|
49
|
+
# ...
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
namespace :jokes do
|
54
|
+
commands :knock_knock do
|
55
|
+
# ...
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Excavator.run(ARGV) # This runs it
|
60
|
+
|
61
|
+
Now you can execute the commands.
|
62
|
+
|
63
|
+
$ my_commands jokes:knock_knock
|
64
|
+
...
|
65
|
+
$ my_commands happy_quotes:smile
|
66
|
+
...
|
67
|
+
$
|
68
|
+
|
69
|
+
When the commands pile up, you can move them to other files and tell Excavator where all your commands live.
|
70
|
+
|
71
|
+
For instance, let's assume we have the same `my_commands` script above, but we also add the directory `commands/`. In `commands/`, we refactor the previous commands from `my_commands`. The file hiearchy now looks like this:
|
72
|
+
|
73
|
+
/Users/paydro/
|
74
|
+
commands/
|
75
|
+
happy_quotes.rb
|
76
|
+
jokes.rb
|
77
|
+
my_commands # The original file
|
78
|
+
|
79
|
+
Now, inside `my_commands`, all we have is this:
|
80
|
+
|
81
|
+
#!/usr/bin/env ruby
|
82
|
+
require 'excavator'
|
83
|
+
Excavator.command_paths = ["/Users/paydro/commands"]
|
84
|
+
Excavator.run(ARGV)
|
85
|
+
|
86
|
+
By default, Excavator already looks into the `commands/` if it exists next to
|
87
|
+
the script, but it's shown above for completeness.
|
88
|
+
|
89
|
+
## Commands
|
90
|
+
|
91
|
+
Commands are created with the `command` method.
|
92
|
+
|
93
|
+
command :list do
|
94
|
+
# ...
|
95
|
+
end
|
96
|
+
|
97
|
+
command :another do
|
98
|
+
# ...
|
99
|
+
end
|
100
|
+
|
101
|
+
### Call Other Commands
|
102
|
+
|
103
|
+
Use `execute` to call other commands from within a command.
|
104
|
+
|
105
|
+
command :print do
|
106
|
+
execute :my_name
|
107
|
+
end
|
108
|
+
|
109
|
+
command :my_name do
|
110
|
+
puts "paydro"
|
111
|
+
end
|
112
|
+
|
113
|
+
You can even pass parameters to other commands with a hash.
|
114
|
+
|
115
|
+
# Prints "paydro"
|
116
|
+
command :print do
|
117
|
+
execute :my_name, :name => "paydro"
|
118
|
+
end
|
119
|
+
|
120
|
+
param :name
|
121
|
+
command :my_name do
|
122
|
+
puts params[:name]
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
## Command Description
|
127
|
+
|
128
|
+
Describe your command using the `desc` method above the command definition.
|
129
|
+
|
130
|
+
desc "Prints all the servers in the cluster"
|
131
|
+
command :list_servers do
|
132
|
+
# ...
|
133
|
+
end
|
134
|
+
|
135
|
+
## Namespaces
|
136
|
+
|
137
|
+
Group similar or related commands via `namespace`.
|
138
|
+
|
139
|
+
# Creates the following commands:
|
140
|
+
# - servers:list
|
141
|
+
# - servers:create
|
142
|
+
# - servers:boot:with_ruby
|
143
|
+
|
144
|
+
namespace :servers do
|
145
|
+
command :list do
|
146
|
+
# ...
|
147
|
+
end
|
148
|
+
|
149
|
+
command :create do
|
150
|
+
# ...
|
151
|
+
end
|
152
|
+
|
153
|
+
namespace :boot do
|
154
|
+
command :with_ruby do
|
155
|
+
# ...
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
## Parameters
|
161
|
+
|
162
|
+
Parameters are available within the command body via the `params`.
|
163
|
+
|
164
|
+
param :first_name
|
165
|
+
param :last_name
|
166
|
+
command :print do
|
167
|
+
puts params[:first_name]
|
168
|
+
puts params[:last_name]
|
169
|
+
end
|
170
|
+
|
171
|
+
### Required, Optional, Defaults
|
172
|
+
|
173
|
+
Params are required by default. To make them optional, specify the optional flag or specify a default
|
174
|
+
|
175
|
+
# Required - if not passed, will stop the script
|
176
|
+
param :first_name
|
177
|
+
|
178
|
+
# Optional
|
179
|
+
param :last_name, :optional => true
|
180
|
+
|
181
|
+
# Optional - has a default
|
182
|
+
param :country, :default => "USA"
|
183
|
+
command :print do
|
184
|
+
puts params[:first_name]
|
185
|
+
puts params[:last_name] # Might be nil!
|
186
|
+
puts params[:country] # USA unless something was passed
|
187
|
+
end
|
188
|
+
|
189
|
+
### Description
|
190
|
+
|
191
|
+
Params can take descriptions so that the script can print them out with `-h`.
|
192
|
+
|
193
|
+
param :last_name, :desc => "Your last name"
|
194
|
+
|
195
|
+
|
196
|
+
### Long and Short Switches
|
197
|
+
|
198
|
+
Parameters are assigned long and short switches unless they are specified. The
|
199
|
+
short switch is usually the first character in the name of the param. If there
|
200
|
+
are duplicates, then the param parser will continue to move to the next
|
201
|
+
character in the name until it finds a unique character. If it cannot find a
|
202
|
+
unique character, then the parameter will not have a short switch.
|
203
|
+
|
204
|
+
# --abc, -a
|
205
|
+
param :abc
|
206
|
+
|
207
|
+
# --bc, -b
|
208
|
+
param :bc
|
209
|
+
|
210
|
+
# --ab
|
211
|
+
# "a" and "b" are already taken above, so this one doesn't have a short
|
212
|
+
# switch
|
213
|
+
param :ab
|
214
|
+
|
215
|
+
# Force a short switch, -c
|
216
|
+
param :aabb, :short => "c"
|
217
|
+
|
218
|
+
## Help Commands
|
219
|
+
|
220
|
+
You can list all commands you've created by calling your script with `--help`, `-h`, or `-?`. This will print out all the commands and their descriptions for you.
|
221
|
+
|
222
|
+
# Lists all commands (assuming our commands are in "servers")
|
223
|
+
$ servers --help
|
224
|
+
... prints out commands ...
|
225
|
+
$
|
226
|
+
|
227
|
+
To view the usage of a single command, pass the `-h` or `--help` switch when
|
228
|
+
calling the command.
|
229
|
+
|
230
|
+
# Lists usage of "list" (assuming our commands are in "servers")
|
231
|
+
$ servers list -h
|
232
|
+
... usage ...
|
233
|
+
$
|
234
|
+
|
235
|
+
|
236
|
+
# Add Helpers For Commands
|
237
|
+
|
238
|
+
Commands run within and instance of `Excavator::Environment`, and it has a few methods that you can use. To extend this, you can use `Excavator::Environment#modify`. This is really just a helper give you access to the `Excavator::Environment` scope (i.e., for including other modules).
|
239
|
+
|
240
|
+
require 'excavator'
|
241
|
+
|
242
|
+
module Helpers
|
243
|
+
def hello
|
244
|
+
puts "hello!"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
module ExpensiveHTTPCalls
|
249
|
+
# ...
|
250
|
+
end
|
251
|
+
|
252
|
+
Excavator.environment_class.modify Helpers, ExpensiveHTTPCalls
|
253
|
+
|
254
|
+
# OR
|
255
|
+
|
256
|
+
Excavator.environment_class.modify do
|
257
|
+
include Helpers
|
258
|
+
end
|
259
|
+
|
260
|
+
command :example do
|
261
|
+
hello
|
262
|
+
end
|
263
|
+
|
264
|
+
# Contrib
|
265
|
+
|
266
|
+
## Bugs
|
267
|
+
|
268
|
+
Submit bugs [on GitHub](https://github.com/paydro/excavator/issues).
|
269
|
+
|
270
|
+
## Hacking
|
271
|
+
|
272
|
+
Please fork the [repository](https://github.com/paydro/excavator) on GitHub and send me a pull request. Here's a few guidelines:
|
273
|
+
|
274
|
+
* Use 80 character columns
|
275
|
+
* Write tests!
|
276
|
+
* Follow other examples in the code
|
277
|
+
|
278
|
+
# Credits
|
279
|
+
|
280
|
+
[Peter Bui](peter@paydrotalks.com)
|
data/lib/excavator.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
require 'pathname'
|
2
4
|
|
5
|
+
# Excavator automatically creates a command line parser for your params as well
|
6
|
+
# as building a simple usage and option messages for your scripts.
|
3
7
|
module Excavator
|
4
8
|
class ExcavatorError < ::StandardError; end
|
5
9
|
|
@@ -11,37 +15,44 @@ module Excavator
|
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@defaults[name.to_sym] = default
|
18
|
-
module_eval <<-MOD, __FILE__, __LINE__ + 1
|
19
|
-
def self.#{name}
|
20
|
-
@config[:#{name}] || @defaults[:#{name}]
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.#{name}=(val)
|
24
|
-
@config[:#{name}] = val
|
25
|
-
end
|
26
|
-
MOD
|
27
|
-
end
|
28
|
-
|
18
|
+
# Public: The current working directory for the Excavator script.
|
19
|
+
#
|
20
|
+
# Returns a Pathname.
|
29
21
|
def self.cwd
|
30
22
|
@cwd ||= Pathname.new(Dir.pwd).expand_path
|
31
23
|
end
|
32
24
|
|
33
|
-
def self.reset!
|
34
|
-
self.runner = nil
|
35
|
-
end
|
36
25
|
|
26
|
+
# Public: The global Runner object. This object is called from
|
27
|
+
# Excavator.run to start the whole command loading, commandline parsing and
|
28
|
+
# command execution process.
|
29
|
+
#
|
30
|
+
# Returns a Runner class.
|
37
31
|
def self.runner
|
38
32
|
@runner ||= runner_class.new
|
39
33
|
end
|
40
34
|
|
35
|
+
# Public: The global Runner assignment method.
|
36
|
+
#
|
37
|
+
# runner - Any Class that implements Excavator::Runner's public api.
|
38
|
+
#
|
41
39
|
def self.runner=(runner)
|
42
40
|
@runner = runner
|
43
41
|
end
|
44
42
|
|
43
|
+
# Public: Start Excavator.
|
44
|
+
#
|
45
|
+
# On command error, this method will exit with a status of 1 and print
|
46
|
+
# the error message to $stderr.
|
47
|
+
#
|
48
|
+
# params - An Array of parameters. This is normally ARGV.
|
49
|
+
#
|
50
|
+
# Examples
|
51
|
+
#
|
52
|
+
# Excavator.run(ARGV)
|
53
|
+
# # => executes command passed in via ARGV
|
54
|
+
#
|
55
|
+
# Returns the object returned to by the command or exits with a status of 1.
|
45
56
|
def self.run(params)
|
46
57
|
begin
|
47
58
|
runner.run(params)
|
@@ -56,6 +67,48 @@ module Excavator
|
|
56
67
|
end
|
57
68
|
end
|
58
69
|
|
70
|
+
# Internal: Setup class level configuration variables with defaults.
|
71
|
+
#
|
72
|
+
# Examples
|
73
|
+
#
|
74
|
+
# module Excavator
|
75
|
+
# config :test, "test"
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# Excavator.test
|
79
|
+
# # => "test"
|
80
|
+
#
|
81
|
+
# Excavator.test = "123"
|
82
|
+
# Excavator.test
|
83
|
+
# # => "123"
|
84
|
+
#
|
85
|
+
# Returns nothing.
|
86
|
+
def self.config(name, default)
|
87
|
+
@config ||= {}
|
88
|
+
@defaults ||= {}
|
89
|
+
@defaults[name.to_sym] = default
|
90
|
+
module_eval <<-MOD, __FILE__, __LINE__ + 1
|
91
|
+
def self.#{name}
|
92
|
+
@config[:#{name}] || @defaults[:#{name}]
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.#{name}=(val)
|
96
|
+
@config[:#{name}] = val
|
97
|
+
end
|
98
|
+
MOD
|
99
|
+
end
|
100
|
+
|
101
|
+
# Internal: Resets the global Runner object. This is primarily used in
|
102
|
+
# testing.
|
103
|
+
#
|
104
|
+
# Examples
|
105
|
+
#
|
106
|
+
# Excavator.reset!
|
107
|
+
#
|
108
|
+
# Returns nothing.
|
109
|
+
def self.reset!
|
110
|
+
self.runner = nil
|
111
|
+
end
|
59
112
|
end
|
60
113
|
|
61
114
|
require 'excavator/version'
|
@@ -69,7 +122,6 @@ require 'excavator/runner'
|
|
69
122
|
require 'excavator/table_view'
|
70
123
|
|
71
124
|
module Excavator
|
72
|
-
# Setup defaults classes
|
73
125
|
config :command_paths, []
|
74
126
|
config :runner_class, Runner
|
75
127
|
config :namespace_class, Namespace
|
data/lib/excavator/command.rb
CHANGED
@@ -1,24 +1,37 @@
|
|
1
|
-
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
2
3
|
module Excavator
|
4
|
+
|
5
|
+
# Public: A Command is building block in Excavator. Commands are built
|
6
|
+
# incrementally (normally via methods in Excavator::DSL).
|
3
7
|
class Command
|
4
8
|
# Descriptors
|
5
9
|
attr_accessor :name, :desc, :namespace
|
6
10
|
|
7
|
-
#
|
11
|
+
# The logic for the command
|
8
12
|
attr_accessor :block
|
9
13
|
|
10
14
|
# A list of Param objects
|
11
15
|
attr_reader :param_definitions
|
12
16
|
|
17
|
+
# Public: Parsed params (parsed using ParamParser)
|
13
18
|
attr_reader :params
|
19
|
+
|
20
|
+
# Public: An Array copy of arguments passed into this command
|
21
|
+
# (i.e, before ParamParser#parse! is called)
|
14
22
|
attr_reader :raw_params
|
23
|
+
|
24
|
+
# Public: An Array of unparsed parameters. These are the left over arguments
|
25
|
+
# after ParamParser#parse! is called.
|
15
26
|
attr_reader :unparsed_params
|
16
27
|
|
28
|
+
# Public: Reference to the Runner
|
17
29
|
attr_reader :runner
|
18
30
|
|
19
31
|
def initialize(runner, options = {})
|
20
32
|
@runner = runner
|
21
33
|
@name = options[:name]
|
34
|
+
@desc = options[:desc]
|
22
35
|
@block = options[:block]
|
23
36
|
@param_definitions = options[:param_definitions] || []
|
24
37
|
@namespace = options[:namespace]
|
@@ -27,21 +40,61 @@ module Excavator
|
|
27
40
|
@params = {}
|
28
41
|
end
|
29
42
|
|
43
|
+
# Public: Add a Param to this command.
|
44
|
+
#
|
45
|
+
# Examples
|
46
|
+
#
|
47
|
+
# command = Command.new
|
48
|
+
# command.add_param(Param.new(:test))
|
49
|
+
#
|
50
|
+
# Returns nothing.
|
30
51
|
def add_param(param)
|
31
52
|
self.param_definitions << param
|
32
53
|
end
|
33
54
|
|
34
|
-
|
35
|
-
|
36
|
-
|
55
|
+
# Public: Execute the Command's block within a Excavator::Environment
|
56
|
+
# instance. Arguments are parsed, setup with default values, and checked
|
57
|
+
# to ensure the Command has all the proper parameters.
|
58
|
+
#
|
59
|
+
# args - An Array of arguments to pass into the block. This is normally
|
60
|
+
# the same format as ARGV.
|
61
|
+
#
|
62
|
+
# Examples
|
63
|
+
#
|
64
|
+
# command = Command.new( ... )
|
65
|
+
# command.execute(["-a", "abc", "--foo", "bar"])
|
66
|
+
#
|
67
|
+
# Returns the value returned by the Command's block.
|
68
|
+
def execute(*args)
|
69
|
+
args.flatten!
|
70
|
+
parse_params args
|
37
71
|
run
|
38
72
|
end
|
39
73
|
|
74
|
+
# Public: Execute this command. This is like #execute except the parameters
|
75
|
+
# is a Hash.
|
76
|
+
#
|
77
|
+
# parsed_params - A Hash of params to pass into the Command's block.
|
78
|
+
#
|
79
|
+
# Examples
|
80
|
+
#
|
81
|
+
# command = Command.new( ... )
|
82
|
+
# command.execute({:a => "abc", :foo => "bar})
|
83
|
+
#
|
84
|
+
# Returns the value returned by the Command's block.
|
40
85
|
def execute_with_params(parsed_params = {})
|
41
86
|
parse_params [parsed_params]
|
42
87
|
run
|
43
88
|
end
|
44
89
|
|
90
|
+
# Public: The full name of the Command. This includes the Namespace's name
|
91
|
+
# and the Namespace's ancestor's names.
|
92
|
+
#
|
93
|
+
# Returns a String.
|
94
|
+
def full_name
|
95
|
+
namespace.nil? ? name.to_s : namespace.full_name(name)
|
96
|
+
end
|
97
|
+
|
45
98
|
protected
|
46
99
|
|
47
100
|
# Internal
|
@@ -65,11 +118,8 @@ module Excavator
|
|
65
118
|
|
66
119
|
def build_parser
|
67
120
|
return if @parser_built
|
68
|
-
command_name = ""
|
69
|
-
command_name << "#{namespace.full_name}:" if namespace
|
70
|
-
command_name << name.to_s
|
71
121
|
@param_parser.build(
|
72
|
-
:name =>
|
122
|
+
:name => full_name,
|
73
123
|
:desc => desc,
|
74
124
|
:params => param_definitions
|
75
125
|
)
|