issues

Github, Jira, Gitlab issues from Plan 9's acme
Log | Files | Refs | README | LICENSE

acme.go (3668B)


      1 package main
      2 
      3 import (
      4 	"bytes"
      5 	"errors"
      6 	"fmt"
      7 	"io/fs"
      8 	"path"
      9 	"strconv"
     10 	"strings"
     11 
     12 	"9fans.net/go/acme"
     13 	"olowe.co/issues/issue/hub"
     14 )
     15 
     16 type awin struct {
     17 	*acme.Win
     18 }
     19 
     20 func (w *awin) pathname() string {
     21 	b, err := w.ReadAll("tag")
     22 	if err != nil {
     23 		w.Errf("read tag: %v", err)
     24 		return ""
     25 	}
     26 	name := strings.Fields(string(b))[0]
     27 	return strings.TrimPrefix(name, "/issue/")
     28 }
     29 
     30 func (w *awin) Look(text string) bool {
     31 	var name string
     32 	if n, _ := strconv.Atoi(strings.TrimPrefix(text, "#")); 0 < n && n < 1000000 {
     33 		name = fmt.Sprintf("%d/issue", n)
     34 	} else {
     35 		name = path.Join(w.pathname(), text)
     36 	}
     37 
     38 	stat, err := fs.Stat(fsys, name)
     39 	if errors.Is(err, fs.ErrNotExist) {
     40 		return false
     41 	} else if err != nil {
     42 		w.Err(err.Error())
     43 		return false
     44 	}
     45 	w1 := new(awin)
     46 	w1.Win, err = acme.New()
     47 	if err != nil {
     48 		w.Err(err.Error())
     49 		return false
     50 	}
     51 	if stat.IsDir() {
     52 		w1.Name("/issue/" + name + "/")
     53 	} else {
     54 		w1.Name("/issue/" + name)
     55 	}
     56 	go w1.load()
     57 	go w1.EventLoop(w1)
     58 	w1.Ctl("show")
     59 	return true
     60 }
     61 
     62 func (w *awin) load() {
     63 	name := w.pathname()
     64 	if name == "" {
     65 		name = "." // special name for root
     66 	}
     67 
     68 	defer w.Ctl("clean")
     69 	if strings.HasSuffix(name, "/") || name == "." {
     70 		entries, err := fs.ReadDir(fsys, path.Clean(name))
     71 		if err != nil {
     72 			w.Err(err.Error())
     73 			return
     74 		}
     75 		for _, dir := range entries {
     76 			if dir.IsDir() {
     77 				w.PrintTabbed(dir.Name() + "/")
     78 			} else {
     79 				w.PrintTabbed(dir.Name())
     80 			}
     81 		}
     82 	} else {
     83 		b, err := fs.ReadFile(fsys, name)
     84 		if err != nil {
     85 			w.Err(err.Error())
     86 			return
     87 		}
     88 		_, err = w.Write("body", b)
     89 		if err != nil {
     90 			w.Err(err.Error())
     91 			return
     92 		}
     93 	}
     94 
     95 	w.Addr("0")
     96 	w.Ctl("dot=addr")
     97 	w.Ctl("show")
     98 }
     99 
    100 const newIssueTemplate = `Title: `
    101 
    102 func (w *awin) Execute(cmd string) bool {
    103 	github, ok := fsys.(*hub.FS)
    104 	if !ok {
    105 		msg := fmt.Sprintf("unexpected FS type %T", fsys)
    106 		w.Err(msg)
    107 		return false
    108 	}
    109 
    110 	switch cmd {
    111 	case "Get":
    112 		w.load()
    113 		w.Ctl("clean")
    114 		return true
    115 	case "Del":
    116 		w.Ctl("del")
    117 		return true
    118 	case "New":
    119 		win := new(awin)
    120 		w1, err := acme.New()
    121 		if err != nil {
    122 			w.Err(err.Error())
    123 			return false
    124 		}
    125 
    126 		win.Win = w1
    127 		win.Name("/issue/new")
    128 		win.Write("body", []byte(newIssueTemplate))
    129 		go win.EventLoop(win)
    130 		return true
    131 	case "Put":
    132 		if w.pathname() != "new" {
    133 			w.Err(fs.ErrPermission.Error())
    134 			return false
    135 		}
    136 		b, err := w.ReadAll("body")
    137 		if err != nil {
    138 			w.Err(err.Error())
    139 			return false
    140 		}
    141 		title, body, err := readNewIssue(bytes.NewReader(b))
    142 		if err != nil {
    143 			w.Err("read issue: " + err.Error())
    144 			return false
    145 		}
    146 		owner, repo, ok := strings.Cut(project, "/")
    147 		if !ok {
    148 			w.Err("botch, invalid project name " + project)
    149 			return false
    150 		}
    151 		issue, err := github.Client.CreateIssue(owner, repo, title, body)
    152 		if err != nil {
    153 			w.Err("create issue: " + err.Error())
    154 			return false
    155 		}
    156 		name := fmt.Sprintf("/issue/%d/issue", issue.Number)
    157 		w.Name(name)
    158 		go w.load()
    159 		return true
    160 	}
    161 
    162 	if strings.HasPrefix(cmd, "Search") {
    163 		query := strings.TrimSpace(strings.TrimPrefix(cmd, "Search"))
    164 		owner, repo, ok := strings.Cut(project, "/")
    165 		if !ok {
    166 			w.Err("botch, invalid project name " + project)
    167 			return false
    168 		}
    169 
    170 		win := new(awin)
    171 		w1, err := acme.New()
    172 		if err != nil {
    173 			w.Err(err.Error())
    174 			return false
    175 		}
    176 		win.Win = w1
    177 		win.Name("/issue/search")
    178 		win.Write("body", []byte("Search "+query+"\n\n"))
    179 		go win.EventLoop(win)
    180 		hits, err := github.Client.SearchIssues(owner, repo, query)
    181 		if err != nil {
    182 			w.Err(fmt.Sprintf("search %q: %v", query, err))
    183 			return false
    184 		}
    185 		for _, issue := range hits {
    186 			win.Fprintf("body", "%d %s\n", issue.Number, issue.Title)
    187 		}
    188 		return true
    189 	}
    190 
    191 	return false
    192 }