x

Programs, configuration and documentation that don't fit anywhere else
Log | Files | Refs | README | LICENSE

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 }