runnable 0.1.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/COPYING +674 -0
- data/README.markdown +93 -0
- data/VERSION +1 -0
- data/lib/runnable/command_parser.rb +42 -0
- data/lib/runnable/extended.rb +33 -0
- data/lib/runnable/gnu.rb +46 -0
- data/lib/runnable.rb +415 -0
- metadata +135 -0
data/README.markdown
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# Runnable
|
2
|
+
A Ruby gem that allow programmer to control UNIX system commands as a Ruby class.
|
3
|
+
|
4
|
+
# Usage
|
5
|
+
All you have to do is to create a class named exactly as command and make it inherit from class Runnable.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class LS < Runnable
|
9
|
+
end
|
10
|
+
```
|
11
|
+
|
12
|
+
That gives you the basics to control the execution of `ls` command.
|
13
|
+
|
14
|
+
Now you can create an instance like this:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
my_command = LS.new
|
18
|
+
```
|
19
|
+
|
20
|
+
And run the command as follows
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
my_command.run
|
24
|
+
```
|
25
|
+
|
26
|
+
Many other options are available; you can stop the command, kill it or look
|
27
|
+
for some important information about the command and its process. Entire
|
28
|
+
documentation of this gem can be found under `./doc` directory or been generated
|
29
|
+
by `yardoc`.
|
30
|
+
|
31
|
+
## Return values
|
32
|
+
Runnable uses another gems called `Publisher`. It allow Runnable to fire
|
33
|
+
events that can be processed or ignored. When a command ends its execution,
|
34
|
+
Runnable always fire and event: `:finish` if commands finalized in a correct way
|
35
|
+
or `:fail` if an error ocurred. In case something went wrong and a `:fail`
|
36
|
+
events was fired, Runnable also provide an array containing the command return
|
37
|
+
value as the parameter of a SystemCallError exception and optionally others
|
38
|
+
exceptions ocurred at runtime.
|
39
|
+
|
40
|
+
This is an example of how can we receive the return value of a command:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
class LS < Runnable
|
44
|
+
end
|
45
|
+
|
46
|
+
my_command = LS.new
|
47
|
+
|
48
|
+
my_command.when :finish do
|
49
|
+
puts "Everything went better than expected :)"
|
50
|
+
end
|
51
|
+
|
52
|
+
my_command.when :fail do |exceptions|
|
53
|
+
puts "Something went wrong"
|
54
|
+
exceptions.each do |exception|
|
55
|
+
puts exception.message
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
my_command.run
|
60
|
+
```
|
61
|
+
|
62
|
+
## Custom exceptions
|
63
|
+
As we saw in previous chapter, if a command execution does not ends
|
64
|
+
succesfully, Runnable fires a `:fail` event whit an exceptions array. We can
|
65
|
+
add exceptions to that array based on the output of command. For example, we
|
66
|
+
can controll that parameters passed to a command are valids if we know the
|
67
|
+
command output for an invalid parameters.
|
68
|
+
|
69
|
+
First we have to do is override the method `exceptions` defined in runnable
|
70
|
+
as follows
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class LS < Runnable
|
74
|
+
def exceptions
|
75
|
+
{ /ls: (invalid option.*)/ => ArgumentError }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
`exceptions` method should return a hash containing a regular expression
|
81
|
+
which will be match against the command output, and a value which will be the
|
82
|
+
exception added to exception array. This means that if the command output match
|
83
|
+
the regular expression, a new exception will be include in `:fail` event parameter.
|
84
|
+
|
85
|
+
# About
|
86
|
+
Runnable is a gem develop by [NoSoloSoftware](http://nosolosoftware.biz)
|
87
|
+
|
88
|
+
# License
|
89
|
+
Runnable is Copyright 2011 NoSoloSoftware, it is free software.
|
90
|
+
|
91
|
+
Runnable is distributed under GPLv3 license. More details can be found at COPYING
|
92
|
+
file.
|
93
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Copyright 2011 NoSoloSoftware
|
2
|
+
|
3
|
+
# This file is part of Runnable.
|
4
|
+
#
|
5
|
+
# Runnable is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# Runnable is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with Runnable. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
|
19
|
+
# <p>Base class to create a command-line parameter parser.</p>
|
20
|
+
# <p>It holds that parameters in a hash and the child has
|
21
|
+
# to be the one who return the formatted string according
|
22
|
+
# to the standard used.</p>
|
23
|
+
class Command_parser
|
24
|
+
# Create a new instance of the parser.
|
25
|
+
def initialize
|
26
|
+
@params = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add params and value to the params hash to be parsed.
|
30
|
+
# @param [String] param Parameter name.
|
31
|
+
# @param [Object] value Parameter value.
|
32
|
+
# @return [nil]
|
33
|
+
def add_param( param, value = nil )
|
34
|
+
@params[param] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
# This method has to be overwritten in the child
|
38
|
+
# @abstract
|
39
|
+
# @return [String]
|
40
|
+
def parse
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Copyright 2011 NoSoloSoftware
|
2
|
+
|
3
|
+
# This file is part of Runnable.
|
4
|
+
#
|
5
|
+
# Runnable is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# Runnable is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with Runnable. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require 'command_parser'
|
19
|
+
|
20
|
+
# <p>Parse the parameter hash using the extended standard.</p>
|
21
|
+
class Extended < Command_parser
|
22
|
+
|
23
|
+
# Convert a hash in a Extended style string options.
|
24
|
+
# @return [String] Extended-style parsed params in a raw character array.
|
25
|
+
def parse
|
26
|
+
options = ""
|
27
|
+
@params.each do | param , value |
|
28
|
+
options = "#{options} -#{param} #{value} "
|
29
|
+
end
|
30
|
+
options.strip
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/runnable/gnu.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Copyright 2011 NoSoloSoftware
|
2
|
+
|
3
|
+
# This file is part of Runnable.
|
4
|
+
#
|
5
|
+
# Runnable is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# Runnable is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with Runnable. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require 'command_parser'
|
19
|
+
|
20
|
+
# <p>Parse the parameter hash using the GNU standard.</p>
|
21
|
+
class Gnu < Command_parser
|
22
|
+
|
23
|
+
# This method convert a hash in a string ready to
|
24
|
+
# be passed to a command that uses GNU style to parse command line
|
25
|
+
# parameters.
|
26
|
+
# @return [String] Gnu-style parsed params in a raw character array.
|
27
|
+
def parse
|
28
|
+
result = ""
|
29
|
+
|
30
|
+
@params.each do |param, value|
|
31
|
+
# We assume that an one character words is preceed by one
|
32
|
+
# lead and two or more characters words are preceed by two
|
33
|
+
# leads
|
34
|
+
result << ( param.length == 1 ? "-#{param} " : "--#{param} " )
|
35
|
+
|
36
|
+
# In case the param have parameter we use the correct assignation
|
37
|
+
# -Param followed by value (without whitespace) to one character params
|
38
|
+
# -Param followed by '=' and value to more than one character params
|
39
|
+
if( value != nil )
|
40
|
+
result << ( param.length == 1 ? "#{value}" : "=#{value}" )
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
return result.strip
|
45
|
+
end
|
46
|
+
end
|
data/lib/runnable.rb
ADDED
@@ -0,0 +1,415 @@
|
|
1
|
+
# Copyright 2011 NoSoloSoftware
|
2
|
+
|
3
|
+
# This file is part of Runnable.
|
4
|
+
#
|
5
|
+
# Runnable is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# Runnable is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with Runnable. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
|
19
|
+
# Convert a executable command in a Ruby-like class
|
20
|
+
# you are able to start, define params and send signals (like kill, or stop)
|
21
|
+
#
|
22
|
+
# @example Usage:
|
23
|
+
# class LS < Runnable
|
24
|
+
# command_style :extended
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# ls = LS.new
|
28
|
+
# ls.alh
|
29
|
+
# ls.run
|
30
|
+
#
|
31
|
+
$LOAD_PATH << File.expand_path( './runnable', __FILE__ )
|
32
|
+
|
33
|
+
require 'publisher'
|
34
|
+
|
35
|
+
class Runnable
|
36
|
+
extend Publisher
|
37
|
+
|
38
|
+
# Fires to know whats happening inside
|
39
|
+
can_fire :fail, :finish
|
40
|
+
|
41
|
+
# Process id.
|
42
|
+
attr_reader :pid
|
43
|
+
# Process owner.
|
44
|
+
attr_reader :owner
|
45
|
+
# Process group.
|
46
|
+
attr_reader :group
|
47
|
+
# Directory where process was called from.
|
48
|
+
attr_reader :pwd
|
49
|
+
|
50
|
+
# Metaprogramming part of the class
|
51
|
+
|
52
|
+
# Define the parameter style to be used.
|
53
|
+
# @return [nil]
|
54
|
+
def self.command_style( style )
|
55
|
+
define_method( :command_style ) do
|
56
|
+
style
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Parameter style used for the command.
|
61
|
+
# @return [Symbol] Command style.
|
62
|
+
def command_style
|
63
|
+
:gnu
|
64
|
+
end
|
65
|
+
|
66
|
+
# List of runnable instances running on the system order by pid.
|
67
|
+
@@processes = Hash.new
|
68
|
+
|
69
|
+
# Constant to calculate cpu usage.
|
70
|
+
HERTZ = 100
|
71
|
+
|
72
|
+
# Create a new instance of a runnable command.
|
73
|
+
# @param [Hash] option_hash Options.
|
74
|
+
# @option option_hash :delete_log (true) Delete the log after execution.
|
75
|
+
# @option option_hash :command_options ("") Command options.
|
76
|
+
# @option option_hash :log_path ("/var/log/runnable") Path for the log files.
|
77
|
+
def initialize( option_hash = {} )
|
78
|
+
# keys :delete_log
|
79
|
+
# :command_options
|
80
|
+
# :log_path
|
81
|
+
|
82
|
+
# If we have the command class in a namespace, we need to remove
|
83
|
+
# the namespace name
|
84
|
+
@command = self.class.to_s.split( "::" ).last.downcase
|
85
|
+
|
86
|
+
# Set the default command option
|
87
|
+
# Empty by default
|
88
|
+
option_hash[:command_options] ||= ""
|
89
|
+
@options = option_hash[:command_options]
|
90
|
+
|
91
|
+
# Set the log path
|
92
|
+
# Default path is "/var/log/runnable"
|
93
|
+
option_hash[:log_path] ||= "/var/log/runnable/"
|
94
|
+
@log_path = option_hash[:log_path]
|
95
|
+
|
96
|
+
# Set the delete_log option
|
97
|
+
# true by default
|
98
|
+
if option_hash[:delete_log] == nil
|
99
|
+
@delete_log = true
|
100
|
+
else
|
101
|
+
@delete_log = option_hash[:delete_log]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Store input options
|
105
|
+
@input = Array.new
|
106
|
+
|
107
|
+
# Store output options
|
108
|
+
@output = Array.new
|
109
|
+
|
110
|
+
# @todo: checks that command is in the PATH
|
111
|
+
# ...
|
112
|
+
|
113
|
+
# we dont set the pid, because we dont know until run
|
114
|
+
@pid = nil
|
115
|
+
@excep_array = []
|
116
|
+
|
117
|
+
|
118
|
+
# Metaprogramming part
|
119
|
+
# Require the class to parse the command line options
|
120
|
+
require command_style.to_s.downcase
|
121
|
+
# Create a new instance of the parser class
|
122
|
+
@command_line_interface = Object.const_get( command_style.to_s.capitalize.to_sym ).new
|
123
|
+
# End Metaprogramming part
|
124
|
+
|
125
|
+
#End of initialize instance variables
|
126
|
+
|
127
|
+
create_log_directory
|
128
|
+
end
|
129
|
+
|
130
|
+
# Start the execution of the command.
|
131
|
+
# @return [nil]
|
132
|
+
# @fire :finish
|
133
|
+
# @fire :fail
|
134
|
+
def run
|
135
|
+
# Create a new mutex
|
136
|
+
@pid_mutex = Mutex.new
|
137
|
+
|
138
|
+
# Create pipes to redirect Standar I/O
|
139
|
+
out_rd, out_wr = IO.pipe
|
140
|
+
# Redirect Error I/O
|
141
|
+
err_rd, err_wr = IO.pipe
|
142
|
+
|
143
|
+
#
|
144
|
+
@pid = Process.spawn( "#{@command} #{@input.join( " " )} \
|
145
|
+
#{@options} #{@command_line_interface.parse} \
|
146
|
+
#{@output.join( " " )}", { :out => out_wr, :err => err_wr } )
|
147
|
+
|
148
|
+
# Include instance in class variable
|
149
|
+
@@processes[@pid] = self
|
150
|
+
|
151
|
+
# Prepare the process info file to be read
|
152
|
+
file_status = File.open( "/proc/#{@pid}/status" ).read.split( "\n" )
|
153
|
+
# Owner: Read the owner of the process from /proc/@pid/status
|
154
|
+
@owner = file_status[6].split( " " )[1]
|
155
|
+
# Group: Read the Group owner from /proc/@pid/status
|
156
|
+
@group = file_status[7].split( " " )[1]
|
157
|
+
|
158
|
+
# Set @output_thread with new threads
|
159
|
+
# wich execute the input/ouput loop
|
160
|
+
create_logs(:out => [out_wr, out_rd], :err => [err_wr, err_rd])
|
161
|
+
|
162
|
+
# Create a new thread to avoid blocked processes
|
163
|
+
@run_thread = Thread.new do
|
164
|
+
# Wait to get the pid process even if it has finished
|
165
|
+
Process.wait( @pid, Process::WUNTRACED )
|
166
|
+
|
167
|
+
# Wait each I/O thread
|
168
|
+
@output_threads.each { |thread| thread.join }
|
169
|
+
# Delete log if its necesary
|
170
|
+
delete_log
|
171
|
+
|
172
|
+
# Get the exit code from command
|
173
|
+
exit_status = $?.exitstatus
|
174
|
+
|
175
|
+
# In case of error add an Exception to the @excep_array
|
176
|
+
@excep_array << SystemCallError.new( exit_status ) if exit_status != 0
|
177
|
+
|
178
|
+
# Fire signals according to the exit code
|
179
|
+
if @excep_array.empty?
|
180
|
+
fire :finish
|
181
|
+
else
|
182
|
+
fire :fail, @excep_array
|
183
|
+
end
|
184
|
+
|
185
|
+
# This instance is finished and we remove it
|
186
|
+
@@processes.delete( @pid )
|
187
|
+
end
|
188
|
+
|
189
|
+
# Satuts Variables
|
190
|
+
# PWD: Current Working Directory get by /proc/@pid/cwd
|
191
|
+
# @rescue If a fast process is runned there isn't time to get
|
192
|
+
# the correct PWD. If the readlink fails, we retry, if the process still alive
|
193
|
+
# until the process finish.
|
194
|
+
begin
|
195
|
+
@pwd = File.readlink( "/proc/#{@pid}/cwd" )
|
196
|
+
rescue
|
197
|
+
# If cwd is not available rerun @run_thread
|
198
|
+
if @run_thread.alive?
|
199
|
+
#If it is alive, we retry to get cwd
|
200
|
+
@run_thread.run
|
201
|
+
retry
|
202
|
+
else
|
203
|
+
#If process has terminated, we set pwd to current working directory of ruby
|
204
|
+
@pwd = Dir.getwd
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Stop the command.
|
210
|
+
# @return [nil]
|
211
|
+
# @todo Raise an exception if process is not running.
|
212
|
+
def stop
|
213
|
+
send_signal( :stop )
|
214
|
+
|
215
|
+
# In order to maintain consistency of @@processes
|
216
|
+
# we must assure that @run_thread finish correctly
|
217
|
+
@run_thread.run if @run_thread.alive?
|
218
|
+
end
|
219
|
+
|
220
|
+
# Kill the comand.
|
221
|
+
# @return [nil]
|
222
|
+
# @todo Raise an exeption if process is not running.
|
223
|
+
def kill
|
224
|
+
send_signal( :kill )
|
225
|
+
|
226
|
+
# In order to maintain consistency of @@processes
|
227
|
+
# we must assure that @run_thread finish correctly
|
228
|
+
join
|
229
|
+
end
|
230
|
+
|
231
|
+
# Wait for command thread to finish it execution.
|
232
|
+
# @return [nil]
|
233
|
+
def join
|
234
|
+
@run_thread.join if @run_thread.alive?
|
235
|
+
end
|
236
|
+
|
237
|
+
# Calculate the estimated memory usage in Kb.
|
238
|
+
# @return [Number] Estimated mem usage in Kb.
|
239
|
+
def mem
|
240
|
+
File.open( "/proc/#{@pid}/status" ).read.split( "\n" )[11].split( " " )[1].to_i
|
241
|
+
end
|
242
|
+
|
243
|
+
# Estimated CPU usage in %.
|
244
|
+
# @return [Number] The estimated cpu usage.
|
245
|
+
def cpu
|
246
|
+
# Open the proc stat file
|
247
|
+
begin
|
248
|
+
stat = File.open( "/proc/#{@pid}/stat" ).read.split
|
249
|
+
|
250
|
+
# Get time variables
|
251
|
+
# utime = User Time
|
252
|
+
# stime = System Time
|
253
|
+
# start_time = Time passed from process starting
|
254
|
+
utime = stat[13].to_f
|
255
|
+
stime = stat[14].to_f
|
256
|
+
start_time = stat[21].to_f
|
257
|
+
|
258
|
+
# uptime = Time passed from system starting
|
259
|
+
uptime = File.open( "/proc/uptime" ).read.split[0].to_f
|
260
|
+
|
261
|
+
# Total time that the process has been executed
|
262
|
+
total_time = utime + stime # in jiffies
|
263
|
+
|
264
|
+
# Seconds passed between start the process and now
|
265
|
+
seconds = uptime - ( start_time / HERTZ )
|
266
|
+
# Percentage of used CPU ( ESTIMATED )
|
267
|
+
(total_time / seconds.to_f)
|
268
|
+
rescue IOError
|
269
|
+
# Fails to open file
|
270
|
+
0
|
271
|
+
rescue ZeroDivisionError
|
272
|
+
# Seconds is Zero!
|
273
|
+
0
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
# Set the input files.
|
279
|
+
# @param [String] param Input to be parsed as command options.
|
280
|
+
# @return [nil]
|
281
|
+
def input( param )
|
282
|
+
@input << param
|
283
|
+
end
|
284
|
+
|
285
|
+
# Set the output files.
|
286
|
+
# @param [String] param Output to be parsed as command options.
|
287
|
+
# @return [nil]
|
288
|
+
def output( param )
|
289
|
+
@output << param
|
290
|
+
end
|
291
|
+
|
292
|
+
# Convert undefined methods (ruby-like syntax) into parameters
|
293
|
+
# to be parsed at the execution time.
|
294
|
+
# This only convert methods with zero or one parameters. A hash can be passed
|
295
|
+
# and each key will define a new method and method name will be ignored.
|
296
|
+
#
|
297
|
+
# @example Valid calls:
|
298
|
+
# find.depth #=> find -depth
|
299
|
+
# find.iname( '"*.rb"') #=> find -iname "*.rb"
|
300
|
+
# find.foo( { :iname => '"*.rb"', :type => '"f"' } ) #=> find -iname "*.rb" - type "f"
|
301
|
+
# @example Invalid calls:
|
302
|
+
# sleep.5 #=> Incorrect. "5" is not a valid call to a ruby method so method_missing will not be invoked and will
|
303
|
+
# raise a tINTEGER exception
|
304
|
+
#
|
305
|
+
# @param [Symbol] method Method called that is missing
|
306
|
+
# @param [Array] params Params in the call
|
307
|
+
# @param [Block] block Block code in method
|
308
|
+
# @return [nil]
|
309
|
+
# @override
|
310
|
+
def method_missing( method, *params, &block )
|
311
|
+
if params.length > 1
|
312
|
+
super( method, params, block )
|
313
|
+
else
|
314
|
+
if params[0].class == Hash
|
315
|
+
# If only one param is passed and its a Hash
|
316
|
+
# we need to expand the hash and call each key as a method with value as params
|
317
|
+
# @see parse_hash for more information
|
318
|
+
parse_hash( params[0] )
|
319
|
+
else
|
320
|
+
@command_line_interface.add_param( method.to_s,
|
321
|
+
params != nil ? params.join(",") : nil )
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# List of runnable instances running on the system.
|
327
|
+
# @return [Hash] Using process pids as keys and instances as values.
|
328
|
+
def self.processes
|
329
|
+
@@processes
|
330
|
+
end
|
331
|
+
|
332
|
+
# @abstract
|
333
|
+
# Returns a hash of regular expressions and exceptions associated to them.
|
334
|
+
# Command output is match against those regular expressions, if it does match
|
335
|
+
# an appropiate exception is included in the return value of execution.
|
336
|
+
# @note This method should be overwritten in child classes.
|
337
|
+
# @example Usage:
|
338
|
+
# class ls < Runnable
|
339
|
+
# def exceptions
|
340
|
+
# { /ls: (invalid option.*)/ => ArgumentError }
|
341
|
+
# end
|
342
|
+
# end
|
343
|
+
#
|
344
|
+
# @return [Hash] Using regular expressions as keys and exceptions that should
|
345
|
+
# be raised as values.
|
346
|
+
def exceptions
|
347
|
+
{}
|
348
|
+
end
|
349
|
+
|
350
|
+
protected
|
351
|
+
|
352
|
+
# Send the desired signal to the command.
|
353
|
+
# @param [Symbol] Signal to be send to the command.
|
354
|
+
# @todo raise ESRCH if pid is not in system
|
355
|
+
# or EPERM if pid is not from user.
|
356
|
+
def send_signal( signal )
|
357
|
+
if signal == :stop
|
358
|
+
Process.kill( :SIGINT, @pid )
|
359
|
+
elsif signal == :kill
|
360
|
+
Process.kill( :SIGKILL, @pid )
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Redirect command I/O to log files.
|
365
|
+
# These files are located in /var/log/runnable.
|
366
|
+
# @param [Hash] Outputs options.
|
367
|
+
# @option outputs stream [Symbol] Stream name.
|
368
|
+
# @option outputs pipes [IO] I/O stream to be redirected.
|
369
|
+
# @return [nil]
|
370
|
+
def create_logs( outputs = {} )
|
371
|
+
# Create an empty file for logging
|
372
|
+
FileUtils.touch "#{@log_path}#{@command}_#{@pid}.log"
|
373
|
+
|
374
|
+
@output_threads = []
|
375
|
+
# for each io stream we create a thread wich read that
|
376
|
+
# stream and write it in a log file
|
377
|
+
outputs.each do |output_name, pipes|
|
378
|
+
@output_threads << Thread.new do
|
379
|
+
pipes[0].close
|
380
|
+
|
381
|
+
pipes[1].each_line do |line|
|
382
|
+
File.open("#{@log_path}#{@command}_#{@pid}.log", "a") do |log_file|
|
383
|
+
log_file.puts( "[#{Time.new.inspect} || [STD#{output_name.to_s.upcase} || [#{@pid}]] #{line}" )
|
384
|
+
end
|
385
|
+
# Match custom exceptions
|
386
|
+
# if we get a positive match, add it to the exception array
|
387
|
+
# in order to inform the user of what had happen
|
388
|
+
exceptions.each do | reg_expr, value |
|
389
|
+
@excep_array<< value.new( $1 ) if reg_expr =~ line
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def create_log_directory
|
397
|
+
Dir.mkdir( @log_path ) unless Dir.exist?( @log_path )
|
398
|
+
end
|
399
|
+
|
400
|
+
def delete_log
|
401
|
+
File.delete( "#{@log_path}#{@command}_#{@pid}.log" ) if @delete_log == true
|
402
|
+
end
|
403
|
+
|
404
|
+
# Expand a parameter hash calling each key as method and value as param
|
405
|
+
# forcing method misssing to be called.
|
406
|
+
# @param [Hash] hash Parameters to be expand and included in command execution
|
407
|
+
# @return [nil]
|
408
|
+
def parse_hash( hash )
|
409
|
+
hash.each do |key, value|
|
410
|
+
# Call to a undefined method which trigger overwritten method_missing
|
411
|
+
# unless its named as a runnable method
|
412
|
+
self.public_send( key.to_sym, value ) unless self.respond_to?( key.to_sym )
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|