forked from deanishe/awgo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
github.go
132 lines (119 loc) · 2.98 KB
/
github.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright (c) 2018 Dean Jackson <[email protected]>
// MIT Licence - http://opensource.org/licenses/MIT
package update
import (
"encoding/json"
"errors"
"fmt"
"log"
"path/filepath"
"regexp"
"sort"
"strings"
aw "github.com/deanishe/awgo"
)
// matches filename of a compiled Alfred workflow
var rxWorkflowFile = regexp.MustCompile(`\.alfred(\d+)?workflow$`)
// GitHub is a Workflow Option. It sets a Workflow Updater for the specified GitHub repo.
// Repo name should be of the form "username/repo", e.g. "deanishe/alfred-ssh".
func GitHub(repo string) aw.Option {
return newOption(&source{
URL: "https://api.github.com/repos/" + repo + "/releases",
fetch: getURL,
})
}
// create new Updater option from Source.
func newOption(src Source) aw.Option {
return func(wf *aw.Workflow) aw.Option {
u, _ := NewUpdater(src, wf.Version(), filepath.Join(wf.CacheDir(), "_aw/update"))
return aw.Update(u)(wf)
}
}
type source struct {
URL string
dls []Download
fetch func(URL string) ([]byte, error)
}
// Downloads implements Source.
func (src *source) Downloads() ([]Download, error) {
if src.dls != nil {
return src.dls, nil
}
src.dls = []Download{}
js, err := src.fetch(src.URL)
if err != nil {
return nil, err
}
if src.dls, err = parseReleases(js); err != nil {
return nil, err
}
return src.dls, nil
}
// parse GitHub/Gitea releases JSON.
func parseReleases(js []byte) ([]Download, error) {
var (
dls = []Download{}
rels = []struct {
Name string `json:"name"`
Prerelease bool `json:"prerelease"`
Assets []struct {
Name string `json:"name"`
URL string `json:"browser_download_url"`
MinAlfredVersion SemVer `json:"-"`
} `json:"assets"`
Tag string `json:"tag_name"`
}{}
)
if err := json.Unmarshal(js, &rels); err != nil {
return nil, err
}
for _, r := range rels {
if len(r.Assets) == 0 {
continue
}
v, err := NewSemVer(r.Tag)
if err != nil {
log.Printf("ignored release %s: not semantic: %v", r.Tag, err)
continue
}
var all []Download
for _, a := range r.Assets {
m := rxWorkflowFile.FindStringSubmatch(a.Name)
if len(m) != 2 {
log.Printf("ignored release %s: no workflow files", r.Tag)
continue
}
w := Download{
URL: a.URL,
Filename: a.Name,
Version: v,
Prerelease: r.Prerelease,
}
all = append(all, w)
}
if err := isValidRelease(all); err != nil {
log.Printf("ignored release %s: %v", r.Tag, err)
continue
}
dls = append(dls, all...)
}
sort.Sort(sort.Reverse(byVersion(dls)))
return dls, nil
}
// Reject releases that are empty or contain multiple files with the same extension.
func isValidRelease(dls []Download) error {
if len(dls) == 0 {
return errors.New("empty slice")
}
counts := map[string]int{}
for _, dl := range dls {
x := strings.ToLower(filepath.Ext(dl.Filename))
counts[x]++
}
for x, n := range counts {
if n > 1 {
return fmt.Errorf("multiple files with extension %q", x)
}
}
return nil
}