github.com/xyproto/orbiton/v2@v2.65.12-0.20240516144430-e10a419274ec/sortstrings.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 ) 8 9 // Word is a type for a string in a list of strings, that may be quoted 10 type Word struct { 11 s string 12 singleQuoted bool 13 doubleQuoted bool 14 } 15 16 // Words is a slice of Word 17 type Words []Word 18 19 // Len helps make Words sortable. 20 // Len is the number of elements in the collection. 21 func (ws Words) Len() int { 22 return len(ws) 23 } 24 25 // Less helps make Words sortable. 26 // Less reports whether the element with 27 // index i should sort before the element with index j. 28 func (ws Words) Less(i, j int) bool { 29 // This did not provide a good sorting order for strings: 30 // return ws[i].s < ws[j].s 31 32 // This worked out, but is probably quite a bit slower: 33 twoStrings := []string{ws[i].s, ws[j].s} 34 sort.Strings(twoStrings) 35 return twoStrings[0] == ws[i].s 36 } 37 38 // Swap helps make Words sortable. 39 // Swap swaps the elements with indexes i and j. 40 func (ws Words) Swap(i, j int) { 41 ws[i], ws[j] = ws[j], ws[i] 42 } 43 44 // sortStrings tries to identify and sort a given list of strings in a string. 45 // The strings are expected to be surrounded by either () or {}, 46 // or that the entire given string is a list of strings. 47 // The strings may be space separated or comma separated (consistently). 48 // The strings may be single quoted, double quoted or none (may be non-consistent). 49 func sortStrings(line string) (string, error) { 50 trimmedLine := strings.TrimSpace(line) 51 52 // TODO: Bake the ({[ detection into the rune loop below 53 54 // This check is very basic and will fail at strings like: 55 // example=[[ "hello (0)", "hello (1)" ]] 56 surroundedByCurly := strings.Count(trimmedLine, "{") == 1 && strings.Count(trimmedLine, "}") == 1 57 surroundedByPar := strings.Count(trimmedLine, "(") == 1 && strings.Count(trimmedLine, ")") == 1 58 surroundedBySquareBrackets := strings.Count(trimmedLine, "[") == 1 && strings.Count(trimmedLine, "]") == 1 59 60 // Find the "center" string (a list of strings within {} or () on the current line) 61 center := "" 62 if surroundedByCurly { 63 fields := strings.SplitN(trimmedLine, "{", 2) 64 if len(fields) != 2 { 65 return line, fmt.Errorf("curly brackets have an unusual order: %v", fields) 66 } 67 fields2 := strings.SplitN(fields[1], "}", 2) 68 if len(fields2) != 2 { 69 return line, fmt.Errorf("curly brackets have an unusual order: %v", fields2) 70 } 71 center = fields2[0] 72 } else if surroundedByPar { 73 fields := strings.SplitN(trimmedLine, "(", 2) 74 if len(fields) != 2 { 75 return line, fmt.Errorf("parentheses have an unusual order %v", fields) 76 } 77 fields2 := strings.SplitN(fields[1], ")", 2) 78 if len(fields2) != 2 { 79 return line, fmt.Errorf("parentheses have an unusual order %v", fields2) 80 } 81 center = fields2[0] 82 } else if surroundedBySquareBrackets { 83 fields := strings.SplitN(trimmedLine, "[", 2) 84 if len(fields) != 2 { 85 return line, fmt.Errorf("square brackets have an unusual order %v", fields) 86 } 87 fields2 := strings.SplitN(fields[1], "]", 2) 88 if len(fields2) != 2 { 89 return line, fmt.Errorf("square brackets have an unusual order %v", fields2) 90 } 91 center = fields2[0] 92 } else { 93 // Assume that the entire given string is a list of strings, without surrounding (), {} or [] 94 center = trimmedLine 95 } 96 97 // Okay, we have a "center" string containing a list of strings, that may be quoted, may be comma separated 98 // The only thing sure to be consistent is either commas or spaces 99 inSingleQuote := false 100 inDoubleQuote := false 101 commaCount := 0 102 spaceCount := 0 103 104 // Loop over the runes in the "center" string to count the commas 105 // and spaces that are not within single or double quotes. 106 for _, r := range center { 107 switch r { 108 case '\'': // single quote 109 if !inDoubleQuote { 110 inSingleQuote = !inSingleQuote 111 } 112 case '"': // double quote 113 if !inSingleQuote { 114 inDoubleQuote = !inDoubleQuote 115 } 116 case ',': 117 if !inSingleQuote && !inDoubleQuote { 118 commaCount++ 119 } 120 case ' ': 121 if !inSingleQuote && !inDoubleQuote { 122 spaceCount++ 123 } 124 } 125 } 126 127 // Are we dealing with comma-separated or space-separated strings? 128 // TODO: This will not work if the strings contains spaces 129 commaSeparated := commaCount >= spaceCount 130 131 // Split the string into a []string 132 var fields []string 133 if commaSeparated { 134 fields = strings.Split(center, ",") 135 } else { 136 fields = strings.Split(center, " ") 137 } 138 139 // Convert the string fields to Words 140 words := make(Words, len(fields)) 141 for i, field := range fields { 142 trimmedElement := strings.TrimSpace(field) 143 // Remove the trailing comma after the word, if any 144 if strings.HasSuffix(trimmedElement, ",") { 145 trimmedElement = strings.TrimSpace(trimmedElement[:len(trimmedElement)-1]) 146 } 147 var w Word 148 // Prepare a Word struct, depending on how this trimmed element is quoted 149 if strings.HasPrefix(trimmedElement, "'") && strings.HasSuffix(trimmedElement, "'") { 150 w.s = trimmedElement[1 : len(trimmedElement)-1] 151 w.singleQuoted = true 152 // fmt.Println("SINGLE QUOTED:", w.s) 153 } else if strings.HasPrefix(trimmedElement, "\"") && strings.HasSuffix(trimmedElement, "\"") { 154 w.s = trimmedElement[1 : len(trimmedElement)-1] 155 w.doubleQuoted = true 156 // fmt.Println("DOUBLE QUOTED:", w.s) 157 } else { 158 w.s = trimmedElement 159 // fmt.Println("NOT QUOTED:", w.s) 160 } 161 // Save the Word 162 words[i] = w 163 } 164 165 // fmt.Println("WORDS", words) 166 167 // Sort the Words 168 sort.Sort(words) 169 170 // Join the words to a center string with the same type of separation and quoting as the original string 171 var sb strings.Builder 172 lastIndex := len(words) - 1 173 for i, word := range words { 174 if word.singleQuoted { // single quoted 175 sb.WriteRune('\'') 176 sb.WriteString(word.s) 177 sb.WriteRune('\'') 178 } else if word.doubleQuoted { // double quoted 179 sb.WriteRune('"') 180 sb.WriteString(word.s) 181 sb.WriteRune('"') 182 } else { // bare 183 sb.WriteString(word.s) 184 } 185 if i == lastIndex { 186 // Break before adding a ", " or " " suffix to the string 187 break 188 } 189 if commaSeparated { 190 sb.WriteRune(',') 191 } 192 // Write a space after the comma, or just a space if the strings are space separated. 193 // This covers both. 194 sb.WriteRune(' ') 195 } 196 newCenter := sb.String() 197 198 // Okay, now replace the old list of strings with the new one, once 199 return strings.Replace(line, center, newCenter, 1), nil 200 } 201 202 // SortStrings tries to find and sort a list of strings on the current line. 203 // The strings are expected to be surrounded by either () or {}, 204 // or that the entire given string is a list of strings. 205 // The strings may be space separated or comma separated (consistently). 206 // The strings may be single quoted, double quoted or none (may be non-consistent). 207 func (e *Editor) SortStrings() error { 208 // Sort the strings in the current line, return an error if there are problems 209 newCurrentLine, err := sortStrings(e.CurrentLine()) 210 if err != nil { 211 return err 212 } 213 // Use the sorted line and return 214 e.SetLine(e.DataY(), newCurrentLine) 215 return nil 216 }