36
36
# - ::ln, ::link: Creates hard links.
37
37
# - ::ln_s, ::symlink: Creates symbolic links.
38
38
# - ::ln_sf: Creates symbolic links, overwriting if necessary.
39
+ # - ::ln_sr: Creates symbolic links relative to targets
39
40
#
40
41
# === Deleting
41
42
#
@@ -690,6 +691,7 @@ def cp_lr(src, dest, noop: nil, verbose: nil,
690
691
# Keyword arguments:
691
692
#
692
693
# - <tt>force: true</tt> - overwrites +dest+ if it exists.
694
+ # - <tt>relative: false</tt> - create links relative to +dest+.
693
695
# - <tt>noop: true</tt> - does not create links.
694
696
# - <tt>verbose: true</tt> - prints an equivalent command:
695
697
#
@@ -709,7 +711,10 @@ def cp_lr(src, dest, noop: nil, verbose: nil,
709
711
#
710
712
# Related: FileUtils.ln_sf.
711
713
#
712
- def ln_s ( src , dest , force : nil , noop : nil , verbose : nil )
714
+ def ln_s ( src , dest , force : nil , relative : false , target_directory : true , noop : nil , verbose : nil )
715
+ if relative
716
+ return ln_sr ( src , dest , force : force , noop : noop , verbose : verbose )
717
+ end
713
718
fu_output_message "ln -s#{ force ? 'f' : '' } #{ [ src , dest ] . flatten . join ' ' } " if verbose
714
719
return if noop
715
720
fu_each_src_dest0 ( src , dest ) do |s , d |
@@ -729,6 +734,48 @@ def ln_sf(src, dest, noop: nil, verbose: nil)
729
734
end
730
735
module_function :ln_sf
731
736
737
+ # Like FileUtils.ln_s, but create links relative to +dest+.
738
+ #
739
+ def ln_sr ( src , dest , target_directory : true , force : nil , noop : nil , verbose : nil )
740
+ options = "#{ force ? 'f' : '' } #{ target_directory ? '' : 'T' } "
741
+ dest = File . path ( dest )
742
+ srcs = Array ( src )
743
+ link = proc do |s , target_dir_p = true |
744
+ s = File . path ( s )
745
+ if target_dir_p
746
+ d = File . join ( destdirs = dest , File . basename ( s ) )
747
+ else
748
+ destdirs = File . dirname ( d = dest )
749
+ end
750
+ destdirs = fu_split_path ( File . realpath ( destdirs ) )
751
+ if fu_starting_path? ( s )
752
+ srcdirs = fu_split_path ( ( File . realdirpath ( s ) rescue File . expand_path ( s ) ) )
753
+ base = fu_relative_components_from ( srcdirs , destdirs )
754
+ s = File . join ( *base )
755
+ else
756
+ srcdirs = fu_clean_components ( *fu_split_path ( s ) )
757
+ base = fu_relative_components_from ( fu_split_path ( Dir . pwd ) , destdirs )
758
+ while srcdirs . first &. == ".." and base . last &.!=( ".." ) and !fu_starting_path? ( base . last )
759
+ srcdirs . shift
760
+ base . pop
761
+ end
762
+ s = File . join ( *base , *srcdirs )
763
+ end
764
+ fu_output_message "ln -s#{ options } #{ s } #{ d } " if verbose
765
+ next if noop
766
+ remove_file d , true if force
767
+ File . symlink s , d
768
+ end
769
+ case srcs . size
770
+ when 0
771
+ when 1
772
+ link [ srcs [ 0 ] , target_directory && File . directory? ( dest ) ]
773
+ else
774
+ srcs . each ( &link )
775
+ end
776
+ end
777
+ module_function :ln_sr
778
+
732
779
# Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]; returns +nil+.
733
780
#
734
781
# Arguments +src+ and +dest+
@@ -2436,15 +2483,15 @@ def fu_each_src_dest(src, dest) #:nodoc:
2436
2483
end
2437
2484
private_module_function :fu_each_src_dest
2438
2485
2439
- def fu_each_src_dest0 ( src , dest ) #:nodoc:
2486
+ def fu_each_src_dest0 ( src , dest , target_directory = true ) #:nodoc:
2440
2487
if tmp = Array . try_convert ( src )
2441
2488
tmp . each do |s |
2442
2489
s = File . path ( s )
2443
- yield s , File . join ( dest , File . basename ( s ) )
2490
+ yield s , ( target_directory ? File . join ( dest , File . basename ( s ) ) : dest )
2444
2491
end
2445
2492
else
2446
2493
src = File . path ( src )
2447
- if File . directory? ( dest )
2494
+ if target_directory and File . directory? ( dest )
2448
2495
yield src , File . join ( dest , File . basename ( src ) )
2449
2496
else
2450
2497
yield src , File . path ( dest )
@@ -2468,6 +2515,56 @@ def fu_output_message(msg) #:nodoc:
2468
2515
end
2469
2516
private_module_function :fu_output_message
2470
2517
2518
+ def fu_split_path ( path )
2519
+ path = File . path ( path )
2520
+ list = [ ]
2521
+ until ( parent , base = File . split ( path ) ; parent == path or parent == "." )
2522
+ list << base
2523
+ path = parent
2524
+ end
2525
+ list << path
2526
+ list . reverse!
2527
+ end
2528
+ private_module_function :fu_split_path
2529
+
2530
+ def fu_relative_components_from ( target , base ) #:nodoc:
2531
+ i = 0
2532
+ while target [ i ] &.== base [ i ]
2533
+ i += 1
2534
+ end
2535
+ Array . new ( base . size -i , '..' ) . concat ( target [ i ..-1 ] )
2536
+ end
2537
+ private_module_function :fu_relative_components_from
2538
+
2539
+ def fu_clean_components ( *comp )
2540
+ comp . shift while comp . first == "."
2541
+ return comp if comp . empty?
2542
+ clean = [ comp . shift ]
2543
+ path = File . join ( *clean , "" ) # ending with File::SEPARATOR
2544
+ while c = comp . shift
2545
+ if c == ".." and clean . last != ".." and !( fu_have_symlink? && File . symlink? ( path ) )
2546
+ clean . pop
2547
+ path . chomp! ( %r((?<=\A |/)[^/]+/\z ) , "" )
2548
+ else
2549
+ clean << c
2550
+ path << c << "/"
2551
+ end
2552
+ end
2553
+ clean
2554
+ end
2555
+ private_module_function :fu_clean_components
2556
+
2557
+ if fu_windows?
2558
+ def fu_starting_path? ( path )
2559
+ path &.start_with? ( %r(\w :|/) )
2560
+ end
2561
+ else
2562
+ def fu_starting_path? ( path )
2563
+ path &.start_with? ( "/" )
2564
+ end
2565
+ end
2566
+ private_module_function :fu_starting_path?
2567
+
2471
2568
# This hash table holds command options.
2472
2569
OPT_TABLE = { } #:nodoc: internal use only
2473
2570
( private_instance_methods & methods ( false ) ) . inject ( OPT_TABLE ) { |tbl , name |
0 commit comments