summaryrefslogtreecommitdiff
path: root/tool/ln_sr.rb
blob: e1b5b6f76b443697cf0564437297c317fd6f4477 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/usr/bin/ruby

target_directory = true
noop = false
force = false
quiet = false

until ARGV.empty?
  case ARGV[0]
  when '-n'
    noop = true
  when '-f'
    force = true
  when '-T'
    target_directory = false
  when '-q'
    quiet = true
  else
    break
  end
  ARGV.shift
end

unless ARGV.size == 2
  abort "usage: #{$0} src destdir"
end
src, dest = ARGV

require 'fileutils'

include FileUtils
unless respond_to?(:ln_sr)
  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)
      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)
        s = File.join(*base)
      else
        srcdirs = fu_clean_components(*fu_split_path(s))
        base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs)
        while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last)
          srcdirs.shift
          base.pop
        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

  def fu_split_path(path)
    path = File.path(path)
    list = []
    until (parent, base = File.split(path); parent == path or parent == ".")
      list << base
      path = parent
    end
    list << path
    list.reverse!
  end

  def fu_relative_components_from(target, base) #:nodoc:
    i = 0
    while target[i]&.== base[i]
      i += 1
    end
    Array.new(base.size-i, '..').concat(target[i..-1])
  end

  def fu_clean_components(*comp)
    comp.shift while comp.first == "."
    return comp if comp.empty?
    clean = [comp.shift]
    path = File.join(*clean, "") # ending with File::SEPARATOR
    while c = comp.shift
      if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path))
        clean.pop
        path.sub!(%r((?<=\A|/)[^/]+/\z), "")
      else
        clean << c
        path << c << "/"
      end
    end
    clean
  end

  if fu_windows?
    def fu_starting_path?(path)
      path&.start_with?(%r(\w:|/))
    end
  else
    def fu_starting_path?(path)
      path&.start_with?("/")
    end
  end
end

if File.respond_to?(:symlink)
  if quiet and File.identical?(src, dest)
    exit
  end
  begin
    ln_sr(src, dest, verbose: true, target_directory: target_directory, force: force, noop: noop)
  rescue NotImplementedError, Errno::EPERM, Errno::EACCES
  else
    exit
  end
end

cp_r(src, dest)