github.com/quantumghost/awgo@v0.15.0/update/github.go (about) 1 // 2 // Copyright (c) 2016 Dean Jackson <deanishe@deanishe.net> 3 // 4 // MIT Licence. See http://opensource.org/licenses/MIT 5 // 6 // Created on 2016-11-03 7 // 8 9 package update 10 11 import ( 12 "encoding/json" 13 "fmt" 14 "log" 15 "net/url" 16 "strings" 17 18 aw "github.com/deanishe/awgo" 19 ) 20 21 const ( 22 ghBaseURL = "https://api.github.com/repos/" 23 ) 24 25 // GitHub is a Workflow Option. It sets a Workflow Updater for the specified GitHub repo. 26 // Repo name should be of the form "username/repo", e.g. "deanishe/alfred-ssh". 27 func GitHub(repo string) aw.Option { 28 return func(wf *aw.Workflow) aw.Option { 29 u, _ := New(wf, &GitHubReleaser{Repo: repo}) 30 return aw.Update(u)(wf) 31 } 32 } 33 34 // GitHubReleaser updates from a GitHub repo's releases. Repo should be in 35 // the form "username/reponame", e.g. "deanishe/alfred-ssh". Releases 36 // are marked as pre-releases based on the "This is a pre-release" 37 // checkbox on the website, *not* the version number/tag. 38 type GitHubReleaser struct { 39 Repo string // Repo name in form username/repo 40 releases []*Release // GitHub releases for Repo 41 } 42 43 // Releases implements Releaser. Returns a slice of available releases that 44 // contain an .alfredworkflow file. 45 func (gh *GitHubReleaser) Releases() ([]*Release, error) { 46 if gh.releases == nil { 47 gh.releases = []*Release{} 48 // rels := []*Release{} 49 js, err := getURL(gh.url()) 50 if err != nil { 51 log.Printf("Error fetching GitHub releases: %s", err) 52 return nil, err 53 } 54 // log.Printf("%d bytes of JSON", len(js)) 55 rels, err := parseGitHubReleases(js) 56 if err != nil { 57 log.Printf("Error parsing GitHub releases: %s", err) 58 return nil, err 59 } 60 gh.releases = rels 61 } 62 log.Printf("%d release(s) in repo %s", len(gh.releases), gh.Repo) 63 return gh.releases, nil 64 } 65 66 func (gh *GitHubReleaser) url() *url.URL { 67 u, _ := url.Parse(fmt.Sprintf("%s%s/releases", ghBaseURL, gh.Repo)) 68 return u 69 } 70 71 // ghRelease is the data model for GitHub releases JSON. 72 type ghRelease struct { 73 Name string `json:"name"` 74 Prerelease bool `json:"prerelease"` 75 Assets []*ghAsset `json:"assets"` 76 Tag string `json:"tag_name"` 77 } 78 79 // ghAsset is the data model for GitHub releases JSON. 80 type ghAsset struct { 81 Name string `json:"name"` 82 URL string `json:"browser_download_url"` 83 } 84 85 // parseGitHubReleases parses GitHub releases JSON. 86 func parseGitHubReleases(js []byte) ([]*Release, error) { 87 ghrels := []*ghRelease{} 88 rels := []*Release{} 89 if err := json.Unmarshal(js, &ghrels); err != nil { 90 return nil, err 91 } 92 for _, ghr := range ghrels { 93 r, err := ghReleaseToRelease(ghr) 94 if err != nil { 95 log.Printf("invalid release: %s", err) 96 } else { 97 rels = append(rels, r) 98 } 99 } 100 return rels, nil 101 } 102 103 func ghReleaseToRelease(ghr *ghRelease) (*Release, error) { 104 rel := &Release{Prerelease: ghr.Prerelease} 105 // Check version 106 v, err := NewSemVer(ghr.Tag) 107 if err != nil { 108 return nil, fmt.Errorf("invalid version/tag %q: %s", ghr.Tag, err) 109 } 110 rel.Version = v 111 // Check files (assets) 112 assets := []*ghAsset{} 113 assets3 := []*ghAsset{} // .alfred3workflow files 114 for _, gha := range ghr.Assets { 115 if strings.HasSuffix(gha.Name, ".alfredworkflow") { 116 assets = append(assets, gha) 117 } else if strings.HasSuffix(gha.Name, ".alfred3workflow") { 118 assets3 = append(assets3, gha) 119 } 120 } 121 122 // Prefer .alfred3workflow files if present 123 if len(assets3) > 0 { 124 assets = assets3 125 } 126 127 // Reject bad releases 128 if len(assets) > 1 { 129 return nil, fmt.Errorf("multiple (%d) workflow files in release %s", len(assets), ghr.Tag) 130 } 131 if len(assets) == 0 { 132 return nil, fmt.Errorf("no workflow files in release %s", ghr.Tag) 133 } 134 135 rel.Filename = assets[0].Name 136 u, err := url.Parse(assets[0].URL) 137 if err != nil { 138 return nil, err 139 } 140 rel.URL = u 141 return rel, nil 142 }