excavator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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