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.
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