excavator 0.0.1
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/.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
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/bin/excavator
ADDED
data/excavator.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "excavator/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "excavator"
|
7
|
+
s.version = Excavator::VERSION
|
8
|
+
s.authors = ["Peter Bui"]
|
9
|
+
s.email = ["peter@paydrotalks.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A scripting framework for *nix systems.}
|
12
|
+
s.description = <<DESC
|
13
|
+
Excavator is a scripting framework for writing multi-command executables for the
|
14
|
+
unix environment.
|
15
|
+
DESC
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency "minitest", ">= 2.10.0"
|
23
|
+
s.add_development_dependency "ruby-debug19"
|
24
|
+
s.add_development_dependency "rake"
|
25
|
+
end
|
data/lib/excavator.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Excavator
|
4
|
+
class ExcavatorError < ::StandardError; end
|
5
|
+
|
6
|
+
class MissingParamsError < ExcavatorError
|
7
|
+
attr_reader :params
|
8
|
+
def initialize(params)
|
9
|
+
@params = params
|
10
|
+
super "Missing parameters: #{params.join(", ")}."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
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
|
+
|
29
|
+
def self.cwd
|
30
|
+
@cwd ||= Pathname.new(Dir.pwd).expand_path
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.reset!
|
34
|
+
self.runner = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.runner
|
38
|
+
@runner ||= runner_class.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.runner=(runner)
|
42
|
+
@runner = runner
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.run(params)
|
46
|
+
begin
|
47
|
+
runner.run(params)
|
48
|
+
|
49
|
+
rescue => e
|
50
|
+
$stderr.puts e.message
|
51
|
+
if ENV['DEBUG'] == '1'
|
52
|
+
e.backtrace.each { |line| $stderr.puts line }
|
53
|
+
end
|
54
|
+
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
require 'excavator/version'
|
62
|
+
require 'excavator/dsl'
|
63
|
+
require 'excavator/namespace'
|
64
|
+
require 'excavator/param'
|
65
|
+
require 'excavator/param_parser'
|
66
|
+
require 'excavator/command'
|
67
|
+
require 'excavator/environment'
|
68
|
+
require 'excavator/runner'
|
69
|
+
require 'excavator/table_view'
|
70
|
+
|
71
|
+
module Excavator
|
72
|
+
# Setup defaults classes
|
73
|
+
config :command_paths, []
|
74
|
+
config :runner_class, Runner
|
75
|
+
config :namespace_class, Namespace
|
76
|
+
config :param_parser_class, ParamParser
|
77
|
+
config :param_class, Param
|
78
|
+
config :command_class, Command
|
79
|
+
config :environment_class, Environment
|
80
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
module Excavator
|
3
|
+
class Command
|
4
|
+
# Descriptors
|
5
|
+
attr_accessor :name, :desc, :namespace
|
6
|
+
|
7
|
+
# Block to run when command is executed
|
8
|
+
attr_accessor :block
|
9
|
+
|
10
|
+
# A list of Param objects
|
11
|
+
attr_reader :param_definitions
|
12
|
+
|
13
|
+
attr_reader :params
|
14
|
+
attr_reader :raw_params
|
15
|
+
attr_reader :unparsed_params
|
16
|
+
|
17
|
+
attr_reader :runner
|
18
|
+
|
19
|
+
def initialize(runner, options = {})
|
20
|
+
@runner = runner
|
21
|
+
@name = options[:name]
|
22
|
+
@block = options[:block]
|
23
|
+
@param_definitions = options[:param_definitions] || []
|
24
|
+
@namespace = options[:namespace]
|
25
|
+
@param_parser = options[:param_parser] ||
|
26
|
+
Excavator.param_parser_class.new
|
27
|
+
@params = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_param(param)
|
31
|
+
self.param_definitions << param
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute(*inputs)
|
35
|
+
inputs.flatten!
|
36
|
+
parse_params inputs
|
37
|
+
run
|
38
|
+
end
|
39
|
+
|
40
|
+
def execute_with_params(parsed_params = {})
|
41
|
+
parse_params [parsed_params]
|
42
|
+
run
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# Internal
|
48
|
+
def run
|
49
|
+
env = Excavator.environment_class.new(
|
50
|
+
:runner => runner,
|
51
|
+
:params => params,
|
52
|
+
:unparsed_params => unparsed_params,
|
53
|
+
:raw_params => raw_params
|
54
|
+
)
|
55
|
+
env.instance_eval(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Internal
|
59
|
+
def parse_params(inputs)
|
60
|
+
build_parser
|
61
|
+
@raw_params = inputs.dup
|
62
|
+
@params = @param_parser.parse!(inputs) if param_definitions.size > 0
|
63
|
+
@unparsed_params = inputs
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_parser
|
67
|
+
return if @parser_built
|
68
|
+
command_name = ""
|
69
|
+
command_name << "#{namespace.full_name}:" if namespace
|
70
|
+
command_name << name.to_s
|
71
|
+
@param_parser.build(
|
72
|
+
:name => command_name,
|
73
|
+
:desc => desc,
|
74
|
+
:params => param_definitions
|
75
|
+
)
|
76
|
+
@parser_built = true
|
77
|
+
end
|
78
|
+
end # Command
|
79
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Excavator
|
2
|
+
# Needs to depend on the runner
|
3
|
+
module DSL
|
4
|
+
def namespace(name)
|
5
|
+
Excavator.runner.in_namespace(name) do
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def desc(description)
|
11
|
+
Excavator.runner.last_command.desc = description
|
12
|
+
end
|
13
|
+
|
14
|
+
def param(name, options = {})
|
15
|
+
param = Excavator.param_class.new(name, options)
|
16
|
+
Excavator.runner.last_command.add_param(param)
|
17
|
+
end
|
18
|
+
|
19
|
+
def command(name, &block)
|
20
|
+
cmd = Excavator.runner.last_command
|
21
|
+
cmd.name = name
|
22
|
+
cmd.block = block
|
23
|
+
Excavator.runner.current_namespace << cmd
|
24
|
+
Excavator.runner.clear_last_command!
|
25
|
+
cmd
|
26
|
+
end
|
27
|
+
|
28
|
+
end # DSL
|
29
|
+
end # Excavator
|
30
|
+
|
31
|
+
self.extend Excavator::DSL
|
32
|
+
|
33
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Excavator
|
2
|
+
class Environment
|
3
|
+
|
4
|
+
def self.modify(*mods, &block)
|
5
|
+
mods.each { |m| include m }
|
6
|
+
instance_eval &block if block
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :runner, :params, :unparsed_params, :raw_params
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@runner = options[:runner]
|
13
|
+
@params = options[:params]
|
14
|
+
@raw_params = options[:raw_params]
|
15
|
+
@unparsed_params = options[:unparsed_params]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Execute another command
|
19
|
+
def execute(command, params = {})
|
20
|
+
command = runner.find_command(command)
|
21
|
+
command.execute(params)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Excavator
|
2
|
+
class Namespace
|
3
|
+
attr_reader :name
|
4
|
+
attr_accessor :parent
|
5
|
+
|
6
|
+
def initialize(name = nil, options = {})
|
7
|
+
@name = name.to_sym if name
|
8
|
+
@namespaces = {}
|
9
|
+
@commands = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def <<(obj)
|
13
|
+
if self.class === obj
|
14
|
+
obj.parent = self
|
15
|
+
@namespaces[obj.name] = obj
|
16
|
+
else
|
17
|
+
@commands[obj.name] = obj
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def command(name)
|
22
|
+
@commands[name.to_sym]
|
23
|
+
end
|
24
|
+
|
25
|
+
def namespace(name)
|
26
|
+
@namespaces[name.to_sym]
|
27
|
+
end
|
28
|
+
|
29
|
+
def full_name(command_name = nil)
|
30
|
+
return "#{command_name}" if parent.nil?
|
31
|
+
|
32
|
+
parts = []
|
33
|
+
parts << parent.full_name if parent.full_name != ""
|
34
|
+
parts << name.to_s
|
35
|
+
parts << command_name.to_s if command_name
|
36
|
+
|
37
|
+
parts.compact.join(":")
|
38
|
+
end
|
39
|
+
|
40
|
+
def commands_and_descriptions
|
41
|
+
items = []
|
42
|
+
@commands.each do |cmd_name, cmd|
|
43
|
+
items << [
|
44
|
+
full_name(cmd_name),
|
45
|
+
cmd.desc
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
@namespaces.each do |ns_name, ns|
|
50
|
+
items.push(*ns.commands_and_descriptions)
|
51
|
+
end
|
52
|
+
|
53
|
+
items.sort
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Excavator
|
2
|
+
class Param
|
3
|
+
attr_reader :name
|
4
|
+
attr_reader :default
|
5
|
+
attr_reader :desc
|
6
|
+
attr_accessor :short # Set by the ParamParser
|
7
|
+
|
8
|
+
def initialize(name, options = {})
|
9
|
+
@name = name
|
10
|
+
@default = options[:default]
|
11
|
+
@optional = options[:optional]
|
12
|
+
@desc = options[:desc]
|
13
|
+
@short = options[:short]
|
14
|
+
end
|
15
|
+
|
16
|
+
def required?
|
17
|
+
!optional?
|
18
|
+
end
|
19
|
+
|
20
|
+
def optional?
|
21
|
+
@default || (@default.nil? && @optional)
|
22
|
+
end
|
23
|
+
|
24
|
+
end # Param
|
25
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
module Excavator
|
3
|
+
class ParamParser
|
4
|
+
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :banner
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@parser = OptionParser.new
|
10
|
+
@parsed_params = {}
|
11
|
+
@params = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def build(options = {})
|
15
|
+
@name = options[:name] if options[:name]
|
16
|
+
@params = options[:params] if options[:params]
|
17
|
+
@desc = options[:desc] if options[:desc]
|
18
|
+
|
19
|
+
required_params = []
|
20
|
+
optional_params = []
|
21
|
+
|
22
|
+
@parser.banner = @desc
|
23
|
+
@parser.separator ""
|
24
|
+
@parser.separator "USAGE: #{@name} [options]"
|
25
|
+
@params.each do |param|
|
26
|
+
opts = []
|
27
|
+
|
28
|
+
# Long option
|
29
|
+
opts << "--#{param.name}"
|
30
|
+
|
31
|
+
# params require an argument (for now)
|
32
|
+
opts << "=#{param.name.to_s.upcase}"
|
33
|
+
|
34
|
+
# Short option
|
35
|
+
opts << short_switch(param)
|
36
|
+
|
37
|
+
opts << param.desc if param.desc
|
38
|
+
opts << "Defaults to: #{param.default}" if param.default
|
39
|
+
|
40
|
+
opts << Proc.new do |val|
|
41
|
+
@parsed_params[param.name] = val
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.compact!
|
45
|
+
|
46
|
+
if param.required?
|
47
|
+
required_params << opts
|
48
|
+
else
|
49
|
+
optional_params << opts
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if required_params.size > 0
|
54
|
+
@parser.separator ""
|
55
|
+
@parser.separator "REQUIRED:"
|
56
|
+
required_params.each { |opts| @parser.on(*opts) }
|
57
|
+
end
|
58
|
+
|
59
|
+
if optional_params.size > 0
|
60
|
+
@parser.separator ""
|
61
|
+
@parser.separator "OPTIONAL:"
|
62
|
+
optional_params.each { |opts| @parser.on(*opts) }
|
63
|
+
end
|
64
|
+
|
65
|
+
@parser.separator ""
|
66
|
+
@parser.on_tail("-h", "--help", "This message.") do
|
67
|
+
puts usage
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse!(args)
|
73
|
+
@parsed_params = args.last.is_a?(Hash) ? args.pop : {}
|
74
|
+
|
75
|
+
@parser.parse!(args)
|
76
|
+
set_default_params
|
77
|
+
detect_missing_params!
|
78
|
+
|
79
|
+
@parsed_params
|
80
|
+
end
|
81
|
+
|
82
|
+
def detect_missing_params!
|
83
|
+
missing_params = []
|
84
|
+
@params.each do |p|
|
85
|
+
if p.required? && !@parsed_params.has_key?(p.name)
|
86
|
+
missing_params << p.name
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
raise MissingParamsError.new(missing_params) if missing_params.size > 0
|
91
|
+
end
|
92
|
+
|
93
|
+
def usage
|
94
|
+
@parser.to_s
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def set_default_params
|
100
|
+
@params.each do |param|
|
101
|
+
next unless param.default
|
102
|
+
unless @parsed_params.has_key?(param.name)
|
103
|
+
@parsed_params[param.name] = param.default
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def short_switch(param)
|
109
|
+
# TODO Raise error for params with specified short switches that collide.
|
110
|
+
# For now, we'll assume the user knows if they have the same
|
111
|
+
# short switches when specifying them.
|
112
|
+
return "-#{param.short}" if param.short
|
113
|
+
|
114
|
+
param_name = param.name.to_s
|
115
|
+
short = nil
|
116
|
+
param_name.each_char do |c|
|
117
|
+
short = c
|
118
|
+
if @params.detect { |p| p != param && p.short == c }
|
119
|
+
short = nil if c == param_name[-1]
|
120
|
+
next
|
121
|
+
else
|
122
|
+
break
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
return nil unless short
|
127
|
+
|
128
|
+
# Set the param's short var so that later auto short switch creation
|
129
|
+
# can determine a collision
|
130
|
+
param.short = short
|
131
|
+
|
132
|
+
"-#{short}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|