strobemonkey-god 0.7.13

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 (115) hide show
  1. data/History.txt +289 -0
  2. data/Manifest.txt +114 -0
  3. data/README.txt +59 -0
  4. data/Rakefile +35 -0
  5. data/bin/god +128 -0
  6. data/examples/events.god +84 -0
  7. data/examples/gravatar.god +54 -0
  8. data/examples/single.god +66 -0
  9. data/ext/god/extconf.rb +55 -0
  10. data/ext/god/kqueue_handler.c +123 -0
  11. data/ext/god/netlink_handler.c +167 -0
  12. data/init/god +42 -0
  13. data/lib/god/behavior.rb +52 -0
  14. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  15. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  16. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  17. data/lib/god/cli/command.rb +229 -0
  18. data/lib/god/cli/run.rb +176 -0
  19. data/lib/god/cli/version.rb +23 -0
  20. data/lib/god/condition.rb +96 -0
  21. data/lib/god/conditions/always.rb +23 -0
  22. data/lib/god/conditions/complex.rb +86 -0
  23. data/lib/god/conditions/cpu_usage.rb +80 -0
  24. data/lib/god/conditions/degrading_lambda.rb +52 -0
  25. data/lib/god/conditions/disk_usage.rb +27 -0
  26. data/lib/god/conditions/file_mtime.rb +28 -0
  27. data/lib/god/conditions/flapping.rb +128 -0
  28. data/lib/god/conditions/http_response_code.rb +168 -0
  29. data/lib/god/conditions/lambda.rb +25 -0
  30. data/lib/god/conditions/memory_usage.rb +82 -0
  31. data/lib/god/conditions/process_exits.rb +72 -0
  32. data/lib/god/conditions/process_running.rb +74 -0
  33. data/lib/god/conditions/tries.rb +44 -0
  34. data/lib/god/configurable.rb +57 -0
  35. data/lib/god/contact.rb +106 -0
  36. data/lib/god/contacts/campfire.rb +82 -0
  37. data/lib/god/contacts/email.rb +95 -0
  38. data/lib/god/contacts/jabber.rb +65 -0
  39. data/lib/god/contacts/twitter.rb +39 -0
  40. data/lib/god/contacts/webhook.rb +47 -0
  41. data/lib/god/dependency_graph.rb +41 -0
  42. data/lib/god/diagnostics.rb +37 -0
  43. data/lib/god/driver.rb +206 -0
  44. data/lib/god/errors.rb +24 -0
  45. data/lib/god/event_handler.rb +111 -0
  46. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  47. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  48. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  49. data/lib/god/logger.rb +120 -0
  50. data/lib/god/metric.rb +59 -0
  51. data/lib/god/process.rb +341 -0
  52. data/lib/god/registry.rb +32 -0
  53. data/lib/god/simple_logger.rb +53 -0
  54. data/lib/god/socket.rb +96 -0
  55. data/lib/god/sugar.rb +47 -0
  56. data/lib/god/system/portable_poller.rb +42 -0
  57. data/lib/god/system/process.rb +42 -0
  58. data/lib/god/system/slash_proc_poller.rb +92 -0
  59. data/lib/god/task.rb +491 -0
  60. data/lib/god/timeline.rb +25 -0
  61. data/lib/god/trigger.rb +43 -0
  62. data/lib/god/watch.rb +183 -0
  63. data/lib/god.rb +667 -0
  64. data/test/configs/child_events/child_events.god +44 -0
  65. data/test/configs/child_events/simple_server.rb +3 -0
  66. data/test/configs/child_polls/child_polls.god +37 -0
  67. data/test/configs/child_polls/simple_server.rb +12 -0
  68. data/test/configs/complex/complex.god +59 -0
  69. data/test/configs/complex/simple_server.rb +3 -0
  70. data/test/configs/contact/contact.god +84 -0
  71. data/test/configs/contact/simple_server.rb +3 -0
  72. data/test/configs/daemon_events/daemon_events.god +37 -0
  73. data/test/configs/daemon_events/simple_server.rb +8 -0
  74. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  75. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  76. data/test/configs/daemon_polls/simple_server.rb +6 -0
  77. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  78. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  79. data/test/configs/matias/matias.god +50 -0
  80. data/test/configs/real.rb +59 -0
  81. data/test/configs/running_load/running_load.god +16 -0
  82. data/test/configs/stress/simple_server.rb +3 -0
  83. data/test/configs/stress/stress.god +15 -0
  84. data/test/configs/task/logs/.placeholder +0 -0
  85. data/test/configs/task/task.god +26 -0
  86. data/test/configs/test.rb +61 -0
  87. data/test/helper.rb +151 -0
  88. data/test/suite.rb +6 -0
  89. data/test/test_behavior.rb +21 -0
  90. data/test/test_campfire.rb +41 -0
  91. data/test/test_condition.rb +50 -0
  92. data/test/test_conditions_disk_usage.rb +56 -0
  93. data/test/test_conditions_http_response_code.rb +109 -0
  94. data/test/test_conditions_process_running.rb +44 -0
  95. data/test/test_conditions_tries.rb +67 -0
  96. data/test/test_contact.rb +109 -0
  97. data/test/test_dependency_graph.rb +62 -0
  98. data/test/test_driver.rb +11 -0
  99. data/test/test_email.rb +45 -0
  100. data/test/test_event_handler.rb +80 -0
  101. data/test/test_god.rb +598 -0
  102. data/test/test_handlers_kqueue_handler.rb +16 -0
  103. data/test/test_logger.rb +63 -0
  104. data/test/test_metric.rb +72 -0
  105. data/test/test_process.rb +246 -0
  106. data/test/test_registry.rb +15 -0
  107. data/test/test_socket.rb +42 -0
  108. data/test/test_sugar.rb +42 -0
  109. data/test/test_system_portable_poller.rb +17 -0
  110. data/test/test_system_process.rb +30 -0
  111. data/test/test_task.rb +262 -0
  112. data/test/test_timeline.rb +37 -0
  113. data/test/test_trigger.rb +59 -0
  114. data/test/test_watch.rb +279 -0
  115. metadata +193 -0
@@ -0,0 +1,51 @@
1
+ module God
2
+ module Behaviors
3
+
4
+ class NotifyWhenFlapping < Behavior
5
+ attr_accessor :failures # number of failures
6
+ attr_accessor :seconds # number of seconds
7
+ attr_accessor :notifier # class to notify with
8
+
9
+ def initialize
10
+ super
11
+ @startup_times = []
12
+ end
13
+
14
+ def valid?
15
+ valid = true
16
+ valid &= complain("Attribute 'failures' must be specified", self) unless self.failures
17
+ valid &= complain("Attribute 'seconds' must be specified", self) unless self.seconds
18
+ valid &= complain("Attribute 'notifier' must be specified", self) unless self.notifier
19
+
20
+ # Must take one arg or variable args
21
+ unless self.notifier.respond_to?(:notify) and [1,-1].include?(self.notifier.method(:notify).arity)
22
+ valid &= complain("The 'notifier' must have a method 'notify' which takes 1 or variable args", self)
23
+ end
24
+
25
+ valid
26
+ end
27
+
28
+ def before_start
29
+ now = Time.now.to_i
30
+ @startup_times << now
31
+ check_for_flapping(now)
32
+ end
33
+
34
+ def before_restart
35
+ now = Time.now.to_i
36
+ @startup_times << now
37
+ check_for_flapping(now)
38
+ end
39
+
40
+ private
41
+
42
+ def check_for_flapping(now)
43
+ @startup_times.select! {|time| time >= now - self.seconds }
44
+ if @startup_times.length >= self.failures
45
+ self.notifier.notify("#{self.watch.name} has called start/restart #{@startup_times.length} times in #{self.seconds} seconds")
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,229 @@
1
+ module God
2
+ module CLI
3
+
4
+ class Command
5
+ def initialize(command, options, args)
6
+ @command = command
7
+ @options = options
8
+ @args = args
9
+
10
+ dispatch
11
+ end
12
+
13
+ def setup
14
+ # connect to drb unix socket
15
+ DRb.start_service("druby://127.0.0.1:0")
16
+ @server = DRbObject.new(nil, God::Socket.socket(@options[:port]))
17
+
18
+ # ping server to ensure that it is responsive
19
+ begin
20
+ @server.ping
21
+ rescue DRb::DRbConnError
22
+ puts "The server is not available (or you do not have permissions to access it)"
23
+ abort
24
+ end
25
+ end
26
+
27
+ def dispatch
28
+ if %w{load status signal log quit terminate}.include?(@command)
29
+ setup
30
+ send("#{@command}_command")
31
+ elsif %w{start stop restart monitor unmonitor remove}.include?(@command)
32
+ setup
33
+ lifecycle_command
34
+ elsif @command == 'check'
35
+ check_command
36
+ else
37
+ puts "Command '#{@command}' is not valid. Run 'god --help' for usage"
38
+ abort
39
+ end
40
+ end
41
+
42
+ def load_command
43
+ file = @args[1]
44
+
45
+ puts "Sending '#{@command}' command"
46
+ puts
47
+
48
+ unless File.exist?(file)
49
+ abort "File not found: #{file}"
50
+ end
51
+
52
+ names, errors = *@server.running_load(File.read(file), File.expand_path(file))
53
+
54
+ # output response
55
+ unless names.empty?
56
+ puts 'The following tasks were affected:'
57
+ names.each do |w|
58
+ puts ' ' + w
59
+ end
60
+ end
61
+
62
+ unless errors.empty?
63
+ puts errors
64
+ exit(1)
65
+ end
66
+ end
67
+
68
+ def status_command
69
+ watches = {}
70
+ @server.status.each do |name, status|
71
+ g = status[:group] || ''
72
+ unless watches.has_key?(g)
73
+ watches[g] = {}
74
+ end
75
+ watches[g][name] = status
76
+ end
77
+ watches.keys.sort.each do |group|
78
+ puts "#{group}:" unless group.empty?
79
+ watches[group].keys.sort.each do |name|
80
+ state = watches[group][name][:state]
81
+ print " " unless group.empty?
82
+ puts "#{name}: #{state}"
83
+ end
84
+ end
85
+ end
86
+
87
+ def signal_command
88
+ # get the name of the watch/group
89
+ name = @args[1]
90
+ signal = @args[2]
91
+
92
+ puts "Sending signal '#{signal}' to '#{name}'"
93
+
94
+ t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } }
95
+
96
+ watches = @server.signal(name, signal)
97
+
98
+ # output response
99
+ t.kill; STDOUT.puts
100
+ unless watches.empty?
101
+ puts 'The following watches were affected:'
102
+ watches.each do |w|
103
+ puts ' ' + w
104
+ end
105
+ else
106
+ puts 'No matching task or group'
107
+ end
108
+ end
109
+
110
+ def log_command
111
+ begin
112
+ Signal.trap('INT') { exit }
113
+ name = @args[1]
114
+
115
+ unless name
116
+ puts "You must specify a Task or Group name"
117
+ exit!
118
+ end
119
+
120
+ t = Time.at(0)
121
+ loop do
122
+ print @server.running_log(name, t)
123
+ t = Time.now
124
+ sleep 1
125
+ end
126
+ rescue God::NoSuchWatchError
127
+ puts "No such watch"
128
+ rescue DRb::DRbConnError
129
+ puts "The server went away"
130
+ end
131
+ end
132
+
133
+ def quit_command
134
+ begin
135
+ @server.terminate
136
+ abort 'Could not stop god'
137
+ rescue DRb::DRbConnError
138
+ puts 'Stopped god'
139
+ end
140
+ end
141
+
142
+ def terminate_command
143
+ t = Thread.new { loop { STDOUT.print('.'); STDOUT.flush; sleep(1) } }
144
+ if @server.stop_all
145
+ t.kill; STDOUT.puts
146
+ puts 'Stopped all watches'
147
+ else
148
+ t.kill; STDOUT.puts
149
+ puts 'Could not stop all watches within 10 seconds'
150
+ end
151
+
152
+ begin
153
+ @server.terminate
154
+ abort 'Could not stop god'
155
+ rescue DRb::DRbConnError
156
+ puts 'Stopped god'
157
+ end
158
+ end
159
+
160
+ def check_command
161
+ Thread.new do
162
+ begin
163
+ event_system = God::EventHandler.event_system
164
+ puts "using event system: #{event_system}"
165
+
166
+ if God::EventHandler.loaded?
167
+ puts "starting event handler"
168
+ God::EventHandler.start
169
+ else
170
+ puts "[fail] event system did not load"
171
+ exit(1)
172
+ end
173
+
174
+ puts 'forking off new process'
175
+
176
+ pid = fork do
177
+ loop { sleep(1) }
178
+ end
179
+
180
+ puts "forked process with pid = #{pid}"
181
+
182
+ God::EventHandler.register(pid, :proc_exit) do
183
+ puts "[ok] process exit event received"
184
+ exit!(0)
185
+ end
186
+
187
+ sleep(1)
188
+
189
+ puts "killing process"
190
+
191
+ ::Process.kill('KILL', pid)
192
+ rescue => e
193
+ puts e.message
194
+ puts e.backtrace.join("\n")
195
+ end
196
+ end
197
+
198
+ sleep(2)
199
+
200
+ puts "[fail] never received process exit event"
201
+ exit(1)
202
+ end
203
+
204
+ def lifecycle_command
205
+ # get the name of the watch/group
206
+ name = @args[1]
207
+
208
+ puts "Sending '#{@command}' command"
209
+
210
+ t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } }
211
+
212
+ # send @command
213
+ watches = @server.control(name, @command)
214
+
215
+ # output response
216
+ t.kill; STDOUT.puts
217
+ unless watches.empty?
218
+ puts 'The following watches were affected:'
219
+ watches.each do |w|
220
+ puts ' ' + w
221
+ end
222
+ else
223
+ puts 'No matching task or group'
224
+ end
225
+ end
226
+ end # Command
227
+
228
+ end
229
+ end
@@ -0,0 +1,176 @@
1
+ module God
2
+ module CLI
3
+
4
+ class Run
5
+ def initialize(options)
6
+ @options = options
7
+
8
+ dispatch
9
+ end
10
+
11
+ def dispatch
12
+ # have at_exit start god
13
+ $run = true
14
+
15
+ # run
16
+ if @options[:daemonize]
17
+ run_daemonized
18
+ else
19
+ run_in_front
20
+ end
21
+ end
22
+
23
+ def attach
24
+ process = System::Process.new(@options[:attach])
25
+ Thread.new do
26
+ loop do
27
+ unless process.exists?
28
+ applog(nil, :info, "Going down because attached process #{@options[:attach]} exited")
29
+ exit!
30
+ end
31
+ sleep 5
32
+ end
33
+ end
34
+ end
35
+
36
+ def default_run
37
+ # make sure we have STDIN/STDOUT redirected immediately
38
+ setup_logging
39
+
40
+ # start attached pid watcher if necessary
41
+ if @options[:attach]
42
+ self.attach
43
+ end
44
+
45
+ if @options[:port]
46
+ God.port = @options[:port]
47
+ end
48
+
49
+ if @options[:events]
50
+ God::EventHandler.load
51
+ end
52
+
53
+ # set log level, defaults to WARN
54
+ if @options[:log_level]
55
+ God.log_level = @options[:log_level]
56
+ else
57
+ God.log_level = @options[:daemonize] ? :warn : :info
58
+ end
59
+
60
+ if @options[:config]
61
+ unless File.exist?(@options[:config])
62
+ abort "File not found: #{@options[:config]}"
63
+ end
64
+
65
+ # start the event handler
66
+ God::EventHandler.start if God::EventHandler.loaded?
67
+
68
+ load_config @options[:config]
69
+ end
70
+ setup_logging
71
+ end
72
+
73
+ def run_in_front
74
+ require 'god'
75
+
76
+ if @options[:bleakhouse]
77
+ BleakHouseDiagnostic.install
78
+ end
79
+
80
+ default_run
81
+ end
82
+
83
+ def run_daemonized
84
+ # trap and ignore SIGHUP
85
+ Signal.trap('HUP') {}
86
+
87
+ pid = fork do
88
+ begin
89
+ require 'god'
90
+
91
+ # set pid if requested
92
+ if @options[:pid] # and as deamon
93
+ God.pid = @options[:pid]
94
+ end
95
+
96
+ unless @options[:syslog]
97
+ Logger.syslog = false
98
+ end
99
+
100
+ default_run
101
+
102
+ unless God::EventHandler.loaded?
103
+ puts
104
+ puts "***********************************************************************"
105
+ puts "*"
106
+ puts "* Event conditions are not available for your installation of god."
107
+ puts "* You may still use and write custom conditions using the poll system"
108
+ puts "*"
109
+ puts "***********************************************************************"
110
+ puts
111
+ end
112
+
113
+ rescue => e
114
+ puts e.message
115
+ puts e.backtrace.join("\n")
116
+ abort "There was a fatal system error while starting god (see above)"
117
+ end
118
+ end
119
+
120
+ if @options[:pid]
121
+ File.open(@options[:pid], 'w') { |f| f.write pid }
122
+ end
123
+
124
+ ::Process.detach pid
125
+
126
+ exit
127
+ end
128
+
129
+ def setup_logging
130
+ log_file = God.log_file
131
+ log_file = File.expand_path(@options[:log]) if @options[:log]
132
+ log_file = "/dev/null" if !log_file && @options[:daemonize]
133
+ if log_file
134
+ puts "Sending output to log file: #{log_file}" unless @options[:daemonize]
135
+
136
+ # reset file descriptors
137
+ STDIN.reopen "/dev/null"
138
+ STDOUT.reopen(log_file, "a")
139
+ STDERR.reopen STDOUT
140
+ STDOUT.sync = true
141
+ end
142
+ end
143
+
144
+ def load_config(config)
145
+ if File.directory? config
146
+ files_loaded = false
147
+ Dir[File.expand_path('**/*.god', config)].each do |god_file|
148
+ files_loaded ||= load_god_file(File.expand_path(god_file))
149
+ end
150
+ unless files_loaded
151
+ abort "No files could be loaded"
152
+ end
153
+ else
154
+ unless load_god_file(File.expand_path(config))
155
+ abort "File could not be loaded"
156
+ end
157
+ end
158
+ end
159
+
160
+ def load_god_file(god_file)
161
+ load File.expand_path(god_file)
162
+ rescue Exception => e
163
+ if e.instance_of?(SystemExit)
164
+ raise
165
+ else
166
+ puts "There was an error in #{god_file}"
167
+ puts "\t" + e.message
168
+ puts "\t" + e.backtrace.join("\n\t")
169
+ return false
170
+ end
171
+ end
172
+
173
+ end # Run
174
+
175
+ end
176
+ end
@@ -0,0 +1,23 @@
1
+ module God
2
+ module CLI
3
+
4
+ class Version
5
+ def self.version
6
+ require 'god'
7
+
8
+ # print version
9
+ puts "Version #{God::VERSION}"
10
+ exit
11
+ end
12
+
13
+ def self.version_extended
14
+ puts "Version: #{God::VERSION}"
15
+ puts "Polls: enabled"
16
+ puts "Events: " + God::EventHandler.event_system
17
+
18
+ exit
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,96 @@
1
+ module God
2
+
3
+ class Condition < Behavior
4
+ attr_accessor :transition, :notify, :info, :phase
5
+
6
+ # Generate a Condition of the given kind. The proper class if found by camel casing the
7
+ # kind (which is given as an underscored symbol).
8
+ # +kind+ is the underscored symbol representing the class (e.g. :foo_bar for God::Conditions::FooBar)
9
+ def self.generate(kind, watch)
10
+ sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
11
+ c = God::Conditions.const_get(sym).new
12
+
13
+ unless c.kind_of?(PollCondition) || c.kind_of?(EventCondition) || c.kind_of?(TriggerCondition)
14
+ abort "Condition '#{c.class.name}' must subclass God::PollCondition, God::EventCondition, or God::TriggerCondition"
15
+ end
16
+
17
+ if !EventHandler.loaded? && c.kind_of?(EventCondition)
18
+ abort "Condition '#{c.class.name}' requires an event system but none has been loaded"
19
+ end
20
+
21
+ c.watch = watch
22
+ c
23
+ rescue NameError
24
+ raise NoSuchConditionError.new("No Condition found with the class name God::Conditions::#{sym}")
25
+ end
26
+
27
+ def self.valid?(condition)
28
+ valid = true
29
+ if condition.notify
30
+ begin
31
+ Contact.normalize(condition.notify)
32
+ rescue ArgumentError => e
33
+ valid &= Configurable.complain("Attribute 'notify' " + e.message, condition)
34
+ end
35
+ end
36
+ valid
37
+ end
38
+
39
+ # Construct the friendly name of this Condition, looks like:
40
+ #
41
+ # Condition FooBar on Watch 'baz'
42
+ def friendly_name
43
+ "Condition #{self.class.name.split('::').last} on Watch '#{self.watch.name}'"
44
+ end
45
+ end
46
+
47
+ class PollCondition < Condition
48
+ # all poll conditions can specify a poll interval
49
+ attr_accessor :interval
50
+
51
+ # Override this method in your Conditions (optional)
52
+ def before
53
+ end
54
+
55
+ # Override this method in your Conditions (mandatory)
56
+ #
57
+ # Return true if the test passes (everything is ok)
58
+ # Return false otherwise
59
+ def test
60
+ raise AbstractMethodNotOverriddenError.new("PollCondition#test must be overridden in subclasses")
61
+ end
62
+
63
+ # Override this method in your Conditions (optional)
64
+ def after
65
+ end
66
+ end
67
+
68
+ class EventCondition < Condition
69
+ def register
70
+ raise AbstractMethodNotOverriddenError.new("EventCondition#register must be overridden in subclasses")
71
+ end
72
+
73
+ def deregister
74
+ raise AbstractMethodNotOverriddenError.new("EventCondition#deregister must be overridden in subclasses")
75
+ end
76
+ end
77
+
78
+ class TriggerCondition < Condition
79
+ def process(event, payload)
80
+ raise AbstractMethodNotOverriddenError.new("TriggerCondition#process must be overridden in subclasses")
81
+ end
82
+
83
+ def trigger
84
+ self.watch.trigger(self)
85
+ end
86
+
87
+ def register
88
+ Trigger.register(self)
89
+ end
90
+
91
+ def deregister
92
+ Trigger.deregister(self)
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,23 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Always < PollCondition
5
+ attr_accessor :what
6
+
7
+ def initialize
8
+ self.info = "always"
9
+ end
10
+
11
+ def valid?
12
+ valid = true
13
+ valid &= complain("Attribute 'what' must be specified", self) if self.what.nil?
14
+ valid
15
+ end
16
+
17
+ def test
18
+ @what
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Complex < PollCondition
5
+ AND = 0x1
6
+ OR = 0x2
7
+ NOT = 0x4
8
+
9
+ def initialize()
10
+ super
11
+
12
+ @oper_stack = []
13
+ @op_stack = []
14
+
15
+ @this = nil
16
+ end
17
+
18
+ def valid?
19
+ @oper_stack.inject(true) { |acc, oper| acc & oper.valid? }
20
+ end
21
+
22
+ def prepare
23
+ @oper_stack.each { |oper| oper.prepare }
24
+ end
25
+
26
+ def new_oper(kind, op)
27
+ oper = Condition.generate(kind, self.watch)
28
+ @oper_stack.push(oper)
29
+ @op_stack.push(op)
30
+ oper
31
+ end
32
+
33
+ def this(kind)
34
+ @this = Condition.generate(kind, self.watch)
35
+ yield @this if block_given?
36
+ end
37
+
38
+ def and(kind)
39
+ oper = new_oper(kind, 0x1)
40
+ yield oper if block_given?
41
+ end
42
+
43
+ def and_not(kind)
44
+ oper = new_oper(kind, 0x5)
45
+ yield oper if block_given?
46
+ end
47
+
48
+ def or(kind)
49
+ oper = new_oper(kind, 0x2)
50
+ yield oper if block_given?
51
+ end
52
+
53
+ def or_not(kind)
54
+ oper = new_oper(kind, 0x6)
55
+ yield oper if block_given?
56
+ end
57
+
58
+ def test
59
+ if @this.nil?
60
+ # Although this() makes sense semantically and therefore
61
+ # encourages easy-to-read conditions, being able to omit it
62
+ # allows for more DRY code in some cases, so we deal with a
63
+ # nil @this here by initially setting res to true or false,
64
+ # depending on whether the first operator used is AND or OR
65
+ # respectively.
66
+ if 0 < @op_stack[0] & AND
67
+ res = true
68
+ else
69
+ res = false
70
+ end
71
+ else
72
+ res = @this.test
73
+ end
74
+
75
+ @op_stack.each do |op|
76
+ cond = @oper_stack.shift
77
+ eval "res " + ((0 < op & AND) ? "&&" : "||") + "= " + ((0 < op & NOT) ? "!" : "") + "cond.test"
78
+ @oper_stack.push cond
79
+ end
80
+
81
+ res
82
+ end
83
+ end
84
+
85
+ end
86
+ end