mattock 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/README +2 -0
- data/doc/Specifications +36 -0
- data/doc/coverage/assets/0.5.3/app.js +88 -0
- data/doc/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
- data/doc/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +363 -0
- data/doc/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +44 -0
- data/doc/coverage/assets/0.5.3/favicon_green.png +0 -0
- data/doc/coverage/assets/0.5.3/favicon_red.png +0 -0
- data/doc/coverage/assets/0.5.3/favicon_yellow.png +0 -0
- data/doc/coverage/assets/0.5.3/highlight.css +129 -0
- data/doc/coverage/assets/0.5.3/highlight.pack.js +1 -0
- data/doc/coverage/assets/0.5.3/jquery-1.6.2.min.js +18 -0
- data/doc/coverage/assets/0.5.3/jquery.dataTables.min.js +152 -0
- data/doc/coverage/assets/0.5.3/jquery.timeago.js +141 -0
- data/doc/coverage/assets/0.5.3/jquery.url.js +174 -0
- data/doc/coverage/assets/0.5.3/loading.gif +0 -0
- data/doc/coverage/assets/0.5.3/magnify.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/doc/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +295 -0
- data/doc/coverage/assets/0.5.3/stylesheet.css +383 -0
- data/doc/coverage/index.html +3969 -0
- data/lib/mattock.rb +4 -0
- data/lib/mattock/command-line.rb +157 -0
- data/lib/mattock/command-task.rb +51 -0
- data/lib/mattock/configurable.rb +129 -0
- data/lib/mattock/remote-command-task.rb +30 -0
- data/lib/mattock/tasklib.rb +76 -0
- data/lib/mattock/template-host.rb +54 -0
- data/lib/mattock/testing/mock-command-line.rb +26 -0
- data/lib/mattock/testing/rake-example-group.rb +79 -0
- data/spec/command-line.rb +59 -0
- data/spec/command-task.rb +39 -0
- data/spec/configurable.rb +45 -0
- data/spec/tasklib.rb +42 -0
- data/spec_help/gem_test_suite.rb +17 -0
- data/spec_help/spec_helper.rb +2 -0
- metadata +215 -0
data/lib/mattock.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
module Mattock
|
2
|
+
class CommandRunResult
|
3
|
+
def initialize(status, streams)
|
4
|
+
@process_status = status
|
5
|
+
@streams = streams
|
6
|
+
end
|
7
|
+
attr_reader :process_status, :streams
|
8
|
+
|
9
|
+
def stdout
|
10
|
+
@streams[1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def stderr
|
14
|
+
@streams[2]
|
15
|
+
end
|
16
|
+
|
17
|
+
def exit_code
|
18
|
+
@process_status.exitstatus
|
19
|
+
end
|
20
|
+
alias exit_status exit_code
|
21
|
+
|
22
|
+
def succeeded?
|
23
|
+
must_succeed!
|
24
|
+
return true
|
25
|
+
rescue
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
|
29
|
+
def must_succeed!
|
30
|
+
case exit_code
|
31
|
+
when 0
|
32
|
+
return exit_code
|
33
|
+
else
|
34
|
+
fail "Command '#{name}' failed with exit status #{$?.exitstatus}: \n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class CommandLine
|
40
|
+
def initialize(executable, *options)
|
41
|
+
@executable = executable
|
42
|
+
@options = options
|
43
|
+
@redirections = []
|
44
|
+
yield self if block_given?
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_accessor :name, :executable, :options
|
48
|
+
|
49
|
+
def name
|
50
|
+
@name || executable
|
51
|
+
end
|
52
|
+
|
53
|
+
def command
|
54
|
+
([executable] + options + @redirections).join(" ")
|
55
|
+
end
|
56
|
+
|
57
|
+
def redirect_to(stream, path)
|
58
|
+
@redirections << "#{stream}>#{path}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def redirect_from(path, stream)
|
62
|
+
@redirections << "#{stream}<#{path}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def copy_stream_to(from, to)
|
66
|
+
@redirections << "#{from}>&#{to}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def redirect_stdout(path)
|
70
|
+
redirect_to(1, path)
|
71
|
+
end
|
72
|
+
|
73
|
+
def redirect_stderr(path)
|
74
|
+
redirect_to(2, path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def redirect_stdin(path)
|
78
|
+
redirect_from(path, 0)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.execute(command)
|
82
|
+
pipe = IO.popen(command)
|
83
|
+
pid = pipe.pid
|
84
|
+
pid, status = Process.wait2(pid)
|
85
|
+
result = CommandRunResult.new(status, {1 => pipe.read})
|
86
|
+
pipe.close
|
87
|
+
return result
|
88
|
+
end
|
89
|
+
|
90
|
+
def run
|
91
|
+
print command + " " if $verbose
|
92
|
+
result = self.class.execute(command)
|
93
|
+
print "=> #{result.exit_code}" if $verbose
|
94
|
+
return result
|
95
|
+
ensure
|
96
|
+
puts if $verbose
|
97
|
+
end
|
98
|
+
|
99
|
+
def succeeds?
|
100
|
+
run.succeeded?
|
101
|
+
end
|
102
|
+
|
103
|
+
def must_succeed!
|
104
|
+
run.must_succeed!
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class ShellEscaped < CommandLine
|
109
|
+
def initialize(cmd)
|
110
|
+
@escaped = cmd
|
111
|
+
end
|
112
|
+
|
113
|
+
def command
|
114
|
+
"'" + @escaped.command.gsub(/'/,"\'") + "'"
|
115
|
+
end
|
116
|
+
|
117
|
+
def name
|
118
|
+
@name || @escaped.name
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class CommandChain < CommandLine
|
123
|
+
def initialize
|
124
|
+
@commands = []
|
125
|
+
yield self if block_given?
|
126
|
+
end
|
127
|
+
|
128
|
+
attr_reader :commands
|
129
|
+
|
130
|
+
def add(cmd)
|
131
|
+
yield cmd if block_given?
|
132
|
+
@commands << cmd
|
133
|
+
end
|
134
|
+
|
135
|
+
def name
|
136
|
+
@name || @commands.last.name
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class WrappingChain < CommandChain
|
141
|
+
def command
|
142
|
+
@commands.map{|cmd| cmd.command}.join(" -- ")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class PrereqChain < CommandChain
|
147
|
+
def command
|
148
|
+
@commands.map{|cmd| cmd.command}.join(" && ")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class PipelineChain < CommandChain
|
153
|
+
def command
|
154
|
+
@commands.map{|cmd| cmd.command}.join(" | ")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'mattock/tasklib'
|
2
|
+
require 'mattock/command-line'
|
3
|
+
|
4
|
+
module Mattock
|
5
|
+
module NeededPredicate
|
6
|
+
def needed_predicate(&block)
|
7
|
+
(class << self; self; end).instance_eval do
|
8
|
+
define_method(:needed?, &block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class CommandTask < TaskLib
|
14
|
+
setting(:task_name, :run)
|
15
|
+
setting(:command)
|
16
|
+
setting(:verify_command, nil)
|
17
|
+
|
18
|
+
def verify_command
|
19
|
+
if @verify_command.respond_to?(:call)
|
20
|
+
@verify_command = @verify_command.call
|
21
|
+
end
|
22
|
+
@verify_command
|
23
|
+
end
|
24
|
+
|
25
|
+
def decorated(cmd)
|
26
|
+
cmd
|
27
|
+
end
|
28
|
+
|
29
|
+
def command_task
|
30
|
+
@command_task ||=
|
31
|
+
begin
|
32
|
+
task task_name do
|
33
|
+
decorated(command).must_succeed!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def define
|
39
|
+
in_namespace do
|
40
|
+
command_task
|
41
|
+
unless verify_command.nil?
|
42
|
+
needed = decorated(verify_command)
|
43
|
+
command_task.extend NeededPredicate
|
44
|
+
command_task.needed_predicate do
|
45
|
+
!needed.succeeds?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Mattock
|
2
|
+
module Configurable
|
3
|
+
RequiredField = Object.new
|
4
|
+
class << RequiredField
|
5
|
+
def to_s
|
6
|
+
"<unset>"
|
7
|
+
end
|
8
|
+
|
9
|
+
def inspect
|
10
|
+
to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
RequiredField.freeze
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def default_values
|
18
|
+
@default_values ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_defaults_on(instance)
|
22
|
+
if Configurable > superclass
|
23
|
+
superclass.set_defaults_on(instance)
|
24
|
+
end
|
25
|
+
default_values.each_pair do |name,value|
|
26
|
+
instance.__send__("#{name}=", value)
|
27
|
+
if Configurable === value
|
28
|
+
value.class.set_defaults_on(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def missing_required_fields_on(instance)
|
34
|
+
missing = []
|
35
|
+
if Configurable > superclass
|
36
|
+
missing = superclass.missing_required_fields_on(instance)
|
37
|
+
end
|
38
|
+
default_values.each_pair do |name,value|
|
39
|
+
set_value = instance.__send__(name)
|
40
|
+
if value == RequiredField and set_value == RequiredField
|
41
|
+
missing << name
|
42
|
+
next
|
43
|
+
end
|
44
|
+
if Configurable === set_value
|
45
|
+
missing += set_value.class.missing_required_fields_on(set_value).map do |field|
|
46
|
+
[name, field].join(".")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
return missing
|
51
|
+
end
|
52
|
+
|
53
|
+
def nested(hash=nil)
|
54
|
+
obj = Class.new(Struct).new
|
55
|
+
obj.settings(hash || {})
|
56
|
+
return obj
|
57
|
+
end
|
58
|
+
|
59
|
+
def nil_fields(*names)
|
60
|
+
names.each do |name|
|
61
|
+
setting(name, nil)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
alias nil_field nil_fields
|
65
|
+
|
66
|
+
def required_fields(*names)
|
67
|
+
names.each do |name|
|
68
|
+
setting(name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
alias required_field required_fields
|
72
|
+
|
73
|
+
def setting(name, default_value = RequiredField)
|
74
|
+
name = name.to_sym
|
75
|
+
attr_accessor(name)
|
76
|
+
if default_values.has_key?(name) and default_values[name] != default_value
|
77
|
+
warn "Changing default value of #{self.name}##{name} from #{default_values[name].inspect} to #{default_value.inspect}"
|
78
|
+
end
|
79
|
+
default_values[name] = default_value
|
80
|
+
end
|
81
|
+
|
82
|
+
def settings(hash)
|
83
|
+
hash.each_pair do |name, value|
|
84
|
+
setting(name, value)
|
85
|
+
end
|
86
|
+
return self
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.included(mod)
|
91
|
+
mod.extend ClassMethods
|
92
|
+
end
|
93
|
+
|
94
|
+
def setup_defaults
|
95
|
+
self.class.set_defaults_on(self)
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def check_required
|
100
|
+
missing = self.class.missing_required_fields_on(self)
|
101
|
+
unless missing.empty?
|
102
|
+
raise "Required field#{missing.length > 1 ? "s" : ""} #{missing.join(", ")} unset on #{self.inspect}"
|
103
|
+
end
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
def setting(name, default_value = nil)
|
108
|
+
self.class.setting(name, default_value)
|
109
|
+
instance_variable_set("@#{name}", default_value)
|
110
|
+
end
|
111
|
+
|
112
|
+
def settings(hash)
|
113
|
+
hash.each_pair do |name, value|
|
114
|
+
setting(name, value)
|
115
|
+
end
|
116
|
+
return self
|
117
|
+
end
|
118
|
+
|
119
|
+
def required_fields(*names)
|
120
|
+
self.class.required_fields(*names)
|
121
|
+
self
|
122
|
+
end
|
123
|
+
alias required_field required_fields
|
124
|
+
|
125
|
+
class Struct
|
126
|
+
include Configurable
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'mattock/command-task'
|
2
|
+
module Mattock
|
3
|
+
class RemoteCommandTask < CommandTask
|
4
|
+
setting(:remote_server, nested(
|
5
|
+
:address => nil,
|
6
|
+
:user => nil
|
7
|
+
))
|
8
|
+
setting(:ssh_options, [])
|
9
|
+
nil_fields(:id_file, :free_arguments)
|
10
|
+
|
11
|
+
def decorated(command_on_remote)
|
12
|
+
fail "Need remote server for #{self.class.name}" unless remote_server.address
|
13
|
+
|
14
|
+
raise "Empty remote command" if command_on_remote.nil?
|
15
|
+
Mattock::WrappingChain.new do |cmd|
|
16
|
+
cmd.add Mattock::CommandLine.new("ssh") do |cmd|
|
17
|
+
cmd.options << "-u #{remote_server.user}" if remote_server.user
|
18
|
+
cmd.options << "-i #{id_file}" if id_file
|
19
|
+
unless ssh_options.empty?
|
20
|
+
ssh_options.each do |opt|
|
21
|
+
cmd.options "-o #{opt}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
cmd.options << remote_server.address
|
25
|
+
end
|
26
|
+
cmd.add Mattock::ShellEscaped.new(command_on_remote)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
require 'mattock/configurable'
|
4
|
+
|
5
|
+
module Mattock
|
6
|
+
class TaskLib < Rake::TaskLib
|
7
|
+
include Configurable
|
8
|
+
|
9
|
+
attr_writer :namespace_name
|
10
|
+
def self.default_namespace(name)
|
11
|
+
setting(:namespace_name, name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(*tasklibs)
|
15
|
+
@tasks = []
|
16
|
+
|
17
|
+
default_configuration(*tasklibs)
|
18
|
+
|
19
|
+
yield self if block_given?
|
20
|
+
|
21
|
+
resolve_configuration
|
22
|
+
|
23
|
+
define
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :tasks
|
27
|
+
|
28
|
+
def task(*args)
|
29
|
+
a_task = super
|
30
|
+
@tasks << a_task
|
31
|
+
return a_task
|
32
|
+
end
|
33
|
+
|
34
|
+
def default_configuration(*tasklibs)
|
35
|
+
setup_defaults
|
36
|
+
end
|
37
|
+
|
38
|
+
def resolve_configuration
|
39
|
+
check_required
|
40
|
+
end
|
41
|
+
|
42
|
+
def define
|
43
|
+
end
|
44
|
+
|
45
|
+
def in_namespace(*tasknames)
|
46
|
+
if tasknames.empty?
|
47
|
+
if block_given?
|
48
|
+
if @namespace_name.nil?
|
49
|
+
yield
|
50
|
+
else
|
51
|
+
namespace @namespace_name do
|
52
|
+
yield
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
else
|
57
|
+
tasknames.map do |taskname|
|
58
|
+
[@namespace_name, taskname].compact.join(":")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def root_task
|
64
|
+
@namespace_name || :default
|
65
|
+
end
|
66
|
+
|
67
|
+
def default_namespace
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def [](taskname)
|
72
|
+
in_namespace(taskname).first
|
73
|
+
end
|
74
|
+
end
|
75
|
+
Tasklib = TaskLib #Because I can never remember
|
76
|
+
end
|