runnable 0.2.4 → 0.3.0
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/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: []
|