runnable 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +41 -36
- data/VERSION +1 -1
- data/lib/runnable.rb +231 -213
- data/runnable.gemspec +4 -3
- metadata +61 -22
data/README.markdown
CHANGED
@@ -5,10 +5,17 @@ A Ruby gem that allow programmer to control UNIX system commands as a Ruby class
|
|
5
5
|
All you have to do is to create a class named exactly as command and make it
|
6
6
|
inherit from class Runnable.
|
7
7
|
|
8
|
-
class LS
|
8
|
+
class LS
|
9
|
+
include Runnable
|
9
10
|
end
|
10
11
|
|
11
12
|
That gives you the basics to control the execution of ```ls``` command.
|
13
|
+
You can overwrite the name of the command by using the ```executes``` macro:
|
14
|
+
class MyLs
|
15
|
+
include Runnable
|
16
|
+
|
17
|
+
executes :ls
|
18
|
+
end
|
12
19
|
|
13
20
|
Now you can create an instance like this:
|
14
21
|
|
@@ -23,53 +30,51 @@ for some important information about the command and its process. Entire
|
|
23
30
|
documentation of this gem can be generated using ```yardoc```. To do this use
|
24
31
|
```rake doc```.
|
25
32
|
|
26
|
-
##
|
27
|
-
Runnable
|
28
|
-
|
29
|
-
ocurred. In case something went wrong and a ```:fail``` method is called, Runnable
|
30
|
-
also provide an array containing the command return value as the parameter of a
|
31
|
-
SystemCallError exception and optionally others exceptions ocurred at runtime.
|
33
|
+
## Custom output and exceptions
|
34
|
+
Runnable parse a set of user defined regular expresion to set up the command return
|
35
|
+
values.
|
32
36
|
|
33
37
|
This is an example of how we can receive the return value of a command:
|
34
38
|
|
35
|
-
class
|
39
|
+
class Nmap
|
40
|
+
include Runnable
|
41
|
+
|
42
|
+
executes :nmap
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
44
|
+
define_command( :scan, :blocking => true ) { |ip, subnet| "-sP #{ip}/#{subnet}" }
|
45
|
+
scan_processors(
|
46
|
+
:exceptions => { /^Illegal netmask value/ => ArgumentError },
|
47
|
+
:outputs => { /Nmap scan report for (.*)/ => :ip }
|
48
|
+
)
|
49
|
+
end
|
40
50
|
|
41
|
-
|
42
|
-
puts "Something went wrong :("
|
43
|
-
exceptions.each do |exception|
|
44
|
-
puts exception.message
|
45
|
-
end
|
46
|
-
end
|
51
|
+
Nmap.new.scan("192.168.1.1", "24") # should return an array with the ips
|
47
52
|
|
48
|
-
|
53
|
+
Runnable can also raise custom exceptions, using the previously Nmap defined class:
|
54
|
+
Nmap.new.scan("192.168.1.1", "1000")
|
55
|
+
Will raise an ArgumentError exception.
|
56
|
+
Note that Runnable will also raise an exception if the command returned value is not 0.
|
49
57
|
|
50
|
-
|
51
|
-
|
58
|
+
## Background usage
|
59
|
+
Runnable can be used with background process:
|
52
60
|
|
53
|
-
|
54
|
-
|
55
|
-
succesfully, Runnable fires a ```:fail``` event whit an exceptions array. We can
|
56
|
-
add exceptions to that array based on the output of command. For example, we
|
57
|
-
can controll that parameters passed to a command are valids if we know the
|
58
|
-
command output for an invalid parameters.
|
61
|
+
class Ping
|
62
|
+
include Runnable
|
59
63
|
|
60
|
-
|
61
|
-
as follows
|
64
|
+
define_command( :goping, :blocking => false) { "-c5 www.google.es" }
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
66
|
+
goping_processors(
|
67
|
+
:outputs => { /64 bytes from .* time=(.*) ms/ => :time }
|
68
|
+
)
|
67
69
|
end
|
68
70
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
71
|
+
p = Ping.new
|
72
|
+
p.goping
|
73
|
+
|
74
|
+
while p.running?
|
75
|
+
p p.output[:time]
|
76
|
+
sleep 1
|
77
|
+
end
|
73
78
|
|
74
79
|
# About
|
75
80
|
Runnable is a gem developed by [NoSoloSoftware](http://nosolosoftware.biz).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/runnable.rb
CHANGED
@@ -24,14 +24,97 @@ require 'fileutils'
|
|
24
24
|
# you are able to start, define params and send signals (like kill, or stop)
|
25
25
|
#
|
26
26
|
# @example Usage:
|
27
|
-
# class LS
|
27
|
+
# class LS
|
28
|
+
# include Runnable
|
29
|
+
#
|
30
|
+
# executes :ls
|
28
31
|
# command_style :extended
|
29
32
|
# end
|
30
33
|
#
|
31
34
|
# ls = LS.new
|
32
35
|
# ls.alh
|
33
36
|
# ls.run
|
34
|
-
|
37
|
+
module Runnable
|
38
|
+
def self.included(klass)
|
39
|
+
klass.extend ClassMethods
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
# Define the command to be executed
|
44
|
+
# @return [nil]
|
45
|
+
# @param [Symbol] command Command to be executed
|
46
|
+
def executes( cmd )
|
47
|
+
define_method( :command ) { cmd }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Define the parameter style to be used.
|
51
|
+
# @return [nil]
|
52
|
+
def command_style( style )
|
53
|
+
define_method( :command_style ) { style }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create a user definde command
|
57
|
+
# @return [nil]
|
58
|
+
# @param [Symbol] name The user defined command name
|
59
|
+
# @param [Hash] options Options.
|
60
|
+
# @option options :blocking (false) Describe if the execution is blocking or non-blocking
|
61
|
+
# @option options :log_path (false) Path used to store logs # (dont store logs if no path specified)
|
62
|
+
def define_command( name, opts = {}, &block )
|
63
|
+
blocking = opts[:blocking] || false
|
64
|
+
log_path = opts[:log_path] || false
|
65
|
+
|
66
|
+
commands[name] = { :blocking => blocking }
|
67
|
+
|
68
|
+
define_method( name ) do |*args|
|
69
|
+
run name, block.call(*args), log_path
|
70
|
+
join if blocking
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Generic command processor. It allows to define generic processors used in all the
|
75
|
+
# user defined commands
|
76
|
+
# @param [Hash] opts Processing options
|
77
|
+
# @option opts :outputs (nil) Output processing Hash (regexp => output)
|
78
|
+
# @option opts :exceptions (nil) Exceptions processing Hash (regexp => exception)
|
79
|
+
def processors( opts = nil )
|
80
|
+
if opts.is_a? Hash
|
81
|
+
@processors = opts
|
82
|
+
else
|
83
|
+
@processors ||= Hash.new
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Method missing processing for the command processors
|
88
|
+
def method_missing( name, *opts )
|
89
|
+
raise NoMethodError.new( name.to_s ) unless name.to_s =~ /([a-z]*)_([a-z]*)/
|
90
|
+
|
91
|
+
# command_processors
|
92
|
+
if $2 == "processors"
|
93
|
+
commands[$1.to_sym][:outputs] = opts.first[:outputs]
|
94
|
+
commands[$1.to_sym][:exceptions] = opts.first[:exceptions]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# @group Accessors for the module class variables
|
99
|
+
|
100
|
+
# Returns the user defined commands
|
101
|
+
# @return [Hash] commands User defined commands
|
102
|
+
def commands
|
103
|
+
@commands ||= Hash.new
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the list of runnable instances by pid
|
107
|
+
# @return [Hash] list of runnable instances by pid
|
108
|
+
def processes
|
109
|
+
@processes ||= Hash.new
|
110
|
+
end
|
111
|
+
|
112
|
+
# Processes writer
|
113
|
+
def processes=( value )
|
114
|
+
@processes = value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
35
118
|
# Process id.
|
36
119
|
attr_reader :pid
|
37
120
|
# Process owner.
|
@@ -41,137 +124,54 @@ class Runnable
|
|
41
124
|
# Directory where process was called from.
|
42
125
|
attr_reader :pwd
|
43
126
|
|
44
|
-
#
|
45
|
-
|
127
|
+
# Process output
|
128
|
+
attr_reader :output
|
46
129
|
|
47
|
-
#
|
48
|
-
attr_accessor :
|
130
|
+
# Process options
|
131
|
+
attr_accessor :options
|
132
|
+
# Process log output
|
133
|
+
attr_accessor :log_path
|
49
134
|
|
50
135
|
# 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
|
-
|
136
|
+
|
60
137
|
# Parameter style used for the command.
|
61
138
|
# @return [Symbol] Command style.
|
62
139
|
def command_style
|
63
140
|
:gnu
|
64
141
|
end
|
65
142
|
|
66
|
-
#
|
67
|
-
|
143
|
+
# Default command to be executed
|
144
|
+
# @return [String] Command to be executed
|
145
|
+
def command
|
146
|
+
self.class.to_s.split( "::" ).last.downcase
|
147
|
+
end
|
68
148
|
|
69
149
|
# Constant to calculate cpu usage.
|
70
150
|
HERTZ = 100
|
71
151
|
|
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 = String.new
|
106
|
-
|
107
|
-
# Store output options
|
108
|
-
@output = String.new
|
109
|
-
|
110
|
-
# Standar Outputs
|
111
|
-
@std_output = {
|
112
|
-
:out => "",
|
113
|
-
:err => ""
|
114
|
-
}
|
115
|
-
|
116
|
-
# @todo: checks that command is in the PATH
|
117
|
-
# ...
|
118
|
-
|
119
|
-
# we dont set the pid, because we dont know until run
|
120
|
-
@pid = nil
|
121
|
-
@excep_array = []
|
122
|
-
|
123
|
-
|
124
|
-
# Metaprogramming part
|
125
|
-
# Create a new instance of the parser class
|
126
|
-
@command_line_interface = Object.const_get( command_style.to_s.capitalize.to_sym ).new
|
127
|
-
# End Metaprogramming part
|
128
|
-
|
129
|
-
#End of initialize instance variables
|
130
|
-
|
131
|
-
create_log_directory
|
132
|
-
end
|
133
|
-
|
134
152
|
# Start the execution of the command.
|
135
153
|
# @return [nil]
|
136
|
-
def run
|
154
|
+
def run(name = nil, opts = nil, log_path = nil)
|
155
|
+
return false if @pid
|
137
156
|
# Create a new mutex
|
138
157
|
@pid_mutex = Mutex.new
|
158
|
+
|
159
|
+
# Log path should be an instance variable to avoid a mess
|
160
|
+
@log_path = log_path || @log_path
|
139
161
|
|
140
162
|
# Create pipes to redirect Standar I/O
|
141
163
|
out_rd, out_wr = IO.pipe
|
142
164
|
# Redirect Error I/O
|
143
165
|
err_rd, err_wr = IO.pipe
|
144
|
-
|
166
|
+
|
145
167
|
# Reset exceptions array to not store exceptions for
|
146
168
|
# past executions
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
command = []
|
151
|
-
#command << @command
|
152
|
-
command << @input.to_s
|
153
|
-
command << @options.to_s
|
154
|
-
command << @command_line_interface.parse
|
155
|
-
command << @output.to_s
|
156
|
-
#command = command.join( " " )
|
157
|
-
command.flatten!
|
158
|
-
|
159
|
-
command = command.select do |value|
|
160
|
-
!value.to_s.strip.empty?
|
161
|
-
end
|
162
|
-
=begin
|
163
|
-
# Debugging purpose
|
164
|
-
puts "I: #{@input}"
|
165
|
-
puts "OP: #{@options}"
|
166
|
-
puts "CLI: #{@command_line_interface.parse}"
|
167
|
-
puts "O: #{@output}"
|
168
|
-
puts "C: #{command}"
|
169
|
-
=end
|
170
|
-
#@pid = Process.spawn( command, { :out => out_wr, :err => err_wr } )
|
171
|
-
@pid = Process.spawn( @command, *command, { :out => out_wr, :err => err_wr } )
|
169
|
+
command_argument = opts ? opts.split(" ") : compose_command
|
170
|
+
|
171
|
+
@pid = Process.spawn( command.to_s, *command_argument, { :out => out_wr, :err => err_wr } )
|
172
172
|
|
173
173
|
# Include instance in class variable
|
174
|
-
|
174
|
+
self.class.processes[@pid] = self
|
175
175
|
|
176
176
|
# Prepare the process info file to be read
|
177
177
|
file_status = File.open( "/proc/#{@pid}/status" ).read.split( "\n" )
|
@@ -179,46 +179,36 @@ class Runnable
|
|
179
179
|
@owner = file_status[6].split( " " )[1]
|
180
180
|
# Group: Read the Group owner from /proc/@pid/status
|
181
181
|
@group = file_status[7].split( " " )[1]
|
182
|
-
|
182
|
+
|
183
183
|
# Set @output_thread with new threads
|
184
184
|
# wich execute the input/ouput loop
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
185
|
+
stream_info = {
|
186
|
+
:out => [out_wr, out_rd],
|
187
|
+
:err => [err_wr, err_rd]
|
188
|
+
}
|
189
|
+
|
190
|
+
if name
|
191
|
+
cmd_info = self.class.commands[name]
|
192
|
+
stream_processors = {
|
193
|
+
:outputs => cmd_info[:outputs],
|
194
|
+
:exceptions => cmd_info[:exceptions]
|
195
|
+
}
|
196
|
+
end
|
191
197
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
198
|
+
output_threads = process_streams( stream_info, stream_processors )
|
199
|
+
|
200
|
+
# Create a new thread to avoid blocked processes
|
201
|
+
@run_thread = threaded_process(@pid, output_threads)
|
196
202
|
|
197
|
-
# Get the exit code from command
|
198
|
-
exit_status = $?.exitstatus
|
199
|
-
|
200
|
-
# In case of error add an Exception to the @excep_array
|
201
|
-
@excep_array << SystemCallError.new( exit_status ) if exit_status != 0
|
202
|
-
|
203
|
-
# Call methods according to the exit code
|
204
|
-
if @excep_array.empty?
|
205
|
-
finish
|
206
|
-
else
|
207
|
-
failed( @excep_array )
|
208
|
-
end
|
209
|
-
|
210
|
-
# This instance is finished and we remove it
|
211
|
-
@@processes.delete( @pid )
|
212
|
-
end
|
213
|
-
|
214
203
|
# Satuts Variables
|
215
204
|
# PWD: Current Working Directory get by /proc/@pid/cwd
|
216
205
|
# @rescue If a fast process is runned there isn't time to get
|
217
206
|
# the correct PWD. If the readlink fails, we retry, if the process still alive
|
218
207
|
# until the process finish.
|
208
|
+
|
219
209
|
begin
|
220
|
-
@pwd
|
221
|
-
rescue
|
210
|
+
@pwd ||= File.readlink( "/proc/#{@pid}/cwd" )
|
211
|
+
rescue Errno::ENOENT
|
222
212
|
# If cwd is not available rerun @run_thread
|
223
213
|
if @run_thread.alive?
|
224
214
|
#If it is alive, we retry to get cwd
|
@@ -228,6 +218,8 @@ class Runnable
|
|
228
218
|
#If process has terminated, we set pwd to current working directory of ruby
|
229
219
|
@pwd = Dir.getwd
|
230
220
|
end
|
221
|
+
rescue #Errno::EACCESS
|
222
|
+
@pwd = Dir.getwd
|
231
223
|
end
|
232
224
|
end
|
233
225
|
|
@@ -257,6 +249,7 @@ class Runnable
|
|
257
249
|
# @return [nil]
|
258
250
|
def join
|
259
251
|
@run_thread.join if @run_thread.alive?
|
252
|
+
@output unless @output.empty?
|
260
253
|
end
|
261
254
|
|
262
255
|
# Check if prcess is running on the system.
|
@@ -268,13 +261,25 @@ class Runnable
|
|
268
261
|
# Standar output of command
|
269
262
|
# @return [String] Standar output
|
270
263
|
def std_out
|
271
|
-
@
|
264
|
+
@std_out ||= ""
|
272
265
|
end
|
273
266
|
|
274
267
|
# Standar error output of the command
|
275
268
|
# @return [String] Standar error output
|
276
269
|
def std_err
|
277
|
-
@
|
270
|
+
@std_err ||= ""
|
271
|
+
end
|
272
|
+
|
273
|
+
# Sets the command input to be passed to the command execution
|
274
|
+
# @param [String] opt Command input
|
275
|
+
def input=( opt )
|
276
|
+
@command_input = opt
|
277
|
+
end
|
278
|
+
|
279
|
+
# Sets the command output to be passed to the command execution
|
280
|
+
# @param [String] opt Command output
|
281
|
+
def output=( opt )
|
282
|
+
@command_output = opt
|
278
283
|
end
|
279
284
|
|
280
285
|
# Calculate the estimated memory usage in Kb.
|
@@ -315,7 +320,6 @@ class Runnable
|
|
315
320
|
# Seconds is Zero!
|
316
321
|
0
|
317
322
|
end
|
318
|
-
|
319
323
|
end
|
320
324
|
|
321
325
|
# Estimated bandwidth in kb/s.
|
@@ -354,6 +358,8 @@ class Runnable
|
|
354
358
|
# @param [Block] block Block code in method
|
355
359
|
# @return [nil]
|
356
360
|
def method_missing( method, *params, &block )
|
361
|
+
@command_line_interface ||= Object.const_get( command_style.to_s.capitalize.to_sym ).new
|
362
|
+
|
357
363
|
if params.length > 1
|
358
364
|
super( method, params, block )
|
359
365
|
else
|
@@ -363,8 +369,7 @@ class Runnable
|
|
363
369
|
# @see parse_hash for more information
|
364
370
|
parse_hash( params[0] )
|
365
371
|
else
|
366
|
-
@command_line_interface.add_param( method.to_s,
|
367
|
-
params != nil ? params.join(",") : nil )
|
372
|
+
@command_line_interface.add_param( method.to_s, params != nil ? params.join(",") : nil )
|
368
373
|
end
|
369
374
|
end
|
370
375
|
end
|
@@ -375,39 +380,6 @@ class Runnable
|
|
375
380
|
@@processes
|
376
381
|
end
|
377
382
|
|
378
|
-
# @abstract
|
379
|
-
# Returns a hash of regular expressions and exceptions associated to them.
|
380
|
-
# Command output is match against those regular expressions, if it does match
|
381
|
-
# an appropiate exception is included in the return value of execution.
|
382
|
-
# @note This method should be overwritten in child classes.
|
383
|
-
# @example Usage:
|
384
|
-
# class ls < Runnable
|
385
|
-
# def exceptions
|
386
|
-
# { /ls: (invalid option.*)/ => ArgumentError }
|
387
|
-
# end
|
388
|
-
# end
|
389
|
-
#
|
390
|
-
# @return [Hash] Using regular expressions as keys and exceptions that should
|
391
|
-
# be raised as values.
|
392
|
-
def exceptions
|
393
|
-
{}
|
394
|
-
end
|
395
|
-
|
396
|
-
# @abstract
|
397
|
-
# Method called when command ends with no erros.
|
398
|
-
# This method is a hook so it should be overwritten in child classes.
|
399
|
-
# @return [nil]
|
400
|
-
def finish
|
401
|
-
end
|
402
|
-
|
403
|
-
# @abstract
|
404
|
-
# Method called when command executions fail.
|
405
|
-
# This method is a hook so it should be overwritten in child classes.
|
406
|
-
# @param [Array] exceptions Array containing exceptions raised during the command execution.
|
407
|
-
# @return [nil]
|
408
|
-
def failed( exceptions )
|
409
|
-
end
|
410
|
-
|
411
383
|
# Send the desired signal to the command.
|
412
384
|
# @param [Symbol] Signal to be send to the command.
|
413
385
|
# @todo raise ESRCH if pid is not in system
|
@@ -437,50 +409,24 @@ class Runnable
|
|
437
409
|
end
|
438
410
|
|
439
411
|
protected
|
440
|
-
#
|
412
|
+
# Process the command I/O.
|
441
413
|
# These files are located in /var/log/runnable.
|
442
414
|
# @param [Hash] Outputs options.
|
443
415
|
# @option outputs stream [Symbol] Stream name.
|
444
416
|
# @option outputs pipes [IO] I/O stream to be redirected.
|
445
|
-
# @return [
|
446
|
-
def
|
447
|
-
|
448
|
-
|
417
|
+
# @return [Array] output_threads Array containing the output processing threads
|
418
|
+
def process_streams( output_streams = {}, stream_processors = nil )
|
419
|
+
@output = Hash.new
|
420
|
+
@std_output = Hash.new
|
449
421
|
|
450
|
-
|
422
|
+
output_threads = []
|
451
423
|
# for each io stream we create a thread wich read that
|
452
424
|
# stream and write it in a log file
|
453
|
-
|
454
|
-
|
455
|
-
pipes[0].close
|
456
|
-
|
457
|
-
@std_output[output_name] = ""
|
458
|
-
|
459
|
-
pipes[1].each_line do |line|
|
460
|
-
@std_output[output_name] << line
|
461
|
-
|
462
|
-
File.open("#{@log_path}#{@command}_#{@pid}.log", "a") do |log_file|
|
463
|
-
log_file.puts( "[#{Time.new.inspect} || [STD#{output_name.to_s.upcase} || [#{@pid}]] #{line}" )
|
464
|
-
end
|
465
|
-
# Match custom exceptions
|
466
|
-
# if we get a positive match, add it to the exception array
|
467
|
-
# in order to inform the user of what had happen
|
468
|
-
exceptions.each do | reg_expr, value |
|
469
|
-
@excep_array<< value.new( $1 ) if reg_expr =~ line
|
470
|
-
end
|
471
|
-
end
|
472
|
-
end
|
425
|
+
output_streams.collect do |output_name, pipes|
|
426
|
+
threaded_output_processor(output_name, pipes, stream_processors)
|
473
427
|
end
|
474
428
|
end
|
475
429
|
|
476
|
-
def create_log_directory
|
477
|
-
Dir.mkdir( @log_path ) unless Dir.exist?( @log_path )
|
478
|
-
end
|
479
|
-
|
480
|
-
def delete_log
|
481
|
-
File.delete( "#{@log_path}#{@command}_#{@pid}.log" ) if @delete_log == true
|
482
|
-
end
|
483
|
-
|
484
430
|
# Expand a parameter hash calling each key as method and value as param
|
485
431
|
# forcing method misssing to be called.
|
486
432
|
# @param [Hash] hash Parameters to be expand and included in command execution
|
@@ -488,10 +434,82 @@ class Runnable
|
|
488
434
|
def parse_hash( hash )
|
489
435
|
hash.each do |key, value|
|
490
436
|
# Add the param parsed to command_line_interface
|
491
|
-
@command_line_interface.add_param(
|
492
|
-
key.to_s,
|
493
|
-
value != nil ? value.to_s : nil
|
494
|
-
)
|
437
|
+
@command_line_interface.add_param( key.to_s, value != nil ? value.to_s : nil )
|
495
438
|
end
|
496
439
|
end
|
440
|
+
|
441
|
+
private
|
442
|
+
|
443
|
+
def save_log(output_name, line)
|
444
|
+
Dir.mkdir( @log_path ) unless Dir.exist?( @log_path )
|
445
|
+
|
446
|
+
File.open("#{@log_path}/#{self.command}_#{@pid}.log", "a") do |log_file|
|
447
|
+
log_file.puts( "[#{Time.new.inspect} || [STD#{output_name.to_s.upcase} || [#{@pid}]] #{line}" )
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
def compose_command
|
452
|
+
@command_line_interface ||= Object.const_get( command_style.to_s.capitalize.to_sym ).new
|
453
|
+
|
454
|
+
[ @command_input.to_s,
|
455
|
+
@options.to_s,
|
456
|
+
@command_line_interface.parse,
|
457
|
+
@command_output.to_s
|
458
|
+
].select do |value|
|
459
|
+
!value.to_s.strip.empty?
|
460
|
+
end.flatten.select{|x| !x.empty?}
|
461
|
+
end
|
462
|
+
|
463
|
+
def threaded_process(pid, output_threads)
|
464
|
+
Thread.new do
|
465
|
+
# Wait to get the pid process even if it has finished
|
466
|
+
Process.wait( pid, Process::WUNTRACED )
|
467
|
+
|
468
|
+
# Wait each I/O thread
|
469
|
+
output_threads.each { |thread| thread.join }
|
470
|
+
|
471
|
+
# Get the exit code from command
|
472
|
+
exit_status = $?.exitstatus
|
473
|
+
|
474
|
+
# This instance is finished and we remove it
|
475
|
+
self.class.processes.delete( pid )
|
476
|
+
@pid = nil
|
477
|
+
|
478
|
+
# In case of error add an Exception to the @excep_array
|
479
|
+
raise SystemCallError.new( exit_status ) if exit_status != 0
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
def threaded_output_processor(output_name, pipes, stream_processors)
|
484
|
+
exception_processors = stream_processors.is_a?(Hash) ? stream_processors[:exceptions] : {}
|
485
|
+
exception_processors.merge!(self.class.processors[:exceptions] || {})
|
486
|
+
|
487
|
+
output_processors = stream_processors.is_a?(Hash) ? stream_processors[:outputs] : {}
|
488
|
+
output_processors.merge!(self.class.processors[:output] || {})
|
489
|
+
|
490
|
+
Thread.new do
|
491
|
+
pipes[0].close
|
492
|
+
|
493
|
+
pipes[1].each_line do |line|
|
494
|
+
( output_name == :err ? self.std_err : self.std_out ) << line
|
495
|
+
|
496
|
+
save_log(output_name, line) if @log_path
|
497
|
+
|
498
|
+
# Match custom exceptions
|
499
|
+
# if we get a positive match, raise the exception
|
500
|
+
exception_processors.each do | reg_expr, value |
|
501
|
+
raise value.new( line ) if reg_expr =~ line
|
502
|
+
end
|
503
|
+
|
504
|
+
# Match custom outputs
|
505
|
+
# if we get a positive match, add it to the outputs array
|
506
|
+
output_processors.each do | reg_expr, value |
|
507
|
+
@output[value] ||= Array.new
|
508
|
+
@output[value] << $1 if reg_expr =~ line
|
509
|
+
end
|
510
|
+
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
497
515
|
end
|
data/runnable.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{runnable}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Rafael García", "Luis Ciudad", "Pedro Navajas", "Javier Aranda"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2012-01-17}
|
13
13
|
s.description = %q{Convert a executable command in a Ruby-like class you are able to start, define params and send signals (like kill, or stop)}
|
14
14
|
s.email = ["rgarcia@nosolosoftware.biz", "lciudad@nosolosoftware.biz", "pnavajas@nosolosoftware.biz", "jaranda@nosolosoftware.biz"]
|
15
15
|
s.extra_rdoc_files = [
|
@@ -28,10 +28,11 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.homepage = %q{http://github.com/nosolosoftware/runnable}
|
29
29
|
s.licenses = ["GPL-3"]
|
30
30
|
s.require_paths = ["lib"]
|
31
|
-
s.rubygems_version = %q{1.
|
31
|
+
s.rubygems_version = %q{1.3.7}
|
32
32
|
s.summary = %q{A Ruby gem for execute and control system commands}
|
33
33
|
|
34
34
|
if s.respond_to? :specification_version then
|
35
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
35
36
|
s.specification_version = 3
|
36
37
|
|
37
38
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
metadata
CHANGED
@@ -1,86 +1,121 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: runnable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
|
4
|
+
version: 0.3.0
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
prerelease: false
|
10
|
+
segments_generated: true
|
6
11
|
platform: ruby
|
7
12
|
authors:
|
8
13
|
- Rafael García
|
9
14
|
- Luis Ciudad
|
10
15
|
- Pedro Navajas
|
11
16
|
- Javier Aranda
|
12
|
-
autorequire:
|
17
|
+
autorequire: !!null
|
13
18
|
bindir: bin
|
14
19
|
cert_chain: []
|
15
|
-
date:
|
16
|
-
default_executable:
|
20
|
+
date: 2012-01-17 00:00:00.000000000 +01:00
|
21
|
+
default_executable: !!null
|
17
22
|
dependencies:
|
18
23
|
- !ruby/object:Gem::Dependency
|
19
24
|
name: rake
|
20
|
-
requirement: &
|
25
|
+
requirement: &32400380 !ruby/object:Gem::Requirement
|
21
26
|
none: false
|
22
27
|
requirements:
|
23
28
|
- - ! '>='
|
24
29
|
- !ruby/object:Gem::Version
|
25
30
|
version: 0.8.7
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
- 8
|
34
|
+
- 7
|
35
|
+
segments_generated: true
|
26
36
|
type: :development
|
27
37
|
prerelease: false
|
28
|
-
version_requirements: *
|
38
|
+
version_requirements: *32400380
|
29
39
|
- !ruby/object:Gem::Dependency
|
30
40
|
name: yard
|
31
|
-
requirement: &
|
41
|
+
requirement: &32398960 !ruby/object:Gem::Requirement
|
32
42
|
none: false
|
33
43
|
requirements:
|
34
44
|
- - ! '>='
|
35
45
|
- !ruby/object:Gem::Version
|
36
46
|
version: 0.6.8
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
- 6
|
50
|
+
- 8
|
51
|
+
segments_generated: true
|
37
52
|
type: :development
|
38
53
|
prerelease: false
|
39
|
-
version_requirements: *
|
54
|
+
version_requirements: *32398960
|
40
55
|
- !ruby/object:Gem::Dependency
|
41
56
|
name: rspec
|
42
|
-
requirement: &
|
57
|
+
requirement: &32397820 !ruby/object:Gem::Requirement
|
43
58
|
none: false
|
44
59
|
requirements:
|
45
60
|
- - ! '>='
|
46
61
|
- !ruby/object:Gem::Version
|
47
62
|
version: 2.5.0
|
63
|
+
segments:
|
64
|
+
- 2
|
65
|
+
- 5
|
66
|
+
- 0
|
67
|
+
segments_generated: true
|
48
68
|
type: :development
|
49
69
|
prerelease: false
|
50
|
-
version_requirements: *
|
70
|
+
version_requirements: *32397820
|
51
71
|
- !ruby/object:Gem::Dependency
|
52
72
|
name: cucumber
|
53
|
-
requirement: &
|
73
|
+
requirement: &32396420 !ruby/object:Gem::Requirement
|
54
74
|
none: false
|
55
75
|
requirements:
|
56
76
|
- - ! '>='
|
57
77
|
- !ruby/object:Gem::Version
|
58
78
|
version: 0.10.2
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
- 10
|
82
|
+
- 2
|
83
|
+
segments_generated: true
|
59
84
|
type: :development
|
60
85
|
prerelease: false
|
61
|
-
version_requirements: *
|
86
|
+
version_requirements: *32396420
|
62
87
|
- !ruby/object:Gem::Dependency
|
63
88
|
name: jeweler
|
64
|
-
requirement: &
|
89
|
+
requirement: &32388060 !ruby/object:Gem::Requirement
|
65
90
|
none: false
|
66
91
|
requirements:
|
67
92
|
- - ! '>='
|
68
93
|
- !ruby/object:Gem::Version
|
69
94
|
version: 1.6.0
|
95
|
+
segments:
|
96
|
+
- 1
|
97
|
+
- 6
|
98
|
+
- 0
|
99
|
+
segments_generated: true
|
70
100
|
type: :development
|
71
101
|
prerelease: false
|
72
|
-
version_requirements: *
|
102
|
+
version_requirements: *32388060
|
73
103
|
- !ruby/object:Gem::Dependency
|
74
104
|
name: bluecloth
|
75
|
-
requirement: &
|
105
|
+
requirement: &32386220 !ruby/object:Gem::Requirement
|
76
106
|
none: false
|
77
107
|
requirements:
|
78
108
|
- - ! '>='
|
79
109
|
- !ruby/object:Gem::Version
|
80
110
|
version: 2.1.0
|
111
|
+
segments:
|
112
|
+
- 2
|
113
|
+
- 1
|
114
|
+
- 0
|
115
|
+
segments_generated: true
|
81
116
|
type: :development
|
82
117
|
prerelease: false
|
83
|
-
version_requirements: *
|
118
|
+
version_requirements: *32386220
|
84
119
|
description: Convert a executable command in a Ruby-like class you are able to start,
|
85
120
|
define params and send signals (like kill, or stop)
|
86
121
|
email:
|
@@ -105,7 +140,7 @@ has_rdoc: true
|
|
105
140
|
homepage: http://github.com/nosolosoftware/runnable
|
106
141
|
licenses:
|
107
142
|
- GPL-3
|
108
|
-
post_install_message:
|
143
|
+
post_install_message: !!null
|
109
144
|
rdoc_options: []
|
110
145
|
require_paths:
|
111
146
|
- lib
|
@@ -117,17 +152,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
117
152
|
version: '0'
|
118
153
|
segments:
|
119
154
|
- 0
|
120
|
-
|
155
|
+
segments_generated: true
|
156
|
+
hash: -3646317310813273179
|
121
157
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
158
|
none: false
|
123
159
|
requirements:
|
124
160
|
- - ! '>='
|
125
161
|
- !ruby/object:Gem::Version
|
126
162
|
version: '0'
|
163
|
+
segments:
|
164
|
+
- 0
|
165
|
+
segments_generated: true
|
127
166
|
requirements: []
|
128
|
-
rubyforge_project:
|
129
|
-
rubygems_version: 1.
|
130
|
-
signing_key:
|
167
|
+
rubyforge_project: !!null
|
168
|
+
rubygems_version: 1.3.7
|
169
|
+
signing_key: !!null
|
131
170
|
specification_version: 3
|
132
171
|
summary: A Ruby gem for execute and control system commands
|
133
172
|
test_files: []
|