提交 85b4e341 编写于 作者: M Mislav Marohnić

Remove octokit dependency

上级 ba01855c
......@@ -19,29 +19,6 @@
packages = ["."]
revision = "b7ed37b82869576c289d7d97fb2bbd8b64a0cb28"
[[projects]]
name = "github.com/fhs/go-netrc"
packages = ["netrc"]
revision = "4ffed54ee5c32ebfb1b8c7c72fc90bb08dc3ff43"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/jingweno/go-sawyer"
packages = [
".",
"hypermedia",
"mediaheader",
"mediatype"
]
revision = "1999ae5763d678f3ce1112cf1fda7c7e9cf2aadf"
[[projects]]
name = "github.com/jtacoma/uritemplates"
packages = ["."]
revision = "307ae868f90f4ee1b73ebe4596e0394237dacce8"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/kballard/go-shellquote"
......@@ -78,12 +55,6 @@
packages = ["."]
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
[[projects]]
branch = "master"
name = "github.com/octokit/go-octokit"
packages = ["octokit"]
revision = "812e91dfbd64051c1ec72c48feda0278727e8a4e"
[[projects]]
name = "github.com/ogier/pflag"
packages = ["."]
......@@ -114,6 +85,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "0ca844b291d0677146cb19e9591e5e61d3bbbeaf662e25fc27cd3a06297e32dc"
inputs-digest = "8da7746ef8bc5712c5e117753f75f96e6eec12fa8967b601cdd006f1b66cc765"
solver-name = "gps-cdcl"
solver-version = 1
......@@ -26,10 +26,6 @@
branch = "master"
name = "github.com/mitchellh/go-homedir"
[[constraint]]
branch = "master"
name = "github.com/octokit/go-octokit"
[[constraint]]
name = "github.com/ogier/pflag"
version = "0.0.1"
......
......@@ -12,7 +12,6 @@ import (
"time"
"github.com/github/hub/version"
"github.com/octokit/go-octokit/octokit"
)
const (
......@@ -697,20 +696,6 @@ func FormatError(action string, err error) (ee error) {
switch e := err.(type) {
default:
ee = err
case *octokit.ResponseError:
info := &errorInfo{
Message: e.Message,
Response: e.Response,
Errors: []fieldError{},
}
for _, err := range e.Errors {
info.Errors = append(info.Errors, fieldError{
Field: err.Field,
Message: err.Message,
Code: err.Code,
})
}
return FormatError(action, info)
case *errorInfo:
statusCode := e.Response.StatusCode
var reason string
......
Copyright © 2010 Fazlul Shahriar <fshahriar@gmail.com>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
machine mail.google.com
login joe@gmail.com
account gmail
password somethingSecret
machine ray login demo password mypassword
macdef allput
put src/*
default
login anonymous
password joe@example.com
// Copyright © 2010 Fazlul Shahriar <fshahriar@gmail.com>.
// See LICENSE file for license details.
// Package netrc implements a parser for netrc file format.
//
// A netrc file usually resides in $HOME/.netrc and is traditionally used
// by the ftp(1) program to look up login information (username, password,
// etc.) of remote system(s). The file format is (loosely) described in
// this man page: http://linux.die.net/man/5/netrc .
package netrc
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"unicode"
"unicode/utf8"
)
const (
tkMachine = iota
tkDefault
tkLogin
tkPassword
tkAccount
tkMacdef
)
var tokenNames = []string{
"Machine",
"Default",
"Login",
"Password",
"Account",
"Macdef",
}
var keywords = map[string]int{
"machine": tkMachine,
"default": tkDefault,
"login": tkLogin,
"password": tkPassword,
"account": tkAccount,
"macdef": tkMacdef,
}
// Machine contains information about a remote machine.
type Machine struct {
Name string
Login string
Password string
Account string
}
// Macros contains all the macro definitions in a netrc file.
type Macros map[string]string
type token struct {
kind int
macroName string
value string
}
type filePos struct {
name string
line int
}
// Error represents a netrc file parse error.
type Error struct {
Filename string
LineNum int // Line number
Msg string // Error message
}
// Error returns a string representation of error e.
func (e *Error) Error() string {
return fmt.Sprintf("%s:%d: %s", e.Filename, e.LineNum, e.Msg)
}
func getWord(b []byte, pos *filePos) (string, []byte) {
// Skip over leading whitespace
i := 0
for i < len(b) {
r, size := utf8.DecodeRune(b[i:])
if r == '\n' {
pos.line++
}
if !unicode.IsSpace(r) {
break
}
i += size
}
b = b[i:]
// Find end of word
i = bytes.IndexFunc(b, unicode.IsSpace)
if i < 0 {
i = len(b)
}
return string(b[0:i]), b[i:]
}
func getToken(b []byte, pos *filePos) ([]byte, *token, error) {
word, b := getWord(b, pos)
if word == "" {
return b, nil, nil // EOF reached
}
t := new(token)
var ok bool
t.kind, ok = keywords[word]
if !ok {
return b, nil, &Error{pos.name, pos.line, "keyword expected; got " + word}
}
if t.kind == tkDefault {
return b, t, nil
}
word, b = getWord(b, pos)
if word == "" {
return b, nil, &Error{pos.name, pos.line, "word expected"}
}
if t.kind == tkMacdef {
t.macroName = word
// Macro value starts on next line. The rest of current line
// should contain nothing but whitespace
i := 0
for i < len(b) {
r, size := utf8.DecodeRune(b[i:])
if r == '\n' {
i += size
pos.line++
break
}
if !unicode.IsSpace(r) {
return b, nil, &Error{pos.name, pos.line, "unexpected word"}
}
i += size
}
b = b[i:]
// Find end of macro value
i = bytes.Index(b, []byte("\n\n"))
if i < 0 { // EOF reached
i = len(b)
}
t.value = string(b[0:i])
return b[i:], t, nil
}
t.value = word
return b, t, nil
}
func parse(r io.Reader, pos *filePos) ([]*Machine, Macros, error) {
// TODO(fhs): Clear memory containing password.
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, nil, err
}
mach := make([]*Machine, 0, 20)
mac := make(Macros, 10)
var defaultSeen bool
var m *Machine
var t *token
for {
b, t, err = getToken(b, pos)
if err != nil {
return nil, nil, err
}
if t == nil {
break
}
switch t.kind {
case tkMacdef:
mac[t.macroName] = t.value
case tkDefault:
if defaultSeen {
return nil, nil, &Error{pos.name, pos.line, "multiple default token"}
}
if m != nil {
mach, m = append(mach, m), nil
}
m = new(Machine)
m.Name = ""
defaultSeen = true
case tkMachine:
if m != nil {
mach, m = append(mach, m), nil
}
m = new(Machine)
m.Name = t.value
case tkLogin:
if m == nil || m.Login != "" {
return nil, nil, &Error{pos.name, pos.line, "unexpected token login "}
}
m.Login = t.value
case tkPassword:
if m == nil || m.Password != "" {
return nil, nil, &Error{pos.name, pos.line, "unexpected token password"}
}
m.Password = t.value
case tkAccount:
if m == nil || m.Account != "" {
return nil, nil, &Error{pos.name, pos.line, "unexpected token account"}
}
m.Account = t.value
}
}
if m != nil {
mach, m = append(mach, m), nil
}
return mach, mac, nil
}
// ParseFile parses the netrc file identified by filename and returns the set of
// machine information and macros defined in it. The ``default'' machine,
// which is intended to be used when no machine name matches, is identified
// by an empty machine name. There can be only one ``default'' machine.
//
// If there is a parsing error, an Error is returned.
func ParseFile(filename string) ([]*Machine, Macros, error) {
// TODO(fhs): Check if file is readable by anyone besides the user if there is password in it.
fd, err := os.Open(filename)
if err != nil {
return nil, nil, err
}
defer fd.Close()
return parse(fd, &filePos{filename, 1})
}
// FindMachine parses the netrc file identified by filename and returns
// the Machine named by name. If no Machine with name name is found, the
// ``default'' machine is returned.
func FindMachine(filename, name string) (*Machine, error) {
mach, _, err := ParseFile(filename)
if err != nil {
return nil, err
}
var def *Machine
for _, m := range mach {
if m.Name == name {
return m, nil
}
if m.Name == "" {
def = m
}
}
if def == nil {
return nil, errors.New("no machine found")
}
return def, nil
}
Copyright (c) 2013 rick olson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Sawyer
Status: Very experimental
Sawyer is an HTTP user agent for REST APIs. It is a spiritual compliment to
the [Ruby sawyer gem](https://github.com/lostisland/sawyer).
![](http://techno-weenie.net/sawyer/images/sawyer.jpeg)
Use this to build clients for HTTP/JSON APIs that behave like the GitHub API.
## Usage
```go
type User struct {
Login string `json:"login"`
}
class ApiError struct {
Message strign `json:"message"`
}
client := sawyer.NewFromString("https://api.github.com")
// the GitHub API prefers a vendor media type
client.Headers.Set("Accept", "application/vnd.github+json")
apierr := &ApiError{} // decoded from response body on non-20x responses
user := &User{}
req := client.NewRequest("user/21", apierr)
res := req.Get(user)
// get the user's repositories
apierr := &ApiError{}
repos := new([]Repository)
req := client.NewRequest(res.Hyperlink("repos", sawyer.M{"page": "2"}), apierr)
res := req.Get(repos)
// post a new user
mtype := mediatype.Parse("application/vnd.github+json")
apierr := &ApiError{}
userInput := &User{Login: "bob"}
userOutput := &User{}
req := client.NewRequest("users", apierr)
err := req.SetBody(mtype, userInput)
res := req.Post(userOutput)
```
repo = "github.com/lostisland/go-sawyer"
[deps.uritemplates]
import = "github.com/jtacoma/uritemplates"
commit = "2b6fc855d3a722bc0e5525ae50bb3c10703c5450"
[deps.assert]
import = "github.com/bmizerany/assert"
commit = "e17e99893cb6509f428e1728281c2ad60a6b31e3"
// Package hypermedia provides helpers for parsing hypermedia links in resources
// and expanding the links to make further requests.
package hypermedia
import (
"fmt"
"github.com/jtacoma/uritemplates"
"net/url"
"reflect"
)
// Hyperlink is a string url. If it is a uri template, it can be converted to
// a full URL with Expand().
type Hyperlink string
// Expand converts a uri template into a url.URL using the given M map.
func (l Hyperlink) Expand(m M) (*url.URL, error) {
template, err := uritemplates.Parse(string(l))
if err != nil {
return nil, err
}
// clone M to map[string]interface{}
// if we don't do this type assertion will
// fail on jtacoma/uritemplates
// see https://github.com/jtacoma/uritemplates/blob/master/uritemplates.go#L189
mm := make(map[string]interface{}, len(m))
for k, v := range m {
mm[k] = v
}
expanded, err := template.Expand(mm)
if err != nil {
return nil, err
}
return url.Parse(expanded)
}
// M represents a map of values to expand a Hyperlink.
type M map[string]interface{}
// Relations is a map of keys that point to Hyperlink objects.
type Relations map[string]Hyperlink
// Rel fetches and expands the Hyperlink by its given key in the Relations map.
func (h Relations) Rel(name string, m M) (*url.URL, error) {
if rel, ok := h[name]; ok {
return rel.Expand(m)
}
return nil, fmt.Errorf("No %s relation found", name)
}
// A HypermediaResource has link relations for next actions of a resource.
type HypermediaResource interface {
Rels() Relations
}
// The HypermediaDecoder gets the link relations from any HypermediaResource.
func HypermediaDecoder(res HypermediaResource) Relations {
return res.Rels()
}
// HALResource is a resource with hypermedia specified as JSON HAL.
//
// http://stateless.co/hal_specification.html
type HALResource struct {
Links Links `json:"_links"`
rels Relations
}
// Rels gets the link relations from the HALResource's Links field.
func (r *HALResource) Rels() Relations {
if r.rels == nil {
r.rels = make(map[string]Hyperlink)
for name, link := range r.Links {
r.rels[name] = link.Href
}
}
return r.rels
}
// Links is a collection of Link objects in a HALResource. Note that the HAL
// spec allows single link objects or an array of link objects. Sawyer
// currently only supports single link objects.
type Links map[string]Link
// Link represents a single link in a HALResource.
type Link struct {
Href Hyperlink `json:"href"`
}
// Expand converts a uri template into a url.URL using the given M map.
func (l *Link) Expand(m M) (*url.URL, error) {
return l.Href.Expand(m)
}
// The HyperFieldDecoder gets link relations from a resource by reflecting on
// its Hyperlink properties. The relation name is taken either from the name
// of the field, or a "rel" struct tag.
//
// type Foo struct {
// Url Hyperlink `rel:"self" json:"url"`
// CommentsUrl Hyperlink `rel:"comments" json:"comments_url"`
// }
//
func HyperFieldDecoder(res interface{}) Relations {
rels := make(Relations)
t := reflect.TypeOf(res).Elem()
v := reflect.ValueOf(res).Elem()
fieldlen := t.NumField()
for i := 0; i < fieldlen; i++ {
fillRelation(rels, t, v, i)
}
return rels
}
func fillRelation(rels map[string]Hyperlink, t reflect.Type, v reflect.Value, index int) {
f := t.Field(index)
if hyperlinkType != f.Type {
return
}
hl := v.Field(index).Interface().(Hyperlink)
name := f.Name
if rel := f.Tag.Get("rel"); len(rel) > 0 {
name = rel
}
rels[name] = hl
}
var hyperlinkType = reflect.TypeOf(Hyperlink("foo"))
package mediaheader
import (
"github.com/jingweno/go-sawyer/hypermedia"
"net/http"
"net/url"
"strings"
)
// TODO: need a full link header parser for http://tools.ietf.org/html/rfc5988
type Decoder struct {
}
func (d *Decoder) Decode(header http.Header) (mediaHeader *MediaHeader) {
mediaHeader = &MediaHeader{Relations: hypermedia.Relations{}}
link := header.Get("Link")
if len(link) == 0 {
return
}
for _, l := range strings.Split(link, ",") {
l = strings.TrimSpace(l)
segments := strings.Split(l, ";")
if len(segments) < 2 {
continue
}
if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
continue
}
url, err := url.Parse(segments[0][1 : len(segments[0])-1])
if err != nil {
continue
}
link := hypermedia.Hyperlink(url.String())
for _, segment := range segments[1:] {
switch strings.TrimSpace(segment) {
case `rel="next"`:
mediaHeader.Relations["next"] = link
case `rel="prev"`:
mediaHeader.Relations["prev"] = link
case `rel="first"`:
mediaHeader.Relations["first"] = link
case `rel="last"`:
mediaHeader.Relations["last"] = link
}
}
}
return
}
package mediaheader
import (
"github.com/jingweno/go-sawyer/hypermedia"
)
type MediaHeader struct {
Relations hypermedia.Relations
}
package mediatype
import (
"fmt"
"io"
)
var decoders = make(map[string]DecoderFunc)
// DecoderFunc is a function that creates a Decoder from an io.Reader.
type DecoderFunc func(r io.Reader) Decoder
// A Decoder will decode the given value to the Decoder's io.Reader.
type Decoder interface {
Decode(v interface{}) error
}
/*
AddDecoder installs a decoder for a given format.
AddDecoder("json", func(r io.Reader) Encoder { return json.NewDecoder(r) })
mt, err := Parse("application/json")
decoder, err := mt.Decoder(someReader)
*/
func AddDecoder(format string, decfunc DecoderFunc) {
decoders[format] = decfunc
}
// Decoder finds a decoder based on this MediaType's Format field. An error is
// returned if a decoder cannot be found.
func (m *MediaType) Decoder(body io.Reader) (Decoder, error) {
if decfunc, ok := decoders[m.Format]; ok {
return decfunc(body), nil
}
return nil, fmt.Errorf("No decoder found for format %s (%s)", m.Format, m.String())
}
// Encode uses this MediaType's Decoder to decode the io.Reader into the given
// value.
func (m *MediaType) Decode(v interface{}, body io.Reader) error {
if v == nil {
return nil
}
dec, err := m.Decoder(body)
if err != nil {
return err
}
return dec.Decode(v)
}
package mediatype
import (
"bytes"
"fmt"
"io"
)
var encoders = make(map[string]EncoderFunc)
// EncoderFunc is a function that creates an Encoder from an io.Writer.
type EncoderFunc func(w io.Writer) Encoder
// An Encoder will encode the given value to the Encoder's io.Writer.
type Encoder interface {
Encode(v interface{}) error
}
/*
AddEncoder installs an encoder for a given format.
AddEncoder("json", func(w io.Writer) Encoder { return json.NewEncoder(w) })
mt, err := Parse("application/json")
encoder, err := mt.Encoder(someWriter)
*/
func AddEncoder(format string, encfunc EncoderFunc) {
encoders[format] = encfunc
}
// Encoder finds an encoder based on this MediaType's Format field. An error is
// returned if an encoder cannot be found.
func (m *MediaType) Encoder(w io.Writer) (Encoder, error) {
if encfunc, ok := encoders[m.Format]; ok {
return encfunc(w), nil
}
return nil, fmt.Errorf("No encoder found for format %s (%s)", m.Format, m.String())
}
// Encode uses this MediaType's Encoder to encode the given value into a
// bytes.Buffer.
func (m *MediaType) Encode(v interface{}) (*bytes.Buffer, error) {
if v == nil {
return nil, fmt.Errorf("Nothing to encode")
}
buf := new(bytes.Buffer)
enc, err := m.Encoder(buf)
if err != nil {
return buf, err
}
return buf, enc.Encode(v)
}
// Package mediatype contains helpers for parsing media type strings. Uses
// RFC4288 as a guide.
package mediatype
import (
"mime"
"strings"
)
/*
A MediaType is a parsed representation of a media type string.
application/vnd.github.raw+json; version=3; charset=utf-8
This gets broken up into the various fields:
- Type: application/vnd.github.raw+json
- MainType: application
- SubType: vnd.github.raw
- Suffix: json
- Vendor: github
- Version: raw
- Format: json
- Params:
version: 3
charset: utf-8
There are a few special behaviors that prioritize custom media types for APIs:
If an API identifies with an "application/vnd" type, the Vendor and Version
fields are parsed from the remainder. The Version's semantic meaning depends on
the application.
If it's not an "application/vnd" type, the Version field is taken from the
"version" parameter.
The Format is taken from the Suffix by default. If not available, it is guessed
by looking for common strings anywhere in the media type. For instance,
"application/json" will identify as the "json" Format.
The Format is used to get an Encoder and a Decoder.
*/
type MediaType struct {
full string
Type string
MainType string
SubType string
Suffix string
Vendor string
Version string
Format string
Params map[string]string
}
// Parse builds a *MediaType from a given media type string.
func Parse(v string) (*MediaType, error) {
mt, params, err := mime.ParseMediaType(v)
if err != nil {
return nil, err
}
return parse(&MediaType{
full: v,
Type: mt,
Params: params,
})
}
// String returns the full string representation of the MediaType.
func (m *MediaType) String() string {
return m.full
}
// IsVendor determines if this MediaType is associated with commercially
// available products.
func (m *MediaType) IsVendor() bool {
return len(m.Vendor) > 0
}
func parse(m *MediaType) (*MediaType, error) {
pieces := strings.Split(m.Type, typeSplit)
m.MainType = pieces[0]
if len(pieces) > 1 {
subpieces := strings.Split(pieces[1], suffixSplit)
m.SubType = subpieces[0]
if len(subpieces) > 1 {
m.Suffix = subpieces[1]
}
}
if strings.HasPrefix(m.SubType, vndPrefix) {
if vnd := m.SubType[vndLen:]; len(vnd) > 0 {
args := strings.SplitN(vnd, vndSplit, 2)
m.Vendor = args[0]
if len(args) > 1 {
m.Version = args[1]
}
}
}
if len(m.Version) == 0 {
if v, ok := m.Params[versionKey]; ok {
m.Version = v
}
}
if len(m.Suffix) > 0 {
m.Format = m.Suffix
} else {
guessFormat(m)
}
return m, nil
}
func guessFormat(m *MediaType) {
for _, fmt := range guessableTypes {
if strings.Contains(m.Type, fmt) {
m.Format = fmt
return
}
}
}
const (
typeSplit = "/"
suffixSplit = "+"
versionKey = "version"
vndPrefix = "vnd."
vndLen = 4
vndSplit = "."
)
var guessableTypes = []string{"json", "xml"}
package sawyer
import (
"io/ioutil"
"net/http"
"net/url"
"github.com/jingweno/go-sawyer/mediaheader"
"github.com/jingweno/go-sawyer/mediatype"
)
type Request struct {
Client *http.Client
MediaType *mediatype.MediaType
Query url.Values
*http.Request
}
func (c *Client) NewRequest(rawurl string) (*Request, error) {
u, err := c.ResolveReferenceString(rawurl)
if err != nil {
return nil, err
}
httpreq, err := http.NewRequest(GetMethod, u, nil)
for key, _ := range c.Header {
httpreq.Header.Set(key, c.Header.Get(key))
}
return &Request{c.HttpClient, nil, httpreq.URL.Query(), httpreq}, err
}
func (r *Request) Do(method string) *Response {
r.URL.RawQuery = r.Query.Encode()
r.Method = method
httpres, err := r.Client.Do(r.Request)
if err != nil {
return ResponseError(err)
}
mtype, err := mediaType(httpres)
if err != nil {
httpres.Body.Close()
return ResponseError(err)
}
headerDecoder := mediaheader.Decoder{}
mheader := headerDecoder.Decode(httpres.Header)
return &Response{nil, mtype, mheader, UseApiError(httpres.StatusCode), false, httpres}
}
func (r *Request) Head() *Response {
return r.Do(HeadMethod)
}
func (r *Request) Get() *Response {
return r.Do(GetMethod)
}
func (r *Request) Post() *Response {
return r.Do(PostMethod)
}
func (r *Request) Put() *Response {
return r.Do(PutMethod)
}
func (r *Request) Patch() *Response {
return r.Do(PatchMethod)
}
func (r *Request) Delete() *Response {
return r.Do(DeleteMethod)
}
func (r *Request) Options() *Response {
return r.Do(OptionsMethod)
}
func (r *Request) SetBody(mtype *mediatype.MediaType, input interface{}) error {
r.MediaType = mtype
buf, err := mtype.Encode(input)
if err != nil {
return err
}
r.Header.Set(ctypeHeader, mtype.String())
r.ContentLength = int64(buf.Len())
r.Body = ioutil.NopCloser(buf)
return nil
}
const (
ctypeHeader = "Content-Type"
HeadMethod = "HEAD"
GetMethod = "GET"
PostMethod = "POST"
PutMethod = "PUT"
PatchMethod = "PATCH"
DeleteMethod = "DELETE"
OptionsMethod = "OPTIONS"
)
package sawyer
import (
"errors"
"net/http"
"github.com/jingweno/go-sawyer/mediaheader"
"github.com/jingweno/go-sawyer/mediatype"
)
type Response struct {
ResponseError error
MediaType *mediatype.MediaType
MediaHeader *mediaheader.MediaHeader
isApiError bool
BodyClosed bool
*http.Response
}
func (r *Response) AnyError() bool {
return r.IsError() || r.IsApiError()
}
func (r *Response) IsError() bool {
return r.ResponseError != nil
}
func (r *Response) IsApiError() bool {
return r.isApiError
}
func (r *Response) Error() string {
if r.ResponseError != nil {
return r.ResponseError.Error()
}
return ""
}
func (r *Response) Decode(resource interface{}) error {
if r.MediaType == nil {
return errors.New("No media type for this response")
}
if resource == nil || r.ResponseError != nil || r.BodyClosed {
return r.ResponseError
}
defer r.Body.Close()
r.BodyClosed = true
dec, err := r.MediaType.Decoder(r.Body)
if err != nil {
r.ResponseError = err
} else {
r.ResponseError = dec.Decode(resource)
}
return r.ResponseError
}
func (r *Response) decode(output interface{}) {
if !r.isApiError {
r.Decode(output)
}
}
func ResponseError(err error) *Response {
return &Response{ResponseError: err, BodyClosed: true}
}
func UseApiError(status int) bool {
switch {
case status > 199 && status < 300:
return false
case status == 304:
return false
case status == 0:
return false
}
return true
}
func mediaType(res *http.Response) (*mediatype.MediaType, error) {
if ctype := res.Header.Get(ctypeHeader); len(ctype) > 0 {
return mediatype.Parse(ctype)
}
return nil, nil
}
package sawyer
import (
"encoding/json"
"io"
"net/http"
"net/url"
"strings"
"github.com/jingweno/go-sawyer/mediatype"
)
// The default httpClient used if one isn't specified.
var httpClient = &http.Client{}
// A Client wraps an *http.Client with a base url Endpoint and common header and
// query values.
type Client struct {
HttpClient *http.Client
Endpoint *url.URL
Header http.Header
Query url.Values
}
// New returns a new Client with a given a URL and an optional client.
func New(endpoint *url.URL, client *http.Client) *Client {
if client == nil {
client = httpClient
}
if len(endpoint.Path) > 0 && !strings.HasSuffix(endpoint.Path, "/") {
endpoint.Path = endpoint.Path + "/"
}
return &Client{client, endpoint, make(http.Header), endpoint.Query()}
}
// NewFromString returns a new Client given a string URL and an optional client.
func NewFromString(endpoint string, client *http.Client) (*Client, error) {
e, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
return New(e, client), nil
}
// ResolveReference resolves a URI reference to an absolute URI from an absolute
// base URI. It also merges the query values.
func (c *Client) ResolveReference(u *url.URL) *url.URL {
absurl := c.Endpoint.ResolveReference(u)
if len(c.Query) > 0 {
absurl.RawQuery = mergeQueries(c.Query, absurl.Query())
}
return absurl
}
// ResolveReference resolves a string URI reference to an absolute URI from an
// absolute base URI. It also merges the query values.
func (c *Client) ResolveReferenceString(rawurl string) (string, error) {
u, err := url.Parse(rawurl)
if err != nil {
return "", err
}
return c.ResolveReference(u).String(), nil
}
func mergeQueries(queries ...url.Values) string {
merged := make(url.Values)
for _, q := range queries {
if len(q) == 0 {
break
}
for key, _ := range q {
merged.Set(key, q.Get(key))
}
}
return merged.Encode()
}
func init() {
mediatype.AddDecoder("json", func(r io.Reader) mediatype.Decoder {
return json.NewDecoder(r)
})
mediatype.AddEncoder("json", func(w io.Writer) mediatype.Encoder {
return json.NewEncoder(w)
})
}
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
[submodule "tests"]
path = tests
url = https://github.com/uri-templates/uritemplate-test.git
{{ .EmitHeader }}
[![Build Status](https://travis-ci.org/jtacoma/uritemplates.png)](https://travis-ci.org/jtacoma/uritemplates) [![Coverage Status](https://coveralls.io/repos/jtacoma/uritemplates/badge.png)](https://coveralls.io/r/jtacoma/uritemplates)
{{ .EmitSynopsis }}
## License
Use of this source code is governed by a BSD-style license that can be found in
the LICENSE file.
Copyright (c) 2013 Joshua Tacoma. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# uritemplates
--
import "github.com/jtacoma/uritemplates"
[![Build Status](https://travis-ci.org/jtacoma/uritemplates.png)](https://travis-ci.org/jtacoma/uritemplates) [![Coverage Status](https://coveralls.io/repos/jtacoma/uritemplates/badge.png)](https://coveralls.io/r/jtacoma/uritemplates)
Package uritemplates is a level 4 implementation of RFC 6570 (URI
Template, http://tools.ietf.org/html/rfc6570).
To use uritemplates, parse a template string and expand it with a value
map:
template, _ := uritemplates.Parse("https://api.github.com/repos{/user,repo}")
values := make(map[string]interface{})
values["user"] = "jtacoma"
values["repo"] = "uritemplates"
expanded, _ := template.Expand(values)
fmt.Printf(expanded)
## License
Use of this source code is governed by a BSD-style license that can be found in
the LICENSE file.
// Copyright 2013 Joshua Tacoma. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uritemplates is a level 4 implementation of RFC 6570 (URI
// Template, http://tools.ietf.org/html/rfc6570).
//
// To use uritemplates, parse a template string and expand it with a value
// map:
//
// template, _ := uritemplates.Parse("https://api.github.com/repos{/user,repo}")
// values := make(map[string]interface{})
// values["user"] = "jtacoma"
// values["repo"] = "uritemplates"
// expanded, _ := template.Expand(values)
// fmt.Printf(expanded)
//
package uritemplates
import (
"bytes"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")
hex = []byte("0123456789ABCDEF")
)
func pctEncode(src []byte) []byte {
dst := make([]byte, len(src)*3)
for i, b := range src {
buf := dst[i*3 : i*3+3]
buf[0] = 0x25
buf[1] = hex[b/16]
buf[2] = hex[b%16]
}
return dst
}
func escape(s string, allowReserved bool) (escaped string) {
if allowReserved {
escaped = string(reserved.ReplaceAllFunc([]byte(s), pctEncode))
} else {
escaped = string(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
}
return escaped
}
// A UriTemplate is a parsed representation of a URI template.
type UriTemplate struct {
raw string
parts []templatePart
}
// Parse parses a URI template string into a UriTemplate object.
func Parse(rawtemplate string) (template *UriTemplate, err error) {
template = new(UriTemplate)
template.raw = rawtemplate
split := strings.Split(rawtemplate, "{")
template.parts = make([]templatePart, len(split)*2-1)
for i, s := range split {
if i == 0 {
if strings.Contains(s, "}") {
err = errors.New("unexpected }")
break
}
template.parts[i].raw = s
} else {
subsplit := strings.Split(s, "}")
if len(subsplit) != 2 {
err = errors.New("malformed template")
break
}
expression := subsplit[0]
template.parts[i*2-1], err = parseExpression(expression)
if err != nil {
break
}
template.parts[i*2].raw = subsplit[1]
}
}
if err != nil {
template = nil
}
return template, err
}
func (t UriTemplate) String() string {
return t.raw
}
type templatePart struct {
raw string
terms []templateTerm
first string
sep string
named bool
ifemp string
allowReserved bool
}
type templateTerm struct {
name string
explode bool
truncate int
}
func parseExpression(expression string) (result templatePart, err error) {
switch expression[0] {
case '+':
result.sep = ","
result.allowReserved = true
expression = expression[1:]
case '.':
result.first = "."
result.sep = "."
expression = expression[1:]
case '/':
result.first = "/"
result.sep = "/"
expression = expression[1:]
case ';':
result.first = ";"
result.sep = ";"
result.named = true
expression = expression[1:]
case '?':
result.first = "?"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '&':
result.first = "&"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '#':
result.first = "#"
result.sep = ","
result.allowReserved = true
expression = expression[1:]
default:
result.sep = ","
}
rawterms := strings.Split(expression, ",")
result.terms = make([]templateTerm, len(rawterms))
for i, raw := range rawterms {
result.terms[i], err = parseTerm(raw)
if err != nil {
break
}
}
return result, err
}
func parseTerm(term string) (result templateTerm, err error) {
if strings.HasSuffix(term, "*") {
result.explode = true
term = term[:len(term)-1]
}
split := strings.Split(term, ":")
if len(split) == 1 {
result.name = term
} else if len(split) == 2 {
result.name = split[0]
var parsed int64
parsed, err = strconv.ParseInt(split[1], 10, 0)
result.truncate = int(parsed)
} else {
err = errors.New("multiple colons in same term")
}
if !validname.MatchString(result.name) {
err = errors.New("not a valid name: " + result.name)
}
if result.explode && result.truncate > 0 {
err = errors.New("both explode and prefix modifers on same term")
}
return result, err
}
// Names returns the names of all variables within the template.
func (self *UriTemplate) Names() []string {
names := make([]string, 0, len(self.parts))
for _, p := range self.parts {
if len(p.raw) > 0 || len(p.terms) == 0 {
continue
}
for _, term := range p.terms {
names = append(names, term.name)
}
}
return names
}
// Expand expands a URI template with a set of values to produce a string.
func (self *UriTemplate) Expand(value interface{}) (string, error) {
values, ismap := value.(map[string]interface{})
if !ismap {
if m, ismap := struct2map(value); !ismap {
return "", errors.New("expected map[string]interface{}, struct, or pointer to struct.")
} else {
return self.Expand(m)
}
}
var buf bytes.Buffer
for _, p := range self.parts {
err := p.expand(&buf, values)
if err != nil {
return "", err
}
}
return buf.String(), nil
}
func (self *templatePart) expand(buf *bytes.Buffer, values map[string]interface{}) error {
if len(self.raw) > 0 {
buf.WriteString(self.raw)
return nil
}
var zeroLen = buf.Len()
buf.WriteString(self.first)
var firstLen = buf.Len()
for _, term := range self.terms {
value, exists := values[term.name]
if !exists {
continue
}
if buf.Len() != firstLen {
buf.WriteString(self.sep)
}
switch v := value.(type) {
case string:
self.expandString(buf, term, v)
case []interface{}:
self.expandArray(buf, term, v)
case map[string]interface{}:
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, v)
default:
if m, ismap := struct2map(value); ismap {
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, m)
} else {
str := fmt.Sprintf("%v", value)
self.expandString(buf, term, str)
}
}
}
if buf.Len() == firstLen {
original := buf.Bytes()[:zeroLen]
buf.Reset()
buf.Write(original)
}
return nil
}
func (self *templatePart) expandName(buf *bytes.Buffer, name string, empty bool) {
if self.named {
buf.WriteString(name)
if empty {
buf.WriteString(self.ifemp)
} else {
buf.WriteString("=")
}
}
}
func (self *templatePart) expandString(buf *bytes.Buffer, t templateTerm, s string) {
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
self.expandName(buf, t.name, len(s) == 0)
buf.WriteString(escape(s, self.allowReserved))
}
func (self *templatePart) expandArray(buf *bytes.Buffer, t templateTerm, a []interface{}) {
if len(a) == 0 {
return
} else if !t.explode {
self.expandName(buf, t.name, false)
}
for i, value := range a {
if t.explode && i > 0 {
buf.WriteString(self.sep)
} else if i > 0 {
buf.WriteString(",")
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
if self.named && t.explode {
self.expandName(buf, t.name, len(s) == 0)
}
buf.WriteString(escape(s, self.allowReserved))
}
}
func (self *templatePart) expandMap(buf *bytes.Buffer, t templateTerm, m map[string]interface{}) {
if len(m) == 0 {
return
}
if !t.explode {
self.expandName(buf, t.name, len(m) == 0)
}
var firstLen = buf.Len()
for k, value := range m {
if firstLen != buf.Len() {
if t.explode {
buf.WriteString(self.sep)
} else {
buf.WriteString(",")
}
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if t.explode {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune('=')
buf.WriteString(escape(s, self.allowReserved))
} else {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune(',')
buf.WriteString(escape(s, self.allowReserved))
}
}
}
func struct2map(v interface{}) (map[string]interface{}, bool) {
value := reflect.ValueOf(v)
switch value.Type().Kind() {
case reflect.Ptr:
return struct2map(value.Elem().Interface())
case reflect.Struct:
m := make(map[string]interface{})
for i := 0; i < value.NumField(); i++ {
tag := value.Type().Field(i).Tag
var name string
if strings.Contains(string(tag), ":") {
name = tag.Get("uri")
} else {
name = strings.TrimSpace(string(tag))
}
if len(name) == 0 {
name = value.Type().Field(i).Name
}
m[name] = value.Field(i).Interface()
}
return m, true
}
return nil, false
}
Copyright (c) 2013 Jingwen Owen Ou
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package octokit
import (
"encoding/base64"
"fmt"
"net/url"
"os"
"path/filepath"
"github.com/fhs/go-netrc/netrc"
)
// AuthMethod is a general interface for possible forms of authentication.
// In order to act as a form of authentication, a struct must at minimum
// implement the String() method which produces the authentication string.
// See http://developer.github.com/v3/auth/ for more information
type AuthMethod interface {
fmt.Stringer
}
// BasicAuth is a form of authentication involving a simple login and password.
// A OneTimePassword field may be set for two-factor authentication.
type BasicAuth struct {
Login string
Password string
OneTimePassword string
}
// String hashes the login and password to produce the string to be passed for
// authentication purposes.
func (b BasicAuth) String() string {
return fmt.Sprintf("Basic %s", hashAuth(b.Login, b.Password))
}
// NetrcAuth is a form of authentication using a .netrc file for permanent
// authentication by storing credentials.
type NetrcAuth struct {
NetrcPath string
}
// String accesses the credentials from the .netrc file and hashes the associated
// login and password to submit as a form of basic authentication.
func (n NetrcAuth) String() string {
netrcPath := n.NetrcPath
if netrcPath == "" {
netrcPath = filepath.Join(os.Getenv("HOME"), ".netrc")
}
apiURL, _ := url.Parse(gitHubAPIURL)
credentials, err := netrc.FindMachine(netrcPath, apiURL.Host)
if err != nil {
panic(fmt.Errorf("netrc error (%s): %v", apiURL.Host, err))
}
return fmt.Sprintf("Basic %s", hashAuth(credentials.Login, credentials.Password))
}
// hashAuth is a helper function for producing a base64 encoding of a username and
// password pair
func hashAuth(u, p string) string {
var a = fmt.Sprintf("%s:%s", u, p)
return base64.StdEncoding.EncodeToString([]byte(a))
}
// TokenAuth is a form of authentication using an access token
type TokenAuth struct {
AccessToken string
}
// String produces the authentication string using the access token
func (t TokenAuth) String() string {
return fmt.Sprintf("token %s", t.AccessToken)
}
package octokit
import (
"net/url"
"time"
"github.com/jingweno/go-sawyer/hypermedia"
)
// AuthorizationsURL is a template for accessing authorizations possibly associated with a
// given identification number that can be expanded to a full address.
//
// https://developer.github.com/v3/oauth_authorizations/
var AuthorizationsURL = Hyperlink("authorizations{/id}")
// Authorizations creates a AuthorizationsService with a base url.
//
// https://developer.github.com/v3/oauth_authorizations/
func (c *Client) Authorizations(url *url.URL) (auths *AuthorizationsService) {
auths = &AuthorizationsService{client: c, URL: url}
return
}
// AuthorizationsService is a service providing access to OAuth authorizations through
// the authorization API
type AuthorizationsService struct {
client *Client
URL *url.URL
}
// One gets a specific authorization based on the url of the service.
//
// https://developer.github.com/v3/oauth_authorizations/#get-a-single-authorization
func (a *AuthorizationsService) One() (auth *Authorization, result *Result) {
result = a.client.get(a.URL, &auth)
return
}
// All gets a list of all authorizations associated with the url of the service.
//
// https://developer.github.com/v3/oauth_authorizations/#list-your-authorizations
func (a *AuthorizationsService) All() (auths []Authorization, result *Result) {
result = a.client.get(a.URL, &auths)
return
}
// Create posts a new authorization to the authorizations service url.
//
// https://developer.github.com/v3/oauth_authorizations/#create-a-new-authorization
func (a *AuthorizationsService) Create(params interface{}) (auth *Authorization, result *Result) {
result = a.client.post(a.URL, params, &auth)
return
}
// Authorization is a representation of an OAuth passed to or from the authorizations API
type Authorization struct {
*hypermedia.HALResource
ID int `json:"id,omitempty"`
URL string `json:"url,omitempty"`
App App `json:"app,omitempty"`
Token string `json:"token,omitempty"`
Note string `json:"note,omitempty"`
NoteURL string `json:"note_url,omitempty"`
Scopes []string `json:"scopes,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
// App is the unit holding the associated authorization
type App struct {
*hypermedia.HALResource
ClientID string `json:"client_id,omitempty"`
URL string `json:"url,omitempty"`
Name string `json:"name,omitempty"`
}
// AuthorizationParams is the set of parameters used when creating a new
// authorization to be posted to the API
type AuthorizationParams struct {
Scopes []string `json:"scopes,omitempty"`
Note string `json:"note,omitempty"`
NoteURL string `json:"note_url,omitempty"`
ClientID string `json:"client_id,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
}
package octokit
import (
"io"
"net/http"
"net/url"
"github.com/jingweno/go-sawyer"
"github.com/jingweno/go-sawyer/hypermedia"
)
// NewClient creates a new Client using the given authorization method. Provides a
// very simple client for connecting to the GitHub API using Octokit.
func NewClient(authMethod AuthMethod) *Client {
return NewClientWith(gitHubAPIURL, userAgent, authMethod, nil)
}
// NewClientWith creates a new Client with a particular base URL which all requests will
// be appended onto - often the GitHub URL - the user agent being represented, the
// authentication method, and a pointer to a httpClient if a particular client is being
// wrapped. If httpClient is nil a default httpClient will be used.
func NewClientWith(baseURL string, userAgent string, authMethod AuthMethod, httpClient *http.Client) *Client {
client, _ := sawyer.NewFromString(baseURL, httpClient)
return &Client{Client: client, UserAgent: userAgent, AuthMethod: authMethod}
}
// Client wraps a sawyer Client, a higher level wrapper for HttpClient, with a particular
// represented user agent and authentication method for GitHub. Can also store the particular
// hypermedia relations accessible through the root address.
type Client struct {
*sawyer.Client
UserAgent string
AuthMethod AuthMethod
rootRels hypermedia.Relations
}
// NewRequest produces a simple request for the given url and applies the proper headers
// currently associated with the client to that request.
func (c *Client) NewRequest(urlStr string) (req *Request, err error) {
req, err = newRequest(c, urlStr)
if err != nil {
return
}
c.applyRequestHeaders(req)
return
}
// a GET request with specific media type set
func (c *Client) getBody(url *url.URL, mediaType string) (patch io.ReadCloser, result *Result) {
result = sendRequest(c, url, func(req *Request) (*Response, error) {
req.Header.Set("Accept", mediaType)
return req.Get(nil)
})
if result.Response != nil {
patch = result.Response.Body
}
return
}
func (c *Client) head(url *url.URL, output interface{}) (result *Result) {
return sendRequest(c, url, func(req *Request) (*Response, error) {
return req.Head(output)
})
}
func (c *Client) get(url *url.URL, output interface{}) (result *Result) {
return sendRequest(c, url, func(req *Request) (*Response, error) {
return req.Get(output)
})
}
func (c *Client) post(url *url.URL, input interface{}, output interface{}) (result *Result) {
return sendRequest(c, url, func(req *Request) (*Response, error) {
return req.Post(input, output)
})
}
func (c *Client) put(url *url.URL, input interface{}, output interface{}) (result *Result) {
return sendRequest(c, url, func(req *Request) (*Response, error) {
return req.Put(input, output)
})
}
func (c *Client) delete(url *url.URL, input interface{}, output interface{}) (result *Result) {
return sendRequest(c, url, func(req *Request) (*Response, error) {
return req.Delete(input, output)
})
}
func (c *Client) patch(url *url.URL, input interface{}, output interface{}) (result *Result) {
return sendRequest(c, url, func(req *Request) (*Response, error) {
return req.Patch(input, output)
})
}
func (c *Client) upload(uploadUrl *url.URL, asset io.ReadCloser, contentType string, contentLength int64) (result *Result) {
req, err := c.NewRequest(uploadUrl.String())
if err != nil {
result = newResult(nil, err)
return
}
req.Header.Set("Content-Type", contentType)
req.ContentLength = contentLength
req.Body = asset
sawyerResp := req.Request.Post()
resp, err := NewResponse(sawyerResp)
return newResult(resp, err)
}
func (c *Client) applyRequestHeaders(req *Request) {
req.Header.Set("Accept", defaultMediaType)
req.Header.Set("User-Agent", c.UserAgent)
if c.AuthMethod != nil {
req.Header.Set("Authorization", c.AuthMethod.String())
}
if basicAuth, ok := c.AuthMethod.(BasicAuth); ok && basicAuth.OneTimePassword != "" {
req.Header.Set("X-GitHub-OTP", basicAuth.OneTimePassword)
}
// Go doesn't apply `Host` on the header, instead it consults `Request.Host`
// Populate `Host` if it exists in `Client.Header`
// See Bug https://code.google.com/p/go/issues/detail?id=7682
host := c.Header.Get("Host")
if host != "" {
req.Request.Host = host
}
return
}
func sendRequest(c *Client, url *url.URL, fn func(r *Request) (*Response, error)) (result *Result) {
req, err := c.NewRequest(url.String())
if err != nil {
result = newResult(nil, err)
return
}
resp, err := fn(req)
result = newResult(resp, err)
return
}
package octokit
// CollaboratorsURL is the template for accessing the collaborators
// of a particular repository.
var (
CollaboratorsURL = Hyperlink(
"repos/{owner}/{repo}/collaborators{/username}")
)
// Collaborators creates a CollaboratorsService with a base url
func (c *Client) Collaborators() (repos *CollaboratorsService) {
repos = &CollaboratorsService{client: c}
return
}
// CollaboratorsService is a service providing access to a repositories'
// collaborators
type CollaboratorsService struct {
client *Client
}
// All lists all the collaborating users on the given repository
//
// https://developer.github.com/v3/repos/collaborators/#list
func (r *CollaboratorsService) All(uri *Hyperlink, params M) (users []User,
result *Result) {
if uri == nil {
uri = &CollaboratorsURL
}
url, err := uri.Expand(params)
if err != nil {
return nil, &Result{Err: err}
}
result = r.client.get(url, &users)
return
}
// IsCollaborator checks if a user is a collaborator for a repo
//
// https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator
func (r *CollaboratorsService) IsCollaborator(uri *Hyperlink,
params M) (collabStatus bool, result *Result) {
if uri == nil {
uri = &CollaboratorsURL
}
url, err := uri.Expand(params)
if err != nil {
return false, &Result{Err: err}
}
result = r.client.get(url, nil)
collabStatus = false
if result.Err == nil && result.Response.Response.StatusCode == 204 {
collabStatus = true
}
return
}
package octokit
import (
"time"
"github.com/jingweno/go-sawyer/hypermedia"
)
// RepoCommentsURL is a template for comments linked to a specific repository
// CommitCommentsURL is a template for comments linked to a specific commit
var (
RepoCommentsURL = Hyperlink("/repos/{owner}/{repo}/comments{/id}")
CommitCommentsURL = Hyperlink("/repos/{owner}/{repo}/commits/{sha}/comments")
)
// Create a CommitCommentsService
func (c *Client) CommitComments() (k *CommitCommentsService) {
k = &CommitCommentsService{client: c}
return
}
// A service to return comments for commits
type CommitCommentsService struct {
client *Client
}
// All commit comments for a single commit
//
// https://developer.github.com/v3/repos/comments/#list-comments-for-a-single-commit
func (c *CommitCommentsService) All(uri *Hyperlink, uriParams M) (comments []CommitComment, result *Result) {
url, err := ExpandWithDefault(uri, &RepoCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.get(url, &comments)
return
}
// One commit comment by id
//
// https://developer.github.com/v3/repos/comments/#get-a-single-commit-comment
func (c *CommitCommentsService) One(uri *Hyperlink, uriParams M) (comment *CommitComment, result *Result) {
url, err := ExpandWithDefault(uri, &RepoCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.get(url, &comment)
return
}
// Creates a comment on a commit
//
// https://developer.github.com/v3/repos/comments/#create-a-commit-comment
func (c *CommitCommentsService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (comment *CommitComment, result *Result) {
url, err := ExpandWithDefault(uri, &CommitCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.post(url, requestParams, &comment)
return
}
// Updates a comment on a commit
//
// https://developer.github.com/v3/repos/comments/#update-a-commit-comment
func (c *CommitCommentsService) Update(uri *Hyperlink, uriParams M, requestParams interface{}) (comment *CommitComment, result *Result) {
url, err := ExpandWithDefault(uri, &RepoCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.patch(url, requestParams, &comment)
return
}
// Deletes a comment on a commit
//
// https://developer.github.com/v3/repos/comments/#delete-a-commit-comment
func (c *CommitCommentsService) Delete(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &RepoCommentsURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = c.client.delete(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
type CommitComment struct {
*hypermedia.HALResource
ID int `json:"id,omitempty"`
URL string `json:"url,omitempty"`
User User `json:"user,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
Body string `json:"body,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
Position int `json:"position,omitempty"`
Line int `json:"line,omitempty"`
Path string `json:"path,omitempty"`
CommitID string `json:"commit_id,omitempty"`
}
package octokit
import (
"io"
"time"
"github.com/jingweno/go-sawyer/hypermedia"
)
// CommitsURL is a template for accessing commits in a specific owner's
// repository with a particular sha hash that can be expanded to a full address.
//
// https://developer.github.com/v3/repos/commits/
var CommitsURL = Hyperlink("repos/{owner}/{repo}/commits{/sha}")
// Commits creates a CommitsService with a base url.
//
// https://developer.github.com/v3/repos/commits/
func (c *Client) Commits() (commits *CommitsService) {
commits = &CommitsService{client: c}
return
}
// CommitsService is a service providing access to commits from a particular url
type CommitsService struct {
client *Client
}
// All gets a list of all commits associated with the URL of the service
//
// https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
func (c *CommitsService) All(uri *Hyperlink, params M) (commits []Commit, result *Result) {
if uri == nil {
uri = &CommitsURL
}
url, err := uri.Expand(params)
if err != nil {
return make([]Commit, 0), &Result{Err: err}
}
result = c.client.get(url, &commits)
return
}
// One gets a specific commit based on the url of the service
//
// https://developer.github.com/v3/repos/commits/#get-a-single-commit
func (c *CommitsService) One(uri *Hyperlink, params M) (commit Commit, result *Result) {
if uri == nil {
uri = &CommitsURL
}
url, err := uri.Expand(params)
if err != nil {
return Commit{}, &Result{Err: err}
}
result = c.client.get(url, &commit)
return
}
// Patch gets a specific commit patch based on the url of the service
//
// https://developer.github.com/v3/repos/commits/#get-a-single-commit
func (c *CommitsService) Patch(uri *Hyperlink, params M) (patch io.ReadCloser, result *Result) {
if uri == nil {
uri = &CommitsURL
}
url, err := uri.Expand(params)
if err != nil {
return nil, &Result{Err: err}
}
patch, result = c.client.getBody(url, patchMediaType)
return
}
// CommitFile is a representation of a file within a commit
type CommitFile struct {
Additions int `json:"additions,omitempty"`
BlobURL string `json:"blob_url,omitempty"`
Changes int `json:"changes,omitempty"`
ContentsURL string `json:"contents_url,omitempty"`
Deletions int `json:"deletions,omitempty"`
Filename string `json:"filename,omitempty"`
Patch string `json:"patch,omitempty"`
RawURL string `json:"raw_url,omitempty"`
Sha string `json:"sha,omitempty"`
Status string `json:"status,omitempty"`
}
// CommitStats represents the statistics on the changes made in a commit
type CommitStats struct {
Additions int `json:"additions,omitempty"`
Deletions int `json:"deletions,omitempty"`
Total int `json:"total,omitempty"`
}
// CommitCommit is the representation of the metadata regarding the commit as a subset
// of the full commit structure
type CommitCommit struct {
Author struct {
Date *time.Time `json:"date,omitempty"`
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
} `json:"author,omitempty"`
CommentCount int `json:"comment_count,omitempty"`
Committer struct {
Date *time.Time `json:"date,omitempty"`
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
} `json:"committer,omitempty"`
Message string `json:"message,omitempty"`
Tree struct {
Sha string `json:"sha,omitempty"`
URL string `json:"url,omitempty"`
} `json:"tree,omitempty"`
URL string `json:"url,omitempty"`
}
// Commit is a representation of a full commit in git
type Commit struct {
*hypermedia.HALResource
Author *User `json:"author,omitempty"`
CommentsURL string `json:"comments_url,omitempty"`
Commit *CommitCommit `json:"commit,omitempty"`
Committer *User `json:"committer,omitempty"`
Files []CommitFile `json:"files,omitempty"`
HtmlURL string `json:"html_url,omitempty"`
Parents []Commit `json:"parents,omitempty"`
Sha string `json:"sha,omitempty"`
Stats CommitStats `json:"stats,omitempty"`
URL string `json:"url,omitempty"`
}
package octokit
import (
"net/url"
"github.com/jingweno/go-sawyer/hypermedia"
)
// EmailURL is an address for accessing email addresses for the current user
//
// https://developer.github.com/v3/users/emails/
var EmailUrl = Hyperlink("user/emails")
// Create a EmailsService with the base url.URL
//
// https://developer.github.com/v3/users/emails/
func (c *Client) Emails(url *url.URL) (emails *EmailsService) {
emails = &EmailsService{client: c, URL: url}
return
}
// A service to return user email addresses
type EmailsService struct {
client *Client
URL *url.URL
}
// Get a list of email addresses for the current user
//
// https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user
func (e *EmailsService) All() (emails []Email, result *Result) {
result = e.client.get(e.URL, &emails)
return
}
// Adds a list of email address(es) for the current user
//
// https://developer.github.com/v3/users/emails/#add-email-addresses
func (e *EmailsService) Create(params interface{}) (emails []Email, result *Result) {
result = e.client.post(e.URL, params, &emails)
return
}
// Deletes email address(es) for the current user
//
// https://developer.github.com/v3/users/emails/#delete-email-addresses
func (e *EmailsService) Delete(params interface{}) (result *Result) {
result = e.client.delete(e.URL, params, nil)
return
}
type Email struct {
*hypermedia.HALResource
Email string `json:"email,omitempty"`
Verified bool `json:"verified,omitempty"`
Primary bool `json:"primary,omitempty"`
}
package octokit
import (
"net/url"
)
// EmojisURL is an address for accessing the emojis available for use on GitHub.
//
// https://developer.github.com/v3/emojis/
var EmojisURL = Hyperlink("/emojis")
// Emojis creates an EmojisService with a base url
//
// https://developer.github.com/v3/emojis/
func (c *Client) Emojis(url *url.URL) (emojis *EmojisService) {
emojis = &EmojisService{client: c, URL: url}
return
}
// EmojisService is a service providing access to all the emojis available from a
// particular url
type EmojisService struct {
client *Client
URL *url.URL
}
// All gets a list all the available emoji paths from the service
//
// https://developer.github.com/v3/emojis/#emojis
func (s *EmojisService) All() (emojis map[string]string, result *Result) {
result = s.client.get(s.URL, &emojis)
return
}
package octokit
import (
"fmt"
"net/http"
"regexp"
"strings"
"github.com/jingweno/go-sawyer"
)
// ResponseErrorType represents an enumeration of possible response errors
type ResponseErrorType int
const (
ErrorClientError ResponseErrorType = iota // 400-499
ErrorBadRequest ResponseErrorType = iota // 400
ErrorUnauthorized ResponseErrorType = iota // 401
ErrorOneTimePasswordRequired ResponseErrorType = iota // 401
ErrorForbidden ResponseErrorType = iota // 403
ErrorTooManyRequests ResponseErrorType = iota // 403
ErrorTooManyLoginAttempts ResponseErrorType = iota // 403
ErrorNotFound ResponseErrorType = iota // 404
ErrorNotAcceptable ResponseErrorType = iota // 406
ErrorUnsupportedMediaType ResponseErrorType = iota // 414
ErrorUnprocessableEntity ResponseErrorType = iota // 422
ErrorServerError ResponseErrorType = iota // 500-599
ErrorInternalServerError ResponseErrorType = iota // 500
ErrorNotImplemented ResponseErrorType = iota // 501
ErrorBadGateway ResponseErrorType = iota // 502
ErrorServiceUnavailable ResponseErrorType = iota // 503
ErrorMissingContentType ResponseErrorType = iota
ErrorUnknownError ResponseErrorType = iota
)
// ErrorObject represents the general structure of a possible error
type ErrorObject struct {
Resource string `json:"resource,omitempty"`
Code string `json:"code,omitempty"`
Field string `json:"field,omitempty"`
Message string `json:"message,omitempty"`
}
// Error produces a human readable string representation of a given ErrorObject
func (e *ErrorObject) Error() string {
err := fmt.Sprintf("%v error", e.Code)
if e.Field != "" {
err = fmt.Sprintf("%v caused by %v field", err, e.Field)
}
err = fmt.Sprintf("%v on %v resource", err, e.Resource)
if e.Message != "" {
err = fmt.Sprintf("%v: %v", err, e.Message)
}
return err
}
// ResponseError represents an error given as a response to a request
type ResponseError struct {
Response *http.Response `json:"-"`
Type ResponseErrorType `json:"-"`
Message string `json:"message,omitempty"`
Err string `json:"error,omitempty"`
Errors []ErrorObject `json:"errors,omitempty"`
DocumentationURL string `json:"documentation_url,omitempty"`
}
// Error produces a human readable string representation of a given ResponseError
func (e *ResponseError) Error() string {
return fmt.Sprintf("%v %v: %d - %s",
e.Response.Request.Method, e.Response.Request.URL,
e.Response.StatusCode, e.errorMessage())
}
func (e *ResponseError) errorMessage() string {
messages := []string{}
if e.Message != "" {
messages = append(messages, e.Message)
}
if e.Err != "" {
m := fmt.Sprintf("Error: %s", e.Err)
messages = append(messages, m)
}
if len(e.Errors) > 0 {
m := []string{}
m = append(m, "\nError summary:")
for _, e := range e.Errors {
m = append(m, fmt.Sprintf("\t%s", e.Error()))
}
messages = append(messages, strings.Join(m, "\n"))
}
if e.DocumentationURL != "" {
messages = append(messages, fmt.Sprintf("// See: %s", e.DocumentationURL))
}
return strings.Join(messages, "\n")
}
// NewResponseError creates a ResponseError from a given sawyer response that had
// been produced along with an error.
func NewResponseError(resp *sawyer.Response) (err *ResponseError) {
err = &ResponseError{}
e := resp.Decode(&err)
if e != nil {
err.Message = fmt.Sprintf("Problems parsing error message: %s", e)
}
err.Response = resp.Response
err.Type = getResponseErrorType(err)
return
}
func getResponseErrorType(err *ResponseError) ResponseErrorType {
code := err.Response.StatusCode
header := err.Response.Header
switch {
case code == http.StatusBadRequest:
return ErrorBadRequest
case code == http.StatusUnauthorized:
otp := header.Get("X-GitHub-OTP")
r := regexp.MustCompile(`(?i)required; (\w+)`)
if r.MatchString(otp) {
return ErrorOneTimePasswordRequired
}
return ErrorUnauthorized
case code == http.StatusForbidden:
msg := err.Message
rr := regexp.MustCompile("(?i)rate limit exceeded")
if rr.MatchString(msg) {
return ErrorTooManyRequests
}
lr := regexp.MustCompile("(?i)login attempts exceeded")
if lr.MatchString(msg) {
return ErrorTooManyLoginAttempts
}
return ErrorForbidden
case code == http.StatusNotFound:
return ErrorNotFound
case code == http.StatusNotAcceptable:
return ErrorNotAcceptable
case code == http.StatusUnsupportedMediaType:
return ErrorUnsupportedMediaType
case code == 422:
return ErrorUnprocessableEntity
case code >= 400 && code <= 499:
return ErrorClientError
case code == http.StatusInternalServerError:
return ErrorInternalServerError
case code == http.StatusNotImplemented:
return ErrorNotImplemented
case code == http.StatusBadGateway:
return ErrorBadGateway
case code == http.StatusServiceUnavailable:
return ErrorServiceUnavailable
case code >= 500 && code <= 599:
return ErrorServerError
}
return ErrorUnknownError
}
package octokit
// URL templates for actions taken on the followers of users
//
// https://developer.github.com/v3/users/followers/
var (
CurrentFollowerUrl = Hyperlink("user/followers")
FollowerUrl = Hyperlink("users/{user}/followers")
CurrentFollowingUrl = Hyperlink("user/following{/target}")
FollowingUrl = Hyperlink("users/{user}/following{/target}")
)
// Create a FollowersService
//
// https://developer.github.com/v3/users/followers/
func (c *Client) Followers() (followers *FollowersService) {
followers = &FollowersService{client: c}
return
}
// A service to return user followers
type FollowersService struct {
client *Client
}
// Get a list of followers for the user
//
// https://developer.github.com/v3/users/followers/#list-followers-of-a-user
func (f *FollowersService) All(uri *Hyperlink, uriParams M) (followers []User, result *Result) {
url, err := ExpandWithDefault(uri, &CurrentFollowerUrl, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = f.client.get(url, &followers)
return
}
// Checks if you are following a target user
//
// https://developer.github.com/v3/users/followers/#check-if-you-are-following-a-user
func (f *FollowersService) Check(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &CurrentFollowingUrl, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = f.client.get(url, nil)
success = (result.Response.StatusCode == 204)
return
}
// Follows a target user
//
// https://developer.github.com/v3/users/followers/#follow-a-user
func (f *FollowersService) Follow(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &CurrentFollowingUrl, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = f.client.put(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
// Unfollows a target user
//
// https://developer.github.com/v3/users/followers/#unfollow-a-user
func (f *FollowersService) Unfollow(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &CurrentFollowingUrl, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = f.client.delete(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
package octokit
import (
"time"
"github.com/jingweno/go-sawyer/hypermedia"
)
var GistCommentsURL = Hyperlink("/gists/{gist_id}/comments{/id}")
// Create a GistCommentsService
func (c *Client) GistComments() (k *GistCommentsService) {
k = &GistCommentsService{client: c}
return
}
// A service to return comments for gists
type GistCommentsService struct {
client *Client
}
// Get a list of all gist comments
//
// https://developer.github.com/v3/gists/comments/#list-comments-on-a-gist
func (c *GistCommentsService) All(uri *Hyperlink, uriParams M) (comments []GistComment, result *Result) {
url, err := ExpandWithDefault(uri, &GistCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.get(url, &comments)
return
}
// Get a single comment by id
//
// https://developer.github.com/v3/gists/comments/#get-a-single-comment
func (c *GistCommentsService) One(uri *Hyperlink, uriParams M) (comment *GistComment, result *Result) {
url, err := ExpandWithDefault(uri, &GistCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.get(url, &comment)
return
}
// Creates a comment on a gist
//
// https://developer.github.com/v3/gists/comments/#create-a-comment
func (c *GistCommentsService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (comment *GistComment, result *Result) {
url, err := ExpandWithDefault(uri, &GistCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.post(url, requestParams, &comment)
return
}
// Updates a comment on a gist
//
// https://developer.github.com/v3/gists/comments/#edit-a-comment
func (c *GistCommentsService) Update(uri *Hyperlink, uriParams M, requestParams interface{}) (comment *GistComment, result *Result) {
url, err := ExpandWithDefault(uri, &GistCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.patch(url, requestParams, &comment)
return
}
// Deletes a comment on a gist
//
// https://developer.github.com/v3/gists/comments/#delete-a-comment
func (c *GistCommentsService) Delete(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &GistCommentsURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = c.client.delete(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
type GistComment struct {
*hypermedia.HALResource
ID int `json:"id,omitempty"`
URL string `json:"url,omitempty"`
User User `json:"user,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
Body string `json:"body,omitempty"`
}
package octokit
import (
"io"
"net/url"
"time"
"github.com/jingweno/go-sawyer/hypermedia"
)
// URLs for accessing specific endpoints on the gists API. The most general
// is GistsURL, used to list, get, create, edit and delete a gist. GistsUserURL,
// GistsPublicURL and GistsStarredURL are used for listing certain sets of gists.
// GistsRevisionURL can be used to access a specific revision of a gist.
// GistsCommitsURL can be used to access all the commits on a specific gist.
// GistsStarURL is used for starring and unstarring gists, and checking if a gist
// is starred. GistsForksURL is used to fork or delete a fork of a gist, and to
// list a gist's forks.
//
// https://developer.github.com/v3/gists
var (
GistsUserURL = Hyperlink("users/{username}/gists")
GistsURL = Hyperlink("gists{/gist_id}")
GistsPublicURL = Hyperlink("gists/public")
GistsStarredURL = Hyperlink("gists/starred")
GistsRevisionURL = Hyperlink("gists/{gist_id}/{commit_sha}")
GistsCommitsURL = Hyperlink("gists/{gist_id}/commits")
GistsStarURL = Hyperlink("gists/{gist_id}/star")
GistsForksURL = Hyperlink("gists/{gist_id}/forks")
)
// Gists creates a GistsService to be used with any proper URL
//
// https://developer.github.com/v3/gists/
func (c *Client) Gists() (gists *GistsService) {
gists = &GistsService{client: c}
return
}
// GistsService is a service providing access to gists from a particular url
type GistsService struct {
client *Client
}
// All gets a list of all gists associated with the url of the service
//
// https://developer.github.com/v3/gists/#list-gists
func (g *GistsService) All(uri *Hyperlink, uriParams M) (gists []Gist, result *Result) {
url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &gists)
return
}
// One gets a specific gist based on the url of the service
//
// https://developer.github.com/v3/gists/#get-a-single-gist
// https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist
func (g *GistsService) One(uri *Hyperlink, uriParams M) (gist *Gist, result *Result) {
url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &gist)
return
}
// Raw gets the raw contents of first file in a specific gist
//
// https://developer.github.com/v3/gists/#truncation
func (g *GistsService) Raw(uri *Hyperlink, uriParams M) (body io.ReadCloser, result *Result) {
var rawURL *url.URL
gist, result := g.One(uri, uriParams)
for _, file := range gist.Files {
rawURL, _ = url.Parse(file.RawURL)
break
}
body, result = g.client.getBody(rawURL, textMediaType)
return
}
// Create posts a new gist based on parameters in a Gist struct to
// the specified URL
//
// https://developer.github.com/v3/gists/#create-a-gist
func (g *GistsService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (gist *Gist, result *Result) {
url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.post(url, requestParams, &gist)
return
}
// Update modifies a specific gist based on the url of the service
//
// https://developer.github.com/v3/gists/#edit-a-gist
func (g *GistsService) Update(uri *Hyperlink, uriParams M, requestParams interface{}) (gist *Gist, result *Result) {
url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.patch(url, requestParams, &gist)
return
}
// Commits gets a list of all commits to the given gist
//
// https://developer.github.com/v3/gists/#list-gist-commits
func (g *GistsService) Commits(uri *Hyperlink, uriParams M) (gistCommits []GistCommit, result *Result) {
url, err := ExpandWithDefault(uri, &GistsCommitsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &gistCommits)
return
}
// Star stars a gist
//
// https://developer.github.com/v3/gists/#star-a-gist
func (g *GistsService) Star(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &GistsStarURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = g.client.put(url, nil, nil)
success = (!result.HasError() && result.Response.StatusCode == 204)
return
}
// Unstar unstars a gist
//
// https://developer.github.com/v3/gists/#unstar-a-gist
func (g *GistsService) Unstar(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &GistsStarURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = g.client.delete(url, nil, nil)
success = (!result.HasError() && result.Response.StatusCode == 204)
return
}
// CheckStar checks if a gist is starred
//
// https://developer.github.com/v3/gists/#check-if-a-gist-is-starred
func (g *GistsService) CheckStar(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &GistsStarURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = g.client.get(url, nil)
success = (!result.HasError() && result.Response.StatusCode == 204)
return
}
// Fork forks a gist
//
// https://developer.github.com/v3/gists/#fork-a-gist
func (g *GistsService) Fork(uri *Hyperlink, uriParams M) (gist *Gist, result *Result) {
url, err := ExpandWithDefault(uri, &GistsForksURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.post(url, nil, &gist)
return
}
// ListForks lists all the forks of a gist
//
// https://developer.github.com/v3/gists/#list-gist-forks
func (g *GistsService) ListForks(uri *Hyperlink, uriParams M) (gistForks []GistFork, result *Result) {
url, err := ExpandWithDefault(uri, &GistsForksURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &gistForks)
return
}
// Delete deletes a gist by its id
//
// https://developer.github.com/v3/gists/#delete-a-gist
func (g *GistsService) Delete(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = g.client.delete(url, nil, nil)
success = (!result.HasError() && result.Response.StatusCode == 204)
return
}
// GistFile is a representation of the file stored in a gist
type GistFile struct {
*hypermedia.HALResource
FileName string `json:"filename,omitempty"`
Type string `json:"type,omitempty"`
Language string `json:"language,omitempty"`
RawURL string `json:"raw_url,omitempty"`
Size int `json:"size,omitempty"`
Truncated bool `json:"truncated,omitempty"`
Content string `json:"content,omitempty"`
}
// Gist is a representation of a gist on github, a standalone file that acts as a
// sole element of its own repository
type Gist struct {
*hypermedia.HALResource
ID string `json:"id,omitempty"`
Comments float64 `json:"comments,omitempty"`
CommentsURL string `json:"comments_url,omitempty"`
CommitsURL string `json:"commits_url,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
Description string `json:"description,omitempty"`
Files map[string]*GistFile `json:"files,omitempty"`
Forks []GistFork `json:"forks,omitempty"`
ForksURL Hyperlink `json:"forks_url,omitempty"`
GitPullURL Hyperlink `json:"git_pull_url,omitempty"`
History []GistCommit `json:"history,omitempty"`
GitPushURL Hyperlink `json:"git_push_url,omitempty"`
HtmlURL Hyperlink `json:"html_url,omitempty"`
Owner *User `json:"owner,omitempty"`
Public bool `json:"public,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
URL string `json:"url,omitempty"`
User *User `json:"user,omitempty"`
}
//GistFork represents information about a fork of a gist
type GistFork struct {
*hypermedia.HALResource
ID string `json:"id,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
URL string `json:"url,omitempty"`
User *User `json:"user,omitempty"`
}
// GistCommit is a representation of one of the commits to a gist
type GistCommit struct {
*hypermedia.HALResource
ChangeStatus *GistChangeStatus `json:"change_status,omitempty"`
CommittedAt *time.Time `json:"committed_at,omitempty"`
URL string `json:"url,omitempty"`
User *User `json:"user,omitempty"`
Version string `json:"version,omitempty"`
}
// GistChangeStatus represents all changes on a given Gist
type GistChangeStatus struct {
Additions int `json:"additions,omitempty"`
Deletions int `json:"deletions,omitempty"`
Total int `json:"total,omitempty"`
}
package octokit
// GitIgnoreURL is an address for accessing various templates to apply
// to a repository upon creation.
//
// https://developer.github.com/v3/gitignore/
var GitIgnoreURL = Hyperlink("/gitignore/templates{/name}")
// GitIgnore creates a GitIgnoreService to access gitignore templates
//
// https://developer.github.com/v3/gitignore/
func (c *Client) GitIgnore() *GitIgnoreService {
return &GitIgnoreService{client: c}
}
// A service to return gitignore templates
type GitIgnoreService struct {
client *Client
}
// All gets a list all the available templates
//
// https://developer.github.com/v3/gitignore/#listing-available-templates
func (s *GitIgnoreService) All(uri *Hyperlink) (templates []string, result *Result) {
url, err := ExpandWithDefault(uri, &GitIgnoreURL, nil)
if err != nil {
return nil, &Result{Err: err}
}
result = s.client.get(url, &templates)
return
}
// One gets a specific gitignore template based on the passed url
//
// https://developer.github.com/v3/gitignore/#get-a-single-template
func (s *GitIgnoreService) One(uri *Hyperlink, uriParams M) (template *GitIgnoreTemplate, result *Result) {
url, err := ExpandWithDefault(uri, &GitIgnoreURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = s.client.get(url, &template)
return
}
//GitIgnoreTemplate is a representation of a given template returned by the service
type GitIgnoreTemplate struct {
Name string `json:"name,omitempty"`
Source string `json:"source,omitempty"`
}
package octokit
import (
"net/url"
)
// GitTreesURL is a template for accessing git trees at a particular sha hash or branch
// of a particular repository of a particular owner. The request may be set to be
// recursive for a particular level of depth (0 is no recursion) to follow sub-trees from
// the primary repository.
//
// https://developer.github.com/v3/git/trees/
var GitTreesURL = Hyperlink("repos/{owner}/{repo}/git/trees/{sha}{?recursive}")
// GitTrees creates a GitTreesService with a base url
//
// https://developer.github.com/v3/git/trees/
func (c *Client) GitTrees(url *url.URL) (trees *GitTreesService) {
trees = &GitTreesService{client: c, URL: url}
return
}
// GitTreesService is a service providing access to GitTrees from a particular url
type GitTreesService struct {
client *Client
URL *url.URL
}
// One gets a specific GitTree based on the url of the service. May specify
// to get the tree recursively
//
// https://developer.github.com/v3/git/trees/#get-a-tree
// https://developer.github.com/v3/git/trees/#get-a-tree-recursively
func (c *GitTreesService) One() (tree *GitTree, result *Result) {
result = c.client.get(c.URL, &tree)
return
}
// GitTree represents a tree on GitHub, a level in the GitHub equivalent of a
// directory structure
type GitTree struct {
Sha string `json:"sha,omitempty"`
Tree []GitTreeEntry `json:"tree,omitempty"`
Truncated bool `json:"truncated,omitempty"`
URL string `json:"url,omitempty"`
}
// GitTreeEntry represents an element within a GitTree on GitHub, which may either
// be another Tree or a single Blob
type GitTreeEntry struct {
Mode string `json:"mode,omitempty"`
Path string `json:"path,omitempty"`
Sha string `json:"sha,omitempty"`
Size int `json:"size,omitempty"`
Type string `json:"type,omitempty"`
URL string `json:"url,omitempty"`
}
package octokit
import (
"net/url"
"github.com/jingweno/go-sawyer/hypermedia"
)
// M represents a map of values to expand a Hyperlink. The keys in M are elements
// of the template to be replaced with the associated value in the map.
type M map[string]interface{}
// Hyperlink is a string url. If it is a uri template, it can be converted to
// a full URL with Expand().
type Hyperlink string
// Expand utilizes the sawyer expand method to convert a URI template into a full
// URL
func (l Hyperlink) Expand(m M) (u *url.URL, err error) {
sawyerHyperlink := hypermedia.Hyperlink(string(l))
u, err = sawyerHyperlink.Expand(hypermedia.M(m))
return
}
// Expands a link with possible, otherwise it expands the default link
func ExpandWithDefault(link *Hyperlink, defaultLink *Hyperlink, params M) (u *url.URL, err error) {
if link == nil {
link = defaultLink
}
return link.Expand(params)
}
package octokit
import (
"time"
"github.com/jingweno/go-sawyer/hypermedia"
)
var IssueCommentsURL = Hyperlink("/repos/{owner}/{repo}/issues{/number}/comments{/id}")
// Create a IssueCommentsService
func (c *Client) IssueComments() (k *IssueCommentsService) {
k = &IssueCommentsService{client: c}
return
}
// A service to return comments for issues
type IssueCommentsService struct {
client *Client
}
// Get a list of all issue comments
//
// https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
func (c *IssueCommentsService) All(uri *Hyperlink, uriParams M) (comments []IssueComment, result *Result) {
url, err := ExpandWithDefault(uri, &IssueCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.get(url, &comments)
return
}
// Get a single comment by id
//
// https://developer.github.com/v3/issues/comments/#get-a-single-comment
func (c *IssueCommentsService) One(uri *Hyperlink, uriParams M) (comment *IssueComment, result *Result) {
url, err := ExpandWithDefault(uri, &IssueCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.get(url, &comment)
return
}
// Creates a comment on an issue
//
// https://developer.github.com/v3/issues/comments/#create-a-comment
func (c *IssueCommentsService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (comment *IssueComment, result *Result) {
url, err := ExpandWithDefault(uri, &IssueCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.post(url, requestParams, &comment)
return
}
// Updates a comment on an issue
//
// https://developer.github.com/v3/issues/comments/#edit-a-comment
func (c *IssueCommentsService) Update(uri *Hyperlink, uriParams M, requestParams interface{}) (comment *IssueComment, result *Result) {
url, err := ExpandWithDefault(uri, &IssueCommentsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = c.client.patch(url, requestParams, &comment)
return
}
// Deletes a comment on an issue
//
// https://developer.github.com/v3/issues/comments/#delete-a-comment
func (c *IssueCommentsService) Delete(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &IssueCommentsURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = c.client.delete(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
type IssueComment struct {
*hypermedia.HALResource
ID int `json:"id,omitempty"`
URL string `json:"url,omitempty"`
User User `json:"user,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
Body string `json:"body,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
IssueURL string `json:"issue_url",omitempty"`
}
package octokit
// IssueLabelsURL is a URL template for accessing issue labels
//
// https://developer.github.com/v3/issues/labels/
var IssueLabelsURL = Hyperlink("repos/{owner}/{repo}/issues/{number}/labels{/name}")
// IssueLabels creates an IssueLabelsService with a base url
func (c *Client) IssueLabels() (issueLabels *IssueLabelsService) {
issueLabels = &IssueLabelsService{client: c}
return
}
// IssueLabelsService is a service providing access to labels for an issue
type IssueLabelsService struct {
client *Client
}
// Adds labels to an issue
//
// https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue
func (l *IssueLabelsService) Add(uri *Hyperlink, uriParams M, labelsToAdd []string) (labels []Label, result *Result) {
url, err := ExpandWithDefault(uri, &IssueLabelsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = l.client.post(url, labelsToAdd, &labels)
return
}
// All gets a list of all labels for an issue
//
// https://developer.github.com/v3/issues/labels/#list-labels-on-an-issue
func (l *IssueLabelsService) All(uri *Hyperlink, uriParams M) (labels []Label, result *Result) {
url, err := ExpandWithDefault(uri, &IssueLabelsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = l.client.get(url, &labels)
return
}
// Removes a label from an issue
//
// https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue
func (l *IssueLabelsService) Remove(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &IssueLabelsURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = l.client.delete(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
// Removes all labels from an issue
//
// https://developer.github.com/v3/issues/labels/#remove-all-labels-from-an-issue
func (l *IssueLabelsService) RemoveAll(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &IssueLabelsURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = l.client.delete(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
// Replace all labels for an issue
//
// https://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue
func (l *IssueLabelsService) ReplaceAll(uri *Hyperlink, uriParams M, newLabels []string) (labels []Label, result *Result) {
url, err := ExpandWithDefault(uri, &IssueLabelsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = l.client.put(url, newLabels, &labels)
return
}
package octokit
import (
"github.com/jingweno/go-sawyer/hypermedia"
"time"
)
// RepoIssuesURL is a template for accessing issues in a particular
// repository for a particular owner that can be expanded to a full address.
//
// https://developer.github.com/v3/issues/
var RepoIssuesURL = Hyperlink("repos/{owner}/{repo}/issues{/number}{?filter,state,labels,sort}")
// Issues creates an IssuesService with a base url
func (c *Client) Issues() (issues *IssuesService) {
issues = &IssuesService{client: c}
return
}
// IssuesService is a service providing access to issues from a particular url
type IssuesService struct {
client *Client
}
// One gets a specific issue based on the url of the service
//
// https://developer.github.com/v3/issues/#get-a-single-issue
func (i *IssuesService) One(uri *Hyperlink, uriParams M) (issue *Issue, result *Result) {
url, err := ExpandWithDefault(uri, &RepoIssuesURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = i.client.get(url, &issue)
return
}
// All gets a list of all issues associated with the url of the service
//
// https://developer.github.com/v3/issues/#list-issues-for-a-repository
func (i *IssuesService) All(uri *Hyperlink, uriParams M) (issues []Issue, result *Result) {
url, err := ExpandWithDefault(uri, &RepoIssuesURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = i.client.get(url, &issues)
return
}
// Create posts a new issue with particular parameters to the issues service url
//
// https://developer.github.com/v3/issues/#create-an-issue
func (i *IssuesService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (issue *Issue, result *Result) {
url, err := ExpandWithDefault(uri, &RepoIssuesURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = i.client.post(url, requestParams, &issue)
return
}
// Update modifies a specific issue given parameters on the service url
//
// https://developer.github.com/v3/issues/#edit-an-issue
func (i *IssuesService) Update(uri *Hyperlink, uriParams M, requestParams interface{}) (issue *Issue, result *Result) {
url, err := ExpandWithDefault(uri, &RepoIssuesURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = i.client.patch(url, requestParams, &issue)
return
}
// Issue represents an issue on GitHub with all associated fields
type Issue struct {
*hypermedia.HALResource
URL string `json:"url,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
Number int `json:"number,omitempty"`
State string `json:"state,omitempty"`
Title string `json:"title,omitempty"`
Body string `json:"body,omitempty"`
User User `json:"user,omitempty"`
Labels []struct {
URL string `json:"url,omitempty"`
Name string `json:"name,omitempty"`
Color string `json:"color,omitempty"`
}
Assignee User `json:"assignee,omitempty"`
Milestone struct {
URL string `json:"url,omitempty"`
Number int `json:"number,omitempty"`
State string `json:"state,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Creator User `json:"creator,omitempty"`
OpenIssues int `json:"open_issues,omitempty"`
ClosedIssues int `json:"closed_issues,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
DueOn *time.Time `json:"due_on,omitempty"`
}
Comments int `json:"comments,omitempty"`
PullRequest struct {
HTMLURL string `json:"html_url,omitempty"`
DiffURL string `json:"diff_url,omitempty"`
PatchURL string `json:"patch_url,omitempty"`
} `json:"pull_request,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
ClosedAt *time.Time `json:"closed_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
// IssueParams represents the struture used to create or update an Issue
type IssueParams struct {
Title string `json:"title,omitempty"`
Body string `json:"body,omitempty"`
Assignee string `json:"assignee,omitempty"`
State string `json:"state,omitempty"`
Milestone uint64 `json:"milestone,omitempty"`
Labels []string `json:"labels,omitempty"`
}
package octokit
import (
"github.com/jingweno/go-sawyer/hypermedia"
)
// RepoLabelsURL is a URL template for accessing labels for a repository.
//
// https://developer.github.com/v3/issues/labels/
var RepoLabelsURL = Hyperlink("repos/{owner}/{repo}/labels{/name}")
// Labels creates a LabelsService
func (c *Client) Labels() (labels *LabelsService) {
labels = &LabelsService{client: c}
return
}
// LabelsService is a service providing access to labels for a repository
type LabelsService struct {
client *Client
}
// All gets a list of all labels for a repository
//
// https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
func (l *LabelsService) All(uri *Hyperlink, uriParams M) (labels []Label, result *Result) {
url, err := ExpandWithDefault(uri, &RepoLabelsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = l.client.get(url, &labels)
return
}
// One gets a label for a repository
//
// https://developer.github.com/v3/issues/labels/#get-a-single-label
func (l *LabelsService) One(uri *Hyperlink, uriParams M) (label *Label, result *Result) {
url, err := ExpandWithDefault(uri, &RepoLabelsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = l.client.get(url, &label)
return
}
// Create a new label for a repository
//
// https://developer.github.com/v3/issues/labels/#create-a-label
func (l *LabelsService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (label *Label, result *Result) {
url, err := ExpandWithDefault(uri, &RepoLabelsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = l.client.post(url, requestParams, &label)
return
}
// Updates a label for a repository
//
// https://developer.github.com/v3/issues/labels/#update-a-label
func (l *LabelsService) Update(uri *Hyperlink, uriParams M, requestParams interface{}) (label *Label, result *Result) {
url, err := ExpandWithDefault(uri, &RepoLabelsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = l.client.patch(url, requestParams, &label)
return
}
// Delete a label for a repository
//
// https://developer.github.com/v3/issues/labels/#delete-a-label
func (l *LabelsService) Delete(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &RepoLabelsURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = l.client.delete(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
// Label represents a label for a GitHub repository
type Label struct {
*hypermedia.HALResource
URL string `json:"url,omitempty"`
Name string `json:"name,omitempty"`
Color string `json:"color,omitempty"`
}
package octokit
import (
"encoding/json"
"net"
"github.com/jingweno/go-sawyer/hypermedia"
)
// https://developer.github.com/v3/meta/
var (
MetaURL = Hyperlink("/meta")
)
// Meta return an APIInfo with the current API meta information
//
// https://developer.github.com/v3/meta/#meta
func (c *Client) Meta(uri *Hyperlink) (info APIInfo, result *Result) {
url, err := uri.Expand(nil)
if err != nil {
return info, &Result{Err: err}
}
var meta meta
result = c.get(url, &meta)
if !result.HasError() {
info = meta.transform()
}
return
}
type ipNets []*net.IPNet
func (i *ipNets) UnmarshalJSON(raw []byte) error {
*i = (*i)[:0]
var ss []string
if err := json.Unmarshal(raw, &ss); err != nil {
return err
}
for _, s := range ss {
_, ipNet, err := net.ParseCIDR(s)
if err != nil {
return err
}
*i = append(*i, ipNet)
}
return nil
}
type ips []net.IP
func (i *ips) UnmarshalJSON(raw []byte) error {
*i = (*i)[:0]
var ss []string
if err := json.Unmarshal(raw, &ss); err != nil {
return err
}
for _, s := range ss {
*i = append(*i, net.ParseIP(s))
}
return nil
}
type meta struct {
*hypermedia.HALResource
VerifiablePasswordAuthentication bool `json:"verifiable_password_authentication,omitempty"`
GithubServicesSha string `json:"github_services_sha,omitempty"`
Hooks ipNets `json:"hooks,omitempty"`
Git ipNets `json:"git,omitempty"`
Pages ipNets `json:"pages,omitempty"`
Importer ips `json:"importer,omitempty"`
}
func (m meta) transform() (info APIInfo) {
info.VerifiablePasswordAuthentication = m.VerifiablePasswordAuthentication
info.GithubServicesSha = m.GithubServicesSha
info.Hooks = ([]*net.IPNet)(m.Hooks)
info.Git = ([]*net.IPNet)(m.Git)
info.Pages = ([]*net.IPNet)(m.Pages)
info.Importer = ([]net.IP)(m.Importer)
return
}
// APIInfo contains the information described in https://developer.github.com/v3/meta/#body
type APIInfo struct {
*hypermedia.HALResource
VerifiablePasswordAuthentication bool `json:"verifiable_password_authentication,omitempty"`
GithubServicesSha string `json:"github_services_sha,omitempty"`
Hooks []*net.IPNet `json:"hooks,omitempty"`
Git []*net.IPNet `json:"git,omitempty"`
Pages []*net.IPNet `json:"pages,omitempty"`
Importer []net.IP `json:"importer,omitempty"`
}
package octokit
import (
"github.com/jingweno/go-sawyer/hypermedia"
"time"
)
// MilestonesURL is a URL template for accessing repository Milestones
//
// https://developer.github.com/v3/issues/milestones/
var MilestonesURL = Hyperlink("repos/{owner}/{repo}/milestones")
// Milestones creates an MilestonesService with a base url
func (c *Client) Milestones() (milestones *MilestonesService) {
milestones = &MilestonesService{client: c}
return
}
// MilestonesService is a service providing access to milestones for a repository
type MilestonesService struct {
client *Client
}
// All gets a list of all milestones for a repository
//
// https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository
func (m *MilestonesService) All(uri *Hyperlink, uriParams M) (milestones []Milestone, result *Result) {
url, err := ExpandWithDefault(uri, &MilestonesURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = m.client.get(url, &milestones)
return
}
// One gets a milestone for a repository
//
// https://developer.github.com/v3/issues/milestones/#get-a-single-milestone
func (l *MilestonesService) One(uri *Hyperlink, uriParams M) (milestone *Milestone, result *Result) {
url, err := ExpandWithDefault(uri, &MilestonesURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = l.client.get(url, &milestone)
return
}
// Creates a milestone on an repository
//
// https://developer.github.com/v3/issues/milestones/#create-a-milestone
func (m *MilestonesService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (milestone *Milestone, result *Result) {
url, err := ExpandWithDefault(uri, &MilestonesURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = m.client.post(url, requestParams, &milestone)
return
}
// Deletes a milestone from a repository
//
// https://developer.github.com/v3/issues/milestones/#delete-a-milestone
func (m *MilestonesService) Delete(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &MilestonesURL, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = m.client.delete(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
type Milestone struct {
*hypermedia.HALResource
URL string `json:"url,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
LabelsURL string `json:"labels_url",omitempty"`
Number int `json:"number,omitempty"`
ID int `json:"id,omitempty"`
State string `json:"state,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Creator User `json:"creator,omitempty"`
OpenIssues int `json:"open_issues,omitempty"`
ClosedIssues int `json:"closed_issues,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
ClosedAt *time.Time `json:"closed_at,omitempty"`
DueOn *time.Time `json:"due_on,omitempty"`
}
/*
Package octokit is a simple and official wrapper for the GitHub API in Go.
It is a hypermedia API client using go-sawyer to allow for easy access between web resources.
*/
package octokit
const (
gitHubAPIURL = "https://api.github.com"
userAgent = "Octokit Go " + version
version = "0.3.0"
defaultMediaType = "application/vnd.github.v3+json;charset=utf-8"
diffMediaType = "application/vnd.github.v3.diff;charset=utf-8"
patchMediaType = "application/vnd.github.v3.patch;charset=utf-8"
textMediaType = "text/plain;charset=utf-8"
)
package octokit
// Organization is a representation of an organization on GitHub, containing
// all identifying information related to the specific organization.
import (
"time"
)
var (
OrganizationURL = Hyperlink("/orgs/{org}")
OrganizationReposURL = Hyperlink("/orgs/{org}/repos{?type,page,per_page,sort}")
YourOrganizationsURL = Hyperlink("/user/orgs")
UserOrganizationsURL = Hyperlink("/users/{username}/orgs")
)
func (c *Client) Organization() (organization *OrganizationService) {
organization = &OrganizationService{client: c}
return
}
// A service for getting as well as updating organization information
type OrganizationService struct {
client *Client
}
// Get the specified organization's information
//
// https://developer.github.com/v3/orgs/#get-an-organization
func (g *OrganizationService) OrganizationGet(uri *Hyperlink, params M) (
organization Organization, result *Result) {
if uri == nil {
uri = &OrganizationURL
}
url, err := uri.Expand(params)
if err != nil {
return Organization{}, &Result{Err: err}
}
result = g.client.get(url, &organization)
return
}
// Update specified organization's information
//
// https://developer.github.com/v3/orgs/#edit-an-organization
func (g *OrganizationService) OrganizationUpdate(uri *Hyperlink,
input OrganizationParams, URLParams M) (organization Organization,
result *Result) {
if uri == nil {
uri = &OrganizationURL
}
url, err := uri.Expand(URLParams)
if err != nil {
return Organization{}, &Result{Err: err}
}
result = g.client.patch(url, input, &organization)
return
}
// Get the list of repository information of an organization
//
// https://developer.github.com/v3/repos/#list-organization-repositories
func (g *OrganizationService) OrganizationRepos(uri *Hyperlink, params M) (
repos []Repository, result *Result) {
if uri == nil {
uri = &OrganizationReposURL
}
url, err := uri.Expand(params)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &repos)
return
}
// Get information for the list of organizations the current user belongs to
//
// https://developer.github.com/v3/orgs/#list-your-organizations
func (g *OrganizationService) YourOrganizations(uri *Hyperlink, params M) (
organizations []Organization, result *Result) {
if uri == nil {
uri = &YourOrganizationsURL
}
url, err := uri.Expand(params)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &organizations)
return
}
// Get the information for the list of organizations the specified user belongs to
//
// https://developer.github.com/v3/orgs/#list-user-organizations
func (g *OrganizationService) UserOrganizations(uri *Hyperlink, params M) (
organizations []Organization, result *Result) {
if uri == nil {
uri = &UserOrganizationsURL
}
url, err := uri.Expand(params)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &organizations)
return
}
type Organization struct {
Description string `json:"description, omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
PublicMembersURL Hyperlink `json:"public_member_url,omitempty"`
MembersURL Hyperlink `json:"members_url,omitempty"`
EventsURL Hyperlink `json:"events_url,omitempty"`
ReposURL Hyperlink `json:"repos_url,omitempty"`
URL string `json:"url,omitempty"`
ID int `json:"id,omitempty"`
Login string `json:"login,omitempty"`
Name string `json:"name, omitempty"`
Company string `json:"company, omitempty"`
Blog string `json:"blog, omitempty"`
Location string `json:"location, omitempty"`
Email string `json:"email, omitempty"`
PublicRepos int `json:"public_repos,omitempty"`
PublicGists int `json:"public_gists,omitempty"`
Followers int `json:"followers,omitempty"`
Followering int `json:"following,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
Type string `json:"type,omitempty"`
//Limited Access Fields
Total_Private_Repos int `json:"total_private_repos,omitempty"`
Owned_Private_Repos int `json:"owned_private_repos,omitempty"`
Private_Gists int `json:"private_gists,omitempty"`
Disk_Usage int `json:"disk_usage,omitempty"`
Collaborators int `json:"collaborators,omitempty"`
BillingEmail string `json:"billing_email,omitempty"`
Plan Plan `json:"plan,omitempty"`
}
type Plan struct {
Name string `json:"name,omitempty"`
Space int `json:"space,omitempty"`
PrivateRepos int `json:"private_repos,omitempty"`
}
// OrganizationParams represents the struture used to create or update an Organization
type OrganizationParams struct {
BillingEmail string `json:"billing_email,omitempty"`
Blog string `json:"blog,omitempty"`
Company string `json:"company,omitempty"`
Email string `json:"email,omitempty"`
Location string `json:"location,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
}
package octokit
import (
"github.com/jingweno/go-sawyer/hypermedia"
"time"
)
// URL templates for actions taken on the pages in repositories
//
// https://developer.github.com/v3/repos/pages/
var (
PagesURL = Hyperlink("/repos/{owner}/{repo}/pages")
PagesBuildsURL = Hyperlink("/repos/{owner}/{repo}/pages/builds")
PagesLatestBuildURL = Hyperlink("/repos/{owner}/{repo}/pages/builds/latest")
)
// Pages creates a PagesService to access page information including various
// versions of build
//
// https://developer.github.com/v3/repos/pages/
func (c *Client) Pages() *PagesService {
return &PagesService{client: c}
}
// A service to return page information including various versions of build
type PagesService struct {
client *Client
}
// PageInfo gets the information about a Pages site
//
// https://developer.github.com/v3/repos/pages/#get-information-about-a-pages-site
func (g *PagesService) PageInfo(uri *Hyperlink, uriParams M) (page *PageInfo,
result *Result) {
url, err := ExpandWithDefault(uri, &PagesURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &page)
return
}
// PageBuilds lists the builds of a given page
// https://developer.github.com/v3/repos/pages/#list-pages-builds
func (g *PagesService) PageBuilds(uri *Hyperlink, uriParams M) (builds []PageBuild,
result *Result) {
url, err := ExpandWithDefault(uri, &PagesBuildsURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &builds)
return
}
// PageBuildLatest gets the latest build for a page
//
// https://developer.github.com/v3/repos/pages/#list-latest-pages-build
func (g *PagesService) PageBuildLatest(uri *Hyperlink, uriParams M) (build *PageBuild,
result *Result) {
url, err := ExpandWithDefault(uri, &PagesLatestBuildURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &build)
return
}
type PageInfo struct {
*hypermedia.HALResource
URL string `json:"url,omitempty"`
Status string `json:"status,omitempty"`
Cname string `json:"cname,omitempty"`
Custom404 bool `json:"custom_404,omitempty"`
}
type PageBuild struct {
*hypermedia.HALResource
URL string `json:"url,omitempty"`
Status string `json:"status,omitempty"`
Error *ErrorObject `json:"error,omitempty"`
Pusher *User `json:"pusher,omitempty"`
Commit string `json:"commit,omitempty"`
Duration int `json:"duration,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
package octokit
import (
"time"
"github.com/jingweno/go-sawyer/hypermedia"
)
var (
CurrentPublicKeyUrl = Hyperlink("/user/keys{/id}")
PublicKeyUrl = Hyperlink("/users/{user}/keys")
)
// Create a PublicKeysService
func (c *Client) PublicKeys() (k *PublicKeysService) {
k = &PublicKeysService{client: c}
return
}
// A service to return user public keys
type PublicKeysService struct {
client *Client
}
// Get a list of keys for the user
//
// https://developer.github.com/v3/repos/keys/#list-deploy-keys
func (k *PublicKeysService) All(uri *Hyperlink, uriParams M) (keys []Key, result *Result) {
url, err := ExpandWithDefault(uri, &CurrentPublicKeyUrl, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = k.client.get(url, &keys)
return
}
// Get a the data for one key for the current user
//
// https://developer.github.com/v3/repos/keys/#get-a-deploy-key
func (k *PublicKeysService) One(uri *Hyperlink, uriParams M) (key *Key, result *Result) {
url, err := ExpandWithDefault(uri, &CurrentPublicKeyUrl, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = k.client.get(url, &key)
return
}
// Creates a new public key for the current user
//
// https://developer.github.com/v3/repos/keys/#add-a-new-deploy-key
func (k *PublicKeysService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (key *Key, result *Result) {
url, err := ExpandWithDefault(uri, &CurrentPublicKeyUrl, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = k.client.post(url, requestParams, &key)
return
}
// Removes a public key for the current user
//
// https://developer.github.com/v3/repos/keys/#remove-a-deploy-key
func (k *PublicKeysService) Delete(uri *Hyperlink, uriParams M) (success bool, result *Result) {
url, err := ExpandWithDefault(uri, &CurrentPublicKeyUrl, uriParams)
if err != nil {
return false, &Result{Err: err}
}
result = k.client.delete(url, nil, nil)
success = (result.Response.StatusCode == 204)
return
}
type Key struct {
*hypermedia.HALResource
Id int `json:"id,omitempty"`
Key string `json:"key,omitempty"`
URL string `json:"url,omitempty"`
Title string `json:"title,omitempty"`
Verified bool `json:"verified,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
}
package octokit
import (
"io"
"net/url"
"time"
"github.com/jingweno/go-sawyer/hypermedia"
)
const (
MergeStateClean = "clean"
MergeStateUnstable = "unstable"
MergeStateDirty = "dirty"
MergeStateUnknown = "unknown"
)
// PullRequestsURL is a template for accessing pull requests in a particular
// repository for a particular owner that can be expanded to a full address.
//
// https://developer.github.com/v3/pulls/
var PullRequestsURL = Hyperlink("repos/{owner}/{repo}/pulls{/number}")
// PullRequests creates a PullRequestsService with a base url
//
// https://developer.github.com/v3/pulls/
func (c *Client) PullRequests(url *url.URL) (pullRequests *PullRequestsService) {
pullRequests = &PullRequestsService{client: c, URL: url}
return
}
// PullRequestService is a service providing access to pull requests from
// a particular url
type PullRequestsService struct {
client *Client
URL *url.URL
}
// One gets a specific pull request based on the url of the service
//
// https://developer.github.com/v3/pulls/#get-a-single-pull-request
func (p *PullRequestsService) One() (pull *PullRequest, result *Result) {
result = p.client.get(p.URL, &pull)
return
}
// Create posts a new pull request based on the parameters given to the
// pull request service url
//
// https://developer.github.com/v3/pulls/#create-a-pull-request
func (p *PullRequestsService) Create(params interface{}) (pull *PullRequest, result *Result) {
result = p.client.post(p.URL, params, &pull)
return
}
// All gets a list of all pull requests associated with the url of the service
//
// https://developer.github.com/v3/pulls/#list-pull-requests
func (p *PullRequestsService) All() (pulls []PullRequest, result *Result) {
result = p.client.get(p.URL, &pulls)
return
}
// Diff gets the difference of the data in the specific pull request to the branch
// that the pull request is out to be merged with
//
// https://developer.github.com/v3/pulls/#get-a-single-pull-request
func (p *PullRequestsService) Diff() (diff io.ReadCloser, result *Result) {
return p.client.getBody(p.URL, diffMediaType)
}
// Patch gets all the patches made to the specific pull request associated with
// the url given to the service
//
// https://developer.github.com/v3/pulls/#update-a-pull-request
func (p *PullRequestsService) Patch() (patch io.ReadCloser, result *Result) {
return p.client.getBody(p.URL, patchMediaType)
}
// PullRequest represents a pull request on GitHub and all associated parameters
type PullRequest struct {
*hypermedia.HALResource
URL string `json:"url,omitempty"`
ID int `json:"id,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
DiffURL string `json:"diff_url,omitempty"`
PatchURL string `json:"patch_url,omitempty"`
IssueURL string `json:"issue_url,omitempty"`
Title string `json:"title,omitempty"`
Number int `json:"number,omitempty"`
State string `json:"state,omitempty"`
User User `json:"user,omitempty"`
Body string `json:"body,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
ClosedAt *time.Time `json:"closed_at,omitempty"`
MergedAt *time.Time `json:"merged_at,omitempty"`
MergeCommitSha string `json:"merge_commit_sha,omitempty"`
Assignee *User `json:"assignee,omitempty"`
CommitsURL string `json:"commits_url,omitempty"`
ReviewCommentsURL string `json:"review_comments_url,omitempty"`
ReviewCommentURL string `json:"review_comment_url,omitempty"`
CommentsURL string `json:"comments_url,omitempty"`
Head PullRequestCommit `json:"head,omitempty"`
Base PullRequestCommit `json:"base,omitempty"`
Merged bool `json:"merged,omitempty"`
MergedBy User `json:"merged_by,omitempty"`
Comments int `json:"comments,omitempty"`
ReviewComments int `json:"review_comments,omitempty"`
Commits int `json:"commits,omitempty"`
Additions int `json:"additions,omitempty"`
Deletions int `json:"deletions,omitempty"`
ChangedFiles int `json:"changed_files,omitempty"`
Mergeable *bool `json:"mergeable,omitempty"`
MergeableState string `json:"mergeable_state,omitempty"`
}
// PullRequestCommit represents one of the commits associated with the given
// pull request - the head being pulled in or the base being pulled into
type PullRequestCommit struct {
Label string `json:"label,omitempty"`
Ref string `json:"ref,omitempty"`
Sha string `json:"sha,omitempty"`
User User `json:"user,omitempty"`
Repo *Repository `json:"repo,omitempty"`
}
// PullRequestParams represent the set of parameters used to create a new
// pull request
type PullRequestParams struct {
Base string `json:"base,omitempty"`
Head string `json:"head,omitempty"`
Title string `json:"title,omitempty"`
Body string `json:"body,omitempty"`
Assignee string `json:"assignee,omitempty"`
}
// PullRequestForIssueParams represent the set of parameters used to
// create a new pull request for a specific issue
type PullRequestForIssueParams struct {
Base string `json:"base,omitempty"`
Head string `json:"head,omitempty"`
Issue string `json:"issue,omitempty"`
}
package octokit
import (
"net/url"
"time"
"github.com/jingweno/go-sawyer/hypermedia"
)
// ReleasesURL is a template for accessing releases in a particular repository
// for a particular owner that can be expanded to a full address.
//
// https://developer.github.com/v3/repos/releases/
var (
ReleasesURL = Hyperlink("repos/{owner}/{repo}/releases{/id}")
ReleasesLatestURL = Hyperlink("repos/{owner}/{repo}/releases/latest")
)
// Release is a representation of a release on GitHub. Published releases are
// available to everyone.
type Release struct {
*hypermedia.HALResource
ID int `json:"id,omitempty"`
URL string `json:"url,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
AssetsURL string `json:"assets_url,omitempty"`
UploadURL Hyperlink `json:"upload_url,omitempty"`
TagName string `json:"tag_name,omitempty"`
TargetCommitish string `json:"target_commitish,omitempty"`
Name string `json:"name,omitempty"`
Body string `json:"body,omitempty"`
Draft bool `json:"draft,omitempty"`
Prerelease bool `json:"prerelease,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
Assets []Asset `json:"assets,omitempty"`
}
// Asset represents a piece of content produced and associated with a given
// released that may be downloaded
type Asset struct {
ID int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Label string `json:"label,omitempty"`
ContentType string `json:"content_type,omitempty"`
State string `json:"state,omitempty"`
Size int `json:"size,omitempty"`
DownloadCount int `json:"download_count,omitempty"`
URL string `json:"url,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
// Releases creates a ReleasesService with a base url
//
// https://developer.github.com/v3/repos/releases/
func (c *Client) Releases(url *url.URL) (releases *ReleasesService) {
releases = &ReleasesService{client: c, URL: url}
return
}
// ReleasesService is a service providing access to releases from a particular url
type ReleasesService struct {
client *Client
URL *url.URL
}
// All gets all releases for a given repository based on the URL of the service
//
// https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository
func (r *ReleasesService) All() (releases []Release, result *Result) {
result = r.client.get(r.URL, &releases)
return
}
// Latest gets the latest release for a repository
//
// https://developer.github.com/v3/repos/releases/#get-the-latest-release
func (r *ReleasesService) Latest() (release *Release, result *Result) {
result = r.client.get(r.URL, &release)
return
}
// Create posts a new release based on the relase parameters to the releases service url
//
// https://developer.github.com/v3/repos/releases/#create-a-release
func (r *ReleasesService) Create(params interface{}) (release *Release, result *Result) {
result = r.client.post(r.URL, params, &release)
return
}
// Update modifies a release based on the release parameters on the service url
//
// https://developer.github.com/v3/repos/releases/#edit-a-release
func (r *ReleasesService) Update(params interface{}) (release *Release, result *Result) {
result = r.client.patch(r.URL, params, &release)
return
}
// ReleaseParams represent the parameters used to create or update a release
type ReleaseParams struct {
TagName string `json:"tag_name,omitempty"`
TargetCommitish string `json:"target_commitish,omitempty"`
Name string `json:"name,omitempty"`
Body string `json:"body,omitempty"`
Draft bool `json:"draft,omitempty"`
Prerelease bool `json:"prerelease,omitempty"`
}
package octokit
import (
"github.com/jingweno/go-sawyer/hypermedia"
"time"
)
// Hyperlinks to the various repository locations on github.
// RepositoryURL is a template for a particular repository for a
// particular owner.
// ForksURL is a template for the forks of a user's repository.
// UserRepositoriesURL is the address for all user repositories.
// OrgRepositoriesUrl is the template for repositories within a particular organization.
//
// https://developer.github.com/v3/repos/
var (
AllRepositoriesURL = Hyperlink("repositories")
ForksURL = Hyperlink("repos/{owner}/{repo}/forks")
OrgRepositoriesURL = Hyperlink("orgs/{org}/repos")
RepositoryURL = Hyperlink("repos/{owner}/{repo}")
UserRepositoriesURL = Hyperlink("user/repos")
)
// Repositories creates a RepositoriesService with a base url
//
// https://developer.github.com/v3/repos/
func (c *Client) Repositories() (repos *RepositoriesService) {
repos = &RepositoriesService{client: c}
return
}
// RepositoriesService is a service providing access to repositories from a
// particular url
type RepositoriesService struct {
client *Client
}
// One gets a specific repository based on the url of the service
//
// https://developer.github.com/v3/repos/#get
func (r *RepositoriesService) One(uri *Hyperlink, params M) (repo *Repository,
result *Result) {
if uri == nil {
uri = &RepositoryURL
}
url, err := uri.Expand(params)
if err != nil {
return nil, &Result{Err: err}
}
result = r.client.get(url, &repo)
return
}
// All gets a list of all repositories associated with the url of the service
//
// https://developer.github.com/v3/repos/#list-your-repositories
func (r *RepositoriesService) All(uri *Hyperlink, params M) (repos []Repository,
result *Result) {
if uri == nil && len(params) == 0 {
uri = &UserRepositoriesURL
} else if uri == nil {
uri = &AllRepositoriesURL
}
url, err := uri.Expand(params)
if err != nil {
return nil, &Result{Err: err}
}
result = r.client.get(url, &repos)
return
}
// Create posts a new repository based on parameters in a Repository struct to
// the respository service url
//
// https://developer.github.com/v3/repos/#create
func (r *RepositoriesService) Create(uri *Hyperlink, uriParams M,
params interface{}) (repo *Repository, result *Result) {
if uri == nil {
uri = &RepositoryURL
}
url, err := uri.Expand(uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = r.client.post(url, params, &repo)
return
}
// Repository represents a respository on GitHub with all associated metadata
// with respect to the particular accessing url
type Repository struct {
*hypermedia.HALResource
ID int `json:"id,omitempty"`
Owner User `json:"owner,omitempty"`
Name string `json:"name,omitempty"`
FullName string `json:"full_name,omitempty"`
Description string `json:"description,omitempty"`
Private bool `json:"private"`
Fork bool `json:"fork,omitempty"`
URL string `json:"url,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
CloneURL string `json:"clone_url,omitempty"`
GitURL string `json:"git_url,omitempty"`
SSHURL string `json:"ssh_url,omitempty"`
SVNURL string `json:"svn_url,omitempty"`
MirrorURL string `json:"mirror_url,omitempty"`
Homepage string `json:"homepage,omitempty"`
Language string `json:"language,omitempty"`
Forks int `json:"forks,omitempty"`
ForksCount int `json:"forks_count,omitempty"`
StargazersCount int `json:"stargazers_count,omitempty"`
Watchers int `json:"watchers,omitempty"`
WatchersCount int `json:"watchers_count,omitempty"`
Size int `json:"size,omitempty"`
MasterBranch string `json:"master_branch,omitempty"`
OpenIssues int `json:"open_issues,omitempty"`
PushedAt *time.Time `json:"pushed_at,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
Permissions Permissions `json:"permissions,omitempty"`
Organization *Organization `json:"organization,omitempty"`
Parent *Repository `json:"parent,omitempty"`
Source *Repository `json:"source,omitempty"`
HasIssues bool `json:"has_issues,omitempty"`
HasWiki bool `json:"has_wiki,omitempty"`
HasDownloads bool `json:"has_downloads,omitempty"`
}
// Permissions represent the permissions as they apply to the accessing url
type Permissions struct {
Admin bool
Push bool
Pull bool
}
package octokit
import (
"github.com/jingweno/go-sawyer"
"github.com/jingweno/go-sawyer/mediatype"
)
func newRequest(client *Client, urlStr string) (req *Request, err error) {
sawyerReq, err := client.Client.NewRequest(urlStr)
if err != nil {
return
}
req = &Request{client: client, Request: sawyerReq}
return
}
// Request wraps a sawyer Request which is a wrapper for an HttpRequest with
// a particular octokit Client
type Request struct {
*sawyer.Request
client *Client
}
// Head sends a HEAD request through the given client and returns the response
// and any associated errors
func (r *Request) Head(output interface{}) (*Response, error) {
return r.createResponse(r.Request.Head(), output)
}
// Get sends a GET request through the given client and returns the response
// and any associated errors
func (r *Request) Get(output interface{}) (*Response, error) {
if output == nil {
return NewResponse(r.Request.Get())
}
return r.createResponse(r.Request.Get(), output)
}
// Post sends a POST request through the given client and returns the response
// and any associated errors
func (r *Request) Post(input interface{}, output interface{}) (*Response, error) {
r.setBody(input)
return r.createResponse(r.Request.Post(), output)
}
// Put sends a PUT request through the given client and returns the response
// and any associated errors
func (r *Request) Put(input interface{}, output interface{}) (*Response, error) {
r.setBody(input)
return r.createResponse(r.Request.Put(), output)
}
// Delete sends a DELETE request through the given client and returns the response
// and any associated errors
func (r *Request) Delete(input interface{}, output interface{}) (*Response, error) {
r.setBody(input)
return r.createResponse(r.Request.Delete(), output)
}
// Patch sends a PATCH request through the given client and returns the response
// and any associated errors
func (r *Request) Patch(input interface{}, output interface{}) (*Response, error) {
r.setBody(input)
return r.createResponse(r.Request.Patch(), output)
}
// Options sends an OPTIONS request through the given client and returns the response
// and any associated errors
func (r *Request) Options(output interface{}) (*Response, error) {
return r.createResponse(r.Request.Options(), output)
}
func (r *Request) setBody(input interface{}) {
mtype, _ := mediatype.Parse(defaultMediaType)
r.Request.SetBody(mtype, input)
}
func (r *Request) createResponse(sawyerResp *sawyer.Response, output interface{}) (resp *Response, err error) {
resp, err = NewResponse(sawyerResp)
if err == nil {
err = sawyerResp.Decode(output)
}
return
}
package octokit
import (
"net/http"
"github.com/jingweno/go-sawyer"
"github.com/jingweno/go-sawyer/mediaheader"
"github.com/jingweno/go-sawyer/mediatype"
)
// Response is a wrapper for a HttpResponse that adds a cleaned form
// of the MeidaType and MediaHeader
type Response struct {
MediaType *mediatype.MediaType
MediaHeader *mediaheader.MediaHeader
*http.Response
}
// NewResponse unwraps a sawyer Response, producing an error if there
// was one associated in the sawyer response and otherwise creating a
// new Response from the underlying HttpResponse, MediaType and
// MediaHeader
func NewResponse(sawyerResp *sawyer.Response) (resp *Response, err error) {
if sawyerResp.IsError() {
err = sawyerResp.ResponseError
return
}
if sawyerResp.IsApiError() {
err = NewResponseError(sawyerResp)
return
}
resp = &Response{Response: sawyerResp.Response, MediaType: sawyerResp.MediaType, MediaHeader: sawyerResp.MediaHeader}
return
}
package octokit
import (
"strconv"
"strings"
"time"
"github.com/jingweno/go-sawyer/mediaheader"
)
const (
oauthScopes = "X-OAuth-Scopes"
oauthAcceptedScopes = "X-OAuth-Accepted-Scopes"
rateLimitRemaining = "X-RateLimit-Remaining"
rateLimitReset = "X-RateLimit-Reset"
)
type pageable struct {
NextPage *Hyperlink
LastPage *Hyperlink
FirstPage *Hyperlink
PrevPage *Hyperlink
}
// Result is a pageable set of data, with hyperlinks to the first, last,
// previous, and next pages, containing a response to some request and
// associated error, if any
type Result struct {
Response *Response
Err error
pageable
}
// HasError returns true if the error field of the Result is not nil; false
// otherwise
func (r *Result) HasError() bool {
return r.Err != nil
}
// Error returns the string representation of the error if it exists; the
// empty string is returned otherwise
func (r *Result) Error() string {
if r.Err != nil {
return r.Err.Error()
}
return ""
}
func (r *Result) RateLimitReset() *time.Time {
epoc := r.Response.Header.Get(rateLimitReset)
if epoc == "" {
return nil
}
reset, err := strconv.ParseInt(epoc, 10, 64)
if err != nil {
return nil
}
t := time.Unix(reset, 0)
return &t
}
func (r *Result) RateLimitRemaining() int {
rate, err := strconv.Atoi(r.Response.Header.Get(rateLimitRemaining))
if err != nil {
rate = defaultRateLimit(r.Response)
}
return rate
}
func (r *Result) RawScopes() string {
return r.Response.Header.Get(oauthScopes)
}
func (r *Result) Scopes() []string {
return strings.Split(r.RawScopes(), ", ")
}
func (r *Result) RawAcceptedScopes() string {
return r.Response.Header.Get(oauthAcceptedScopes)
}
func (r *Result) AcceptedScopes() []string {
return strings.Split(r.RawAcceptedScopes(), ", ")
}
func (r *Result) ValidScope(scope string) bool {
for _, s := range r.Scopes() {
if s == scope {
return true
}
}
return false
}
func newResult(resp *Response, err error) *Result {
pageable := pageable{}
if resp != nil {
fillPageable(&pageable, resp.MediaHeader)
}
return &Result{Response: resp, pageable: pageable, Err: err}
}
func fillPageable(pageable *pageable, header *mediaheader.MediaHeader) {
if link, ok := header.Relations["next"]; ok {
l := Hyperlink(link)
pageable.NextPage = &l
}
if link, ok := header.Relations["prev"]; ok {
l := Hyperlink(link)
pageable.PrevPage = &l
}
if link, ok := header.Relations["first"]; ok {
l := Hyperlink(link)
pageable.FirstPage = &l
}
if link, ok := header.Relations["last"]; ok {
l := Hyperlink(link)
pageable.LastPage = &l
}
}
func defaultRateLimit(r *Response) int {
if r.Request != nil {
h := r.Request.URL.Host
if !strings.HasSuffix(gitHubAPIURL, h) {
return -1
}
}
return 60
}
package octokit
import (
"net/url"
"github.com/jingweno/go-sawyer/hypermedia"
)
// RootURL is simply the root GitHub address. Accessing this address provides all
// other accessible templates and addresses as hypermedia relations.
//
// https://api.github.com/
var RootURL = Hyperlink("")
// Rel fetches and expands the given name in the the Hyperlink map m
func (c *Client) Rel(name string, m map[string]interface{}) (*url.URL, error) {
if c.rootRels == nil || len(c.rootRels) == 0 {
u, _ := url.Parse("/")
root, res := c.Root(u).One()
if res.HasError() {
return nil, res
}
c.rootRels = root.Rels()
}
return c.rootRels.Rel(name, m)
}
// Root creates a RootService with a base url
//
// https://api.github.com/
func (c *Client) Root(url *url.URL) (root *RootService) {
root = &RootService{client: c, URL: url}
return
}
// RootService is a representation of a simple service to access hyperlinks
// to all the other accessible URLs
type RootService struct {
client *Client
URL *url.URL
}
// One accesses the root URI templates and assigns them to result
//
// https://developer.github.com/v3/#root-endpoint
func (r *RootService) One() (root *Root, result *Result) {
root = &Root{HALResource: &hypermedia.HALResource{}}
result = r.client.get(r.URL, &root)
if root != nil {
// Cached hyperlinks
root.PullsURL = hypermedia.Hyperlink(PullRequestsURL)
}
return
}
// Root represents the base with hyperlinks in template form to all API calls
//
// https://api.github.com/
type Root struct {
*hypermedia.HALResource
UserSearchURL hypermedia.Hyperlink `rel:"user_search" json:"user_search_url,omitempty"`
UserRepositoriesURL hypermedia.Hyperlink `rel:"user_repositories" json:"user_repositories_url,omitempty"`
UserOrganizationsURL hypermedia.Hyperlink `rel:"user_organizations" json:"user_organizations_url,omitempty"`
UserURL hypermedia.Hyperlink `rel:"user" json:"user_url,omitempty"`
TeamURL hypermedia.Hyperlink `rel:"team" json:"team_url,omitempty"`
StarredGistsURL hypermedia.Hyperlink `rel:"starred_gists" json:"starred_gists_url,omitempty"`
StarredURL hypermedia.Hyperlink `rel:"starred" json:"starred_url,omitempty"`
CurrentUserRepositoriesURL hypermedia.Hyperlink `rel:"current_user_repositories" json:"current_user_repositories_url,omitempty"`
RepositorySearchURL hypermedia.Hyperlink `rel:"repository_search" json:"repository_search_url,omitempty"`
RepositoryURL hypermedia.Hyperlink `rel:"repository" json:"repository_url,omitempty"`
RateLimitURL hypermedia.Hyperlink `rel:"rate_limit" json:"rate_limit_url,omitempty"`
GistsURL hypermedia.Hyperlink `rel:"gists" json:"gists_url,omitempty"`
FollowingURL hypermedia.Hyperlink `rel:"following" json:"following_url,omitempty"`
FeedsURL hypermedia.Hyperlink `rel:"feeds" json:"feeds_url,omitempty"`
EventsURL hypermedia.Hyperlink `rel:"events" json:"events_url,omitempty"`
EmojisURL hypermedia.Hyperlink `rel:"emojis" json:"emojis_url,omitempty"`
EmailsURL hypermedia.Hyperlink `rel:"emails" json:"emails_url,omitempty"`
AuthorizationsURL hypermedia.Hyperlink `rel:"authorizations" json:"authorizations_url,omitempty"`
CurrentUserURL hypermedia.Hyperlink `rel:"current_user" json:"current_user_url,omitempty"`
HubURL hypermedia.Hyperlink `rel:"hub" json:"hub_url,omitempty"`
IssueSearchURL hypermedia.Hyperlink `rel:"issue_search" json:"issue_search_url,omitempty"`
IssuesURL hypermedia.Hyperlink `rel:"issues" json:"issues_url,omitempty"`
KeysURL hypermedia.Hyperlink `rel:"keys" json:"keys_url,omitempty"`
NotificationsURL hypermedia.Hyperlink `rel:"notifications" json:"notifications_url,omitempty"`
OrganizationRepositoriesURL hypermedia.Hyperlink `rel:"organization_repositories" json:"organization_repositories_url,omitempty"`
OrganizationURL hypermedia.Hyperlink `rel:"organization" json:"organization_url,omitempty"`
PublicGistsURL hypermedia.Hyperlink `rel:"public_gists" json:"public_gists_url,omitempty"`
PullsURL hypermedia.Hyperlink `rel:"pulls" json:"-"`
rels hypermedia.Relations `json:"-"`
}
// Rels gets the link relations from the HALResource's Links field.
func (r *Root) Rels() hypermedia.Relations {
if r.rels == nil || len(r.rels) == 0 {
r.rels = hypermedia.HyperFieldDecoder(r)
for key, hyperlink := range r.HALResource.Rels() {
r.rels[key] = hyperlink
}
}
return r.rels
}
package octokit
import (
"github.com/jingweno/go-sawyer/hypermedia"
)
// https://developer.github.com/v3/search/
var (
CodeSearchURL = Hyperlink("/search/code?q={query}{&page,per_page,sort,order}")
IssueSearchURL = Hyperlink("/search/issues?q={query}{&page,per_page,sort,order}")
RepositorySearchURL = Hyperlink("/search/repositories?q={query}{&page,per_page,sort,order}")
UserSearchURL = Hyperlink("/search/users?q={query}{&page,per_page,sort,order}")
)
// https://developer.github.com/v3/search/
func (c *Client) Search() *SearchService {
return &SearchService{client: c}
}
// A service to return search records
type SearchService struct {
client *Client
}
// Get the user search results based on SearchService#URL
//
// https://developer.github.com/v3/search/#search-users
func (g *SearchService) Users(uri *Hyperlink, uriParams M) (userSearchResults *UserSearchResults, result *Result) {
url, err := ExpandWithDefault(uri, &UserSearchURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &userSearchResults)
return
}
// Get the issue search results based on SearchService#URL
//
// https://developer.github.com/v3/search/#search-issues
func (g *SearchService) Issues(uri *Hyperlink, uriParams M) (issueSearchResults *IssueSearchResults, result *Result) {
url, err := ExpandWithDefault(uri, &IssueSearchURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &issueSearchResults)
return
}
// Get the repository search results based on SearchService#URL
//
// https://developer.github.com/v3/search/#search-repositories
func (g *SearchService) Repositories(uri *Hyperlink, uriParams M) (repositorySearchResults *RepositorySearchResults, result *Result) {
url, err := ExpandWithDefault(uri, &RepositorySearchURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &repositorySearchResults)
return
}
// Get the code search results based on SearchService#URL
//
// https://developer.github.com/v3/search/#search-code
func (g *SearchService) Code(uri *Hyperlink, uriParams M) (codeSearchResults *CodeSearchResults, result *Result) {
url, err := ExpandWithDefault(uri, &CodeSearchURL, uriParams)
if err != nil {
return nil, &Result{Err: err}
}
result = g.client.get(url, &codeSearchResults)
return
}
type UserSearchResults struct {
*hypermedia.HALResource
TotalCount int `json:"total_count,omitempty"`
IncompleteResults bool `json:"incomplete_results,omitempty"`
Items []User `json:"items,omitempty"`
}
type IssueSearchResults struct {
*hypermedia.HALResource
TotalCount int `json:"total_count,omitempty"`
IncompleteResults bool `json:"incomplete_results,omitempty"`
Items []Issue `json:"items,omitempty"`
}
type RepositorySearchResults struct {
*hypermedia.HALResource
TotalCount int `json:"total_count,omitempty"`
IncompleteResults bool `json:"incomplete_results,omitempty"`
Items []Repository `json:"items,omitempty"`
}
type CodeSearchResults struct {
*hypermedia.HALResource
TotalCount int `json:"total_count,omitempty"`
IncompleteResults bool `json:"incomplete_results,omitempty"`
Items []CodeFile `json:"items,omitempty"`
}
type CodeFile struct {
*hypermedia.HALResource
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
SHA string `json:"sha,omitempty"`
URL Hyperlink `json:"url,omitempty"`
GitURL Hyperlink `json:"git_url,omitempty"`
HTMLURL Hyperlink `json:"html_url,omitempty"`
Repository Repository `json:"repository,omitempty"`
}
package octokit
import (
"io"
"net/url"
)
// Uploads creates an UploadsService with a base url
func (c *Client) Uploads(url *url.URL) (uploads *UploadsService) {
uploads = &UploadsService{client: c, URL: url}
return
}
// UploadsService is a service providing access to asset uploads from a particular url
type UploadsService struct {
client *Client
URL *url.URL
}
// UploadAsset uploads a particular asset of some content type and length to the service
func (u *UploadsService) UploadAsset(asset io.ReadCloser, contentType string, contentLength int64) (result *Result) {
return u.client.upload(u.URL, asset, contentType, contentLength)
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册