@@ -8,10 +8,11 @@ import ( "sort" "strings" "sync" "time" "dmitri.shuralyov.com/go/prefixtitle" "dmitri.shuralyov.com/service/change" "github.com/shurcooL/issues" "github.com/shurcooL/users" "golang.org/x/build/maintner" "golang.org/x/build/maintner/godata" @@ -159,11 +160,11 @@ func issuesAndChanges(repo *maintner.GitHubRepo, gerrit *maintner.Gerrit) map[st err := repo.ForeachIssue(func(i *maintner.GitHubIssue) error { if i.NotExist || i.PullRequest { return nil } pkgs, title := ParsePrefixedTitle(i.Title) pkgs, title := prefixtitle.ParseIssue("", i.Title) var labels []issues.Label for _, l := range i.Labels { labels = append(labels, issues.Label{ Name: l.Name, // TODO: Can we use label ID to figure out its color? @@ -236,11 +237,11 @@ func issuesAndChanges(repo *maintner.GitHubRepo, gerrit *maintner.Gerrit) map[st if !ok { return nil } prefixedTitle := cl.Subject() pkgs, title := ParsePrefixedChangeTitle(root, prefixedTitle) pkgs, title := prefixtitle.ParseChange(root, prefixedTitle) var labels []issues.Label cl.Meta.Hashtags().Foreach(func(name string) { labels = append(labels, issues.Label{ Name: name, Color: issues.RGB{R: 0xed, G: 0xed, B: 0xed}, // Light gray. @@ -334,80 +335,10 @@ var gerritProjects = map[string]string{ "go.googlesource.com/vgo": "golang.org/x/vgo", } const otherPackages = "other" // ParsePrefixedTitle parses a prefixed issue title. // It returns a list of paths from the prefix, and the remaining issue title. // It does not try to verify whether each path is an existing Go package. // // Supported forms include: // // "import/path: Issue title." -> ["import/path"], "Issue title." // "proposal: path: Issue title." -> ["path"], "Issue title." # Proposal. // "Proposal: path: Issue title." -> ["path"], "Issue title." # Proposal. // "x/path: Issue title." -> ["golang.org/x/path"], "Issue title." # "x/..." refers to "golang.org/x/...". // "path1, path2: Issue title." -> ["path1", "path2"], "Issue title." # Multiple comma-separated paths. // // If there's no path prefix (preceded by ": "), title is returned unmodified // with an empty paths list: // // "Issue title." -> [], "Issue title." // func ParsePrefixedTitle(prefixedTitle string) (paths []string, title string) { prefixedTitle = strings.TrimPrefix(prefixedTitle, "proposal: ") // TODO: Consider preserving the "proposal: " prefix? prefixedTitle = strings.TrimPrefix(prefixedTitle, "Proposal: ") idx := strings.Index(prefixedTitle, ": ") if idx == -1 { return nil, prefixedTitle } prefix, title := prefixedTitle[:idx], prefixedTitle[idx+len(": "):] if strings.ContainsAny(prefix, "{}") { // TODO: Parse "image/{png,jpeg}" as ["image/png", "image/jpeg"], maybe? return []string{strings.TrimSpace(prefix)}, title } paths = strings.Split(prefix, ",") for i := range paths { paths[i] = strings.TrimSpace(paths[i]) if strings.HasPrefix(paths[i], "x/") || paths[i] == "x" { // Map "x/..." to "golang.org/x/...". paths[i] = "golang.org/" + paths[i] } } return paths, title } // ParsePrefixedChangeTitle parses a prefixed change title. // It returns a list of paths from the prefix joined with root, and the remaining change title. // It does not try to verify whether each path is an existing Go package. // // Supported forms include: // // "root", "import/path: Change title." -> ["root/import/path"], "Change title." // "root", "path1, path2: Change title." -> ["root/path1", "root/path2"], "Change title." # Multiple comma-separated paths. // // If there's no path prefix (preceded by ": "), title is returned unmodified // with a paths list containing root: // // "root", "Change title." -> ["root"], "Change title." // func ParsePrefixedChangeTitle(root, prefixedTitle string) (paths []string, title string) { idx := strings.Index(prefixedTitle, ": ") if idx == -1 { return []string{root}, prefixedTitle } prefix, title := prefixedTitle[:idx], prefixedTitle[idx+len(": "):] if strings.ContainsAny(prefix, "{}") { // TODO: Parse "image/{png,jpeg}" as ["image/png", "image/jpeg"], maybe? return []string{path.Join(root, strings.TrimSpace(prefix))}, title } paths = strings.Split(prefix, ",") for i := range paths { paths[i] = path.Join(root, strings.TrimSpace(paths[i])) } return paths, title } // ImportPathToFullPrefix returns the an issue title prefix (including ": ") for the given import path. // If path equals to otherPackages, an empty prefix is returned. func ImportPathToFullPrefix(path string) string { switch { default:
@@ -1,106 +1,13 @@ package main_test import ( "reflect" "testing" gido "dmitri.shuralyov.com/website/gido" ) func TestParsePrefixedTitle(t *testing.T) { tests := []struct { in string wantPaths []string wantTitle string }{ { in: "import/path: Issue title.", wantPaths: []string{"import/path"}, wantTitle: "Issue title.", }, { // Proposal. in: "proposal: path: Issue title.", wantPaths: []string{"path"}, wantTitle: "Issue title.", }, { // Proposal. in: "Proposal: path: Issue title.", wantPaths: []string{"path"}, wantTitle: "Issue title.", }, { // "x/..." refers to "golang.org/x/...". in: "x/path: Issue title.", wantPaths: []string{"golang.org/x/path"}, wantTitle: "Issue title.", }, { // "x" refers to "golang.org/x". in: "x: Issue title.", wantPaths: []string{"golang.org/x"}, wantTitle: "Issue title.", }, { // Multiple comma-separated paths. in: "path1, path2: Issue title.", wantPaths: []string{"path1", "path2"}, wantTitle: "Issue title.", }, { // No path prefix. in: "Issue title.", wantPaths: nil, wantTitle: "Issue title.", }, } for i, tc := range tests { gotPaths, gotTitle := gido.ParsePrefixedTitle(tc.in) if !reflect.DeepEqual(gotPaths, tc.wantPaths) { t.Errorf("%d: got paths: %q, want: %q", i, gotPaths, tc.wantPaths) } if gotTitle != tc.wantTitle { t.Errorf("%d: got title: %q, want: %q", i, gotTitle, tc.wantTitle) } } } func TestParsePrefixedChangeTitle(t *testing.T) { tests := []struct { inRoot string in string wantPaths []string wantTitle string }{ { in: "import/path: Change title.", wantPaths: []string{"import/path"}, wantTitle: "Change title.", }, { inRoot: "root", in: "import/path: Change title.", wantPaths: []string{"root/import/path"}, wantTitle: "Change title.", }, { // Multiple comma-separated paths. in: "path1, path2: Change title.", wantPaths: []string{"path1", "path2"}, wantTitle: "Change title.", }, { inRoot: "root", in: "path1, path2: Change title.", wantPaths: []string{"root/path1", "root/path2"}, wantTitle: "Change title.", }, { // No path prefix. in: "Change title.", wantPaths: []string{""}, wantTitle: "Change title.", }, { inRoot: "root", in: "Change title.", wantPaths: []string{"root"}, wantTitle: "Change title.", }, } for i, tc := range tests { gotPaths, gotTitle := gido.ParsePrefixedChangeTitle(tc.inRoot, tc.in) if !reflect.DeepEqual(gotPaths, tc.wantPaths) { t.Errorf("%d: got paths: %q, want: %q", i, gotPaths, tc.wantPaths) } if gotTitle != tc.wantTitle { t.Errorf("%d: got title: %q, want: %q", i, gotTitle, tc.wantTitle) } } } func TestImportPathToFullPrefix(t *testing.T) { tests := []struct { in string want string }{