如何在 Ruby 中使用省略号缩短文件路径?

How to shorten file paths using ellipses in Ruby?

在Ruby(而不是Rails)中,有没有一种直接的方法来获取较长的文件路径并将其中不重要的部分替换为省略号,以使其更短以便显示?

例如:

a/very/long/path/with/plenty/of/characters/that/won't/fit/on/my/screen/easily

变成类似:

a/very/lo.../screen/easily

我需要能够指定最大长度;并且路径的起点和终点应该始终可见。

如果我用头撞得足够久,我可能会想出一个解决方案,但也许有人知道办法?

有点像这样:

shortener = ->(path, length) {
  l = length / 2 - 1
  [path[0...l], path[-l..-1]].join('..')
}
shortener.(path, 10)
#⇒ "a/ve..sily"

我使用左指针和右指针(lt_ptrrt_ptr)构建要保留并用省略号分隔的字符串的两个部分。

我首先减少指向最后一个正斜杠的右指针,然后增加指向第一个正斜杠的左指针,然后减少指向 next-to-last 正斜杠的右指针,依此类推直到进一步移动指针会使包含省略号的字符串长于最大允许长度。

代码

def shorten_string(str, max_len, ellipsis = '...')
  return str if str.size <= max_len
  max_len -= ellipsis.size
  ops = [{ index: :index, ptr: 0, chg: 1 },
         { index: :rindex, ptr: str.size-1, chg: -1 } ].cycle
  op = ops.next
  success = true
  loop do
    op = ops.next
    ptr = str.public_send(op[:index], '/', op[:ptr] + op[:chg] )
    lptr = ptr
    rptr = ops.peek[:ptr]
    lptr, rptr = rptr, lptr if op[:index] == :rindex
    if lptr + 1 + str.size - rptr <= max_len
      op[:ptr] = ptr
    else
      break unless success
      success = false
    end        
  end
  op = ops.next if op[:index] == :rindex
  "%s%s%s" % [str[0..op[:ptr]], ellipsis, str[ops.peek[:ptr]..-1]]
end

例子

str = 'a/very/long/path/with/too/many/characters/to/fit/on/my/screen/easily'
shorten_string(str, 40)
  #=> "a/very/long/path/.../on/my/screen/easily" (length: 40)
shorten_string(str, 30)
  #=> "a/very/.../on/my/screen/easily"           (length: 30)
shorten_string(str, 20)
  #=> "a/.../screen/easily"                      (length: 19) 

当然巧合的是,在前两个示例中,结果字符串(带有省略号)的长度恰好等于最大长度 max_length。请注意,在第二个示例中,省略号之后有 4 个正斜杠,而之前只有 2 个。那是因为在添加 "/my" 之后只能添加 3 个字符,对于 "long/""very/" 之后)来说不够,但对于 "/on".

来说已经足够了

说明

这使用了采用第二个参数的 String#index and String#rindex 形式。

为了更好地理解正在执行的计算,我建议在用 puts 语句对代码加盐之后,针对示例 运行 代码。修改后的方法示例如下。

def shorten_string(str, max_len, ellipsis = '...')
  return str if str.size <= max_len
  max_len -= ellipsis.size
  puts "str.size=#{str.size}"                                   #!!!!
  ops = [{ index: :index, ptr: 0, chg: 1 },
         { index: :rindex, ptr: str.size-1, chg: -1 } ].cycle
  op = ops.next
  success = true
  loop do
    op = ops.next
    puts "\nop=#{op}, ops.peek=#{ops.peek}"                     #!!!!
    ptr = str.public_send(op[:index], '/', op[:ptr] + op[:chg] )
    lptr = ptr
    rptr = ops.peek[:ptr]
    puts "ptr=#{ptr}, lptr=#{lptr}, rptr=#{rptr}"               #!!!!
    lptr, rptr = rptr, lptr if op[:index] == :rindex
    puts "after possible flip, lptr=#{lptr}, rptr=#{rptr}"      #!!!!
    puts "lptr + 1 + str.size - rptr = #{lptr+1+str.size-rptr}" #!!!!
    if lptr + 1 + str.size - rptr <= max_len
      op[:ptr] = ptr
      puts "after lptr+1+str.size-rptr <= max_len, op=#{op}"    #!!!!
    else
      break unless success
      success = false
    end        
  end
  puts "after loop op=#{op}, ops.peek=#{ops.peek}"              #!!!!
  op = ops.next if op[:index] == :rindex
  "%s%s%s" % [str[0..op[:ptr]], ellipsis, str[ops.peek[:ptr]..-1]]
end
ELLIPSIS = '...'
SEPARATOR = '\'


def truncate_at_middle_of_filename(filename: str, maxlength: int):
    # nothing to do, already fits
    print(f'Original : {filename}   length {len(filename)}')
    if len(filename) <= maxlength:
        return filename
    segments = filename.split(SEPARATOR)

    return _remove_segment_one_by_one(segments, maxlength)


def _remove_segment_one_by_one(segments: list, maxlength: int):
    newstring = SEPARATOR.join(segments)
    while len(newstring) > maxlength:
        splitpoint = len(segments) // 2
        try:
            segments[splitpoint] = ELLIPSIS
            newstring = SEPARATOR.join(segments)
            del segments[splitpoint]
        except:
            return ""

    return newstring

result = truncate_at_middle_of_filename("C:\Windows\SysWOW64\catroot\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}",60)
print(f'Truncated: {result}   length {len(result)}')

# Truncated: C:\Windows\...\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}   length 53