v2 / vlib / v / util / quote.v
133 lines · 125 sloc · 2.55 KB · 9f666ad11797f13aa597f2781bf909d756054698
Raw
1module util
2
3import strings
4
5const invalid_escapes = r'({$`.'.bytes()
6
7const backslash = 92
8
9const backslash_r = 13
10
11const backslash_n = 10
12
13const double_quote = 34
14
15const double_escape = '\\\\'
16
17@[direct_array_access]
18pub fn smart_quote(str string, raw bool) string {
19 len := str.len
20 if len == 0 {
21 return ''
22 }
23 if len < 256 {
24 mut is_pure := true
25 for i := 0; i < len; i++ {
26 ch := u8(str[i])
27 if (ch >= 37 && ch <= 90) || (ch >= 95 && ch <= 126) || ch in [` `, `!`, `#`, `[`, `]`] {
28 // safe punctuation + digits + big latin letters,
29 // small latin letters + more safe punctuation,
30 // important punctuation exceptions, that are not
31 // placed conveniently in a consequitive span in
32 // the ASCII table.
33 continue
34 }
35 is_pure = false
36 break
37 }
38 if is_pure {
39 return str
40 }
41 }
42 // ensure there is enough space for the potential expansion of several \\ or \n
43 mut result := strings.new_builder(len + 10)
44 mut pos := -1
45 mut last := u8(0)
46 mut current := u8(0)
47 mut next := u8(0)
48 mut skip_next := false
49 for {
50 pos++
51 if skip_next {
52 skip_next = false
53 pos++
54 }
55 if pos >= len {
56 break
57 }
58 last = current
59 current = str[pos]
60 if pos + 1 < len {
61 next = str[pos + 1]
62 } else {
63 next = 0
64 }
65 if current == double_quote {
66 current = 0
67 result.write_u8(backslash)
68 result.write_u8(double_quote)
69 continue
70 }
71 if current == backslash {
72 if raw {
73 result.write_string(double_escape)
74 continue
75 }
76 if next == backslash {
77 // escaped backslash - keep as is
78 current = 0
79 skip_next = true
80 result.write_string(double_escape)
81 continue
82 }
83 if next != 0 {
84 if raw {
85 skip_next = true
86 result.write_string(double_escape)
87 continue
88 }
89 if next in invalid_escapes {
90 current = 0
91 skip_next = true
92 result.write_u8(next)
93 continue
94 }
95 // keep all valid escape sequences
96 skip_next = true
97 result.write_u8(current)
98 result.write_u8(next)
99 current = 0
100 continue
101 }
102 }
103 if current == backslash_n {
104 // keep newlines in string
105 current = 0
106 result.write_u8(backslash)
107 result.write_u8(`n`)
108 continue
109 }
110 if current == backslash_r && next == backslash_n {
111 result.write_u8(current)
112 result.write_u8(next)
113 current = 0
114 skip_next = true
115 continue
116 }
117 // protect against '\u005c${...}'
118 if next == 0 && current == backslash {
119 continue
120 }
121 if !raw {
122 if current == `$` {
123 if last == backslash {
124 result.write_u8(last)
125 result.write_u8(current)
126 continue
127 }
128 }
129 }
130 result.write_u8(current)
131 }
132 return result.str()
133}
134