issues

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

file.go (4432B)


      1 package hub
      2 
      3 import (
      4 	"bytes"
      5 	"fmt"
      6 	"io"
      7 	"io/fs"
      8 	"strconv"
      9 	"time"
     10 )
     11 
     12 type ftype uint8
     13 
     14 const (
     15 	ftypeRoot = iota
     16 	ftypeIssueDir
     17 	ftypeIssue
     18 	ftypeCommentDir
     19 	ftypeComment
     20 )
     21 
     22 type fakeFile struct {
     23 	*Client
     24 	owner, repo string
     25 	typ         ftype
     26 
     27 	// comment, issue files
     28 	number int
     29 	buf    *bytes.Buffer
     30 
     31 	// directory files
     32 	children []fs.DirEntry
     33 	dirp     int
     34 }
     35 
     36 func (f *fakeFile) Name() string {
     37 	switch f.typ {
     38 	case ftypeRoot:
     39 		return "."
     40 	case ftypeIssueDir:
     41 		return strconv.Itoa(f.number)
     42 	case ftypeIssue:
     43 		return "issue"
     44 	case ftypeCommentDir:
     45 		return "comments"
     46 	case ftypeComment:
     47 		return strconv.Itoa(f.number)
     48 	}
     49 	return "unknown"
     50 }
     51 
     52 func (f *fakeFile) IsDir() bool {
     53 	switch f.typ {
     54 	case ftypeRoot, ftypeIssueDir, ftypeCommentDir:
     55 		return true
     56 	}
     57 	return false
     58 }
     59 
     60 func (f *fakeFile) Type() fs.FileMode {
     61 	if f.IsDir() {
     62 		return fs.ModeDir
     63 	}
     64 	return 0
     65 }
     66 
     67 func (f *fakeFile) Info() (fs.FileInfo, error) { return f.Stat() }
     68 
     69 func (f *fakeFile) Stat() (fs.FileInfo, error) {
     70 	switch f.typ {
     71 	case ftypeRoot:
     72 		return stat{name: ".", mode: fs.ModeDir}, nil
     73 	case ftypeIssueDir, ftypeCommentDir:
     74 		return stat{name: f.Name(), mode: 0o555 | fs.ModeDir}, nil
     75 	case ftypeIssue:
     76 		issue, err := f.Client.LookupIssue(f.owner, f.repo, f.number)
     77 		if err != nil {
     78 			return nil, fmt.Errorf("lookup issue %d: %w", f.number, err)
     79 		}
     80 		buf := &bytes.Buffer{}
     81 		if f.buf == nil {
     82 			f.buf = buf
     83 		}
     84 		_ = printIssue(buf, issue) // cannot fail writing to Buffer
     85 		st := stat{
     86 			name:   f.Name(),
     87 			mtime:  issue.Updated,
     88 			length: int64(buf.Len()),
     89 			sys:    issue,
     90 		}
     91 		return st, nil
     92 	case ftypeComment:
     93 		comment, err := f.Client.LookupComment(f.owner, f.repo, f.number)
     94 		if err != nil {
     95 			return nil, &fs.PathError{"stat", f.Name(), fmt.Errorf("lookup comment %d: %w", f.number, err)}
     96 		}
     97 		buf := &bytes.Buffer{}
     98 		if f.buf == nil {
     99 			f.buf = buf
    100 		}
    101 		_ = printComment(buf, *comment)
    102 		st := stat{
    103 			name:   f.Name(),
    104 			mtime:  comment.Updated,
    105 			length: int64(buf.Len()),
    106 			sys:    comment,
    107 		}
    108 		return st, nil
    109 	}
    110 	return nil, &fs.PathError{"stat", f.Name(), fs.ErrNotExist}
    111 }
    112 
    113 func (f *fakeFile) Read(p []byte) (n int, err error) {
    114 	switch f.typ {
    115 	case ftypeRoot, ftypeIssueDir, ftypeCommentDir:
    116 		return 0, &fs.PathError{"read", f.Name(), fmt.Errorf("is a directory")}
    117 	}
    118 
    119 	if f.buf == nil {
    120 		if _, err := f.Stat(); err != nil {
    121 			return 0, &fs.PathError{"read", f.Name(), err}
    122 		}
    123 	}
    124 	return f.buf.Read(p)
    125 }
    126 
    127 func (f *fakeFile) ReadDir(n int) ([]fs.DirEntry, error) {
    128 	if f.typ == ftypeIssue || f.typ == ftypeComment {
    129 		return nil, &fs.PathError{"readdir", f.Name(), fmt.Errorf("not a directory")}
    130 	}
    131 
    132 	if f.children == nil {
    133 		switch f.typ {
    134 		case ftypeRoot:
    135 			issues, err := f.Client.Issues(f.owner, f.repo)
    136 			if err != nil {
    137 				return nil, &fs.PathError{"readdir", f.Name(), err}
    138 			}
    139 			f.children = make([]fs.DirEntry, len(issues))
    140 			for i, issue := range issues {
    141 				f.children[i] = &fakeFile{
    142 					typ:    ftypeIssueDir,
    143 					number: issue.Number,
    144 				}
    145 			}
    146 		case ftypeIssueDir:
    147 			f.children = []fs.DirEntry{
    148 				&fakeFile{
    149 					typ:    ftypeIssue,
    150 					number: f.number,
    151 				},
    152 				&fakeFile{
    153 					typ:    ftypeCommentDir,
    154 					number: f.number,
    155 				},
    156 			}
    157 		case ftypeCommentDir:
    158 			comments, err := f.Client.Comments(f.owner, f.repo, f.number)
    159 			if err != nil {
    160 				return nil, &fs.PathError{"readdir", f.Name(), err}
    161 			}
    162 			f.children = make([]fs.DirEntry, len(comments))
    163 			for i, c := range comments {
    164 				f.children[i] = &fakeFile{
    165 					typ:    ftypeComment,
    166 					number: c.ID,
    167 				}
    168 			}
    169 		}
    170 	}
    171 
    172 	if f.dirp >= len(f.children) {
    173 		if n <= 0 {
    174 			return nil, nil
    175 		}
    176 		return nil, io.EOF
    177 	}
    178 	if n <= 0 {
    179 		f.dirp = len(f.children)
    180 		return f.children, nil
    181 	}
    182 
    183 	var err error
    184 	d := f.children[f.dirp:]
    185 	if len(d) >= n {
    186 		d = d[:n]
    187 	} else if len(d) <= n {
    188 		err = io.EOF
    189 	}
    190 	f.dirp += n
    191 	return d, err
    192 }
    193 
    194 func (f *fakeFile) Close() error {
    195 	if f.buf != nil {
    196 		f.buf.Reset()
    197 	}
    198 	f.buf = nil
    199 	f.children = nil
    200 	f.Client = nil
    201 	return nil
    202 }
    203 
    204 type stat struct {
    205 	name   string
    206 	length int64
    207 	mode   fs.FileMode
    208 	mtime  time.Time
    209 	sys    any
    210 }
    211 
    212 func (s stat) Name() string       { return s.name }
    213 func (s stat) Size() int64        { return s.length }
    214 func (s stat) Mode() fs.FileMode  { return s.mode }
    215 func (s stat) ModTime() time.Time { return s.mtime }
    216 func (s stat) IsDir() bool        { return s.Mode().IsDir() }
    217 func (s stat) Sys() any           { return s.sys }