util.go (4197B)
1 package kv 2 3 import ( 4 "strconv" 5 "strings" 6 7 "codeberg.org/gruf/go-byteutil" 8 "codeberg.org/gruf/go-kv/format" 9 ) 10 11 // AppendQuoteString will append (and escape/quote where necessary) a field string. 12 func AppendQuoteString(buf *byteutil.Buffer, str string) { 13 switch { 14 case len(str) == 0: 15 // Append empty quotes. 16 buf.B = append(buf.B, `""`...) 17 return 18 19 case len(str) == 1: 20 // Append quote single byte. 21 appendQuoteByte(buf, str[0]) 22 return 23 24 case len(str) > format.SingleTermLine || !format.IsSafeASCII(str): 25 // Long line or contains non-ascii chars. 26 buf.B = strconv.AppendQuote(buf.B, str) 27 return 28 29 case !isQuoted(str): 30 // Not single/double quoted already. 31 32 if format.ContainsSpaceOrTab(str) { 33 // Quote un-enclosed spaces. 34 buf.B = append(buf.B, '"') 35 buf.B = append(buf.B, str...) 36 buf.B = append(buf.B, '"') 37 return 38 } 39 40 if format.ContainsDoubleQuote(str) { 41 // Contains double quote, double quote 42 // and append escaped existing. 43 buf.B = append(buf.B, '"') 44 buf.B = format.AppendEscape(buf.B, str) 45 buf.B = append(buf.B, '"') 46 return 47 } 48 } 49 50 // Double quoted, enclosed in braces, or 51 // literally anything else: append as-is. 52 buf.B = append(buf.B, str...) 53 return 54 } 55 56 // AppendQuoteValue will append (and escape/quote where necessary) a formatted value string. 57 func AppendQuoteValue(buf *byteutil.Buffer, str string) { 58 switch { 59 case len(str) == 0: 60 // Append empty quotes. 61 buf.B = append(buf.B, `""`...) 62 return 63 64 case len(str) == 1: 65 // Append quote single byte. 66 appendQuoteByte(buf, str[0]) 67 return 68 69 case len(str) > format.SingleTermLine || !format.IsSafeASCII(str): 70 // Long line or contains non-ascii chars. 71 buf.B = strconv.AppendQuote(buf.B, str) 72 return 73 74 case !isQuoted(str): 75 // Not single/double quoted already. 76 77 // Get space / tab indices (if any). 78 s := strings.IndexByte(str, ' ') 79 t := strings.IndexByte(str, '\t') 80 81 // Find first whitespace. 82 sp0 := smallest(s, t) 83 if sp0 < 0 { 84 break 85 } 86 87 // Check if str is enclosed by braces. 88 // (but without any key-value separator). 89 if (enclosedBy(str, sp0, '{', '}') || 90 enclosedBy(str, sp0, '[', ']') || 91 enclosedBy(str, sp0, '(', ')')) && 92 strings.IndexByte(str, '=') < 0 { 93 break 94 } 95 96 if format.ContainsDoubleQuote(str) { 97 // Contains double quote, double quote 98 // and append escaped existing. 99 buf.B = append(buf.B, '"') 100 buf.B = format.AppendEscape(buf.B, str) 101 buf.B = append(buf.B, '"') 102 return 103 } 104 105 // Quote un-enclosed spaces. 106 buf.B = append(buf.B, '"') 107 buf.B = append(buf.B, str...) 108 buf.B = append(buf.B, '"') 109 return 110 } 111 112 // Double quoted, enclosed in braces, or 113 // literally anything else: append as-is. 114 buf.B = append(buf.B, str...) 115 return 116 } 117 118 // appendEscapeByte will append byte to buffer, quoting and escaping where necessary. 119 func appendQuoteByte(buf *byteutil.Buffer, c byte) { 120 switch c { 121 // Double quote space. 122 case ' ': 123 buf.B = append(buf.B, '"', c, '"') 124 125 // Escape + double quote. 126 case '\a': 127 buf.B = append(buf.B, '"', '\\', 'a', '"') 128 case '\b': 129 buf.B = append(buf.B, '"', '\\', 'b', '"') 130 case '\f': 131 buf.B = append(buf.B, '"', '\\', 'f', '"') 132 case '\n': 133 buf.B = append(buf.B, '"', '\\', 'n', '"') 134 case '\r': 135 buf.B = append(buf.B, '"', '\\', 'r', '"') 136 case '\t': 137 buf.B = append(buf.B, '"', '\\', 't', '"') 138 case '\v': 139 buf.B = append(buf.B, '"', '\\', 'v', '"') 140 141 // Append as-is. 142 default: 143 buf.B = append(buf.B, c) 144 } 145 } 146 147 // isQuoted checks if string is single or double quoted. 148 func isQuoted(str string) bool { 149 return (str[0] == '"' && str[len(str)-1] == '"') || 150 (str[0] == '\'' && str[len(str)-1] == '\'') 151 } 152 153 // smallest attempts to return the smallest positive value of those given. 154 func smallest(i1, i2 int) int { 155 if i1 >= 0 && (i2 < 0 || i1 < i2) { 156 return i1 157 } 158 return i2 159 } 160 161 // enclosedBy will check if given string is enclosed by end, and at least non-whitespace up to start. 162 func enclosedBy(str string, sp int, start, end byte) bool { 163 // Check for ending char in string. 164 if str[len(str)-1] != end { 165 return false 166 } 167 168 // Check for starting char in string. 169 i := strings.IndexByte(str, start) 170 if i < 0 { 171 return false 172 } 173 174 // Check before space. 175 return i < sp 176 }