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  }