Add iptv smarter android app AND xtream server api compatibility (#2)

Signed-off-by: Pierre-Emmanuel Jacquier <pierre-emmanuel.jacquier@epitech.eu>
This commit is contained in:
Pierre-Emmanuel Jacquier
2019-04-27 17:46:02 +02:00
committed by GitHub
parent 2561702e5c
commit 93430a69ee
27 changed files with 1218 additions and 2153 deletions

View File

@@ -3,14 +3,15 @@ package cmd
import (
"fmt"
"log"
"net/url"
"os"
"strings"
"github.com/jamesnetherton/m3u"
"github.com/pierre-emmanuelJ/iptv-proxy/pkg/config"
"github.com/pierre-emmanuelJ/iptv-proxy/pkg/routes"
"github.com/jamesnetherton/m3u"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -23,7 +24,13 @@ var rootCmd = &cobra.Command{
Use: "iptv-proxy",
Short: "A brief description of your application",
Run: func(cmd *cobra.Command, args []string) {
playlist, err := m3u.Parse(viper.GetString("m3u-url"))
m3uURL := viper.GetString("m3u-url")
playlist, err := m3u.Parse(m3uURL)
if err != nil {
log.Fatal(err)
}
remoteHostURL, err := url.Parse(m3uURL)
if err != nil {
log.Fatal(err)
}
@@ -34,8 +41,12 @@ var rootCmd = &cobra.Command{
Hostname: viper.GetString("hostname"),
Port: viper.GetInt64("port"),
},
User: viper.GetString("user"),
Password: viper.GetString("password"),
RemoteURL: remoteHostURL,
XtreamUser: viper.GetString("xtream-user"),
XtreamPassword: viper.GetString("xtream-password"),
XtreamBaseURL: viper.GetString("xtream-base-url"),
User: viper.GetString("user"),
Password: viper.GetString("password"),
}
if e := routes.Serve(conf); e != nil {
@@ -64,7 +75,10 @@ func init() {
rootCmd.Flags().Int64("port", 8080, "Port to expose the IPTVs endpoints")
rootCmd.Flags().String("hostname", "", "Hostname or IP to expose the IPTVs endpoints")
rootCmd.Flags().String("user", "usertest", "user UNSAFE(temp auth to access proxy)")
rootCmd.Flags().String("password", "passwordtest", "password UNSAFE(temp auth to access proxy)")
rootCmd.Flags().String("password", "passwordtest", "password UNSAFE(auth to access m3u proxy and xtream proxy)")
rootCmd.Flags().String("xtream-user", "xtream_user", "Xtream-code user login")
rootCmd.Flags().String("xtream-password", "xtream_password", "Xtream-code password login")
rootCmd.Flags().String("xtream-base-url", "http://expample.tv:8080", "Xtream-code base url")
if e := viper.BindPFlags(rootCmd.Flags()); e != nil {
log.Fatal("error binding PFlags to viper")

View File

@@ -22,4 +22,12 @@ services:
# Hostname or IP to expose the IPTVs endpoints (for machine not for docker)
HOSTNAME: localhost
GIN_MODE: release
## Xtream-code proxy configuration
XTREAM_USER: xtream_user
XTREAM_PASSWORD: xtream_password
XTREAM_BASE_URL: "http://example.tv:8080"
##### UNSAFE AUTH TODO ADD REAL AUTH
#will be used for m3u and xtream auth poxy
USER: test
PASSWORD: testpassword

2
go.mod
View File

@@ -1,7 +1,6 @@
module github.com/pierre-emmanuelJ/iptv-proxy
require (
github.com/davecgh/go-spew v1.1.1
github.com/gin-contrib/cors v0.0.0-20190226021855-50921afdc5c1
github.com/gin-gonic/gin v1.3.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
@@ -9,4 +8,5 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v0.0.3
github.com/spf13/viper v1.3.1
github.com/tellytv/go.xtream-codes v0.0.0-20190114013623-9b74dcb500e4
)

2
go.sum
View File

@@ -52,6 +52,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38=
github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/tellytv/go.xtream-codes v0.0.0-20190114013623-9b74dcb500e4 h1:V5xNxrc8ApVSY2uHeVXDkByVB2iAgoOcmWOOu01oATM=
github.com/tellytv/go.xtream-codes v0.0.0-20190114013623-9b74dcb500e4/go.mod h1:gWtQ2uZJ49dBh4cWiFuz7Tb5ALxLB9hY1GFoz34lsGs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY+LsWmuwob+CRS1BmdRdjphAm9mH4=
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=

View File

@@ -1,6 +1,10 @@
package config
import "github.com/jamesnetherton/m3u"
import (
"net/url"
"github.com/jamesnetherton/m3u"
)
// HostConfiguration containt host infos
type HostConfiguration struct {
@@ -10,8 +14,12 @@ type HostConfiguration struct {
// ProxyConfig Contain original m3u playlist and HostConfiguration
type ProxyConfig struct {
Playlist *m3u.Playlist
HostConfig *HostConfiguration
Playlist *m3u.Playlist
HostConfig *HostConfiguration
XtreamUser string
XtreamPassword string
XtreamBaseURL string
RemoteURL *url.URL
//XXX Very unsafe
User, Password string
}

View File

@@ -37,7 +37,7 @@ func ReplaceURL(proxyConfig *config.ProxyConfig) (*m3u.Playlist, error) {
}
config := proxyConfig.HostConfig
uri := fmt.Sprintf(
"http://%s:%d%s?user=%s&password=%s",
"http://%s:%d%s?username=%s&password=%s",
config.Hostname,
config.Port,
oriURL.RequestURI(),

View File

@@ -1,12 +1,15 @@
package routes
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"time"
"github.com/jamesnetherton/m3u"
@@ -46,10 +49,16 @@ func Routes(proxyConfig *config.ProxyConfig, r *gin.RouterGroup, newM3U []byte)
}
r.GET("/iptv.m3u", p.authenticate, p.getM3U)
// XXX Private need for external Android app
r.POST("/iptv.m3u", p.authenticate, p.getM3U)
//Xtream, iptv Smarter android app compatibility
r.POST("/player_api.php", p.appAuthenticate, p.xtreamPlayerAPI)
r.GET("/xmltv.php", p.authenticate, p.xtreamXMLTV)
r.GET(fmt.Sprintf("/%s/%s/:id", proxyConfig.User, proxyConfig.Password), p.xtreamStream)
r.GET(fmt.Sprintf("/movie/%s/%s/:id", proxyConfig.User, proxyConfig.Password), p.xtreamStreamMovie)
r.GET(fmt.Sprintf("/series/%s/%s/:id", proxyConfig.User, proxyConfig.Password), p.xtreamStreamSeries)
for i, track := range proxyConfig.Playlist.Tracks {
oriURL, err := url.Parse(track.URI)
if err != nil {
@@ -64,15 +73,25 @@ func Routes(proxyConfig *config.ProxyConfig, r *gin.RouterGroup, newM3U []byte)
}
}
func (p *proxy) getM3U(c *gin.Context) {
c.Header("Content-Disposition", "attachment; filename=\"iptv.m3u\"")
c.Data(http.StatusOK, "application/octet-stream", p.newM3U)
}
func (p *proxy) reverseProxy(c *gin.Context) {
rpURL, err := url.Parse(p.Track.URI)
if err != nil {
log.Fatal(err)
}
resp, err := http.Get(rpURL.String())
stream(c, rpURL)
}
func stream(c *gin.Context, oriURL *url.URL) {
resp, err := http.Get(oriURL.String())
if err != nil {
log.Fatal(err)
c.AbortWithError(http.StatusInternalServerError, err)
return
}
defer resp.Body.Close()
@@ -90,14 +109,9 @@ func copyHTTPHeader(c *gin.Context, header http.Header) {
}
}
func (p *proxy) getM3U(c *gin.Context) {
c.Header("Content-Disposition", "attachment; filename=\"iptv.m3u\"")
c.Data(http.StatusOK, "application/octet-stream", p.newM3U)
}
// AuthRequest handle auth credentials
type AuthRequest struct {
User string `form:"user" binding:"required"`
User string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
} // XXX very unsafe
@@ -113,6 +127,31 @@ func (p *proxy) authenticate(ctx *gin.Context) {
}
}
func (p *proxy) appAuthenticate(c *gin.Context) {
contents, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
q, err := url.ParseQuery(string(contents))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
if len(q["username"]) == 0 || len(q["password"]) == 0 {
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("bad body url query parameters"))
return
}
log.Printf("[iptv-proxy] %v | %s |App Auth\n", time.Now().Format("2006/01/02 - 15:04:05"), c.ClientIP())
//XXX very unsafe
if p.ProxyConfig.User != q["username"][0] || p.ProxyConfig.Password != q["password"][0] {
c.AbortWithStatus(http.StatusUnauthorized)
}
c.Request.Body = ioutil.NopCloser(bytes.NewReader(contents))
}
func initm3u(p *config.ProxyConfig) ([]byte, error) {
playlist, err := proxyM3U.ReplaceURL(p)
if err != nil {

133
pkg/routes/xtream.go Normal file
View File

@@ -0,0 +1,133 @@
package routes
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"time"
"github.com/gin-gonic/gin"
xtreamapi "github.com/pierre-emmanuelJ/iptv-proxy/pkg/xtream-proxy"
)
func (p *proxy) xtreamPlayerAPI(c *gin.Context) {
contents, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
log.Println(string(contents))
q, err := url.ParseQuery(string(contents))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
if len(q["username"]) == 0 || len(q["password"]) == 0 {
c.AbortWithError(http.StatusBadRequest, fmt.Errorf(`bad body url query parameters: missing "username" and "password"`))
return
}
var action string
if len(q["action"]) > 0 {
action = q["action"][0]
}
client, err := xtreamapi.New(p.XtreamUser, p.XtreamPassword, p.XtreamBaseURL)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
var respBody interface{}
switch action {
case xtreamapi.GetLiveCategories:
respBody, err = client.GetLiveCategories()
case xtreamapi.GetLiveStreams:
respBody, err = client.GetLiveStreams("")
case xtreamapi.GetVodCategories:
respBody, err = client.GetVideoOnDemandCategories()
case xtreamapi.GetVodStreams:
respBody, err = client.GetVideoOnDemandStreams("")
case xtreamapi.GetVodInfo:
if len(q["vod_id"]) < 1 {
c.AbortWithError(http.StatusBadRequest, fmt.Errorf(`bad body url query parameters: missing "vod_id"`))
return
}
respBody, err = client.GetVideoOnDemandInfo(q["vod_id"][0])
case xtreamapi.GetSeriesCategories:
respBody, err = client.GetSeriesCategories()
case xtreamapi.GetSeries:
respBody, err = client.GetSeries("")
case xtreamapi.GetSerieInfo:
if len(q["series_id"]) < 1 {
c.AbortWithError(http.StatusBadRequest, fmt.Errorf(`bad body url query parameters: missing "series_id"`))
return
}
respBody, err = client.GetSeriesInfo(q["series_id"][0])
default:
respBody, err = client.Login(p.User, p.Password, "http://"+p.HostConfig.Hostname, int(p.HostConfig.Port))
}
log.Printf("[iptv-proxy] %v | %s |Action\t%s\n", time.Now().Format("2006/01/02 - 15:04:05"), c.ClientIP(), action)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, respBody)
}
func (p *proxy) xtreamXMLTV(c *gin.Context) {
client, err := xtreamapi.New(p.XtreamUser, p.XtreamPassword, p.XtreamBaseURL)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
resp, err := client.GetXMLTV()
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.Data(http.StatusOK, "application/xml", resp)
}
func (p *proxy) xtreamStream(c *gin.Context) {
id := c.Param("id")
rpURL, err := url.Parse(fmt.Sprintf("%s/%s/%s/%s", p.XtreamBaseURL, p.XtreamUser, p.XtreamPassword, id))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
stream(c, rpURL)
}
func (p *proxy) xtreamStreamMovie(c *gin.Context) {
id := c.Param("id")
rpURL, err := url.Parse(fmt.Sprintf("%s/movie/%s/%s/%s", p.XtreamBaseURL, p.XtreamUser, p.XtreamPassword, id))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
stream(c, rpURL)
}
func (p *proxy) xtreamStreamSeries(c *gin.Context) {
id := c.Param("id")
rpURL, err := url.Parse(fmt.Sprintf("%s/series/%s/%s/%s", p.XtreamBaseURL, p.XtreamUser, p.XtreamPassword, id))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
stream(c, rpURL)
}

View File

@@ -0,0 +1,64 @@
package xtreamproxy
import (
xtream "github.com/tellytv/go.xtream-codes"
)
const (
GetLiveCategories = "get_live_categories"
GetLiveStreams = "get_live_streams"
GetVodCategories = "get_vod_categories"
GetVodStreams = "get_vod_streams"
GetVodInfo = "get_vod_info"
GetSeriesCategories = "get_series_categories"
GetSeries = "get_series"
GetSerieInfo = "get_series_info"
)
type Client struct {
*xtream.XtreamClient
}
func New(user, password, baseURL string) (*Client, error) {
cli, err := xtream.NewClient(user, password, baseURL)
if err != nil {
return nil, err
}
return &Client{cli}, nil
}
type Login struct {
UserInfo xtream.UserInfo `json:"user_info"`
ServerInfo xtream.ServerInfo `json:"server_info"`
}
func (c *Client) Login(proxyUser, proxyPassword, proxyURL string, proxyPort int) (Login, error) {
req := Login{
UserInfo: xtream.UserInfo{
Username: proxyUser,
Password: proxyPassword,
Message: c.UserInfo.Message,
Auth: c.UserInfo.Auth,
Status: c.UserInfo.Status,
ExpDate: c.UserInfo.ExpDate,
IsTrial: c.UserInfo.IsTrial,
ActiveConnections: c.UserInfo.ActiveConnections,
CreatedAt: c.UserInfo.CreatedAt,
MaxConnections: c.UserInfo.MaxConnections,
AllowedOutputFormats: c.UserInfo.AllowedOutputFormats,
},
ServerInfo: xtream.ServerInfo{
URL: proxyURL,
Port: proxyPort,
HTTPSPort: proxyPort,
Protocol: c.ServerInfo.Protocol,
RTMPPort: proxyPort,
Timezone: c.ServerInfo.Timezone,
TimestampNow: c.ServerInfo.TimestampNow,
TimeNow: c.ServerInfo.TimeNow,
},
}
return req, nil
}

View File

@@ -1,15 +0,0 @@
ISC License
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,145 +0,0 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// Go versions prior to 1.4 are disabled because they use a different layout
// for interfaces which make the implementation of unsafeReflectValue more complex.
// +build !js,!appengine,!safe,!disableunsafe,go1.4
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
type flag uintptr
var (
// flagRO indicates whether the value field of a reflect.Value
// is read-only.
flagRO flag
// flagAddr indicates whether the address of the reflect.Value's
// value may be taken.
flagAddr flag
)
// flagKindMask holds the bits that make up the kind
// part of the flags field. In all the supported versions,
// it is in the lower 5 bits.
const flagKindMask = flag(0x1f)
// Different versions of Go have used different
// bit layouts for the flags type. This table
// records the known combinations.
var okFlags = []struct {
ro, addr flag
}{{
// From Go 1.4 to 1.5
ro: 1 << 5,
addr: 1 << 7,
}, {
// Up to Go tip.
ro: 1<<5 | 1<<6,
addr: 1 << 8,
}}
var flagValOffset = func() uintptr {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
return field.Offset
}()
// flagField returns a pointer to the flag field of a reflect.Value.
func flagField(v *reflect.Value) *flag {
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) reflect.Value {
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
return v
}
flagFieldPtr := flagField(&v)
*flagFieldPtr &^= flagRO
*flagFieldPtr |= flagAddr
return v
}
// Sanity checks against future reflect package changes
// to the type or semantics of the Value.flag field.
func init() {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
panic("reflect.Value flag field has changed kind")
}
type t0 int
var t struct {
A t0
// t0 will have flagEmbedRO set.
t0
// a will have flagStickyRO set
a t0
}
vA := reflect.ValueOf(t).FieldByName("A")
va := reflect.ValueOf(t).FieldByName("a")
vt0 := reflect.ValueOf(t).FieldByName("t0")
// Infer flagRO from the difference between the flags
// for the (otherwise identical) fields in t.
flagPublic := *flagField(&vA)
flagWithRO := *flagField(&va) | *flagField(&vt0)
flagRO = flagPublic ^ flagWithRO
// Infer flagAddr from the difference between a value
// taken from a pointer and not.
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
flagNoPtr := *flagField(&vA)
flagPtr := *flagField(&vPtrA)
flagAddr = flagNoPtr ^ flagPtr
// Check that the inferred flags tally with one of the known versions.
for _, f := range okFlags {
if flagRO == f.ro && flagAddr == f.addr {
return
}
}
panic("reflect.Value read-only flag has changed semantics")
}

View File

@@ -1,38 +0,0 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build js appengine safe disableunsafe !go1.4
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

View File

@@ -1,341 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
maxNewlineBytes = []byte("<max depth reached>\n")
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}

View File

@@ -1,306 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "safe" build tag specified.
DisablePointerMethods bool
// DisablePointerAddresses specifies whether to disable the printing of
// pointer addresses. This is useful when diffing data structures in tests.
DisablePointerAddresses bool
// DisableCapacities specifies whether to disable the printing of capacities
// for arrays, slices, maps and channels. This is useful when diffing
// data structures in tests.
DisableCapacities bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}

View File

@@ -1,211 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* DisablePointerAddresses
DisablePointerAddresses specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests.
* DisableCapacities
DisableCapacities specifies whether to disable the printing of
capacities for arrays, slices, maps and channels. This is useful when
diffing data structures in tests.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew

View File

@@ -1,509 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound:
d.w.Write(nilAngleBytes)
case cycleFound:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if !d.cs.DisableCapacities && valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}

View File

@@ -1,419 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound:
f.fs.Write(nilAngleBytes)
case cycleFound:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}

View File

@@ -1,148 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}

12
vendor/github.com/tellytv/go.xtream-codes/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,12 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

View File

@@ -0,0 +1,33 @@
{
"Enable": [
"deadcode",
"errcheck",
"gochecknoinits",
"goconst",
"gofmt",
"goimports",
"golint",
"gosec",
"gotype",
"gotypex",
"ineffassign",
"interfacer",
"megacheck",
"misspell",
"nakedret",
"safesql",
"structcheck",
"test",
"testify",
"unconvert",
"unparam",
"varcheck",
"vet",
"vetshadow"
],
"Deadline": "5m",
"Sort": [
"path",
"linter"
]
}

21
vendor/github.com/tellytv/go.xtream-codes/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Telly
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.

2
vendor/github.com/tellytv/go.xtream-codes/README.md generated vendored Normal file
View File

@@ -0,0 +1,2 @@
# go.xtream-codes
A Go library for accessing Xtream-Codes servers

58
vendor/github.com/tellytv/go.xtream-codes/base64.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package xtreamcodes
// Package base64 provides a byte slice type that marshals into json as
// a raw (no padding) base64url value.
// Originally from https://github.com/manifoldco/go-base64
import (
"encoding/base64"
"errors"
"reflect"
)
// Base64Value is a base64url encoded json object,
type Base64Value []byte
// New returns a pointer to a Base64Value, cast from the given byte slice.
// This is a convenience function, handling the address creation that a direct
// cast would not allow.
func New(b []byte) *Base64Value {
v := Base64Value(b)
return &v
}
// NewFromString returns a Base64Value containing the decoded data in encoded.
func NewFromString(encoded string) (*Base64Value, error) {
out, err := base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
return nil, err
}
return New(out), nil
}
// MarshalJSON returns the ba64url encoding of bv for JSON representation.
func (bv *Base64Value) MarshalJSON() ([]byte, error) {
return []byte("\"" + base64.RawURLEncoding.EncodeToString(*bv) + "\""), nil
}
func (bv *Base64Value) String() string {
return base64.RawURLEncoding.EncodeToString(*bv)
}
// UnmarshalJSON sets bv to the bytes represented in the base64url encoding b.
func (bv *Base64Value) UnmarshalJSON(b []byte) error {
if len(b) < 2 || b[0] != byte('"') || b[len(b)-1] != byte('"') {
return errors.New("value is not a string")
}
out := make([]byte, base64.RawURLEncoding.DecodedLen(len(b)-2))
n, err := base64.RawURLEncoding.Decode(out, b[1:len(b)-1])
if err != nil {
return err
}
v := reflect.ValueOf(bv).Elem()
v.SetBytes(out[:n])
return nil
}

181
vendor/github.com/tellytv/go.xtream-codes/ffmpeg.go generated vendored Normal file
View File

@@ -0,0 +1,181 @@
// Copyright 2015 Dave Gradwell
// Under BSD-style license (see LICENSE file)
// Originally from https://github.com/xdave/ff
package xtreamcodes
import (
"encoding/json"
"strconv"
)
// abs is a quick integer function (math package doesn't use ints).
func abs(num int) int {
if num >= 0 {
return num
}
return -num
}
// Tags represents either Format or FFMPEGStreamInfo tags embedded in the file.
type Tags map[string]interface{}
// Format describes container format info.
type Format struct {
BitRate string `json:"bit_rate"`
Duration string `json:"duration"`
Filename string `json:"filename"`
LongName string `json:"format_long_name"`
Name string `json:"format_name"`
NumPrograms int `json:"nb_programs"`
NumStreams int `json:"nb_streams"`
ProbeScore int `json:"probe_score"`
Size string `json:"size"`
StartTime string `json:"start_time"`
Tags Tags `json:"tags"`
}
// Disposition of FFMPEGStreamInfo
type Disposition struct {
AttachedPic int `json:"attached_pic"`
CleanEffects int `json:"clean_effects"`
Comment int `json:"comment"`
Default int `json:"default"`
Dub int `json:"dub"`
Forced int `json:"forced"`
HearingImpaired int `json:"hearing_impaired"`
Karaoke int `json:"karaoke"`
Lyrics int `json:"lyrics"`
Original int `json:"original"`
VisualImpaired int `json:"visual_impaired"`
}
// SideData describes metadata. More fields may be added later as needed.
type SideData struct {
Displaymatrix string `json:"displaymatrix"`
Rotation int `json:"rotation"`
Size int `json:"side_data_size"`
Type string `json:"side_data_type"`
}
// StreamType is used for enumerating the CodecType field in FFMPEGStreamInfo.
type StreamType string
const (
// VideoStream is the constant for video streams.
VideoStream StreamType = "video"
// AudioStream is the constant for audio streams.
AudioStream = "audio"
)
// FFMPEGStreamInfo represents any kind of stream (Audio, Video, etc)
type FFMPEGStreamInfo struct {
AvgFrameRate string `json:"avg_frame_rate"`
BitRate string `json:"bit_rate"`
BitsPerRawSample string `json:"bits_per_raw_sample"`
BitsPerSample int `json:"bits_per_sample"`
ChannelLayout string `json:"channel_layout"`
Channels int `json:"channels"`
ChromaLocation string `json:"chroma_location"`
CodecLongName string `json:"codec_long_name"`
CodecName string `json:"codec_name"`
CodecTag string `json:"codec_tag"`
CodecTagString string `json:"codec_tag_string"`
CodecTimeBase string `json:"codec_time_base"`
CodecType StreamType `json:"codec_type"`
CodedHeight int `json:"coded_height"`
CodedWidth int `json:"coded_width"`
ColorPrimaries string `json:"color_primaries"`
ColorRange string `json:"color_range"`
ColorSpace string `json:"color_space"`
ColorTransfer string `json:"color_transfer"`
DisplayAspectRatio string `json:"display_aspect_ratio"`
DivxPacked string `json:"divx_packed"`
DmixMode string `json:"dmix_mode"`
Duration string `json:"duration"`
DurationTs int `json:"duration_ts"`
HasBFrames int `json:"has_b_frames"`
Height int `json:"height"`
ID string `json:"id"`
Index int `json:"index"`
IsAvc string `json:"is_avc"`
Level int `json:"level"`
LoroCmixlev string `json:"loro_cmixlev"`
LoroSurmixlev string `json:"loro_surmixlev"`
LtrtCmixlev string `json:"ltrt_cmixlev"`
LtrtSurmixlev string `json:"ltrt_surmixlev"`
MaxBitRate string `json:"max_bit_rate"`
NalLengthSize string `json:"nal_length_size"`
NumFrames string `json:"nb_frames"`
PixFmt string `json:"pix_fmt"`
Profile string `json:"profile"`
QuarterSample string `json:"quarter_sample"`
FrameRate string `json:"r_frame_rate"`
Refs int `json:"refs"`
SampleAspectRatio string `json:"sample_aspect_ratio"`
SampleFmt string `json:"sample_fmt"`
SampleRate string `json:"sample_rate"`
StartPts int `json:"start_pts"`
StartTime string `json:"start_time"`
TimeBase string `json:"time_base"`
Timecode string `json:"timecode"`
Width int `json:"width"`
Tags Tags `json:"tags"`
Disposition Disposition `json:"disposition"`
SideDataList []SideData `json:"side_data_list"`
}
// IsRotated returns true if the stream is rotated.
func (s FFMPEGStreamInfo) IsRotated() (bool, error) {
rot, err := s.Rotation()
return rot != 0, err
}
// Rotation gets the rotation value of video stream, either from stream side-data, or tags.
// Returns 0 if it's not rotated, or if we can't figure it out.
func (s FFMPEGStreamInfo) Rotation() (int, error) {
for _, sdata := range s.SideDataList {
return abs(sdata.Rotation), nil
}
rotationTag, ok := s.Tags["rotate"]
if ok {
rotation, ok := rotationTag.(string)
if ok {
val, valErr := strconv.Atoi(rotation)
if valErr != nil {
return 0, valErr
}
return val, nil
}
}
return 0, nil
}
// ProbeInfo is just a nice structure that represents the JSON data returned by ffprobe.
// Mostly auto-generated with http://mholt.github.io/json-to-go
type ProbeInfo struct {
Format Format `json:"format"`
Streams []FFMPEGStreamInfo `json:"streams"`
}
// NewInfo returns a new ProbeInfo structure from the input JSON
func NewInfo(jsonData string) (*ProbeInfo, error) {
info := new(ProbeInfo)
err := json.Unmarshal([]byte(jsonData), info)
if err != nil {
return nil, err
}
return info, nil
}
// FilterStreams filters out streams of the given StreamType.
// Returns a []FFMPEGStreamInfo slice, even if it's empty
func (info ProbeInfo) FilterStreams(t StreamType) []FFMPEGStreamInfo {
streams := []FFMPEGStreamInfo{}
for _, stream := range info.Streams {
if stream.CodecType == t {
streams = append(streams, stream)
}
}
return streams
}

306
vendor/github.com/tellytv/go.xtream-codes/structs.go generated vendored Normal file
View File

@@ -0,0 +1,306 @@
package xtreamcodes
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
)
// Timestamp is a helper struct to convert unix timestamp ints and strings to time.Time.
type Timestamp struct {
time.Time
quoted bool
}
// MarshalJSON returns the Unix timestamp as a string.
func (t Timestamp) MarshalJSON() ([]byte, error) {
if t.quoted {
return []byte(`"` + strconv.FormatInt(t.Time.Unix(), 10) + `"`), nil
}
return []byte(strconv.FormatInt(t.Time.Unix(), 10)), nil
}
// UnmarshalJSON converts the int or string to a Unix timestamp.
func (t *Timestamp) UnmarshalJSON(b []byte) error {
// Timestamps are sometimes quoted, sometimes not, lets just always remove quotes just in case...
t.quoted = strings.Contains(string(b), `"`)
ts, err := strconv.Atoi(strings.Replace(string(b), `"`, "", -1))
if err != nil {
return err
}
t.Time = time.Unix(int64(ts), 0)
return nil
}
// ConvertibleBoolean is a helper type to allow JSON documents using 0/1 or "true" and "false" be converted to bool.
type ConvertibleBoolean struct {
bool
quoted bool
}
// MarshalJSON returns a 0 or 1 depending on bool state.
func (bit ConvertibleBoolean) MarshalJSON() ([]byte, error) {
var bitSetVar int8
if bit.bool {
bitSetVar = 1
}
if bit.quoted {
return json.Marshal(fmt.Sprint(bitSetVar))
}
return json.Marshal(bitSetVar)
}
// UnmarshalJSON converts a 0, 1, true or false into a bool
func (bit *ConvertibleBoolean) UnmarshalJSON(data []byte) error {
bit.quoted = strings.Contains(string(data), `"`)
// Bools as ints are sometimes quoted, sometimes not, lets just always remove quotes just in case...
asString := strings.Replace(string(data), `"`, "", -1)
if asString == "1" || asString == "true" {
bit.bool = true
} else if asString == "0" || asString == "false" {
bit.bool = false
} else {
return fmt.Errorf("Boolean unmarshal error: invalid input %s", asString)
}
return nil
}
// jsonInt is a int64 which unmarshals from JSON
// as either unquoted or quoted (with any amount
// of internal leading/trailing whitespace).
// Originally found at https://bit.ly/2NkJ0SK and
// https://play.golang.org/p/KNPxDL1yqL
type jsonInt int64
func (f jsonInt) MarshalJSON() ([]byte, error) {
return json.Marshal(int64(f))
}
func (f *jsonInt) UnmarshalJSON(data []byte) error {
var v int64
data = bytes.Trim(data, `" `)
err := json.Unmarshal(data, &v)
*f = jsonInt(v)
return err
}
// ServerInfo describes the state of the Xtream-Codes server.
type ServerInfo struct {
HTTPSPort int `json:"https_port,string"`
Port int `json:"port,string"`
Process bool `json:"process"`
RTMPPort int `json:"rtmp_port,string"`
Protocol string `json:"server_protocol"`
TimeNow string `json:"time_now"`
TimestampNow Timestamp `json:"timestamp_now,string"`
Timezone string `json:"timezone"`
URL string `json:"url"`
}
// UserInfo is the current state of the user as it relates to the Xtream-Codes server.
type UserInfo struct {
ActiveConnections int `json:"active_cons,string"`
AllowedOutputFormats []string `json:"allowed_output_formats"`
Auth ConvertibleBoolean `json:"auth"`
CreatedAt Timestamp `json:"created_at"`
ExpDate *Timestamp `json:"exp_date"`
IsTrial ConvertibleBoolean `json:"is_trial,string"`
MaxConnections int `json:"max_connections,string"`
Message string `json:"message"`
Password string `json:"password"`
Status string `json:"status"`
Username string `json:"username"`
}
// AuthenticationResponse is a container for what the server returns after the initial authentication.
type AuthenticationResponse struct {
ServerInfo ServerInfo `json:"server_info"`
UserInfo UserInfo `json:"user_info"`
}
// Category describes a grouping of Stream.
type Category struct {
ID int `json:"category_id,string"`
Name string `json:"category_name"`
Parent int `json:"parent_id"`
// Set by us, not Xtream.
Type string `json:"-"`
}
// Stream is a streamble video source.
type Stream struct {
Added *Timestamp `json:"added"`
CategoryID int `json:"category_id,string"`
ContainerExtension string `json:"container_extension"`
CustomSid string `json:"custom_sid"`
DirectSource string `json:"direct_source,omitempty"`
EPGChannelID string `json:"epg_channel_id"`
Icon string `json:"stream_icon"`
ID int `json:"stream_id"`
Name string `json:"name"`
Number int `json:"num"`
Rating FlexFloat `json:"rating"`
Rating5based float64 `json:"rating_5based"`
TVArchive int `json:"tv_archive"`
TVArchiveDuration *jsonInt `json:"tv_archive_duration"`
Type string `json:"stream_type"`
}
type FlexFloat float64
func (ff *FlexFloat) UnmarshalJSON(b []byte) error {
if b[0] != '"' {
return json.Unmarshal(b, (*float64)(ff))
}
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
if len(s) == 0 {
s = "0"
}
f, err := strconv.ParseFloat(s, 64)
if err != nil {
f = 0
}
*ff = FlexFloat(f)
return nil
}
// SeriesInfo contains information about a TV series.
type SeriesInfo struct {
BackdropPath *JSONStringSlice `json:"backdrop_path,omitempty"`
Cast string `json:"cast"`
CategoryID *int `json:"category_id,string"`
Cover string `json:"cover"`
Director string `json:"director"`
EpisodeRunTime string `json:"episode_run_time"`
Genre string `json:"genre"`
LastModified *Timestamp `json:"last_modified,omitempty"`
Name string `json:"name"`
Num int `json:"num"`
Plot string `json:"plot"`
Rating int `json:"rating,string"`
Rating5 float64 `json:"rating_5based"`
ReleaseDate string `json:"releaseDate"`
SeriesID int `json:"series_id"`
StreamType string `json:"stream_type"`
YoutubeTrailer string `json:"youtube_trailer"`
}
type SeriesEpisode struct {
Added string `json:"added"`
ContainerExtension string `json:"container_extension"`
CustomSid string `json:"custom_sid"`
DirectSource string `json:"direct_source"`
EpisodeNum int `json:"episode_num"`
ID string `json:"id"`
Info struct {
Audio FFMPEGStreamInfo `json:"audio"`
Bitrate int `json:"bitrate"`
Duration string `json:"duration"`
DurationSecs int `json:"duration_secs"`
MovieImage string `json:"movie_image"`
Name string `json:"name"`
Plot string `json:"plot"`
Rating FlexFloat `json:"rating"`
ReleaseDate string `json:"releasedate"`
Video FFMPEGStreamInfo `json:"video"`
} `json:"info"`
Season int `json:"season"`
Title string `json:"title"`
}
type Series struct {
Episodes map[string][]SeriesEpisode `json:"episodes"`
Info SeriesInfo `json:"info"`
Seasons []interface{} `json:"seasons"`
}
// VideoOnDemandInfo contains information about a video on demand stream.
type VideoOnDemandInfo struct {
Info struct {
Audio FFMPEGStreamInfo `json:"audio"`
BackdropPath []string `json:"backdrop_path"`
Bitrate int `json:"bitrate"`
Cast string `json:"cast"`
Director string `json:"director"`
Duration string `json:"duration"`
DurationSecs int `json:"duration_secs"`
Genre string `json:"genre"`
MovieImage string `json:"movie_image"`
Plot string `json:"plot"`
Rating string `json:"rating"`
ReleaseDate string `json:"releasedate"`
TmdbID string `json:"tmdb_id"`
Video FFMPEGStreamInfo `json:"video"`
YoutubeTrailer string `json:"youtube_trailer"`
} `json:"info"`
MovieData struct {
Added Timestamp `json:"added"`
CategoryID int `json:"category_id,string"`
ContainerExtension string `json:"container_extension"`
CustomSid string `json:"custom_sid"`
DirectSource string `json:"direct_source"`
Name string `json:"name"`
StreamID int `json:"stream_id"`
} `json:"movie_data"`
}
type epgContainer struct {
EPGListings []EPGInfo `json:"epg_listings"`
}
// EPGInfo describes electronic programming guide information of a stream.
type EPGInfo struct {
ChannelID string `json:"channel_id"`
Description Base64Value `json:"description"`
End string `json:"end"`
EPGID int `json:"epg_id,string"`
HasArchive ConvertibleBoolean `json:"has_archive"`
ID int `json:"id,string"`
Lang string `json:"lang"`
NowPlaying ConvertibleBoolean `json:"now_playing"`
Start string `json:"start"`
StartTimestamp Timestamp `json:"start_timestamp"`
StopTimestamp Timestamp `json:"stop_timestamp"`
Title Base64Value `json:"title"`
}
// JSONStringSlice is a struct containing a slice of strings.
// It is needed for cases in which we may get an array or may get
// a single string in a JSON response.
type JSONStringSlice struct {
Slice []string `json:"-"`
SingleString bool `json:"-"`
}
// MarshalJSON returns b as the JSON encoding of b.
func (b JSONStringSlice) MarshalJSON() ([]byte, error) {
if !b.SingleString {
return json.Marshal(b.Slice)
}
return json.Marshal(b.Slice[0])
}
// UnmarshalJSON sets *b to a copy of data.
func (b *JSONStringSlice) UnmarshalJSON(data []byte) error {
if data[0] == '"' {
data = append([]byte(`[`), data...)
data = append(data, []byte(`]`)...)
b.SingleString = true
}
return json.Unmarshal(data, &b.Slice)
}

View File

@@ -0,0 +1,316 @@
// Package xtreamcodes provides a Golang interface to the Xtream-Codes IPTV Server API.
package xtreamcodes
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
)
var userAgent = "go.xstream-codes (Go-http-client/1.1)"
// XtreamClient is the client used to communicate with a Xtream-Codes server.
type XtreamClient struct {
Username string
Password string
BaseURL string
ServerInfo ServerInfo
UserInfo UserInfo
// Our HTTP client to communicate with Xtream
HTTP *http.Client
// We store an internal map of Streams for use with GetStreamURL
streams map[int]Stream
}
// NewClient returns an initialized XtreamClient with the given values.
func NewClient(username, password, baseURL string) (*XtreamClient, error) {
_, parseURLErr := url.Parse(baseURL)
if parseURLErr != nil {
return nil, fmt.Errorf("error parsing url: %s", parseURLErr.Error())
}
client := &XtreamClient{
Username: username,
Password: password,
BaseURL: baseURL,
HTTP: http.DefaultClient,
streams: make(map[int]Stream),
}
authData, authErr := client.sendRequest("", nil)
if authErr != nil {
return nil, fmt.Errorf("error sending authentication request: %s", authErr.Error())
}
a := &AuthenticationResponse{}
if jsonErr := json.Unmarshal(authData, &a); jsonErr != nil {
return nil, fmt.Errorf("error unmarshaling json: %s", jsonErr.Error())
}
client.ServerInfo = a.ServerInfo
client.UserInfo = a.UserInfo
return client, nil
}
// GetStreamURL will return a stream URL string for the given streamID and wantedFormat.
func (c *XtreamClient) GetStreamURL(streamID int, wantedFormat string) (string, error) {
// For Live Streams the main format is
// http(s)://domain:port/live/username/password/streamID.ext ( In allowed_output_formats element you have the available ext )
// For VOD Streams the format is:
// http(s)://domain:port/movie/username/password/streamID.ext ( In target_container element you have the available ext )
// For Series Streams the format is
// http(s)://domain:port/series/username/password/streamID.ext ( In target_container element you have the available ext )
validFormat := false
for _, allowedFormat := range c.UserInfo.AllowedOutputFormats {
if wantedFormat == allowedFormat {
validFormat = true
}
}
if !validFormat {
return "", fmt.Errorf("%s is not an allowed output format", wantedFormat)
}
if _, ok := c.streams[streamID]; !ok {
return "", fmt.Errorf("%d is not a valid stream id", streamID)
}
stream := c.streams[streamID]
return fmt.Sprintf("%s/%s/%s/%s/%d.%s", c.BaseURL, stream.Type, c.Username, c.Password, stream.ID, wantedFormat), nil
}
// GetLiveCategories will return a slice of categories for live streams.
func (c *XtreamClient) GetLiveCategories() ([]Category, error) {
return c.GetCategories("live")
}
// GetVideoOnDemandCategories will return a slice of categories for VOD streams.
func (c *XtreamClient) GetVideoOnDemandCategories() ([]Category, error) {
return c.GetCategories("vod")
}
// GetSeriesCategories will return a slice of categories for series streams.
func (c *XtreamClient) GetSeriesCategories() ([]Category, error) {
return c.GetCategories("series")
}
// GetCategories is a helper function used by GetLiveCategories, GetVideoOnDemandCategories and
// GetSeriesCategories to reduce duplicate code.
func (c *XtreamClient) GetCategories(catType string) ([]Category, error) {
catData, catErr := c.sendRequest(fmt.Sprintf("get_%s_categories", catType), nil)
if catErr != nil {
return nil, catErr
}
cats := make([]Category, 0)
jsonErr := json.Unmarshal(catData, &cats)
for idx := range cats {
cats[idx].Type = catType
}
return cats, jsonErr
}
// GetLiveStreams will return a slice of live streams.
// You can also optionally provide a categoryID to limit the output to members of that category.
func (c *XtreamClient) GetLiveStreams(categoryID string) ([]Stream, error) {
return c.GetStreams("live", categoryID)
}
// GetVideoOnDemandStreams will return a slice of VOD streams.
// You can also optionally provide a categoryID to limit the output to members of that category.
func (c *XtreamClient) GetVideoOnDemandStreams(categoryID string) ([]Stream, error) {
return c.GetStreams("vod", categoryID)
}
// GetStreams is a helper function used by GetLiveStreams and GetVideoOnDemandStreams
// to reduce duplicate code.
func (c *XtreamClient) GetStreams(streamAction, categoryID string) ([]Stream, error) {
var params url.Values
if categoryID != "" {
params = url.Values{}
params.Add("category_id", categoryID)
}
// For whatever reason, unlike live and vod, series streams action doesn't have "_streams".
if streamAction != "series" {
streamAction = fmt.Sprintf("%s_streams", streamAction)
}
streamData, streamErr := c.sendRequest(fmt.Sprintf("get_%s", streamAction), params)
if streamErr != nil {
return nil, streamErr
}
streams := make([]Stream, 0)
if jsonErr := json.Unmarshal(streamData, &streams); jsonErr != nil {
return nil, jsonErr
}
for _, stream := range streams {
c.streams[stream.ID] = stream
}
return streams, nil
}
// GetSeries will return a slice of all available Series.
// You can also optionally provide a categoryID to limit the output to members of that category.
func (c *XtreamClient) GetSeries(categoryID string) ([]SeriesInfo, error) {
var params url.Values
if categoryID != "" {
params = url.Values{}
params.Add("category_id", categoryID)
}
seriesData, seriesErr := c.sendRequest("get_series", params)
if seriesErr != nil {
return nil, seriesErr
}
seriesInfos := make([]SeriesInfo, 0)
if jsonErr := json.Unmarshal(seriesData, &seriesInfos); jsonErr != nil {
return nil, jsonErr
}
return seriesInfos, nil
}
// GetSeriesInfo will return a series info for the given seriesID.
func (c *XtreamClient) GetSeriesInfo(seriesID string) (*Series, error) {
if seriesID == "" {
return nil, fmt.Errorf("series ID can not be empty")
}
seriesData, seriesErr := c.sendRequest("get_series_info", url.Values{"series_id": []string{seriesID}})
if seriesErr != nil {
return nil, seriesErr
}
seriesInfo := &Series{}
jsonErr := json.Unmarshal(seriesData, &seriesInfo)
return seriesInfo, jsonErr
}
// GetVideoOnDemandInfo will return VOD info for the given vodID.
func (c *XtreamClient) GetVideoOnDemandInfo(vodID string) (*VideoOnDemandInfo, error) {
if vodID == "" {
return nil, fmt.Errorf("vod ID can not be empty")
}
vodData, vodErr := c.sendRequest("get_vod_info", url.Values{"vod_id": []string{vodID}})
if vodErr != nil {
return nil, vodErr
}
vodInfo := &VideoOnDemandInfo{}
jsonErr := json.Unmarshal(vodData, &vodInfo)
return vodInfo, jsonErr
}
// GetShortEPG returns a short version of the EPG for the given streamID. If no limit is provided, the next 4 items in the EPG will be returned.
func (c *XtreamClient) GetShortEPG(streamID string, limit int) ([]EPGInfo, error) {
return c.getEPG("get_short_epg", streamID, limit)
}
// GetEPG returns the full EPG for the given streamID.
func (c *XtreamClient) GetEPG(streamID string) ([]EPGInfo, error) {
return c.getEPG("get_simple_data_table", streamID, 0)
}
// GetXMLTV will return a slice of bytes for the XMLTV EPG file available from the provider.
func (c *XtreamClient) GetXMLTV() ([]byte, error) {
xmlTVData, xmlTVErr := c.sendRequest("xmltv.php", nil)
if xmlTVErr != nil {
return nil, xmlTVErr
}
return xmlTVData, xmlTVErr
}
func (c *XtreamClient) getEPG(action, streamID string, limit int) ([]EPGInfo, error) {
if streamID == "" {
return nil, fmt.Errorf("stream ID can not be empty")
}
params := url.Values{"stream_id": []string{streamID}}
if limit > 0 {
params.Add("limit", strconv.Itoa(limit))
}
epgData, epgErr := c.sendRequest(action, params)
if epgErr != nil {
return nil, epgErr
}
epgContainer := &epgContainer{}
jsonErr := json.Unmarshal(epgData, &epgContainer)
return epgContainer.EPGListings, jsonErr
}
func (c *XtreamClient) sendRequest(action string, parameters url.Values) ([]byte, error) {
file := "player_api.php"
if action == "xmltv.php" {
file = action
}
url := fmt.Sprintf("%s/%s?username=%s&password=%s", c.BaseURL, file, c.Username, c.Password)
if action != "" {
url = fmt.Sprintf("%s&action=%s", url, action)
}
if parameters != nil {
url = fmt.Sprintf("%s&%s", url, parameters.Encode())
}
request, httpErr := http.NewRequest("GET", url, nil)
if httpErr != nil {
return nil, httpErr
}
request.Header.Set("User-Agent", userAgent)
response, httpErr := c.HTTP.Do(request)
if httpErr != nil {
return nil, fmt.Errorf("cannot reach server. %v", httpErr)
}
if response.StatusCode > 399 {
return nil, fmt.Errorf("status code was %d, expected 2XX-3XX", response.StatusCode)
}
buf := &bytes.Buffer{}
if _, copyErr := io.Copy(buf, response.Body); copyErr != nil {
return nil, copyErr
}
if closeErr := response.Body.Close(); closeErr != nil {
return nil, fmt.Errorf("cannot read response. %v", closeErr)
}
return buf.Bytes(), nil
}

4
vendor/modules.txt vendored
View File

@@ -1,5 +1,3 @@
# github.com/davecgh/go-spew v1.1.1
github.com/davecgh/go-spew/spew
# github.com/fsnotify/fsnotify v1.4.7
github.com/fsnotify/fsnotify
# github.com/gin-contrib/cors v0.0.0-20190226021855-50921afdc5c1
@@ -57,6 +55,8 @@ github.com/spf13/jwalterweatherman
github.com/spf13/pflag
# github.com/spf13/viper v1.3.1
github.com/spf13/viper
# github.com/tellytv/go.xtream-codes v0.0.0-20190114013623-9b74dcb500e4
github.com/tellytv/go.xtream-codes
# github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2
github.com/ugorji/go/codec
# golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb