/
escaped_path.rb
77 lines (67 loc) · 1.79 KB
/
escaped_path.rb
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
# frozen_string_literal: true
module Git
# Represents an escaped Git path string
#
# Git commands that output paths (e.g. ls-files, diff), will escape usual
# characters in the path with backslashes in the same way C escapes control
# characters (e.g. \t for TAB, \n for LF, \\ for backslash) or bytes with values
# larger than 0x80 (e.g. octal \302\265 for "micro" in UTF-8).
#
# @example
# Git::GitPath.new('\302\265').unescape # => "µ"
#
class EscapedPath
UNESCAPES = {
'a' => 0x07,
'b' => 0x08,
't' => 0x09,
'n' => 0x0a,
'v' => 0x0b,
'f' => 0x0c,
'r' => 0x0d,
'e' => 0x1b,
'\\' => 0x5c,
'"' => 0x22,
"'" => 0x27
}.freeze
attr_reader :path
def initialize(path)
@path = path
end
# Convert an escaped path to an unescaped path
def unescape
bytes = escaped_path_to_bytes(path)
str = bytes.pack('C*')
str.force_encoding(Encoding::UTF_8)
end
private
def extract_octal(path, index)
[path[index + 1..index + 4].to_i(8), 4]
end
def extract_escape(path, index)
[UNESCAPES[path[index + 1]], 2]
end
def extract_single_char(path, index)
[path[index].ord, 1]
end
def next_byte(path, index)
if path[index] == '\\' && path[index + 1] >= '0' && path[index + 1] <= '7'
extract_octal(path, index)
elsif path[index] == '\\' && UNESCAPES.include?(path[index + 1])
extract_escape(path, index)
else
extract_single_char(path, index)
end
end
def escaped_path_to_bytes(path)
index = 0
[].tap do |bytes|
while index < path.length
byte, chars_used = next_byte(path, index)
bytes << byte
index += chars_used
end
end
end
end
end