gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }