github.com/lheiskan/zebrapack@v4.1.1-0.20181107023619-e955d028f9bf+incompatible/cmd/addzid/ignorespaces.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 ) 8 9 const ( 10 success = "" 11 needExactValues = "This assertion requires exactly %d comparison values (you provided %d)." 12 shouldMatchModulo = "Expected expected string '%s'\n and actual string '%s'\n to match (ignoring %s)\n (but they did not!; first diff at '%s', pos %d); and \nFull diff -b:\n%s\n" 13 shouldStartWithModulo = "Expected expected PREFIX string '%s'\n to be found at the start of actual string '%s'\n to (ignoring %s)\n (but they did not!; first diff at '%s', pos %d); and \nFull diff -b:\n%s\n" 14 shouldContainModuloWS = "Expected expected string '%s'\n to contain string '%s'\n (ignoring whitespace)\n (but it did not!)" 15 shouldBothBeStrings = "Both arguments to this assertion must be strings (you provided %v and %v)." 16 ) 17 18 // ShouldMatchModulo receives exactly two string parameters and an ignore map. It ensures that the order 19 // of not-ignored characters in the two strings is identical. Runes specified in the ignore map 20 // are ignored for the purposes of this string comparison, and each should map to true. 21 // ShouldMatchModulo thus allows you to do whitespace insensitive comparison, which is very useful 22 // in lexer/parser work. 23 // 24 func ShouldMatchModulo(ignoring map[rune]bool, actual interface{}, expected ...interface{}) string { 25 if fail := need(1, expected); fail != success { 26 return fail 27 } 28 29 value, valueIsString := actual.(string) 30 expec, expecIsString := expected[0].(string) 31 32 if !valueIsString || !expecIsString { 33 return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0])) 34 } 35 36 equal, vpos, _ := stringsEqualIgnoring(value, expec, ignoring) 37 if equal { 38 return success 39 } else { 40 // extract the string fragment at the differnce point to make it easier to diagnose 41 diffpoint := "" 42 const diffMax = 20 43 vrune := []rune(value) 44 n := len(vrune) - vpos 45 if n > diffMax { 46 n = diffMax 47 } 48 if vpos == 0 { 49 vpos = 1 50 } 51 diffpoint = string(vrune[vpos-1 : (vpos - 1 + n)]) 52 53 diff := Diffb(value, expec) 54 55 ignored := "{" 56 switch len(ignoring) { 57 case 0: 58 return fmt.Sprintf(shouldMatchModulo, expec, value, "nothing", diffpoint, vpos-1, diff) 59 case 1: 60 for k := range ignoring { 61 ignored = ignored + fmt.Sprintf("'%c'", k) 62 } 63 ignored = ignored + "}" 64 return fmt.Sprintf(shouldMatchModulo, expec, value, ignored, diffpoint, vpos-1, diff) 65 66 default: 67 for k := range ignoring { 68 ignored = ignored + fmt.Sprintf("'%c', ", k) 69 } 70 ignored = ignored + "}" 71 return fmt.Sprintf(shouldMatchModulo, expec, value, ignored, diffpoint, vpos-1, diff) 72 } 73 } 74 } 75 76 // ShouldMatchModuloSpaces compares two strings but ignores ' ' spaces. 77 // Serves as an example of use of ShouldMatchModulo. 78 // 79 func ShouldMatchModuloSpaces(actual interface{}, expected ...interface{}) string { 80 if fail := need(1, expected); fail != success { 81 return fail 82 } 83 return ShouldMatchModulo(map[rune]bool{' ': true}, actual, expected[0]) 84 } 85 86 func ShouldMatchModuloWhiteSpace(actual interface{}, expected ...interface{}) string { 87 if fail := need(1, expected); fail != success { 88 return fail 89 } 90 return ShouldMatchModulo(map[rune]bool{' ': true, '\n': true, '\t': true}, actual, expected[0]) 91 } 92 93 func ShouldStartWithModuloWhiteSpace(actual interface{}, expectedPrefix ...interface{}) string { 94 if fail := need(1, expectedPrefix); fail != success { 95 return fail 96 } 97 98 ignoring := map[rune]bool{' ': true, '\n': true, '\t': true} 99 100 value, valueIsString := actual.(string) 101 expecPrefix, expecIsString := expectedPrefix[0].(string) 102 103 if !valueIsString || !expecIsString { 104 return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expectedPrefix[0])) 105 } 106 107 equal, vpos, _ := hasPrefixEqualIgnoring(value, expecPrefix, ignoring) 108 if equal { 109 return success 110 } else { 111 diffpoint := "" 112 const diffMax = 20 113 vrune := []rune(value) 114 n := len(vrune) - vpos + 1 115 if n > diffMax { 116 n = diffMax 117 } 118 beg := vpos - 1 119 if beg < 0 { 120 beg = 0 121 } 122 diffpoint = string(vrune[beg:(vpos - 1 + n)]) 123 124 diff := Diffb(value, expecPrefix) 125 126 ignored := "{" 127 switch len(ignoring) { 128 case 0: 129 return fmt.Sprintf(shouldStartWithModulo, expecPrefix, value, "nothing", diffpoint, vpos-1, diff) 130 case 1: 131 for k := range ignoring { 132 ignored = ignored + fmt.Sprintf("'%c'", k) 133 } 134 ignored = ignored + "}" 135 return fmt.Sprintf(shouldStartWithModulo, expecPrefix, value, ignored, diffpoint, vpos-1, diff) 136 137 default: 138 for k := range ignoring { 139 ignored = ignored + fmt.Sprintf("'%c', ", k) 140 } 141 ignored = ignored + "}" 142 return fmt.Sprintf(shouldStartWithModulo, expecPrefix, value, ignored, diffpoint, vpos-1, diff) 143 } 144 } 145 } 146 147 // returns if equal, and if not then rpos and spos hold the position of first mismatch 148 func stringsEqualIgnoring(a, b string, ignoring map[rune]bool) (equal bool, rpos int, spos int) { 149 r := []rune(a) 150 s := []rune(b) 151 152 nextr := 0 153 nexts := 0 154 155 for { 156 // skip past spaces in both r and s 157 for nextr < len(r) { 158 if ignoring[r[nextr]] { 159 nextr++ 160 } else { 161 break 162 } 163 } 164 165 for nexts < len(s) { 166 if ignoring[s[nexts]] { 167 nexts++ 168 } else { 169 break 170 } 171 } 172 173 if nextr >= len(r) && nexts >= len(s) { 174 return true, -1, -1 // full match 175 } 176 177 if nextr >= len(r) { 178 return false, nextr, nexts 179 } 180 if nexts >= len(s) { 181 return false, nextr, nexts 182 } 183 184 if r[nextr] != s[nexts] { 185 return false, nextr, nexts 186 } 187 nextr++ 188 nexts++ 189 } 190 191 return false, nextr, nexts 192 } 193 194 // returns if equal, and if not then rpos and spos hold the position of first mismatch 195 func hasPrefixEqualIgnoring(str, prefix string, ignoring map[rune]bool) (equal bool, spos int, rpos int) { 196 s := []rune(str) 197 r := []rune(prefix) 198 199 nextr := 0 200 nexts := 0 201 202 for { 203 // skip past spaces in both r and s 204 for nextr < len(r) { 205 if ignoring[r[nextr]] { 206 nextr++ 207 } else { 208 break 209 } 210 } 211 212 for nexts < len(s) { 213 if ignoring[s[nexts]] { 214 nexts++ 215 } else { 216 break 217 } 218 } 219 220 if nextr >= len(r) && nexts >= len(s) { 221 return true, -1, -1 // full match 222 } 223 224 if nextr >= len(r) { 225 return true, nexts, nextr // for prefix testing 226 } 227 if nexts >= len(s) { 228 return false, nexts, nextr 229 } 230 231 if r[nextr] != s[nexts] { 232 return false, nexts, nextr 233 } 234 nextr++ 235 nexts++ 236 } 237 238 return false, nexts, nextr 239 } 240 241 func need(needed int, expected []interface{}) string { 242 if len(expected) != needed { 243 return fmt.Sprintf(needExactValues, needed, len(expected)) 244 } 245 return success 246 } 247 248 func ShouldContainModuloWhiteSpace(haystack interface{}, expectedNeedle ...interface{}) string { 249 if fail := need(1, expectedNeedle); fail != success { 250 return fail 251 } 252 253 value, valueIsString := haystack.(string) 254 expecNeedle, expecIsString := expectedNeedle[0].(string) 255 256 if !valueIsString || !expecIsString { 257 return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(haystack), reflect.TypeOf(expectedNeedle[0])) 258 } 259 260 elimWs := func(r rune) rune { 261 if r == ' ' || r == '\t' || r == '\n' { 262 return -1 // drop the rune 263 } 264 return r 265 } 266 267 h := strings.Map(elimWs, value) 268 n := strings.Map(elimWs, expecNeedle) 269 270 if strings.Contains(h, n) { 271 return success 272 } 273 274 return fmt.Sprintf(shouldContainModuloWS, value, expecNeedle) 275 }