diff --git a/enumerator.c b/enumerator.c
index b91b2eb940d445..3b69778f074246 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -3952,46 +3952,14 @@ rb_arithmetic_sequence_beg_len_step(VALUE obj, long *begp, long *lenp, long *ste
return Qnil;
}
-/*
- * call-seq:
- * aseq.first -> num or nil
- * aseq.first(n) -> an_array
- *
- * Returns the first number in this arithmetic sequence,
- * or an array of the first +n+ elements.
- */
static VALUE
-arith_seq_first(int argc, VALUE *argv, VALUE self)
+arith_seq_take(VALUE self, VALUE num)
{
VALUE b, e, s, ary;
long n;
int x;
- rb_check_arity(argc, 0, 1);
-
- b = arith_seq_begin(self);
- e = arith_seq_end(self);
- s = arith_seq_step(self);
- if (argc == 0) {
- if (NIL_P(b)) {
- return Qnil;
- }
- if (!NIL_P(e)) {
- VALUE zero = INT2FIX(0);
- int r = rb_cmpint(rb_num_coerce_cmp(s, zero, idCmp), s, zero);
- if (r > 0 && RTEST(rb_funcall(b, '>', 1, e))) {
- return Qnil;
- }
- if (r < 0 && RTEST(rb_funcall(b, '<', 1, e))) {
- return Qnil;
- }
- }
- return b;
- }
-
- // TODO: the following code should be extracted as arith_seq_take
-
- n = NUM2LONG(argv[0]);
+ n = NUM2LONG(num);
if (n < 0) {
rb_raise(rb_eArgError, "attempt to take negative size");
}
@@ -3999,6 +3967,9 @@ arith_seq_first(int argc, VALUE *argv, VALUE self)
return rb_ary_new_capa(0);
}
+ b = arith_seq_begin(self);
+ e = arith_seq_end(self);
+ s = arith_seq_step(self);
x = arith_seq_exclude_end_p(self);
if (FIXNUM_P(b) && NIL_P(e) && FIXNUM_P(s)) {
@@ -4093,7 +4064,49 @@ arith_seq_first(int argc, VALUE *argv, VALUE self)
return ary;
}
- return rb_call_super(argc, argv);
+ {
+ VALUE argv[1];
+ argv[0] = num;
+ return rb_call_super(1, argv);
+ }
+}
+
+/*
+ * call-seq:
+ * aseq.first -> num or nil
+ * aseq.first(n) -> an_array
+ *
+ * Returns the first number in this arithmetic sequence,
+ * or an array of the first +n+ elements.
+ */
+static VALUE
+arith_seq_first(int argc, VALUE *argv, VALUE self)
+{
+ VALUE b, e, s;
+
+ rb_check_arity(argc, 0, 1);
+
+ b = arith_seq_begin(self);
+ e = arith_seq_end(self);
+ s = arith_seq_step(self);
+ if (argc == 0) {
+ if (NIL_P(b)) {
+ return Qnil;
+ }
+ if (!NIL_P(e)) {
+ VALUE zero = INT2FIX(0);
+ int r = rb_cmpint(rb_num_coerce_cmp(s, zero, idCmp), s, zero);
+ if (r > 0 && RTEST(rb_funcall(b, '>', 1, e))) {
+ return Qnil;
+ }
+ if (r < 0 && RTEST(rb_funcall(b, '<', 1, e))) {
+ return Qnil;
+ }
+ }
+ return b;
+ }
+
+ return arith_seq_take(self, argv[0]);
}
static inline VALUE
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index b9d683797ab03b..5a4fc6e23bf5f2 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -708,9 +708,10 @@ def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: n
if relative
return ln_sr(src, dest, force: force, noop: noop, verbose: verbose)
end
- fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ fu_output_message "ln -s#{force ? 'f' : ''}#{
+ target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose
return if noop
- fu_each_src_dest0(src, dest) do |s,d|
+ fu_each_src_dest0(src, dest, target_directory) do |s,d|
remove_file d, true if force
File.symlink s, d
end
@@ -730,17 +731,17 @@ def ln_sf(src, dest, noop: nil, verbose: nil)
# Like FileUtils.ln_s, but create links relative to +dest+.
#
def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil)
- options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}"
- dest = File.path(dest)
- srcs = Array(src)
- link = proc do |s, target_dir_p = true|
- s = File.path(s)
- if target_dir_p
- d = File.join(destdirs = dest, File.basename(s))
- else
- destdirs = File.dirname(d = dest)
+ fu_output_message "ln -sr#{force ? 'f' : ''}#{
+ target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ unless target_directory
+ destdirs = fu_split_path(File.realdirpath(dest))
+ end
+ fu_each_src_dest0(src, dest, target_directory) do |s,d|
+ if target_directory
+ destdirs = fu_split_path(File.realdirpath(File.dirname(d)))
+ # else d == dest
end
- destdirs = fu_split_path(File.realpath(destdirs))
if fu_starting_path?(s)
srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s)))
base = fu_relative_components_from(srcdirs, destdirs)
@@ -754,18 +755,9 @@ def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil
end
s = File.join(*base, *srcdirs)
end
- fu_output_message "ln -s#{options} #{s} #{d}" if verbose
- next if noop
remove_file d, true if force
File.symlink s, d
end
- case srcs.size
- when 0
- when 1
- link[srcs[0], target_directory && File.directory?(dest)]
- else
- srcs.each(&link)
- end
end
module_function :ln_sr
@@ -800,13 +792,13 @@ def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil
# File.file?('dest1/dir1/t2.txt') # => true
# File.file?('dest1/dir1/t3.txt') # => true
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - dereference_root: true - dereferences +src+ if it is a symbolic link.
- # - remove_destination: true - removes +dest+ before creating links.
+ # - +dereference_root+ - dereferences +src+ if it is a symbolic link (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before creating links (+false+ by default).
#
# Raises an exception if +dest+ is the path to an existing file or directory
- # and keyword argument remove_destination: true is not given.
+ # and optional argument +remove_destination+ is not given.
#
# Related: FileUtils.ln (has different options).
#
@@ -1029,12 +1021,12 @@ def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
# directories, and symbolic links;
# other file types (FIFO streams, device files, etc.) are not supported.
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - dereference_root: true - if +src+ is a symbolic link,
- # follows the link.
- # - preserve: true - preserves file times.
- # - remove_destination: true - removes +dest+ before copying files.
+ # - +dereference_root+ - if +src+ is a symbolic link,
+ # follows the link (+false+ by default).
+ # - +preserve+ - preserves file times (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before copying files (+false+ by default).
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
@@ -1065,12 +1057,12 @@ def copy_entry(src, dest, preserve = false, dereference_root = false, remove_des
# FileUtils.copy_file('src0.txt', 'dest0.txt')
# File.file?('dest0.txt') # => true
#
- # Keyword arguments:
+ # Optional arguments:
#
- # - dereference: false - if +src+ is a symbolic link,
- # does not follow the link.
- # - preserve: true - preserves file times.
- # - remove_destination: true - removes +dest+ before copying files.
+ # - +dereference+ - if +src+ is a symbolic link,
+ # follows the link (+true+ by default).
+ # - +preserve+ - preserves file times (+false+ by default).
+ # - +remove_destination+ - removes +dest+ before copying files (+false+ by default).
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
@@ -2475,6 +2467,9 @@ def fu_each_src_dest(src, dest) #:nodoc:
def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc:
if tmp = Array.try_convert(src)
+ unless target_directory or tmp.size <= 1
+ raise ArgumentError, "extra target #{tmp}"
+ end
tmp.each do |s|
s = File.path(s)
yield s, (target_directory ? File.join(dest, File.basename(s)) : dest)
diff --git a/test/fileutils/test_fileutils.rb b/test/fileutils/test_fileutils.rb
index 1d7be692f52c04..c9d8d0c7d05175 100644
--- a/test/fileutils/test_fileutils.rb
+++ b/test/fileutils/test_fileutils.rb
@@ -955,16 +955,27 @@ def test_ln_pathname
def test_ln_s
check_singleton :ln_s
+ ln_s TARGETS, 'tmp'
+ each_srcdest do |fname, lnfname|
+ assert_equal fname, File.readlink(lnfname)
+ ensure
+ rm_f lnfname
+ end
+
+ lnfname = 'symlink'
+ assert_raise(Errno::ENOENT, "multiple targets need a destination directory") {
+ ln_s TARGETS, lnfname
+ }
+ assert_file.not_exist?(lnfname)
+
TARGETS.each do |fname|
- begin
- fname = "../#{fname}"
- lnfname = 'tmp/lnsdest'
- ln_s fname, lnfname
- assert FileTest.symlink?(lnfname), 'not symlink'
- assert_equal fname, File.readlink(lnfname)
- ensure
- rm_f lnfname
- end
+ fname = "../#{fname}"
+ lnfname = 'tmp/lnsdest'
+ ln_s fname, lnfname
+ assert_file.symlink?(lnfname)
+ assert_equal fname, File.readlink(lnfname)
+ ensure
+ rm_f lnfname
end
end if have_symlink? and !no_broken_symlink?
@@ -1017,22 +1028,52 @@ def test_ln_sf_pathname
def test_ln_sr
check_singleton :ln_sr
- TARGETS.each do |fname|
- begin
- lnfname = 'tmp/lnsdest'
- ln_sr fname, lnfname
- assert FileTest.symlink?(lnfname), 'not symlink'
- assert_equal "../#{fname}", File.readlink(lnfname), fname
+ assert_all_assertions_foreach(nil, *TARGETS) do |fname|
+ lnfname = 'tmp/lnsdest'
+ ln_sr fname, lnfname
+ assert FileTest.symlink?(lnfname), 'not symlink'
+ assert_equal "../#{fname}", File.readlink(lnfname)
+ ensure
+ rm_f lnfname
+ end
+
+ ln_sr TARGETS, 'tmp'
+ assert_all_assertions do |all|
+ each_srcdest do |fname, lnfname|
+ all.for(fname) do
+ assert_equal "../#{fname}", File.readlink(lnfname)
+ end
ensure
rm_f lnfname
end
end
+
mkdir 'data/src'
File.write('data/src/xxx', 'ok')
File.symlink '../data/src', 'tmp/src'
ln_sr 'tmp/src/xxx', 'data'
assert File.symlink?('data/xxx')
assert_equal 'ok', File.read('data/xxx')
+ end
+
+ def test_ln_sr_not_target_directory
+ assert_raise(ArgumentError) {
+ ln_sr TARGETS, 'tmp', target_directory: false
+ }
+ assert_empty(Dir.children('tmp'))
+
+ lnfname = 'symlink'
+ assert_raise(ArgumentError) {
+ ln_sr TARGETS, lnfname, target_directory: false
+ }
+ assert_file.not_exist?(lnfname)
+
+ assert_all_assertions_foreach(nil, *TARGETS) do |fname|
+ assert_raise(Errno::EEXIST, Errno::EACCES) {
+ ln_sr fname, 'tmp', target_directory: false
+ }
+ assert_file.not_exist? File.join('tmp/', File.basename(fname))
+ end
end if have_symlink?
def test_ln_sr_broken_symlink