package remap /* Map returns a map[string][] for regexes with named capture groups matched in bytes b. Note that this supports non-unique group names; [regexp.Regexp] allows for patterns with multiple groups using the same group name (though your IDE might complain; I know GoLand does). Each match for each group is in a slice keyed under that group name, with that slice ordered by the indexing done by the regex match itself. In summary, the parameters are as follows: # inclNoMatch If true, then attempt to return a non-nil matches (as long as b isn't nil). Group keys will be populated and explicitly defined as nil. For example, if a pattern ^(?Pfoo)(?Pbar)(?Pbaz)$ is provided but b does not match then matches will be: map[string][][]byte{ "g1": nil, "g2": nil, } # inclNoMatchStrict If true (and inclNoMatch is true), instead of a single nil the group's values will be a slice of nil values explicitly matching the number of times the group name is specified in the pattern. For example, if a pattern: ^(?Pfoo)(?Pbar)(?Pbaz)$ is provided but b does not match then matches will be: map[string][][]byte{ "g1": [][]byte{ nil, nil, }, "g2": [][]byte{ nil, }, } # mustMatch If true, matches will be nil if the entirety of b does not match the pattern (and thus no capture groups matched) (overrides inclNoMatch) -- explicitly: matches == nil Otherwise if false (and assuming inclNoMatch is false), matches will be: map[string][][]byte{}{} # Condition Tree In detail, matches and/or its values may be nil or empty under the following condition tree: IF b is nil: THEN matches will always be nil ELSE: IF all of b does not match pattern IF mustMuch is true THEN matches == nil ELSE THEN matches == map[string][][]byte{} (non-nil but empty) ELSE IF pattern has no named capture groups IF inclNoMatch is true THEN matches == map[string][][]byte{} (non-nil but empty) ELSE THEN matches == nil ELSE IF there are no named group matches IF inclNoMatch is true THEN matches is non-nil; matches[, ...] is/are defined but nil (_, ok = matches[]; ok == true) ELSE THEN matches == nil ELSE IF does not have a match IF inclNoMatch is true IF inclNoMatchStrict is true THEN matches[] is defined and non-nil, but populated with placeholder nils (matches[] == [][]byte{nil[, nil...]}) ELSE THEN matches[] is guaranteed defined but may be nil (_, ok = matches[]; ok == true) ELSE THEN matches[] is not defined (_, ok = matches[]; ok == false) ELSE matches[] == []{[, ...]} */ func (r *ReMap) Map(b []byte, inclNoMatch, inclNoMatchStrict, mustMatch bool) (matches map[string][][]byte) { var ok bool var mIdx int var match []byte var grpNm string var names []string var matchBytes [][]byte var tmpMap map[string][][]byte = make(map[string][][]byte) if b == nil { return } names = r.Regexp.SubexpNames() matchBytes = r.Regexp.FindSubmatch(b) if matchBytes == nil { // b does not match pattern if !mustMatch { matches = make(map[string][][]byte) } return } if names == nil || len(names) == 0 || len(names) == 1 { /* no named capture groups; technically only the last condition would be the case. */ if inclNoMatch { matches = make(map[string][][]byte) } return } names = names[1:] if len(matchBytes) == 0 || len(matchBytes) == 1 { /* no submatches whatsoever. *technically* I don't think this condition can actually be reached. This is more of a safe-return before we re-slice. */ matches = make(map[string][][]byte) if inclNoMatch { if len(names) >= 1 { for _, grpNm = range names { matches[grpNm] = nil } } } return } matchBytes = matchBytes[1:] for mIdx, match = range matchBytes { grpNm = names[mIdx] /* Thankfully, it's actually a build error if a pattern specifies a named capture group with an empty name. So we don't need to worry about accounting for that, and can just skip over grpNm == "" (which is an *unnamed* capture group). */ if grpNm == "" { continue } if match == nil { // group did not match if !inclNoMatch { continue } if _, ok = tmpMap[grpNm]; !ok { if !inclNoMatchStrict { tmpMap[grpNm] = nil } else { tmpMap[grpNm] = [][]byte{nil} } } else { if inclNoMatchStrict { tmpMap[grpNm] = append(tmpMap[grpNm], nil) } } continue } if _, ok = tmpMap[grpNm]; !ok { tmpMap[grpNm] = make([][]byte, 0) } tmpMap[grpNm] = append(tmpMap[grpNm], match) } // This *technically* should be completely handled above. if inclNoMatch { for _, grpNm = range names { if _, ok = tmpMap[grpNm]; !ok { tmpMap[grpNm] = nil } } } if len(tmpMap) > 0 { matches = tmpMap } return } /* MapString is exactly like ReMap.Map(), but operates on (and returns) strings instead. (matches will always be nil if s == “.) A small deviation, though; empty strings instead of nils (because duh) will occupy slice placeholders (if `inclNoMatchStrict` is specified). This unfortunately *does not provide any indication* if an empty string positively matched the pattern (a "hit") or if it was simply not matched at all (a "miss"). If you need definitive determination between the two conditions, it is instead recommended to either *not* use inclNoMatchStrict or to use ReMap.Map() instead and convert any non-nil values to strings after. Particularly: # inclNoMatch If true, then attempt to return a non-nil matches (as long as s isn't empty). Group keys will be populated and explicitly defined as nil. For example, if a pattern ^(?Pfoo)(?Pbar)(?Pbaz)$ is provided but s does not match then matches will be: map[string][]string{ "g1": nil, "g2": nil, } # inclNoMatchStrict If true (and inclNoMatch is true), instead of a single nil the group's values will be a slice of eempty string values explicitly matching the number of times the group name is specified in the pattern. For example, if a pattern: ^(?Pfoo)(?Pbar)(?Pbaz)$ is provided but s does not match then matches will be: map[string][]string{ "g1": []string{ "", "", }, "g2": []string{ "", }, } # mustMatch If true, matches will be nil if the entirety of s does not match the pattern (and thus no capture groups matched) (overrides inclNoMatch) -- explicitly: matches == nil Otherwise if false (and assuming inclNoMatch is false), matches will be: map[string][]string{}{} # Condition Tree In detail, matches and/or its values may be nil or empty under the following condition tree: IF s is empty: THEN matches will always be nil ELSE: IF all of s does not match pattern IF mustMuch is true THEN matches == nil ELSE THEN matches == map[string][]string{} (non-nil but empty) ELSE IF pattern has no named capture groups IF inclNoMatch is true THEN matches == map[string][]string{} (non-nil but empty) ELSE THEN matches == nil ELSE IF there are no named group matches IF inclNoMatch is true THEN matches is non-nil; matches[, ...] is/are defined but nil (_, ok = matches[]; ok == true) ELSE THEN matches == nil ELSE IF does not have a match IF inclNoMatch is true IF inclNoMatchStrict is true THEN matches[] is defined and non-nil, but populated with placeholder nils (matches[] == []string{""[, ""...]}) ELSE THEN matches[] is guaranteed defined but may be nil (_, ok = matches[]; ok == true) ELSE THEN matches[] is not defined (_, ok = matches[]; ok == false) ELSE matches[] == []{[, ...]} */ func (r *ReMap) MapString(s string, inclNoMatch, inclNoMatchStrict, mustMatch bool) (matches map[string][]string) { var ok bool var endIdx int var startIdx int var chunkIdx int var grpNm string var names []string var matchStr string var idxChunks [][]int var matchIndices []int var chunkIndices []int // always 2 elements; start pos and end pos var tmpMap map[string][]string = make(map[string][]string) /* OK so this is a bit of a deviation. It's not as straightforward as above, because there isn't an explicit way like above to determine if a patterb was *matched as an empty string* vs. *not matched*. So instead do roundabout index-y things. */ if s == "" { return } names = r.Regexp.SubexpNames() matchIndices = r.Regexp.FindStringSubmatchIndex(s) if matchIndices == nil { // s does not match pattern if !mustMatch { matches = make(map[string][]string) } return } if names == nil || len(names) == 0 || len(names) == 1 { /* no named capture groups; technically only the last condition would be the case. */ if inclNoMatch { matches = make(map[string][]string) } return } names = names[1:] if len(matchIndices) == 0 || len(matchIndices) == 1 { /* no submatches whatsoever. *technically* I don't think this condition can actually be reached. This is more of a safe-return before we chunk the indices. */ matches = make(map[string][]string) if inclNoMatch { if len(names) >= 1 { for _, grpNm = range names { matches[grpNm] = nil } } } return } /* The reslice starts at 2 because they're in pairs: []int{, , , , ...} and the first *pair* is the entire pattern match. Thus the len(matchIndices) == 2*len(names). Keep in mind that since the first element of names is removed, the first pair here is also removed. */ matchIndices = matchIndices[2:] idxChunks = make([][]int, len(names)) for startIdx = 0; startIdx < len(idxChunks); startIdx += 2 { endIdx = startIdx + 2 grpNm = names[chunkIdx] /* Thankfully, it's actually a build error if a pattern specifies a named capture group with an empty name. So we don't need to worry about accounting for that, and can just skip over grpNm == "" (which is an *unnamed* capture group). */ if grpNm == "" { continue } // This technically should never happen. if endIdx > len(matchIndices) { endIdx = len(matchIndices) } chunkIndices = matchIndices[startIdx:endIdx] if chunkIndices[0] == -1 || chunkIndices[1] == -1 { // group did not match if !inclNoMatch { continue } if _, ok = tmpMap[grpNm]; !ok { if !inclNoMatchStrict { tmpMap[grpNm] = nil } else { tmpMap[grpNm] = []string{""} } } else { if inclNoMatchStrict { tmpMap[grpNm] = append(tmpMap[grpNm], "") } } continue } matchStr = s[chunkIndices[0]:chunkIndices[1]] if _, ok = tmpMap[grpNm]; !ok { tmpMap[grpNm] = make([]string, 0) } tmpMap[grpNm] = append(tmpMap[grpNm], matchStr) chunkIdx++ } // This *technically* should be completely handled above. if inclNoMatch { for _, grpNm = range names { if _, ok = tmpMap[grpNm]; !ok { tmpMap[grpNm] = nil } } } if len(tmpMap) > 0 { matches = tmpMap } return }