rant 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. data/NEWS +21 -0
  2. data/README +8 -0
  3. data/Rantfile +30 -16
  4. data/TODO +3 -4
  5. data/devel-notes +16 -0
  6. data/doc/csharp.rdoc +2 -3
  7. data/doc/examples/myprog/README +2 -0
  8. data/doc/examples/myprog/Rantfile +13 -0
  9. data/doc/examples/myprog/src/Rantfile +15 -0
  10. data/doc/examples/myprog/src/lib.c +6 -0
  11. data/doc/examples/myprog/src/lib.h +4 -0
  12. data/doc/examples/myprog/src/main.c +7 -0
  13. data/doc/rantfile.rdoc +108 -3
  14. data/lib/rant/cs_compiler.rb +6 -4
  15. data/lib/rant/import.rb +13 -0
  16. data/lib/rant/import/rubypackage.rb +43 -20
  17. data/lib/rant/plugin/configure.rb +21 -7
  18. data/lib/rant/plugin/csharp.rb +8 -9
  19. data/lib/rant/rantenv.rb +3 -0
  20. data/lib/rant/rantfile.rb +66 -33
  21. data/lib/rant/rantlib.rb +133 -27
  22. data/lib/rant/rantsys.rb +197 -42
  23. data/rantmethods.rb +46 -0
  24. data/setup.rb +18 -4
  25. data/test/plugin/configure/Rantfile +0 -6
  26. data/test/plugin/configure/test_configure.rb +2 -2
  27. data/test/plugin/csharp/test_csharp.rb +7 -2
  28. data/test/plugin/rantfile +45 -0
  29. data/test/plugin/test_conf_csharp.rb +53 -0
  30. data/test/project1/Rantfile +4 -0
  31. data/test/project1/test_project.rb +33 -8
  32. data/test/project2/buildfile +3 -3
  33. data/test/project2/sub1/Rantfile +3 -3
  34. data/test/project2/test_project.rb +6 -4
  35. data/test/project_rb1/test_project_rb1.rb +2 -2
  36. data/test/standalone.rf +10 -0
  37. data/test/subdirs/Rantfile +26 -0
  38. data/test/subdirs/sub1/Rantfile +15 -0
  39. data/test/subdirs/sub2/rantfile.rb +14 -0
  40. data/test/subdirs/sub2/sub/rantfile +15 -0
  41. data/test/subdirs/test_subdirs.rb +96 -0
  42. data/test/test_env.rb +3 -3
  43. data/test/test_filelist.rb +143 -0
  44. data/test/test_lighttask.rb +21 -0
  45. data/test/test_metatask.rb +1 -1
  46. data/test/test_rant_interface.rb +5 -5
  47. data/test/test_sys.rb +7 -1
  48. data/test/test_task.rb +25 -0
  49. data/test/toplevel.rf +0 -1
  50. data/test/tutil.rb +26 -1
  51. metadata +39 -14
@@ -14,7 +14,7 @@ require 'rant/rantfile'
14
14
  require 'rant/rantsys'
15
15
 
16
16
  module Rant
17
- VERSION = '0.3.0'
17
+ VERSION = '0.3.2'
18
18
 
19
19
  # Those are the filenames for rantfiles.
20
20
  # Case matters!
@@ -83,7 +83,7 @@ class Array
83
83
  end
84
84
 
85
85
  module Rant::Lib
86
-
86
+
87
87
  # Parses one string (elem) as it occurs in the array
88
88
  # which is returned by caller.
89
89
  # E.g.:
@@ -317,9 +317,15 @@ class Rant::RantApp
317
317
  # "private" options intended for debugging, testing and
318
318
  # internal use. A private option is distuingished from others
319
319
  # by having +nil+ as description!
320
+
320
321
  [ "--stop-after-load", GetoptLong::NO_ARGUMENT, nil ],
322
+ # Print caller to $stderr on abort.
323
+ [ "--trace-abort", GetoptLong::NO_ARGUMENT, nil ],
321
324
  ]
322
325
 
326
+ ROOT_DIR_ID = "#"
327
+ ESCAPE_ID = "\\"
328
+
323
329
  # Arguments, usually those given on commandline.
324
330
  attr_reader :args
325
331
  # A list of all Rantfiles used by this app.
@@ -346,6 +352,8 @@ class Rant::RantApp
346
352
  # A list with of all imports (code loaded with +import+).
347
353
  attr_reader :imports
348
354
 
355
+ attr_reader :current_subdir
356
+
349
357
  def initialize *args
350
358
  @args = args.flatten
351
359
  # Rantfiles will be loaded in the context of this object.
@@ -371,14 +379,10 @@ class Rant::RantApp
371
379
  @task_desc = nil
372
380
 
373
381
  @orig_pwd = nil
382
+ @current_subdir = ""
374
383
 
375
384
  end
376
385
 
377
- # Just ensure that Rant.rantapp holds an RantApp after loading
378
- # this file. The code in initialize will register the new app with
379
- # Rant.rantapp= if necessary.
380
- self.new
381
-
382
386
  def [](opt)
383
387
  @opts[opt]
384
388
  end
@@ -393,7 +397,8 @@ class Rant::RantApp
393
397
  end
394
398
 
395
399
  def rootdir
396
- @opts[:directory].dup
400
+ od = @opts[:directory]
401
+ od ? od.dup : ""
397
402
  end
398
403
 
399
404
  def rootdir=(newdir)
@@ -402,9 +407,71 @@ class Rant::RantApp
402
407
  "be changed after calling `run'"
403
408
  end
404
409
  @opts[:directory] = newdir.dup
405
- rootdir # return a dup of the new rootdir
406
410
  end
407
411
 
412
+ ### experimental support for subdirectories ######################
413
+ def expand_project_path(path)
414
+ expand_path(@current_subdir, path)
415
+ =begin
416
+ case path
417
+ when nil: @current_subdir.dup
418
+ when "": @current_subdir.dup
419
+ when /^#/: path.sub(/^#/, '')
420
+ when /^\\#/: path.sub(/^\\/, '')
421
+ else
422
+ #puts "epp: current_subdir: #@current_subdir"
423
+ if @current_subdir.empty?
424
+ # we are in project's root directory
425
+ path
426
+ else
427
+ File.join(@current_subdir, path)
428
+ end
429
+ end
430
+ =end
431
+ end
432
+ def expand_path(subdir, path)
433
+ case path
434
+ when nil: subdir.dup
435
+ when "": subdir.dup
436
+ when /^#/: path.sub(/^#/, '')
437
+ when /^\\#/: path.sub(/^\\/, '')
438
+ else
439
+ #puts "epp: current_subdir: #@current_subdir"
440
+ if subdir.empty?
441
+ # we are in project's root directory
442
+ path
443
+ else
444
+ File.join(subdir, path)
445
+ end
446
+ end
447
+ end
448
+ # Returns an absolute path. If path resolves to a directory this
449
+ # method ensures that the returned absolute path doesn't end in a
450
+ # slash.
451
+ def project_to_fs_path(path)
452
+ base = rootdir.empty? ? Dir.pwd : rootdir
453
+ sub = expand_project_path(path)
454
+ sub.empty? ? base : File.join(base, sub)
455
+ end
456
+ def goto(dir)
457
+ # TODO: optimize
458
+ p_dir = expand_project_path(dir)
459
+ abs_path = project_to_fs_path(dir)
460
+ @current_subdir = p_dir
461
+ unless Dir.pwd == abs_path
462
+ #puts "pwd: #{Dir.pwd}; abs_path: #{abs_path}"
463
+ #puts " current subdir: #@current_subdir"
464
+ Dir.chdir abs_path
465
+ msg 1, "in #{abs_path}"
466
+ #STDERR.puts "rant: in #{p_dir}"
467
+ end
468
+ end
469
+ def goto_project_dir(dir)
470
+ # TODO: optimize
471
+ goto "##{dir}"
472
+ end
473
+ ##################################################################
474
+
408
475
  def ran?
409
476
  @ran
410
477
  end
@@ -423,10 +490,12 @@ class Rant::RantApp
423
490
  # Set pwd.
424
491
  opts_dir = @opts[:directory]
425
492
  if opts_dir
493
+ opts_dir = File.expand_path(opts_dir)
426
494
  unless test(?d, opts_dir)
427
495
  abort("No such directory - #{opts_dir}")
428
496
  end
429
- opts_dir != @orig_pwd && Dir.chdir(opts_dir)
497
+ Dir.chdir(opts_dir) if opts_dir != @orig_pwd
498
+ @opts[:directory] = opts_dir
430
499
  else
431
500
  @opts[:directory] = @orig_pwd
432
501
  end
@@ -594,8 +663,14 @@ class Rant::RantApp
594
663
  # and a new file task.
595
664
  def enhance targ, &block
596
665
  prepare_task(targ, block) { |name,pre,blk|
597
- t = select_task { |t| t.name == name }
666
+ t = @tasks[name]
667
+ if Rant::MetaTask === t
668
+ t = t.last
669
+ end
598
670
  if t
671
+ unless t.respond_to? :enhance
672
+ abort("Can't enhance task `#{name}'")
673
+ end
599
674
  t.enhance(pre, &blk)
600
675
  return t
601
676
  end
@@ -622,9 +697,9 @@ class Rant::RantApp
622
697
  ln = cinf[:ln] || 0
623
698
  file = cinf[:file]
624
699
  args.each { |arg|
625
- if arg.is_a? Symbol
626
- arg = arg.to_s
627
- elsif arg.respond_to? :to_str
700
+ #if arg.is_a? Symbol
701
+ # arg = arg.to_s
702
+ if arg.respond_to? :to_str
628
703
  arg = arg.to_str
629
704
  end
630
705
  unless arg.is_a? String
@@ -632,13 +707,25 @@ class Rant::RantApp
632
707
  "in `subdirs' command: arguments must be strings")
633
708
  end
634
709
  loaded = false
635
- rantfiles_in_dir(arg).each { |f|
636
- loaded = true
637
- rf, is_new = rantfile_for_path(f)
638
- if is_new
639
- load_file rf
640
- end
641
- }
710
+ prev_subdir = @current_subdir
711
+ begin
712
+ #puts "* subdir *",
713
+ # " rootdir: #{rootdir}",
714
+ # " current subdir: #@current_subdir",
715
+ # " pwd: #{Dir.pwd}",
716
+ # " arg: #{arg}"
717
+ goto arg
718
+ rantfiles_in_dir.each { |f|
719
+ loaded = true
720
+ rf, is_new = rantfile_for_path(f)
721
+ if is_new
722
+ load_file rf
723
+ end
724
+ }
725
+ ensure
726
+ #puts " going back to project dir: #{prev_subdir}"
727
+ goto_project_dir prev_subdir
728
+ end
642
729
  unless loaded || quiet?
643
730
  warn_msg(pos_text(file, ln) + "; in `subdirs' command:",
644
731
  "No Rantfile in subdir `#{arg}'.")
@@ -649,7 +736,7 @@ class Rant::RantApp
649
736
  "in `subdirs' command: " + e.message)
650
737
  end
651
738
 
652
- def sys *args
739
+ def sys(*args)
653
740
  if args.empty?
654
741
  @sys
655
742
  else
@@ -665,9 +752,10 @@ class Rant::RantApp
665
752
  td
666
753
  end
667
754
 
668
- # Prints msg as error message and throws a RantAbortException.
755
+ # Prints msg as error message and raises a RantAbortException.
669
756
  def abort *msg
670
757
  err_msg(msg) unless msg.empty?
758
+ $stderr.puts caller if @opts[:trace_abort]
671
759
  raise Rant::RantAbortException
672
760
  end
673
761
 
@@ -709,6 +797,8 @@ class Rant::RantApp
709
797
  @opts[:quiet] = false
710
798
  end
711
799
 
800
+ # This is actually an integer indicating the verbosity level.
801
+ # Usual values range from 0 to 3.
712
802
  def verbose
713
803
  @opts[:verbose]
714
804
  end
@@ -812,7 +902,9 @@ class Rant::RantApp
812
902
  opt[:force] = true
813
903
  @force_targets.delete(target)
814
904
  end
815
- (select_tasks { |t| t.name == target }).each { |t|
905
+ ### pre 0.3.1 ###
906
+ #(select_tasks { |t| t.name == target }).each { |t|
907
+ select_tasks_by_name(target).each { |t|
816
908
  matching_tasks += 1
817
909
  begin
818
910
  t.invoke(opt)
@@ -845,8 +937,10 @@ class Rant::RantApp
845
937
 
846
938
  # Returns an array (might be a MetaTask) with all tasks that have
847
939
  # the given name.
848
- def select_tasks_by_name name
849
- s = @tasks[name]
940
+ def select_tasks_by_name name, project_dir = @current_subdir
941
+ # pre 0.3.1
942
+ #s = @tasks[name]
943
+ s = @tasks[expand_path(project_dir, name)]
850
944
  case s
851
945
  when nil: []
852
946
  when Rant::Worker: [s]
@@ -969,6 +1063,8 @@ class Rant::RantApp
969
1063
  @opts[:targets] = true
970
1064
  when "--stop-after-load"
971
1065
  @opts[:stop_after_load] = true
1066
+ when "--trace-abort"
1067
+ @opts[:trace_abort] = true
972
1068
  end
973
1069
  }
974
1070
  rescue GetoptLong::Error => e
@@ -986,7 +1082,9 @@ class Rant::RantApp
986
1082
  }
987
1083
  end
988
1084
 
1085
+ # Every task has to be registered with this method.
989
1086
  def prepare_task(targ, block, clr = caller[2])
1087
+ #STDERR.puts "prepare task (#@current_subdir):\n #{targ.inspect}"
990
1088
 
991
1089
  # Allow override of caller, usefull for plugins and libraries
992
1090
  # that define tasks.
@@ -1018,7 +1116,8 @@ class Rant::RantApp
1018
1116
  public :prepare_task
1019
1117
 
1020
1118
  def hash_task task
1021
- n = task.name
1119
+ n = task.full_name
1120
+ #STDERR.puts "hash_task: `#{n}'"
1022
1121
  et = @tasks[n]
1023
1122
  case et
1024
1123
  when nil
@@ -1109,10 +1208,17 @@ class Rant::RantApp
1109
1208
  file = @rantfiles.find { |rf| rf.absolute_path == abs_path }
1110
1209
  [file, false]
1111
1210
  else
1211
+ # create new Rantfile object
1112
1212
  file = Rant::Rantfile.new(abs_path, abs_path)
1213
+ file.project_subdir = @current_subdir
1113
1214
  @rantfiles << file
1114
1215
  [file, true]
1115
1216
  end
1116
1217
  end
1117
1218
 
1219
+ # Just ensure that Rant.rantapp holds an RantApp after loading
1220
+ # this file. The code in initialize will register the new app with
1221
+ # Rant.rantapp= if necessary.
1222
+ self.new
1223
+
1118
1224
  end # class Rant::RantApp
@@ -4,39 +4,170 @@ require 'rant/rantenv'
4
4
 
5
5
  module Rant
6
6
 
7
- class FileList < Array
7
+ class Glob < String
8
+ class << self
9
+ # A synonym for +new+.
10
+ def [](pattern)
11
+ new(pattern)
12
+ end
13
+ end
14
+ end
15
+
16
+ class FileList
17
+ include Enumerable
8
18
 
9
- attr_reader :pattern
19
+ ESC_SEPARATOR = Regexp.escape(File::SEPARATOR)
20
+ ESC_ALT_SEPARATOR = File::ALT_SEPARATOR ?
21
+ Regexp.escape(File::ALT_SEPARATOR) : nil
22
+
23
+ # Flags for the File::fnmatch method.
24
+ # Initialized to 0.
25
+ attr_accessor :glob_flags
10
26
 
11
27
  class << self
12
- def [] pattern
13
- new(pattern).no_suffix("~").no_suffix(".bak")
28
+ def [](*patterns)
29
+ new(*patterns)
14
30
  end
15
31
  end
16
32
 
17
- def initialize(pattern, flags = 0)
18
- super(Dir.glob(pattern, flags))
19
- @actions = []
33
+ def initialize(*patterns)
34
+ @glob_flags = 0
35
+ @files = []
36
+ @actions = patterns.map { |pat| [:apply_include, pat] }
37
+ @pending = true
38
+ yield self if block_given?
39
+ end
40
+
41
+ protected
42
+ attr_reader :actions, :files
43
+ attr_accessor :pending
44
+
45
+ public
46
+ ### Methods having an equivalent in the Array class. #########
47
+
48
+ def each &block
49
+ resolve if @pending
50
+ @files.each(&block)
51
+ end
52
+
53
+ def to_ary
54
+ resolve if @pending
55
+ @files
56
+ end
57
+
58
+ def to_a
59
+ to_ary
20
60
  end
21
61
 
22
62
  def +(other)
23
- fl = self.dup
24
- fl.concat(other.to_ary)
25
- fl
26
- end
27
-
28
- # Reevaluate pattern. Replay all modifications
29
- # if replay is given.
30
- def update(replay = true)
31
- self.replace(Dir[pattern])
32
- if replay
33
- @actions.each { |action|
34
- self.send(*action)
35
- }
63
+ case other
64
+ when Array
65
+ dup.files.concat(other)
66
+ when self.class
67
+ c = other.dup
68
+ c.actions.concat(@actions)
69
+ c.files.concat(@files)
70
+ c.pending = !c.actions.empty?
71
+ c
72
+ else
73
+ raise "argument has to be an Array or FileList"
74
+ end
75
+ end
76
+
77
+ def <<(file)
78
+ @files << file
79
+ self
80
+ end
81
+
82
+ def size
83
+ resolve if @pending
84
+ @files.size
85
+ end
86
+
87
+ def method_missing(sym, *args, &block)
88
+ if @files.respond_to? sym
89
+ resolve if @pending
90
+ @files.send(sym, *args, &block)
91
+ else
92
+ super
36
93
  end
94
+ end
95
+ ##############################################################
96
+
97
+ def resolve
98
+ @pending = false
99
+ @actions.each { |action|
100
+ self.send(*action)
101
+ }
37
102
  @actions.clear
38
103
  end
39
104
 
105
+ def include(*patterns)
106
+ patterns.each { |pat|
107
+ @actions << [:apply_include, pat]
108
+ }
109
+ @pending = true
110
+ self
111
+ end
112
+ alias glob include
113
+
114
+ def apply_include(pattern)
115
+ @files.concat Dir.glob(pattern, @glob_flags)
116
+ end
117
+ private :apply_include
118
+
119
+ def exclude(*patterns)
120
+ patterns.each { |pat|
121
+ if Regexp === pat
122
+ @actions << [:apply_exclude_rx, pat]
123
+ else
124
+ @actions << [:apply_exclude, pat]
125
+ end
126
+ }
127
+ @pending = true
128
+ self
129
+ end
130
+
131
+ def apply_exclude(pattern)
132
+ @files.reject! { |elem|
133
+ File.fnmatch? pattern, elem, @glob_flags
134
+ }
135
+ end
136
+ private :apply_exclude
137
+
138
+ def apply_exclude_rx(rx)
139
+ @files.reject! { |elem|
140
+ elem =~ rx
141
+ }
142
+ end
143
+ private :apply_exclude_rx
144
+
145
+ def exclude_all(*files)
146
+ files.each { |file|
147
+ @actions << [:apply_exclude_rx, mk_all_rx(file)]
148
+ }
149
+ @pending = true
150
+ self
151
+ end
152
+ alias shun exclude_all
153
+
154
+ if File::ALT_SEPARATOR
155
+ # TODO: check for FS case sensitivity?
156
+ def mk_all_rx(file)
157
+
158
+ /(^|(#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+)
159
+ #{Regexp.escape(file)}
160
+ ((#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+|$)/x
161
+ end
162
+ else
163
+ def mk_all_rx(file)
164
+ /(^|#{ESC_SEPARATOR}+)
165
+ #{Regexp.escape(file)}
166
+ (#{ESC_SEPARATOR}+|$)/x
167
+ end
168
+ end
169
+ private :mk_all_rx
170
+
40
171
  # Remove all entries which contain a directory with the
41
172
  # given name.
42
173
  # If no argument or +nil+ given, remove all directories.
@@ -48,16 +179,21 @@ module Rant
48
179
  # src/CVS/lib.c
49
180
  # CVS/foo/bar/
50
181
  def no_dir(name = nil)
51
- @actions << [:no_dir, name]
182
+ @actions << [:apply_no_dir, name]
183
+ @pending = true
184
+ self
185
+ end
186
+
187
+ def apply_no_dir(name)
52
188
  entry = nil
53
189
  unless name
54
- self.reject! { |entry|
190
+ @files.reject! { |entry|
55
191
  test(?d, entry)
56
192
  }
57
- return self
193
+ return
58
194
  end
59
195
  elems = nil
60
- self.reject! { |entry|
196
+ @files.reject! { |entry|
61
197
  elems = Sys.split_path(entry)
62
198
  i = elems.index(name)
63
199
  if i
@@ -67,45 +203,61 @@ module Rant
67
203
  false
68
204
  end
69
205
  }
70
- self
71
206
  end
207
+ private :apply_no_dir
72
208
 
73
209
  # Remove all files which have the given name.
74
210
  def no_file(name)
75
- @actions << [:no_file, name]
76
- self.reject! { |entry|
211
+ @actions << [:apply_no_file, name]
212
+ @pending = true
213
+ self
214
+ end
215
+
216
+ def apply_no_file(name)
217
+ @files.reject! { |entry|
77
218
  entry == name and test(?f, entry)
78
219
  }
79
- self
80
220
  end
221
+ private :apply_no_file
81
222
 
82
223
  # Remove all entries which contain an element
83
224
  # with the given suffix.
84
225
  def no_suffix(suffix)
85
226
  @actions << [:no_suffix, suffix]
86
- elems = elem = nil
87
- self.reject! { |entry|
227
+ @pending = true
228
+ self
229
+ end
230
+
231
+ def apply_no_suffix(suffix)
232
+ elems = nil
233
+ elem = nil
234
+ @files.reject! { |entry|
88
235
  elems = Sys.split_path(entry)
89
236
  elems.any? { |elem|
90
237
  elem =~ /#{suffix}$/
91
238
  }
92
239
  }
93
- self
94
240
  end
241
+ private :apply_no_suffix
95
242
 
96
243
  # Remove all entries which contain an element
97
244
  # with the given prefix.
98
245
  def no_prefix(prefix)
99
246
  @actions << [:no_prefix, prefix]
247
+ @pending = true
248
+ self
249
+ end
250
+
251
+ def apply_no_prefix(prefix)
100
252
  elems = elem = nil
101
- self.reject! { |entry|
253
+ @files.reject! { |entry|
102
254
  elems = Sys.split_path(entry)
103
255
  elems.any? { |elem|
104
256
  elem =~ /^#{prefix}/
105
257
  }
106
258
  }
107
- self
108
259
  end
260
+ private :apply_no_prefix
109
261
 
110
262
  # Get a string with all entries. This is very usefull
111
263
  # if you invoke a shell:
@@ -115,6 +267,9 @@ module Rant
115
267
  # rdoc foo\bar "with space"
116
268
  # on other systems:
117
269
  # rdoc foo/bar 'with space'
270
+ def arglist
271
+ to_ary.arglist
272
+ end
118
273
  =begin
119
274
  def arglist
120
275
  self.list.join(' ')
@@ -141,6 +296,7 @@ module Rant
141
296
  end
142
297
  end
143
298
  =end
299
+
144
300
  end # class FileList
145
301
 
146
302
  class CommandError < StandardError
@@ -182,26 +338,25 @@ module Rant
182
338
  # We override the output method of the FileUtils module to
183
339
  # allow the Rant application to control output.
184
340
  def fu_output_message(msg) #:nodoc:
185
- ::Rant.rantapp.cmd_msg msg
341
+ ::Rant.rantapp.cmd_msg msg if ::Rant.rantapp
186
342
  end
187
343
 
188
344
  def sh(*cmd_args, &block)
189
345
  cmd_args.flatten!
190
346
  cmd = cmd_args.join(" ")
191
- unless block_given?
192
- block = lambda { |succ, status|
193
- succ or raise CommandError.new(cmd, status)
194
- }
195
- end
196
347
  fu_output_message cmd
197
- block.call(system(*cmd_args), $?)
348
+ if block_given?
349
+ block[system(*cmd_args), $?]
350
+ else
351
+ system(*cmd_args) or raise CommandError.new(cmd, $?)
352
+ end
198
353
  end
199
354
 
200
355
  def ruby(*args, &block)
201
356
  if args.size > 1
202
- sh(*([Env::RUBY] + args), &block)
357
+ sh([Env::RUBY] + args, &block)
203
358
  else
204
- sh(Env::RUBY + ' ' + args.join(' '), &block)
359
+ sh("#{Env::RUBY} #{args.first}", &block)
205
360
  end
206
361
  end
207
362