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 ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in excavator.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task :default => :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = FileList['test/**/*_test.rb']
9
+ end
10
+
data/bin/excavator ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'excavator'
3
+ Excavator.run(ARGV)
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