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 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.
@@ -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)
@@ -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
- def self.config(name, default)
15
- @config ||= {}
16
- @defaults ||= {}
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
@@ -1,24 +1,37 @@
1
- require 'timeout'
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
- # Block to run when command is executed
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
- def execute(*inputs)
35
- inputs.flatten!
36
- parse_params inputs
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 => command_name,
122
+ :name => full_name,
73
123
  :desc => desc,
74
124
  :params => param_definitions
75
125
  )