squared 0.5.18 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,6 +39,10 @@ module Squared
39
39
  TEXT_STYLE = [:bold, :dim, :italic, :underline, :blinking, nil, :inverse, :hidden, :strikethrough].freeze
40
40
  private_constant :AIX_TERM, :BOX_GRAPH, :BOX_BORDER, :TEXT_STYLE
41
41
 
42
+ String.define_method(:stripstyle) { gsub(/\x1B\[(?:\d+;?)+m/, '') }
43
+ String.define_method(:stripext) { File.basename(self, '.*') }
44
+ String.define_method(:subhint) { |s| s.nil? || (s.is_a?(::String) && s.empty?) ? self : "#{self} (#{s})" }
45
+
42
46
  def enable_aixterm
43
47
  unless (colors = __get__(:colors)).frozen?
44
48
  colors.update(AIX_TERM)
@@ -67,7 +71,6 @@ module Squared
67
71
  end
68
72
  wrap = ->(s, n) { "\x1B[#{n.join(';')}m#{s}\x1B[0m" }
69
73
  code = []
70
- args.clear if args.size == 1 && args.first.nil?
71
74
  args.concat(Array(styles)).flatten.each_with_index do |type, i|
72
75
  next unless type
73
76
 
@@ -86,7 +89,7 @@ module Squared
86
89
  end
87
90
  else
88
91
  t = type.to_sym
89
- if (c = __get__(:colors)[t])
92
+ if (c = __get__(:colors)[t] || __get__(:colors)[t.to_s.sub('bright_', '').to_sym])
90
93
  if index == -1
91
94
  s = wrap.call(s, [c])
92
95
  else
@@ -95,7 +98,7 @@ module Squared
95
98
  else
96
99
  next unless (n = TEXT_STYLE.index(t))
97
100
 
98
- s = "\x1B[#{n + 1}m#{s}\x1B[#{n == 0 ? 22 : n + 21}m"
101
+ s = "\x1B[#{n.succ}m#{s}\x1B[#{n == 0 ? 22 : n + 21}m"
99
102
  end
100
103
  end
101
104
  if index == -1
@@ -127,8 +130,8 @@ module Squared
127
130
  colors = __get__(:colors)
128
131
  Array(args).flatten.compact.each do |val|
129
132
  if !val.is_a?(::Numeric)
130
- val = val.to_sym
131
- ret << val if colors.key?(val) || TEXT_STYLE.include?(val)
133
+ k = val.to_sym
134
+ ret << k if colors.key?(k) || colors.key?(k.to_s.sub('bright_', '').to_sym) || TEXT_STYLE.include?(k)
132
135
  elsif val.between?(0, 256)
133
136
  ret << val
134
137
  elsif val < 0 && (b = val.to_s.split('.')[1])
@@ -152,6 +155,10 @@ module Squared
152
155
  end
153
156
  end
154
157
 
158
+ def opt_style(styles, pat = nil, index = 1)
159
+ { styles: styles, pat: pat, index: index }
160
+ end
161
+
155
162
  def log_sym(level)
156
163
  if level.is_a?(::Numeric)
157
164
  case level
@@ -181,20 +188,23 @@ module Squared
181
188
  args = args.map(&:to_s)
182
189
  if level.is_a?(::Numeric)
183
190
  if append && respond_to?(:log)
184
- ref = log rescue nil
185
- ref.add(level, message(subject, *args, hint: hint, space: ', ')) if ref.is_a?(::Logger)
191
+ (log rescue nil).tap do |ref|
192
+ ref.add(level, message(subject, *args, hint: hint, space: ', ')) if ref.is_a?(::Logger)
193
+ end
186
194
  end
187
- return false unless pass || level >= ARG[:LEVEL]
195
+ return false if !pass && level < ARG[:LEVEL]
188
196
  end
189
- if hint.nil? ? args.size > 1 : !hint
197
+ if (args.size > 1 && !hint) || hint == false
190
198
  title = log_title(level, color: false)
191
- sub = [pat: /\A(#{Regexp.escape(title)})(.*)\z/m, styles: __get__(:theme)[:logger][log_sym(level)]] if color
192
- emphasize(args, title: title + (subject ? " #{subject}" : ''), sub: sub, pipe: -1)
199
+ emphasize(args,
200
+ title: title + (subject ? " #{subject}" : ''),
201
+ pipe: -1,
202
+ sub: if color
203
+ opt_style(__get__(:theme)[:logger][log_sym(level)], /\A(#{Regexp.escape(title)})(.*)\z/m)
204
+ end)
193
205
  else
194
206
  msg = [log_title(level, color: color)]
195
- if subject
196
- msg << (color ? sub_style(subject.to_s, (@theme.is_a?(::Hash) && @theme[:subject]) || :bold) : subject)
197
- end
207
+ msg << (color ? sub_style(subject.to_s, styles: (@theme && @theme[:subject]) || :bold) : subject) if subject
198
208
  msg << args.shift if msg.size == 1
199
209
  message(msg.join(' '), *args, hint: hint)
200
210
  end
@@ -207,7 +217,7 @@ module Squared
207
217
  begin
208
218
  File.open(pipe, 'a') do |f|
209
219
  br = File::SEPARATOR == '\\' ? "\r\n" : "\n"
210
- args.flatten.each { |val| f.write(strip_style(val.chomp) + br) }
220
+ args.flatten.each { |val| f.write(val.chomp.stripstyle + br) }
211
221
  end
212
222
  return
213
223
  rescue StandardError
@@ -217,17 +227,16 @@ module Squared
217
227
  (pipe == 2 ? $stderr : $stdout).puts(*args)
218
228
  end
219
229
 
220
- alias puts_oe log_console
221
-
222
230
  module_function
223
231
 
224
232
  def message(*args, hint: nil, empty: false, space: ARG[:SPACE])
225
233
  (empty ? args.reject { |val| val.nil? || (val.respond_to?(:empty?) && val.empty?) } : args)
226
- .join(space) + (hint ? " (#{hint})" : '')
234
+ .join(space)
235
+ .subhint(hint)
227
236
  end
228
237
 
229
238
  def emphasize(val, title: nil, footer: nil, right: false, cols: nil, sub: nil, pipe: nil,
230
- border: @theme.is_a?(::Hash) && @theme[:border])
239
+ border: @theme && @theme[:border])
231
240
  n = 0
232
241
  max = ->(a) { n = [n, a.max_by(&:size).size].max }
233
242
  set = ->(s) { Array(s).map(&:to_s).tap { |a| max.call(a) } }
@@ -239,7 +248,7 @@ module Squared
239
248
  lines = val.to_s.lines(chomp: true)
240
249
  lines[0] = "#{val.class}: #{lines.first}" if (err = val.is_a?(::StandardError))
241
250
  end
242
- n = cols || max.call(lines)
251
+ n = (cols.is_a?(::Array) ? cols.map(&:size).max : cols) || max.call(lines)
243
252
  if $stdout.tty?
244
253
  require 'io/console'
245
254
  (n = [n, $stdout.winsize[1] - 4].min) rescue nil
@@ -257,14 +266,14 @@ module Squared
257
266
  sub.each { |h| s = sub_style(s, **h) }
258
267
  s = "#{b0} #{s} #{b0}"
259
268
  if border
260
- s = sub_style(s, pat: /\A(#{Regexp.escape(b0)})(.+)\z/om, styles: border)
261
- s = sub_style(s, pat: /\A(.+)(#{Regexp.escape(b0)})\z/om, styles: border, index: 2)
269
+ s = sub_style(s, **opt_style(border, /\A(#{Regexp.escape(b0)})(.+)\z/m))
270
+ s = sub_style(s, **opt_style(border, /\A(.+)(#{Regexp.escape(b0)})\z/m, 2))
262
271
  end
263
272
  s
264
273
  end
265
274
  out << draw.call(b2, b3)
266
275
  if title
267
- out.concat(title.map { |t| pr.call(t) })
276
+ out.concat(title.map! { |t| pr.call(t) })
268
277
  out << draw.call(b6, b7)
269
278
  end
270
279
  lines.each { |line| out << pr.call(line) }
@@ -292,22 +301,19 @@ module Squared
292
301
  else
293
302
  pipe = $stdout unless pipe.respond_to?(:puts)
294
303
  end
295
- pipe.puts(out)
304
+ pipe.puts out
296
305
  else
297
306
  err ? warn(out) : puts(out)
298
307
  end
299
308
  end
300
309
 
301
- def strip_style(val)
302
- val.gsub(/\x1B\[(\d+;?)+m/, '')
303
- end
304
-
305
- def stripext(val)
306
- File.basename(val, '.*')
307
- end
308
-
309
- def raise_error(*args, hint: nil, kind: ArgumentError)
310
- raise kind, message(*args, hint: hint, empty: true), caller_locations(1).map(&:to_s)
310
+ def raise_error(*args, hint: nil, kind: RuntimeError, start: 0)
311
+ kind = args.shift if args.first.is_a?(::Class) && args.first < ::Exception
312
+ raise kind, message(*args, hint: hint, empty: true), if ARG[:BACKTRACE]
313
+ caller(start.succ)
314
+ else
315
+ caller_locations(start.succ, 1).first&.base_label
316
+ end
311
317
  end
312
318
  end
313
319
  end
@@ -1,12 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ unless defined?(Readline)
4
+ if RUBY_ENGINE == 'ruby' && RUBY_VERSION < '2.7'
5
+ require 'readline'
6
+ else
7
+ begin
8
+ require 'reline'
9
+ Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline)
10
+ Readline = Reline
11
+ rescue LoadError
12
+ require 'readline'
13
+ end
14
+ end
15
+ end
16
+
3
17
  module Squared
4
18
  module Common
5
19
  module Prompt
6
20
  module_function
7
21
 
8
- def confirm(msg, default = nil, agree: 'Y', cancel: 'N', attempts: 5, timeout: 30)
9
- require 'readline'
22
+ def confirm(msg, default = nil, agree: 'Y', cancel: 'N', attempts: 3, timeout: 60)
10
23
  require 'timeout'
11
24
  if agree == 'Y' && cancel == 'N' && !msg.match?(%r{\[(?:Yn|nY|Y/n|y/N)\]})
12
25
  case default
@@ -38,9 +51,8 @@ module Squared
38
51
  end
39
52
  end
40
53
 
41
- def choice(msg, list = nil, min: 1, max: 1, multiple: false, force: true, grep: nil, auto: true,
42
- attempts: 5, timeout: 0)
43
- require 'readline'
54
+ def choice(msg, list = nil, min: 1, max: 1, multiple: false, index: false, grep: nil, border: nil, auto: true,
55
+ force: true, attempts: 3, timeout: 0)
44
56
  require 'timeout'
45
57
  if list
46
58
  grep &&= Array(grep).map { |val| Regexp.new(val) }
@@ -52,39 +64,51 @@ module Squared
52
64
  puts '%2d. %s' % [items.size, val]
53
65
  end
54
66
  max = items.size
55
- raise_error 'empty selection list' if max == 0
67
+ raise ArgumentError, 'empty selection list' if max == 0
68
+
56
69
  min = grep ? 1 : [min, max].min
57
70
  if auto
58
- msg = "#{msg}: [#{min}-#{max}#{if multiple
59
- "|,#{multiple.is_a?(::Numeric) ? "{#{multiple}}" : ''}"
60
- end}] "
71
+ auto.times { puts } if auto.is_a?(::Numeric)
72
+ if border == true
73
+ puts print_footer
74
+ elsif border
75
+ puts print_footer(border: border)
76
+ end
77
+ msg = "#{msg + (force ? ':' : '?')} [#{min}-#{max}#{if (n = multiple)
78
+ "|,#{n.is_a?(::Numeric) ? "{#{n}}" : '*'}"
79
+ end}] "
61
80
  end
62
81
  end
63
- valid = ->(s) { s.match?(/^\d+$/) && s.to_i.between?(min, max) }
82
+ between = ->(s) { s.match?(/^\d+$/) && s.to_i.between?(min, max) }
64
83
  Timeout.timeout(timeout) do
65
84
  while (ch = Readline.readline(msg))
66
85
  unless (ch = ch.strip).empty?
67
86
  if multiple
68
- a = ch.split(/\s*,\s*/)
69
- b = a.map do |s|
70
- if s =~ /^(\d+)-(\d+)$/
71
- next unless valid.call($1) && valid.call($2)
87
+ k = if ch == '*'
88
+ (min..max).to_a
89
+ else
90
+ ch.split(/\s*,\s*/).map! do |s|
91
+ if s =~ /^(\d+)-(\d+)$/
92
+ next unless between.call($1) && between.call($2)
72
93
 
73
- c = $1.to_i
74
- d = $2.to_i
75
- next (c..d).to_a if c < d
76
- elsif valid.call(s)
77
- s.to_i
94
+ i = $1.to_i
95
+ j = $2.to_i
96
+ next (i..j).to_a if i < j
97
+ elsif between.call(s)
98
+ s.to_i
99
+ end
100
+ end
101
+ end
102
+ unless k.include?(nil)
103
+ k.flatten!
104
+ k.uniq!
105
+ k.sort!
106
+ unless multiple.is_a?(::Numeric) && multiple != k.size
107
+ return index || !items ? k : k.map! { |i| items[i.pred] }
78
108
  end
79
109
  end
80
- unless b.include?(nil)
81
- b.flatten!
82
- b.uniq!
83
- b.sort!
84
- return items ? b.map! { |i| items[i - 1] } : b unless multiple.is_a?(::Numeric) && multiple != b.size
85
- end
86
- elsif valid.call(ch)
87
- return items ? items[ch.to_i - 1] : ch.to_i
110
+ elsif between.call(ch)
111
+ return index || !items ? ch.to_i : items[ch.to_i.pred]
88
112
  end
89
113
  end
90
114
  attempts -= 1
@@ -97,12 +121,11 @@ module Squared
97
121
  puts
98
122
  exit 0
99
123
  else
100
- multiple ? [] : nil
124
+ [] if multiple
101
125
  end
102
126
  end
103
127
 
104
128
  def readline(msg, history = false, force: nil, multiline: nil, &blk)
105
- require 'readline'
106
129
  multiline = if multiline && Readline.respond_to?(:readmultiline)
107
130
  multiline.is_a?(::Enumerable) || block_given? ? multiline : [multiline.to_s]
108
131
  end
@@ -117,11 +140,11 @@ module Squared
117
140
  end
118
141
  case force
119
142
  when ::TrueClass, ::FalseClass
120
- msg = "#{msg} %s " % if multiline
121
- multiline.is_a?(::Enumerable) ? "{#{multiline.to_a.join('|')}}" : multiline
122
- else
123
- "(#{force ? 'required' : 'optional'}):"
124
- end
143
+ msg = "#{msg}%s%s " % if multiline
144
+ [' ', multiline.is_a?(::Enumerable) ? "{#{multiline.to_a.join('|')}}" : multiline]
145
+ else
146
+ [force ? ':' : '?', '']
147
+ end
125
148
  ret = (prompt.call || '').strip
126
149
  multiline.each { |val| break if ret.delete_suffix!(val.to_s) } if multiline.is_a?(::Enumerable)
127
150
  exit 1 if force && ret.empty?
@@ -6,14 +6,11 @@ require 'shellwords'
6
6
  module Squared
7
7
  module Common
8
8
  module Shell
9
- QUOTE_VALUE = /\A(["'])(.*)\1\z/m.freeze
10
- private_constant :QUOTE_VALUE
11
-
12
9
  module_function
13
10
 
14
11
  def shell_escape(val, quote: false, force: false, double: false, option: false, override: false)
15
- if (r = /\A(--?)([^=\s]+)((=|\s+)(["'])?(?(5)(.*)\5|(.*)))?\z/m.match(val = val.to_s))
16
- if (data = r[2].match(QUOTE_VALUE))
12
+ if (r = /\A(--?)([^= ]+)((=|\s+)(["'])?(?(5)(.*)\5|(.*)))?\z/m.match(val = val.to_s))
13
+ if (data = r[2].match(/\A(["'])(.+)\1\z/))
17
14
  double = data[1] == '"'
18
15
  override = true
19
16
  elsif !r[3] || r[6]
@@ -28,9 +25,9 @@ module Squared
28
25
 
29
26
  r[7]
30
27
  end
31
- r[1] + (data ? data[2] : r[2]) + r[4] + shell_quote(opt, force: force, double: double, override: override)
32
- elsif option && val =~ /\A(-{0,2}[^=\s-][^=\s]*)=(.+)\z/m
33
- return val if $2.match?(QUOTE_VALUE)
28
+ r[1] + (data ? data[2] : r[2]) + r[4] + shell_quote(opt, double: double, force: force, override: override)
29
+ elsif option && val =~ /\A([^=]+)=(.+)\z/m
30
+ return val if $2.match?(/\A(["']).+\1\z/m)
34
31
 
35
32
  "#{$1}=%s" % if $2.include?(' ')
36
33
  shell_quote($2, option: false)
@@ -40,7 +37,7 @@ module Squared
40
37
  Shellwords.escape($2)
41
38
  end
42
39
  elsif Rake::Win32.windows?
43
- quote ? shell_quote(val, force: force, double: double) : val
40
+ quote ? shell_quote(val, double: double, force: force) : val
44
41
  elsif val.empty?
45
42
  ''
46
43
  else
@@ -48,60 +45,46 @@ module Squared
48
45
  end
49
46
  end
50
47
 
51
- def shell_quote(val, option: true, force: true, double: false, preserve: true, override: false)
48
+ def shell_quote(val, option: true, force: true, double: false, override: false)
52
49
  val = val.to_s
53
50
  return val if (!force && !val.include?(' ')) || val.empty?
54
51
 
55
- if option
56
- pat = /\A(?:-[^=\s-](?:=|\s+)?|(--)?[^=\s-][^=\s]*(?(1)(?:=|\s+)|=))(["']).+\2\z/m
57
- return val if val.match?(pat)
58
- end
59
- q = ->(s) { s.gsub("'\\\\''", "'") }
60
- if val =~ QUOTE_VALUE
61
- return val if $1 == '"' && Rake::Win32.windows? && val.match?(/(?:[#{File::SEPARATOR} ]|\\")/o)
62
-
63
- base = $2 unless preserve
64
- end
65
- if double || Rake::Win32.windows? || (ARG[:QUOTE] == '"' && !override)
66
- "\"#{q.call(base || val).gsub(/(?<!\\)"/, '\\"')}\""
52
+ if option && val.match?(/(?:\A|\A[^=\s]+(?:=|\s+)|#{Rake::Win32.windows? ? '[\\\/]' : '\/'})(["']).+\1\z/mo)
53
+ val
54
+ elsif double || Rake::Win32.windows? || (ARG[:QUOTE] == '"' && !override)
55
+ "\"#{val.gsub(/(?<!\\)"/, '\\"')}\""
67
56
  else
68
- base ? val : "'#{q.call(val).gsub("'", "'\\\\''")}'"
57
+ "'#{val.gsub("'", "'\\\\''")}'"
69
58
  end
70
59
  end
71
60
 
72
- def shell_option(flag, val = nil, escape: true, quote: true, option: true, force: true, double: false,
73
- merge: false, override: false)
61
+ def shell_option(flag, val = nil, sep: '=', escape: true, quote: true, force: true, double: false, merge: false,
62
+ override: false)
74
63
  flag = flag.to_s
75
- if flag =~ QUOTE_VALUE
64
+ if flag =~ /\A(["'])(.+)\1\z/
76
65
  double = $1 == '"'
77
66
  flag = $2
78
67
  escape = false
79
68
  override = true
80
69
  end
81
- sep = unless flag.empty?
82
- if flag[0] == '-'
83
- if flag[1] == '-'
84
- '='
85
- else
86
- merge ? '' : ' '
87
- end
88
- elsif flag.size == 1
89
- pre = '-'
90
- merge ? '' : ' '
91
- else
92
- pre = '--'
93
- '='
94
- end
95
- end
96
- "#{pre}#{flag}#{unless val.nil?
97
- "#{sep}#{if escape
98
- shell_escape(val, quote: quote, double: double, override: override)
99
- elsif quote
100
- shell_quote(val, option: option, force: force, double: double, override: override)
101
- else
102
- val
103
- end}"
104
- end}"
70
+ b = if flag[0] == '-'
71
+ flag[1] == '-' ? sep : ' '
72
+ elsif flag.size == 1
73
+ a = '-'
74
+ merge ? '' : ' '
75
+ else
76
+ a = '--'
77
+ sep
78
+ end
79
+ "#{a}#{flag}#{unless val.nil?
80
+ "#{b}#{if escape
81
+ shell_escape(val, quote: quote, double: double, override: override)
82
+ elsif quote
83
+ shell_quote(val, option: false, force: force, double: double, override: override)
84
+ else
85
+ val
86
+ end}"
87
+ end}"
105
88
  end
106
89
 
107
90
  def shell_split(val, join: nil, **kwargs)
@@ -111,11 +94,45 @@ module Squared
111
94
  ret.join(join.is_a?(::String) ? join : ' ')
112
95
  end
113
96
 
97
+ def shell_parse(val, escape: false, force: true, **kwargs)
98
+ a = []
99
+ b = []
100
+ c = []
101
+ d = []
102
+ e = [a, b]
103
+ j = -1
104
+ val.shellsplit.each_with_index do |opt, i|
105
+ if opt == '--'
106
+ e = [c, d]
107
+ elsif opt =~ /\A--?[^=]+(=|\z)/
108
+ j = $1 == '=' ? -1 : i
109
+ e[0] << [opt]
110
+ elsif j >= 0
111
+ e[0][j] << opt
112
+ else
113
+ e[1] << shell_quote(opt, option: false, force: force)
114
+ end
115
+ end
116
+ ret = [[a, b], [], [c, d]].flat_map do |e, f|
117
+ next '--' unless e
118
+
119
+ e.flat_map do |item|
120
+ if item.size == 1
121
+ fill_option(item.first)
122
+ else
123
+ flag = item.shift
124
+ item.map! { |s| shell_option(flag, s, escape: escape, force: force, **kwargs) }
125
+ end
126
+ end.concat(f)
127
+ end
128
+ ret.pop if ret.last == '--'
129
+ ret
130
+ end
131
+
114
132
  def shell_bin(name, env: true)
115
- key = name.to_s.upcase
116
- key = File.basename(key, '.*') if Rake::Win32.windows?
117
- shell_quote((env && ENV["PATH_#{key}"]) || PATH[key] || PATH[key.to_sym] || name,
118
- option: false, force: false, double: true)
133
+ key = name.upcase
134
+ shell_quote((env && ENV["PATH_#{key}"]) || PATH[key] || PATH[key.to_sym] || name, option: false, force: false,
135
+ double: true)
119
136
  end
120
137
 
121
138
  def line_width(lines)
@@ -124,7 +141,6 @@ module Squared
124
141
  end
125
142
 
126
143
  def fill_option(val, **kwargs)
127
- return val unless val.is_a?(::String)
128
144
  return "-#{val}" if val.match?(/\A(?:[a-z]\d*|\d)\z/i)
129
145
 
130
146
  shell_escape(val.start_with?('-') ? val : "--#{val}", **kwargs)
@@ -6,6 +6,25 @@ require 'rake'
6
6
  module Squared
7
7
  module Common
8
8
  module System
9
+ class << self
10
+ private
11
+
12
+ def parse_link(val)
13
+ case val
14
+ when ::TrueClass, 's'
15
+ 1
16
+ when 'r'
17
+ 2
18
+ when 'h'
19
+ 3
20
+ else
21
+ raise ArgumentError, "unrecognized 'link' flag: #{val}" if val
22
+
23
+ 0
24
+ end
25
+ end
26
+ end
27
+
9
28
  module_function
10
29
 
11
30
  def shell(*args, name: :system, **kwargs)
@@ -29,11 +48,11 @@ module Squared
29
48
  raise $?.to_s
30
49
  end
31
50
 
32
- def copy_dir(src, dest, glob = ['**/*'], create: false, link: nil, force: false, pass: nil, hidden: false,
33
- verbose: true)
51
+ def copy_dir(src, dest, glob = ['**/*'], create: false, link: nil, preserve: nil, force: false, verbose: true,
52
+ pass: nil, hidden: false)
34
53
  base = Pathname.new(src)
35
54
  target = Pathname.new(dest)
36
- raise "#{target.cleanpath} (not found)" if !create && !target.parent.exist?
55
+ raise Errno::ENOENT, dest.cleanpath.to_s unless create || target.parent.exist?
37
56
 
38
57
  subdir = {}
39
58
  target.mkpath if create
@@ -57,53 +76,67 @@ module Squared
57
76
  end
58
77
  count = 0
59
78
  soft = 0
79
+ type = System.send :parse_link, link
60
80
  subdir.each do |dir, files|
61
- if link
62
- files.dup.tap do |items|
63
- files.clear
64
- items.each do |file|
65
- if file.exist?
66
- if !file.symlink?
67
- files << file
68
- elsif !force
69
- next
70
- end
71
- end
72
- if link == 'hard'
73
- FileUtils.ln(file, dir, force: force, verbose: false)
74
- else
75
- FileUtils.ln_s(file, dir, force: force, verbose: false)
81
+ unless type == 0
82
+ items = files.dup
83
+ files.clear
84
+ items.each do |file|
85
+ if file.exist?
86
+ if !file.symlink?
87
+ files << file
88
+ elsif !force
89
+ next
76
90
  end
77
- soft += 1
78
91
  end
92
+ case type
93
+ when 1
94
+ FileUtils.ln_s(file, dir, force: force, verbose: false)
95
+ when 2
96
+ FileUtils.ln_s(file.relative_path_from(dir), dir, force: force, verbose: false)
97
+ else
98
+ FileUtils.ln(file, dir, force: force, verbose: false)
99
+ end
100
+ soft += 1
79
101
  end
80
102
  end
81
103
  next if files.empty?
82
104
 
83
- out = FileUtils.cp(files, dir, verbose: false)
105
+ out = FileUtils.cp(files, dir, preserve: preserve, verbose: false)
84
106
  count += out.size
85
107
  end
86
108
  puts [target.realpath, subdir.size, soft > 0 ? "#{count}+#{soft}" : count].join(' => ') if verbose
87
109
  end
88
110
 
89
- def copy_guard(src, dest, link: nil, force: false, verbose: true)
90
- unless force
91
- target = Pathname.new(dest)
92
- if target.directory?
93
- src = Array(src).reject { |val| target.join(File.basename(val)).exist? }
94
- return if src.empty?
95
- elsif target.exist?
96
- return
111
+ def copy_guard(*src, dest, base: '.', create: false, link: nil, preserve: nil, force: false, verbose: true)
112
+ src = src.compact.flatten
113
+ dest = Pathname.new(dest).realdirpath
114
+ base = Pathname.new(base).realpath
115
+ dir = if dest.directory?
116
+ true
117
+ elsif src.size > 1
118
+ raise Errno::ENOENT, dest.cleanpath.to_s unless create && !dest.exist?
119
+
120
+ dest.mkpath
121
+ true
122
+ end
123
+ targets = src.map! { |file| [base + file, dir ? dest + File.basename(file) : dest] }
124
+ return if !force && (targets = targets.reject { |to| to[1].exist? }).empty?
125
+
126
+ type = System.send :parse_link, link
127
+ targets.each do |file, to|
128
+ case type
129
+ when 0
130
+ FileUtils.cp(file, to, preserve: preserve, verbose: verbose)
131
+ when 1
132
+ FileUtils.ln_s(file, to, force: force, verbose: verbose)
133
+ when 2
134
+ FileUtils.ln_s(file.relative_path_from(dir ? to.dirname : to), to, force: force, verbose: verbose)
135
+ else
136
+ FileUtils.ln(file, to, force: force, verbose: verbose)
97
137
  end
98
138
  end
99
- case link
100
- when 'hard', 1
101
- FileUtils.ln(src, dest, force: force, verbose: verbose)
102
- when ::TrueClass, 'soft', 0
103
- FileUtils.ln_s(src, dest, force: force, verbose: verbose)
104
- else
105
- FileUtils.cp(src, dest, verbose: verbose)
106
- end
139
+ nil
107
140
  end
108
141
  end
109
142
  end