github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/internal/regex_helpers.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package internal contains miscellaneous functions and objects useful within Scalibr 16 package internal 17 18 import "regexp" 19 20 // MatchNamedCaptureGroups takes a regular expression and string and returns all of the named capture group results in a map. 21 // This is only for the first match in the regex. Callers shouldn't be providing regexes with multiple capture groups with the same name. 22 func MatchNamedCaptureGroups(regEx *regexp.Regexp, content string) map[string]string { 23 // note: we are looking across all matches and stopping on the first non-empty match. Why? Take the following example: 24 // input: "cool something to match against" pattern: `((?P<name>match) (?P<version>against))?`. Since the pattern is 25 // encapsulated in an optional capture group, there will be results for each character, but the results will match 26 // on nothing. The only "true" match will be at the end ("match against"). 27 allMatches := regEx.FindAllStringSubmatch(content, -1) 28 results := map[string]string{} 29 for _, match := range allMatches { 30 // fill a candidate results map with named capture group results, accepting empty values, but not groups with 31 // no names 32 for nameIdx, name := range regEx.SubexpNames() { 33 if nameIdx > len(match) || len(name) == 0 { 34 continue 35 } 36 results[name] = match[nameIdx] 37 } 38 // note: since we are looking for the first best potential match we should stop when we find the first one 39 // with non-empty results. 40 if !isEmptyMap(results) { 41 break 42 } 43 } 44 return results 45 } 46 47 func isEmptyMap(m map[string]string) bool { 48 if len(m) == 0 { 49 return true 50 } 51 for _, value := range m { 52 if value != "" { 53 return false 54 } 55 } 56 return true 57 }