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  }