rprogram 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.md +33 -11
- data/README.md +7 -3
- data/lib/rprogram/program.rb +214 -24
- data/lib/rprogram/sudo.rb +14 -0
- data/lib/rprogram/sudo_task.rb +68 -0
- data/lib/rprogram/system.rb +316 -0
- data/lib/rprogram/task.rb +144 -16
- data/lib/rprogram/version.rb +1 -1
- data/rprogram.gemspec +1 -1
- data/spec/classes/aliased_program.rb +1 -5
- data/spec/classes/named_program.rb +1 -5
- data/spec/program_spec.rb +84 -1
- data/spec/scripts/echo.rb +3 -0
- data/spec/scripts/fail.rb +3 -0
- data/spec/scripts/print.rb +3 -0
- data/spec/scripts/success.rb +3 -0
- data/spec/system_spec.rb +80 -0
- data/spec/task_spec.rb +0 -15
- metadata +15 -20
- data/lib/rprogram/compat.rb +0 -124
- data/lib/rprogram/extensions.rb +0 -1
- data/lib/rprogram/nameable.rb +0 -2
- data/lib/rprogram/nameable/class_methods.rb +0 -84
- data/lib/rprogram/nameable/nameable.rb +0 -34
- data/lib/rprogram/options.rb +0 -2
- data/lib/rprogram/options/class_methods.rb +0 -112
- data/lib/rprogram/options/options.rb +0 -39
- data/spec/compat_spec.rb +0 -21
- data/spec/nameable_spec.rb +0 -83
@@ -0,0 +1,316 @@
|
|
1
|
+
require 'rprogram/exceptions/program_not_found'
|
2
|
+
require 'rprogram/rprogram'
|
3
|
+
|
4
|
+
require 'env/variables'
|
5
|
+
|
6
|
+
module RProgram
|
7
|
+
#
|
8
|
+
# @since 0.3.0
|
9
|
+
#
|
10
|
+
module System
|
11
|
+
extend Env::Variables
|
12
|
+
|
13
|
+
@arch, @platform = RUBY_PLATFORM.split('-',2)
|
14
|
+
@platform ||= @arch
|
15
|
+
|
16
|
+
#
|
17
|
+
# Determines the native architecture.
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
# The native architecture.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# System.arch
|
24
|
+
# # => "x86-64"
|
25
|
+
#
|
26
|
+
# @since 0.3.0
|
27
|
+
#
|
28
|
+
def System.arch
|
29
|
+
@arch
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Determines the native platform.
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
# The native platform.
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# System.platform
|
40
|
+
# # => "linux"
|
41
|
+
#
|
42
|
+
def System.platform
|
43
|
+
@platform
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Determines if the platform is Windows.
|
48
|
+
#
|
49
|
+
# @return [Boolean]
|
50
|
+
# Specifies whether the platform is Windows.
|
51
|
+
#
|
52
|
+
# @since 0.3.0
|
53
|
+
#
|
54
|
+
def System.windows?
|
55
|
+
if @platform
|
56
|
+
@platform.include?('mingw') || @platform.include?('mswin')
|
57
|
+
else
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Determines if the current Ruby VM is from the 1.8.x family.
|
64
|
+
#
|
65
|
+
# @return [Boolean]
|
66
|
+
# Specifies if the current Ruby VM is from the 1.8.x family.
|
67
|
+
#
|
68
|
+
# @since 0.3.0
|
69
|
+
#
|
70
|
+
def System.ruby_1_8?
|
71
|
+
RUBY_VERSION[0,4] == '1.8.'
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Determines if the current Ruby VM is JRuby.
|
76
|
+
#
|
77
|
+
# @return [Boolean]
|
78
|
+
# Specifies whether the Ruby VM is JRuby.
|
79
|
+
#
|
80
|
+
# @since 0.3.0
|
81
|
+
#
|
82
|
+
def System.jruby?
|
83
|
+
Object.const_defined?(:RUBY_ENGINE) && \
|
84
|
+
Object.const_get(:RUBY_ENGINE) == 'jruby'
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Finds the full-path of the program with the matching name.
|
89
|
+
#
|
90
|
+
# @param [String] name
|
91
|
+
# The name of the program to find.
|
92
|
+
#
|
93
|
+
# @return [Pathname, nil]
|
94
|
+
# The full-path of the desired program.
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# System.find_program('as')
|
98
|
+
# #=> #<Pathname:/usr/bin/as>
|
99
|
+
#
|
100
|
+
def System.find_program(name)
|
101
|
+
# add the `.exe` suffix to the name, if running on Windows
|
102
|
+
if windows?
|
103
|
+
name = "#{name}.exe"
|
104
|
+
end
|
105
|
+
|
106
|
+
paths.each do |dir|
|
107
|
+
full_path = dir.join(name).expand_path
|
108
|
+
|
109
|
+
return full_path if full_path.file?
|
110
|
+
end
|
111
|
+
|
112
|
+
return nil
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Finds the program matching one of the matching names.
|
117
|
+
#
|
118
|
+
# @param [Array] names
|
119
|
+
# The names of the program to use while searching for the program.
|
120
|
+
#
|
121
|
+
# @return [Pathname, nil]
|
122
|
+
# The first full-path for the program.
|
123
|
+
#
|
124
|
+
# @example
|
125
|
+
# System.find_program_by_names("gas","as")
|
126
|
+
# # => #<Pathname:/usr/bin/as>
|
127
|
+
#
|
128
|
+
def System.find_program_by_names(*names)
|
129
|
+
names.each do |name|
|
130
|
+
if (path = find_program(name))
|
131
|
+
return path
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
return nil
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Runs a program.
|
140
|
+
#
|
141
|
+
# @overload run(path,*arguments)
|
142
|
+
# Run the program with the given arguments.
|
143
|
+
#
|
144
|
+
# @param [Pathname, String] path
|
145
|
+
# The path of the program to run.
|
146
|
+
#
|
147
|
+
# @param [Array] arguments
|
148
|
+
# Additional arguments to run the program with.
|
149
|
+
#
|
150
|
+
# @overload run(path,*arguments,options)
|
151
|
+
# Run the program with the given arguments and options.
|
152
|
+
#
|
153
|
+
# @param [Pathname, String] path
|
154
|
+
# The path of the program to run.
|
155
|
+
#
|
156
|
+
# @param [Array] arguments
|
157
|
+
# Additional arguments to run the program with.
|
158
|
+
#
|
159
|
+
# @param [Hash] options
|
160
|
+
# Additional options to execute the program with.
|
161
|
+
#
|
162
|
+
# @option options [Hash{String => String}] :env
|
163
|
+
# Environment variables to execute the program with.
|
164
|
+
#
|
165
|
+
# @option options [String] :popen
|
166
|
+
# Specifies to run the program using `IO.popen` with the given
|
167
|
+
# IO mode.
|
168
|
+
#
|
169
|
+
# @return [Boolean]
|
170
|
+
# Specifies whether the program exited successfully.
|
171
|
+
#
|
172
|
+
# @raise [RuntimeError]
|
173
|
+
# Passing `:popen`, `:env` or exec options is not supported
|
174
|
+
# before Ruby 1.9.1.
|
175
|
+
#
|
176
|
+
# @see http://rubydoc.info/stdlib/core/1.9.2/Kernel#spawn-instance_method
|
177
|
+
# For acceptable options.
|
178
|
+
#
|
179
|
+
def System.run(*arguments)
|
180
|
+
# extra tailing options and ENV variables from arguments
|
181
|
+
if arguments.last.kind_of?(Hash)
|
182
|
+
options = arguments.pop
|
183
|
+
env = (options.delete(:env) || {})
|
184
|
+
popen = options.delete(:popen)
|
185
|
+
else
|
186
|
+
options = {}
|
187
|
+
env = {}
|
188
|
+
end
|
189
|
+
|
190
|
+
# all arguments must be Strings
|
191
|
+
arguments = arguments.map { |arg| arg.to_s }
|
192
|
+
|
193
|
+
# print debugging information
|
194
|
+
if RProgram.debug
|
195
|
+
command = ''
|
196
|
+
|
197
|
+
env.each do |name,value|
|
198
|
+
command << "#{name}=#{value} "
|
199
|
+
end
|
200
|
+
|
201
|
+
command << arguments.join(' ')
|
202
|
+
command << " #{options.inspect}" unless options.empty?
|
203
|
+
|
204
|
+
STDERR.puts ">>> #{command}"
|
205
|
+
end
|
206
|
+
|
207
|
+
# passing ENV variables or exec options is not supported before 1.9.1
|
208
|
+
if (!options.empty? && ruby_1_8?)
|
209
|
+
raise("cannot pass exec options to Kernel.system in #{RUBY_VERSION}")
|
210
|
+
end
|
211
|
+
|
212
|
+
if popen
|
213
|
+
# IO.popen does not accept multiple arguments on Ruby 1.8.x.
|
214
|
+
if ruby_1_8?
|
215
|
+
raise("cannot use :popen on #{RUBY_VERSION}, please use 1.9.x")
|
216
|
+
end
|
217
|
+
|
218
|
+
# :popen can only be used on Unix, or on Windows with JRuby
|
219
|
+
if (windows? && !jruby?)
|
220
|
+
raise("cannot use :popen on Windows, unless under JRuby")
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# re-add ENV variables and exec options
|
225
|
+
arguments.unshift(env) unless env.empty?
|
226
|
+
arguments.push(options) unless options.empty?
|
227
|
+
|
228
|
+
if popen
|
229
|
+
IO.popen(arguments,popen)
|
230
|
+
else
|
231
|
+
Kernel.system(*arguments)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
#
|
236
|
+
# The path to the `sudo` program.
|
237
|
+
#
|
238
|
+
# @return [Pathname, nil]
|
239
|
+
# The path to the `sudo` program.
|
240
|
+
#
|
241
|
+
# @since 0.3.0
|
242
|
+
#
|
243
|
+
def System.sudo_path
|
244
|
+
@sudo ||= find_program('sudo')
|
245
|
+
end
|
246
|
+
|
247
|
+
#
|
248
|
+
# Sets the path to the `sudo` program.
|
249
|
+
#
|
250
|
+
# @param [String, Pathname] path
|
251
|
+
# The new path to use.
|
252
|
+
#
|
253
|
+
# @return [Pathanme]
|
254
|
+
# The new path to the `sudo` program.
|
255
|
+
#
|
256
|
+
# @since 0.3.0
|
257
|
+
#
|
258
|
+
def System.sudo_path=(path)
|
259
|
+
@sudo = Pathname.new(path)
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Determines whether `sudo` is available on the system.
|
264
|
+
#
|
265
|
+
# @return [Boolean]
|
266
|
+
# Specifies whether the `sudo` program is installed on the system.
|
267
|
+
#
|
268
|
+
# @since 0.3.0
|
269
|
+
#
|
270
|
+
def System.sudo?
|
271
|
+
!sudo_path.nil?
|
272
|
+
end
|
273
|
+
|
274
|
+
#
|
275
|
+
# Runs a program under sudo.
|
276
|
+
#
|
277
|
+
# @overload run(path,*arguments)
|
278
|
+
# Run the program with the given arguments.
|
279
|
+
#
|
280
|
+
# @param [Pathname, String] path
|
281
|
+
# The path of the program to run.
|
282
|
+
#
|
283
|
+
# @param [Array] arguments
|
284
|
+
# Additional arguments to run the program with.
|
285
|
+
#
|
286
|
+
# @overload run(path,*arguments,options)
|
287
|
+
# Run the program with the given arguments and options.
|
288
|
+
#
|
289
|
+
# @param [Pathname, String] path
|
290
|
+
# The path of the program to run.
|
291
|
+
#
|
292
|
+
# @param [Array] arguments
|
293
|
+
# Additional arguments to run the program with.
|
294
|
+
#
|
295
|
+
# @param [Hash] options
|
296
|
+
# Additional options to execute the program with.
|
297
|
+
#
|
298
|
+
# @return [Boolean]
|
299
|
+
# Specifies whether the program exited successfully.
|
300
|
+
#
|
301
|
+
# @raise [ProgramNotFound]
|
302
|
+
# Indicates that the `sudo` program could not be located.
|
303
|
+
#
|
304
|
+
# @since 0.1.8
|
305
|
+
#
|
306
|
+
# @see run
|
307
|
+
#
|
308
|
+
def System.sudo(*arguments)
|
309
|
+
unless sudo?
|
310
|
+
raise(ProgramNotFound,'could not find the "sudo" program')
|
311
|
+
end
|
312
|
+
|
313
|
+
return run(sudo_path,*arguments)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
data/lib/rprogram/task.rb
CHANGED
@@ -1,14 +1,10 @@
|
|
1
|
-
require 'rprogram/
|
1
|
+
require 'rprogram/option'
|
2
2
|
require 'rprogram/option_list'
|
3
|
+
require 'rprogram/non_option'
|
3
4
|
|
4
5
|
module RProgram
|
5
6
|
class Task
|
6
7
|
|
7
|
-
include Options
|
8
|
-
|
9
|
-
# Specifies whether the task will be run under sudo
|
10
|
-
attr_accessor :sudo
|
11
|
-
|
12
8
|
#
|
13
9
|
# Creates a new Task object.
|
14
10
|
#
|
@@ -30,13 +26,119 @@ module RProgram
|
|
30
26
|
# end
|
31
27
|
#
|
32
28
|
def initialize(options={},&block)
|
33
|
-
@sudo = (options.delete(:sudo) || false)
|
34
29
|
@subtasks = {}
|
35
30
|
@options = options
|
36
31
|
|
37
32
|
block.call(self) if block
|
38
33
|
end
|
39
34
|
|
35
|
+
#
|
36
|
+
# @return [Hash]
|
37
|
+
# All defined non-options of the class.
|
38
|
+
#
|
39
|
+
def self.non_options
|
40
|
+
@non_options ||= {}
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Searches for the non-option with the matching name in the class
|
45
|
+
# and it's ancestors.
|
46
|
+
#
|
47
|
+
# @param [Symbol, String] name
|
48
|
+
# The name to search for.
|
49
|
+
#
|
50
|
+
# @return [true, false]
|
51
|
+
# Specifies whether the non-option with the matching name was
|
52
|
+
# defined.
|
53
|
+
#
|
54
|
+
def self.has_non_option?(name)
|
55
|
+
name = name.to_sym
|
56
|
+
|
57
|
+
ancestors.each do |base|
|
58
|
+
if base < RProgram::Task
|
59
|
+
return true if base.non_options.include?(name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Searches for the non-option with the matching name in the class
|
68
|
+
# and it's ancestors.
|
69
|
+
#
|
70
|
+
# @param [Symbol, String] name
|
71
|
+
# The name to search for.
|
72
|
+
#
|
73
|
+
# @return [NonOption]
|
74
|
+
# The non-option with the matching name.
|
75
|
+
#
|
76
|
+
def self.get_non_option(name)
|
77
|
+
name = name.to_sym
|
78
|
+
|
79
|
+
ancestors.each do |base|
|
80
|
+
if base < RProgram::Task
|
81
|
+
if base.non_options.has_key?(name)
|
82
|
+
return base.non_options[name]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# @return [Hash]
|
92
|
+
# All defined options for the class.
|
93
|
+
#
|
94
|
+
def self.options
|
95
|
+
@options ||= {}
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Searches for the option with the matching name in the class and
|
100
|
+
# it's ancestors.
|
101
|
+
#
|
102
|
+
# @param [Symbol, String] name
|
103
|
+
# The name to search for.
|
104
|
+
#
|
105
|
+
# @return [true, false]
|
106
|
+
# Specifies whether the option with the matching name was defined.
|
107
|
+
#
|
108
|
+
def self.has_option?(name)
|
109
|
+
name = name.to_sym
|
110
|
+
|
111
|
+
ancestors.each do |base|
|
112
|
+
if base < RProgram::Task
|
113
|
+
return true if base.options.has_key?(name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
return false
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Searches for the option with the matching name in the class and
|
122
|
+
# it's ancestors.
|
123
|
+
#
|
124
|
+
# @param [Symbol, String] name
|
125
|
+
# The name to search for.
|
126
|
+
#
|
127
|
+
# @return [Option]
|
128
|
+
# The option with the matching name.
|
129
|
+
#
|
130
|
+
def self.get_option(name)
|
131
|
+
name = name.to_sym
|
132
|
+
|
133
|
+
ancestors.each do |base|
|
134
|
+
if base < RProgram::Task
|
135
|
+
return base.options[name] if base.options.has_key?(name)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
return nil
|
140
|
+
end
|
141
|
+
|
40
142
|
#
|
41
143
|
# Creates a new Task object, then formats command-line arguments
|
42
144
|
# using the Task object.
|
@@ -69,15 +171,31 @@ module RProgram
|
|
69
171
|
end
|
70
172
|
|
71
173
|
#
|
72
|
-
#
|
174
|
+
# @see has_non_option?
|
175
|
+
#
|
176
|
+
def has_non_option?(name)
|
177
|
+
self.class.has_non_option?(name)
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# @see get_non_option
|
182
|
+
#
|
183
|
+
def get_non_option(name)
|
184
|
+
self.class.get_non_option(name)
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# @see has_option?
|
73
189
|
#
|
74
|
-
|
75
|
-
|
190
|
+
def has_option?(name)
|
191
|
+
self.class.has_option?(name)
|
192
|
+
end
|
193
|
+
|
76
194
|
#
|
77
|
-
# @
|
195
|
+
# @see get_option
|
78
196
|
#
|
79
|
-
def
|
80
|
-
|
197
|
+
def get_option(name)
|
198
|
+
self.class.get_option(name)
|
81
199
|
end
|
82
200
|
|
83
201
|
#
|
@@ -166,9 +284,17 @@ module RProgram
|
|
166
284
|
# options and tailing non-options of the task and it's sub-tasks.
|
167
285
|
#
|
168
286
|
def arguments
|
169
|
-
|
287
|
+
tailing_args = tailing_non_options
|
288
|
+
|
289
|
+
if tailing_args.any? { |arg| arg[0,1] == '-' }
|
290
|
+
tailing_args.unshift('--')
|
291
|
+
end
|
292
|
+
|
293
|
+
return leading_non_options + options + tailing_args
|
170
294
|
end
|
171
295
|
|
296
|
+
alias to_a arguments
|
297
|
+
|
172
298
|
protected
|
173
299
|
|
174
300
|
#
|
@@ -185,20 +311,22 @@ module RProgram
|
|
185
311
|
#
|
186
312
|
def self.subtask(name,task)
|
187
313
|
name = name.to_s
|
314
|
+
file = __FILE__
|
315
|
+
line = __LINE__ + 3
|
188
316
|
|
189
317
|
class_eval %{
|
190
318
|
def #{name}(options={},&block)
|
191
319
|
if @subtasks[#{name.dump}]
|
192
320
|
@subtasks[#{name.dump}].options.merge!(options)
|
193
321
|
|
194
|
-
|
322
|
+
yield(@subtasks[#{name.dump}]) if block_given?
|
195
323
|
else
|
196
324
|
@subtasks[#{name.dump}] = #{task}.new(options,&block)
|
197
325
|
end
|
198
326
|
|
199
327
|
return @subtasks[#{name.dump}]
|
200
328
|
end
|
201
|
-
}
|
329
|
+
}, file, line
|
202
330
|
end
|
203
331
|
|
204
332
|
#
|