bbfeed.go (2861B)
1 package main 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "fmt" 7 "io/fs" 8 "log" 9 "net/http" 10 "os" 11 "os/user" 12 "path" 13 "strconv" 14 15 "olowe.co/x/atom" 16 ) 17 18 func Feed(prs []PullRequest) *atom.Feed { 19 feed := atom.Feed{ 20 Entries: make([]atom.Entry, len(prs)), 21 } 22 for i, pr := range prs { 23 pr := pr 24 feed.Entries[i] = atom.Entry{ 25 Title: pr.Title, 26 ID: pr.Links.Self.HRef, 27 Links: []atom.Link{ 28 {pr.Links.Self.HRef + "/patch", "alternate", "application/mbox"}, 29 {pr.Links.HTML.HRef, "canonical", "text/html"}, 30 }, 31 Updated: pr.Updated, 32 Published: &pr.Created, 33 Summary: pr.Description, 34 Author: atom.Author{Name: pr.Author.DisplayName}, 35 Content: []byte(pr.Summary.HTML), 36 } 37 if pr.Updated.After(feed.Updated) { 38 feed.Updated = pr.Updated 39 } 40 } 41 return &feed 42 } 43 44 func readAuth() (username, password string, err error) { 45 confDir, err := os.UserConfigDir() 46 if err != nil { 47 return "", "", err 48 } 49 50 b, err := os.ReadFile(path.Join(confDir, "atlassian/bitbucket.org")) 51 if err != nil { 52 return "", "", err 53 } 54 b = bytes.TrimSpace(b) 55 u, p, ok := bytes.Cut(b, []byte(":")) 56 if !ok { 57 return "", "", fmt.Errorf("parse credentials: missing %q separator", ":") 58 } 59 return string(u), string(p), nil 60 } 61 62 const usage string = "usage: bbfeed" 63 64 func main() { 65 if len(os.Args) != 1 { 66 fmt.Println(usage) 67 os.Exit(2) 68 } 69 70 username, password, err := readAuth() 71 if err != nil { 72 log.Fatalf("read credentials: %v", err) 73 } 74 client := &Client{username, password, http.DefaultClient} 75 srv := &Server{client} 76 http.HandleFunc("/", srv.handleReq) 77 log.Fatal(http.ListenAndServe(":8069", nil)) 78 return 79 80 81 workspace := os.Args[1] 82 repos, err := client.Repositories(workspace) 83 if err != nil { 84 log.Fatalf("get repositories in %s: %v", os.Args[1], err) 85 } 86 87 u, err := user.Current() 88 if err != nil { 89 log.Fatalf("lookup current user: %v", err) 90 } 91 uid, err := strconv.Atoi(u.Uid) 92 if err != nil { 93 log.Fatalf("parse uid: %v", err) 94 } 95 gid, err := strconv.Atoi(u.Gid) 96 if err != nil { 97 log.Fatalf("parse gid: %v", err) 98 } 99 tw := tar.NewWriter(os.Stdout) 100 tw.WriteHeader(&tar.Header{ 101 Name: path.Dir(os.Args[1]) + "/", 102 Mode: int64(0o444 | fs.ModeDir), 103 Uid: uid, 104 Gid: gid, 105 }) 106 for _, repo := range repos { 107 name := path.Join(workspace, repo.Name) 108 prs, err := client.PullRequests(workspace, repo.Name) 109 if err != nil { 110 log.Printf("get %s pull requests: %v", name, err) 111 continue 112 } 113 feed := Feed(prs) 114 feed.Title = name + " pull requests" 115 feed.ID = fmt.Sprintf("https://bitbucket.org/" + name) 116 b, err := atom.Marshal(feed) 117 if err != nil { 118 log.Printf("marshal %s feed: %v", name, err) 119 continue 120 } 121 hdr := tar.Header{ 122 Name: name, 123 Size: int64(len(b)), 124 Mode: 0o644, 125 Uid: uid, 126 Gid: gid, 127 } 128 if err := tw.WriteHeader(&hdr); err != nil { 129 log.Fatal(err) 130 } 131 tw.Write(b) 132 } 133 tw.Flush() 134 tw.Close() 135 }