@@ -690,6 +690,7 @@ def cp_lr(src, dest, noop: nil, verbose: nil,
690690  # Keyword arguments: 
691691  # 
692692  # - <tt>force: true</tt> - overwrites +dest+ if it exists. 
693+   # - <tt>relative: false</tt> - create links relative to +dest+. 
693694  # - <tt>noop: true</tt> - does not create links. 
694695  # - <tt>verbose: true</tt> - prints an equivalent command: 
695696  # 
@@ -709,7 +710,10 @@ def cp_lr(src, dest, noop: nil, verbose: nil,
709710  # 
710711  # Related: FileUtils.ln_sf. 
711712  # 
712-   def  ln_s ( src ,  dest ,  force : nil ,  noop : nil ,  verbose : nil ) 
713+   def  ln_s ( src ,  dest ,  force : nil ,  relative : false ,  target_directory : true ,  noop : nil ,  verbose : nil ) 
714+     if  relative 
715+       return  ln_sr ( src ,  dest ,  force : force ,  noop : noop ,  verbose : verbose ) 
716+     end 
713717    fu_output_message  "ln -s#{ force  ? 'f'  : '' }   #{ [ src , dest ] . flatten . join  ' ' }  "  if  verbose 
714718    return  if  noop 
715719    fu_each_src_dest0 ( src ,  dest )  do  |s , d |
@@ -729,6 +733,48 @@ def ln_sf(src, dest, noop: nil, verbose: nil)
729733  end 
730734  module_function  :ln_sf 
731735
736+   # Like FileUtils.ln_s, but create links relative to +dest+. 
737+   # 
738+   def  ln_sr ( src ,  dest ,  target_directory : true ,  force : nil ,  noop : nil ,  verbose : nil ) 
739+     options  =  "#{ force  ? 'f'  : '' } #{ target_directory  ? ''  : 'T' }  " 
740+     dest  =  File . path ( dest ) 
741+     srcs  =  Array ( src ) 
742+     link  =  proc  do  |s ,  target_dir_p  =  true |
743+       s  =  File . path ( s ) 
744+       if  target_dir_p 
745+         d  =  File . join ( destdirs  =  dest ,  File . basename ( s ) ) 
746+       else 
747+         destdirs  =  File . dirname ( d  =  dest ) 
748+       end 
749+       destdirs  =  fu_split_path ( File . realpath ( destdirs ) ) 
750+       if  fu_starting_path? ( s ) 
751+         srcdirs  =  fu_split_path ( ( File . realdirpath ( s )  rescue  File . expand_path ( s ) ) ) 
752+         base  =  fu_relative_components_from ( srcdirs ,  destdirs ) 
753+         s  =  File . join ( *base ) 
754+       else 
755+         srcdirs  =  fu_clean_components ( *fu_split_path ( s ) ) 
756+         base  =  fu_relative_components_from ( fu_split_path ( Dir . pwd ) ,  destdirs ) 
757+         while  srcdirs . first &. == ".."  and  base . last &.!=( ".." )  and  !fu_starting_path? ( base . last ) 
758+           srcdirs . shift 
759+           base . pop 
760+         end 
761+         s  =  File . join ( *base ,  *srcdirs ) 
762+       end 
763+       fu_output_message  "ln -s#{ options }   #{ s }   #{ d }  "  if  verbose 
764+       next  if  noop 
765+       remove_file  d ,  true  if  force 
766+       File . symlink  s ,  d 
767+     end 
768+     case  srcs . size 
769+     when  0 
770+     when  1 
771+       link [ srcs [ 0 ] ,  target_directory  && File . directory? ( dest ) ] 
772+     else 
773+       srcs . each ( &link ) 
774+     end 
775+   end 
776+   module_function  :ln_sr 
777+ 
732778  # Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]; returns +nil+. 
733779  # 
734780  # Arguments +src+ and +dest+ 
@@ -2428,15 +2474,15 @@ def fu_each_src_dest(src, dest)   #:nodoc:
24282474  end 
24292475  private_module_function  :fu_each_src_dest 
24302476
2431-   def  fu_each_src_dest0 ( src ,  dest )    #:nodoc: 
2477+   def  fu_each_src_dest0 ( src ,  dest ,   target_directory   =   true )    #:nodoc: 
24322478    if  tmp  =  Array . try_convert ( src ) 
24332479      tmp . each  do  |s |
24342480        s  =  File . path ( s ) 
2435-         yield  s ,  File . join ( dest ,  File . basename ( s ) ) 
2481+         yield  s ,  ( target_directory  ?  File . join ( dest ,  File . basename ( s ) )  :  dest ) 
24362482      end 
24372483    else 
24382484      src  =  File . path ( src ) 
2439-       if  File . directory? ( dest ) 
2485+       if  target_directory   and   File . directory? ( dest ) 
24402486        yield  src ,  File . join ( dest ,  File . basename ( src ) ) 
24412487      else 
24422488        yield  src ,  File . path ( dest ) 
@@ -2460,6 +2506,56 @@ def fu_output_message(msg)   #:nodoc:
24602506  end 
24612507  private_module_function  :fu_output_message 
24622508
2509+   def  fu_split_path ( path ) 
2510+     path  =  File . path ( path ) 
2511+     list  =  [ ] 
2512+     until  ( parent ,  base  =  File . split ( path ) ;  parent  == path  or  parent  == "." ) 
2513+       list  << base 
2514+       path  =  parent 
2515+     end 
2516+     list  << path 
2517+     list . reverse! 
2518+   end 
2519+   private_module_function  :fu_split_path 
2520+ 
2521+   def  fu_relative_components_from ( target ,  base )  #:nodoc: 
2522+     i  =  0 
2523+     while  target [ i ] &.== base [ i ] 
2524+       i  += 1 
2525+     end 
2526+     Array . new ( base . size -i ,  '..' ) . concat ( target [ i ..-1 ] ) 
2527+   end 
2528+   private_module_function  :fu_relative_components_from 
2529+ 
2530+   def  fu_clean_components ( *comp ) 
2531+     comp . shift  while  comp . first  == "." 
2532+     return  comp  if  comp . empty? 
2533+     clean  =  [ comp . shift ] 
2534+     path  =  File . join ( *clean ,  "" )  # ending with File::SEPARATOR 
2535+     while  c  =  comp . shift 
2536+       if  c  == ".."  and  clean . last  != ".."  and  !( fu_have_symlink?  && File . symlink? ( path ) ) 
2537+         clean . pop 
2538+         path . chomp! ( %r((?<=\A |/)[^/]+/\z ) ,  "" ) 
2539+       else 
2540+         clean  << c 
2541+         path  << c  << "/" 
2542+       end 
2543+     end 
2544+     clean 
2545+   end 
2546+   private_module_function  :fu_clean_components 
2547+ 
2548+   if  fu_windows? 
2549+     def  fu_starting_path? ( path ) 
2550+       path &.start_with? ( %r(\w :|/) ) 
2551+     end 
2552+   else 
2553+     def  fu_starting_path? ( path ) 
2554+       path &.start_with? ( "/" ) 
2555+     end 
2556+   end 
2557+   private_module_function  :fu_starting_path? 
2558+ 
24632559  # This hash table holds command options. 
24642560  OPT_TABLE  =  { }     #:nodoc: internal use only 
24652561  ( private_instance_methods  & methods ( false ) ) . inject ( OPT_TABLE )  { |tbl ,  name |
0 commit comments