Module: LibPath::Util::Windows::LibPath_Util_Windows_Methods

Included in:
LibPath::Util::Windows, LibPath::Util::Windows
Defined in:
lib/libpath/util/windows.rb

Overview

Module defining instance functions that will be included and extended into any class or module including/extending module LibPath::Util::Windows

Instance Method Summary collapse

Instance Method Details

#combine_paths(*args, **options) ⇒ Object

Combines a number of path parts into a single path, ignoring any parts that are preceded by an absolute part

Because Windows paths’ absolute nature comprises two elements - the volume(/drive) and the root directory - it is possible to combine elements where either the volume or the root directory is missing.

NOTE: The behaviour of this method is undefined if any of the parts are malformed. See ::LibPath::Form::Windows::name_is_malformed?



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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/libpath/util/windows.rb', line 74

def combine_paths *args, **options

  _Form_Windows           = Form::Windows
  _Internal_Windows_Form  = Internal_::Windows::Form

  args.each_with_index { |arg, index| Diagnostics.check_string_parameter(arg, "arg#{index}", allow_nil: true) } if $DEBUG

  first = []
  dirs  = []
  last  = []

  if options[:elide_single_dots]

    args = args.map do |arg|

      case arg
      when '.', './'

        nil
      else

        arg
      end
    end
  end

  args    = args.reject { |arg| arg.nil? || arg.empty? }

  rix_abs = nil
  rix_drv = nil
  rix_dir = nil

  args.each_with_index do |arg, index|

    vol, rem, _ = _Internal_Windows_Form.get_windows_volume arg

    rem = nil unless rem && _Internal_Windows_Form.char_is_path_name_separator?(rem[0])

    if vol

      if rem

        rix_abs = index
      else

        rix_drv = index
      end
    elsif rem

      rix_dir = index
    end
  end

  rix_drv = nil if (rix_drv || -1) <= (rix_abs || -1)
  rix_dir = nil if (rix_dir || -1) <= (rix_abs || -1)

  if rix_drv && rix_dir && rix_abs

    if rix_abs < rix_drv && rix_abs < rix_dir

      rix_abs +=  1
      args    =   args[rix_abs..-1]
      rix_drv -=  rix_abs
      rix_dir -=  rix_abs
      rix_abs =   nil
    end
  end

  if rix_drv.nil? && rix_dir.nil?

    if rix_abs

      args = args[rix_abs..-1]
    end

    dirs  =   args
    last  <<  args.pop unless args.empty?
  else

    if false

      ;
    elsif rix_drv

      if rix_dir

        drv     =   args.delete_at rix_drv
        rix_dir -=  1 if rix_drv < rix_dir
        dir     =   args.delete_at rix_dir

        args = args[rix_dir..-1]

        if dir.size > 1

          args.unshift dir[1..-1]
          dir = dir[0]
        end

        root  =   _Internal_Windows_Form.append_trailing_slash("#{drv}#{dir}")

        first <<  root
        last  <<  args.pop unless args.empty?
        dirs  =   args
      elsif rix_abs

        drv     =   args.delete_at rix_drv
        rix_abs -=  1 if rix_drv < rix_abs
        abs     =   args.delete_at rix_abs

        _, _, dir, bas, _, _, _, _ = _Internal_Windows_Form.split_path abs

        args = args[rix_abs..-1]

        if dir.size > 1

          args.unshift dir[1..-1]
          dir = dir[0]
        end

        root  =   _Internal_Windows_Form.append_trailing_slash("#{drv}#{dir}#{bas}")

        first <<  root
        last  <<  args.pop unless args.empty?
        dirs  =   args
      else

        first <<  args.delete_at(rix_drv)
        last  <<  args.pop unless args.empty?
        dirs  =   args
      end
    elsif rix_dir

      if rix_abs

        abs     =   args.delete_at rix_abs
        rix_dir -=  1 if rix_abs < rix_dir
        dir     =   args.delete_at rix_dir

        _, vol, _, _, _, _, _, _ = _Internal_Windows_Form.split_path abs

        args = args[rix_dir..-1]

        root  =   _Internal_Windows_Form.append_trailing_slash("#{vol}#{dir}")

        first <<  root
        last  <<  args.pop unless args.empty?
        dirs  =   args
      else

        args  =   args[rix_dir..-1]
        last  <<  args.pop unless args.empty?
        dirs  =   args
      end
    else

      ;
    end
  end

  dirs = dirs.map { |el| _Internal_Windows_Form.append_trailing_slash el }

  (first + dirs + last).join('')
end

#derive_relative_path(origin, path, **options) ⇒ Object

Obtains the form of the given path relative to the given origin

NOTE: The behaviour of this method is undefined if any of the parts are malformed. See ::LibPath::Form::Windows::name_is_malformed?

Signature

  • Options:

+:home+:: (String)
+:locator+:: (boolean)
+:make_canonical+:: (boolean)
+:pwd+:: (String)


251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/libpath/util/windows.rb', line 251

def derive_relative_path origin, path, **options

  return path if origin.nil? || origin.empty?
  return path if path.nil? || path.empty?

  _Form_Windows           = Form::Windows
  _Util_Windows           = Util::Windows
  _Internal_Windows_Form  = Internal_::Windows::Form

  _MPA_COMMON_OPTIONS = %i{ home locator pwd }

  tr_sl = _Internal_Windows_Form.get_trailing_slash(path)

  # Possibly naive home-correction

  return derive_relative_path(absolute_path(origin), path, **options) if _Form_Windows.path_is_homed?(origin)
  return derive_relative_path(origin, absolute_path(path), **options) if _Form_Windows.path_is_homed?(path)


  o_vol, o_rem, _ = _Internal_Windows_Form.get_windows_volume origin
  p_vol, p_rem, _ = _Internal_Windows_Form.get_windows_volume path

  if o_vol && p_vol

    # always give absolute answer when 'volume's are different

    if o_vol != p_vol

      if options[:make_path_canonical]

        path = _Util_Windows.make_path_canonical(path, make_slashes_canonical: true)
      else

        path = path.tr('/', '\\')
      end

      return path
    end
  end


  o_is_rooted = o_rem && _Internal_Windows_Form.char_is_path_name_separator?(o_rem[0])
  p_is_rooted = p_rem && _Internal_Windows_Form.char_is_path_name_separator?(p_rem[0])

  o_is_abs = o_vol && o_is_rooted
  p_is_abs = p_vol && p_is_rooted

  mpa_opts = options.select { |k| _MPA_COMMON_OPTIONS.include?(k) }

  if o_is_abs != p_is_abs || o_is_rooted != p_is_rooted

    origin  = _Util_Windows.make_path_absolute(origin, **mpa_opts) unless o_is_abs
    path    = _Util_Windows.make_path_absolute(path, **mpa_opts) unless p_is_abs

    return derive_relative_path(origin, path, **options)
  end

  origin  = _Util_Windows.make_path_canonical(origin, make_slashes_canonical: true)
  path    = _Util_Windows.make_path_canonical(path, make_slashes_canonical: true)

  return '.' + tr_sl.to_s if origin == path
  return path if '.\\' == origin

  if o_is_abs != p_is_abs || '.\\' == path

    origin  = _Util_Windows.make_path_absolute(origin, make_canonical: true, **options.select { |k| _MPA_COMMON_OPTIONS.include?(k) })
    path    = _Util_Windows.make_path_absolute(path, make_canonical: true, **options.select { |k| _MPA_COMMON_OPTIONS.include?(k) })
  end


  _, _, _, o3_basename, _, _, o6_parts, _ = _Internal_Windows_Form.split_path(origin)
  _, _, _, p3_basename, _, _, p6_parts, _ = _Internal_Windows_Form.split_path(path)

  o_parts =   o6_parts
  o_parts <<  o3_basename if o3_basename && '.' != o3_basename

  p_parts =   p6_parts
  p_parts <<  p3_basename if p3_basename && '.' != p3_basename


  while true

    break if o_parts.empty?
    break if p_parts.empty?

    o_part = o_parts[0]
    p_part = p_parts[0]

    if 1 == o_parts.size || 1 == p_parts.size

      o_part = _Internal_Windows_Form.append_trailing_slash o_part
      p_part = _Internal_Windows_Form.append_trailing_slash p_part
    end

    parts_equal = false

    if o_part.size == p_part.size

      o_part = o_part.tr('/', '\\') if o_part.include? '/'
      p_part = p_part.tr('/', '\\') if p_part.include? '/'

      parts_equal = o_part == p_part
    end


    if parts_equal

      o_parts.shift
      p_parts.shift
    else

      break
    end
  end


  return '.' + tr_sl.to_s if 0 == (o_parts.size + p_parts.size)

  return o_parts.map { |rp| '..' }.join('\\') + (tr_sl || (o_parts.size > 0 ? '\\' : nil)).to_s if p_parts.empty?


  ar    = [ '..' ] * o_parts.size + p_parts
  last  = ar.pop
  ar    = ar.map { |el| _Internal_Windows_Form.append_trailing_slash(el) }

  last[-1] = '\\' if '/' == last[-1]

  ar.join + last.to_s
end

#make_compare_path(path, **options) ⇒ Object

Returns a “compare path” for the given absolute path

A compare path is one that would refer definitely to a given entry, regardless of such operating system-specific issues such as case-insensitivity

NOTE: the function does not make path absolute. That is up to the caller if required

NOTE: The behaviour of this method is undefined if any of the parts are malformed. See ::LibPath::Form::Windows::name_is_malformed?

Signature

  • Parameters:

  • path

    (String) The path whose definitive equivalent is to be

    obtained
    
  • options

    (Hash) options

  • Options:

  • :splits

    ([ String ]) An array of string-like objects. If the

    object at index 1 exists and supports the +form+ attribute and
    that returns one of { +:form_2+, +:form_3+, +:form_4+, +:form_5+,
    +:form_6+ } then it is assumed to be the volume, and the objects
    at indexes 2 and 3 are assumed to be the directory and the
    basename, respectively. In this case, the compare path is
    constructed from a UNC-respecting form
    


408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/libpath/util/windows.rb', line 408

def make_compare_path path, **options

  if splits = options[:splits]

    if volume = splits[1]

      if volume.respond_to?(:form)

        case volume.form
        when :form_2, :form_3, :form_4, :form_5, :form_6

          directory = splits[2] || ''
          basename  = splits[3] || ''

          return "#{volume}#{directory.upcase}#{basename.upcase}"
        else

        end
      end
    end
  end

  path.upcase
end

#make_path_absolute(path, **options) ⇒ Object

NOTE: The behaviour of this method is undefined if any of the parts are malformed. See ::LibPath::Form::Windows::name_is_malformed?



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/libpath/util/windows.rb', line 436

def make_path_absolute path, **options

  _Form_Windows           = Form::Windows
  _Internal_Windows_Form  = Internal_::Windows::Form

  Diagnostics.check_string_parameter(path, "path") if $DEBUG
  Diagnostics.check_options(options, known: %i{ home locator make_canonical pwd }) if $DEBUG

  return path if path.nil? || path.empty?

  r = nil

  if false

    ;
  elsif _Form_Windows.path_is_homed? path

    home  =   nil
    home  ||= options[:home]
    home  ||= options[:locator].home if options.has_key?(:locator)
    home  ||= Dir.home

    unless _Internal_Windows_Form.has_trailing_slash? home

      home = home + path[1].to_s
    end

    r = combine_paths(home, path[2..-1])
  elsif _Form_Windows.path_is_UNC? path

    r = path
  elsif _Form_Windows.path_is_absolute? path

    r = path
  elsif _Form_Windows.path_is_rooted? path

    pwd =   nil
    pwd ||= options[:pwd]
    pwd ||= options[:locator].pwd if options.has_key?(:locator)
    pwd ||= Dir.pwd

    r = pwd[0..1] + path
  else

    pwd =   nil
    pwd ||= options[:pwd]
    pwd ||= options[:locator].pwd if options.has_key?(:locator)
    pwd ||= Dir.pwd

    r = combine_paths(pwd, path, elide_single_dots: false)
  end

  if options[:make_canonical]

    r = make_path_canonical r
  else

    vol, rem, _ = _Internal_Windows_Form.get_windows_volume r

    _Internal_Windows_Form.elide_redundant_path_name_separators! rem

    r = "#{vol}#{rem}"
  end

  return r
end

#make_path_canonical(path, **options) ⇒ Object

Converts a path into canonical form, which is to say that all possible dots directory parts are removed:

  • single-dot parts - ‘./’ or ‘.\’ - are all removed

  • double-dot parts - ‘../’ or ‘..\’ - are removed where they follow a

    non-dots directory part, or where they follow the root
    

NOTE: The behaviour of this method is undefined if any of the parts are malformed. See ::LibPath::Form::Windows::name_is_malformed?

Signature

  • Parameters:

    • path

      (String) The path to be evaluated. May not be nil

  • Options:

    • :make_slashes_canonical

      (boolean) Determines whether to

      additionally convert all forward slashes to backslashes
      


521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/libpath/util/windows.rb', line 521

def make_path_canonical path, **options

  Diagnostics.check_string_parameter(path, "path") if $DEBUG

  if path.include?('/') && options[:make_slashes_canonical]

    path = path.tr '/', '\\'
  end

  return path unless '.' == path[-1] || path =~ /[.\\\/][\\\/]/

  _Form   = ::LibPath::Internal_::Windows::Form
  _Array  = ::LibPath::Internal_::Array

  path = path[0...-1] if '.' == path[-1] && _Form.char_is_path_name_separator?(path[-2])


  f0_path, f1_volume, f2_directory, f3_basename, _, _, f6_dir_parts, _ = _Form.split_path path

  if f6_dir_parts.empty?

    case f3_basename
    when '.'

      return "#{f1_volume}.\\"
    when '..'

      return "#{f1_volume}..\\"
    else

      return f0_path
    end
  end

  last_slash = nil

  case f3_basename
  when '.', '..'

    f6_dir_parts  <<  f3_basename + '\\'
    basename      =   nil
  when nil

    last_slash = _Form.get_trailing_slash(f2_directory) || '\\'
  else

    basename = f3_basename
  end

  is_rooted = _Form.char_is_path_name_separator?(f2_directory[0])

  new_parts = f6_dir_parts.dup
  new_parts.reject! { |p| '.\\' == p || './' == p }
  ix_nodots = new_parts.index { |p| '../' != p && '..\\' != p } || new_parts.size
  ix_2dots  = _Array.index2(new_parts, '../', '..\\', ix_nodots)

  return "#{f1_volume}#{new_parts.join}#{basename}" unless new_parts.size != f6_dir_parts.size || ix_2dots

  while (ix_2dots || 0) > 0

    new_parts.delete_at(ix_2dots - 0)
    new_parts.delete_at(ix_2dots - 1) if ix_2dots != 1 || !is_rooted

    ix_nodots = new_parts.index { |p| '../' != p && '..\\' != p } or break
    ix_2dots  = _Array.index2(new_parts, '../', '..\\', ix_nodots)
  end

  if new_parts.empty? && (basename || '').empty?

    case f3_basename
    when nil, '.', '..'

      return '.' + (last_slash || '\\').to_s
    else

      return '.'
    end
    return '.' + last_slash.to_s
  end

  return f1_volume.to_s + new_parts.join('') + basename.to_s
end