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 }