package traceimpact import ( "regexp" "strings" ) var ( explicitKeyRe = regexp.MustCompile(`\b((?:UR|RS|REQ|TC|RF|RP|DI)-\d+)\b`) untypedKeyRe = regexp.MustCompile(`\b([A-Z]{2,10}-\d+)\b`) ) // ExtractedKey is a business key found in markdown. type ExtractedKey struct { Key string Kind string Explicit bool Line string } // ExtractKeys returns explicit UR/RS/REQ/TC-style keys and untyped ID mentions. // Untyped mentions stay candidates and never become confirmed edges by themselves. func ExtractKeys(markdown string) []ExtractedKey { lines := strings.Split(markdown, "\n") var out []ExtractedKey seen := map[string]bool{} for _, line := range lines { for _, m := range explicitKeyRe.FindAllStringSubmatch(line, -1) { key := m[1] if seen["e:"+key] { continue } seen["e:"+key] = true kind := KindRequirement if strings.HasPrefix(key, "TC-") { kind = KindTest } out = append(out, ExtractedKey{Key: key, Kind: kind, Explicit: true, Line: line}) } for _, m := range untypedKeyRe.FindAllStringSubmatch(line, -1) { key := m[1] if explicitKeyRe.MatchString(key) || seen["u:"+key] || seen["e:"+key] { continue } seen["u:"+key] = true out = append(out, ExtractedKey{Key: key, Kind: KindRequirement, Explicit: false, Line: line}) } } return out } // ExplicitPairsOnLine returns ordered pairs of explicit keys that share a line. func ExplicitPairsOnLine(markdown string) [][2]string { var pairs [][2]string seen := map[string]bool{} for _, line := range strings.Split(markdown, "\n") { found := explicitKeyRe.FindAllStringSubmatch(line, -1) if len(found) < 2 { continue } for i := 0; i < len(found); i++ { for j := i + 1; j < len(found); j++ { a, b := found[i][1], found[j][1] k := a + ">" + b if seen[k] { continue } seen[k] = true pairs = append(pairs, [2]string{a, b}) } } } return pairs }