mattock 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/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
|