summaryrefslogtreecommitdiff
path: root/tool/ln_sr.rb
blob: 307daa32027244a9caed5cce06d68703762fe489 (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
#!/usr/bin/ruby

target_directory = true
noop = false
force = false

until ARGV.empty?
  case ARGV[0]
  when '-n'
    noop = true
  when '-f'
    force = true
  when '-T'
    target_directory = false
  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.try_convert(src) || [src]
    link = proc do |s, target_dir_p = true|
      s = File.path(s)
      if fu_starting_path?(s)
        srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s)))
      else
        srcdirs = fu_clean_components(*fu_split_path(s))
      end
      destdirs = fu_split_path(File.realdirpath(dest))
      destdirs.pop unless target_dir_p
      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)
      d = target_dir_p ? File.join(dest, File.basename(s)) : dest
      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.chomp!(%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)
  begin
    ln_sr(src, dest, verbose: true, target_directory: target_directory, force: force, noop: noop)
  rescue NotImplementedError, Errno::EPERM
  else
    exit
  end
end

cp_r(src, dest)