mattock 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/doc/README +2 -0
  2. data/doc/Specifications +36 -0
  3. data/doc/coverage/assets/0.5.3/app.js +88 -0
  4. data/doc/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
  5. data/doc/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
  6. data/doc/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
  7. data/doc/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
  8. data/doc/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
  9. data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
  10. data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
  11. data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
  12. data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
  13. data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
  14. data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
  15. data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
  16. data/doc/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
  17. data/doc/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
  18. data/doc/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
  19. data/doc/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
  20. data/doc/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
  21. data/doc/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
  22. data/doc/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
  23. data/doc/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
  24. data/doc/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +363 -0
  25. data/doc/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +44 -0
  26. data/doc/coverage/assets/0.5.3/favicon_green.png +0 -0
  27. data/doc/coverage/assets/0.5.3/favicon_red.png +0 -0
  28. data/doc/coverage/assets/0.5.3/favicon_yellow.png +0 -0
  29. data/doc/coverage/assets/0.5.3/highlight.css +129 -0
  30. data/doc/coverage/assets/0.5.3/highlight.pack.js +1 -0
  31. data/doc/coverage/assets/0.5.3/jquery-1.6.2.min.js +18 -0
  32. data/doc/coverage/assets/0.5.3/jquery.dataTables.min.js +152 -0
  33. data/doc/coverage/assets/0.5.3/jquery.timeago.js +141 -0
  34. data/doc/coverage/assets/0.5.3/jquery.url.js +174 -0
  35. data/doc/coverage/assets/0.5.3/loading.gif +0 -0
  36. data/doc/coverage/assets/0.5.3/magnify.png +0 -0
  37. data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  38. data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  39. data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  40. data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  41. data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  42. data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  43. data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  44. data/doc/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  45. data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
  46. data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  47. data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
  48. data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
  49. data/doc/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  50. data/doc/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +295 -0
  51. data/doc/coverage/assets/0.5.3/stylesheet.css +383 -0
  52. data/doc/coverage/index.html +3969 -0
  53. data/lib/mattock.rb +4 -0
  54. data/lib/mattock/command-line.rb +157 -0
  55. data/lib/mattock/command-task.rb +51 -0
  56. data/lib/mattock/configurable.rb +129 -0
  57. data/lib/mattock/remote-command-task.rb +30 -0
  58. data/lib/mattock/tasklib.rb +76 -0
  59. data/lib/mattock/template-host.rb +54 -0
  60. data/lib/mattock/testing/mock-command-line.rb +26 -0
  61. data/lib/mattock/testing/rake-example-group.rb +79 -0
  62. data/spec/command-line.rb +59 -0
  63. data/spec/command-task.rb +39 -0
  64. data/spec/configurable.rb +45 -0
  65. data/spec/tasklib.rb +42 -0
  66. data/spec_help/gem_test_suite.rb +17 -0
  67. data/spec_help/spec_helper.rb +2 -0
  68. metadata +215 -0
data/lib/mattock.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'mattock/tasklib'
2
+ require 'mattock/command-line'
3
+ require 'mattock/remote-command-task'
4
+ require 'mattock/template-host'
@@ -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