Adding Download options (#2)
* Adding crowdin branch support Signed-off-by: Jonas Franz <info@jonasfranz.de> * Adding documentation for ignore_branche Signed-off-by: Jonas Franz <info@jonasfranz.de> * Adding Download functionality Adding Unzip utility Adding Download options to DOCS.md Signed-off-by: Jonas Franz <info@jonasfranz.de> * Fixing tests Signed-off-by: Jonas Franz <info@jonasfranz.de> * Fixing tests Signed-off-by: Jonas Franz <info@jonasfranz.de> * Fixing URLs Signed-off-by: Jonas Franz <info@jonasfranz.de> * Adding unit tests Signed-off-by: Jonas Franz <info@jonasfranz.de> * Adding unit tests Signed-off-by: Jonas Franz <info@jonasfranz.de> * Adding unit tests Signed-off-by: Jonas Franz <info@jonasfranz.de> * Removing fmt.Println debug Signed-off-by: Jonas Franz <info@jonasfranz.de>pull/3/head
parent
39fb5cfbe0
commit
77f8b6e9d9
@ -1,11 +1,42 @@ |
||||
package main |
||||
|
||||
import "testing" |
||||
import ( |
||||
"github.com/stretchr/testify/assert" |
||||
"testing" |
||||
) |
||||
|
||||
func TestConfig_ToURL(t *testing.T) { |
||||
exampleConfig := &Config{Identifier: "test", Key: "MYKEY"} |
||||
result := exampleConfig.ToURL() |
||||
if result != "https://api.crowdin.com/api/project/test/update-file?key=MYKEY" { |
||||
t.Fatalf("ToURL returns \"%s\" instead of the expected \"%s\"", result, "https://api.crowdin.com/api/project/test/update-file?key=MYKEY") |
||||
func setupExamplePlugin() *Plugin { |
||||
return &Plugin{ |
||||
ExportDirectory: "demo/", |
||||
Languages: []string{"all"}, |
||||
DoDownload: true, |
||||
Files: map[string]string{"locale_en-US.ini": "LICENSE"}, |
||||
Config: Config{ |
||||
Key: "MYKEY", |
||||
Identifier: "test", |
||||
}, |
||||
Branch: "master", |
||||
} |
||||
} |
||||
|
||||
func TestConfig_ToUploadURL(t *testing.T) { |
||||
exampleConfig := setupExamplePlugin().Config |
||||
result := exampleConfig.ToUploadURL() |
||||
assert.Equal(t, "https://api.crowdin.com/api/project/test/update-file?key=MYKEY", result, "ToUploadURL") |
||||
} |
||||
|
||||
func TestPlugin_ToLanguageDownloadURL(t *testing.T) { |
||||
examplePlugin := setupExamplePlugin() |
||||
result := examplePlugin.ToLanguageDownloadURL(examplePlugin.Languages[0]) |
||||
assert.Equal(t, "https://api.crowdin.com/api/project/test/download/all.zip?key=MYKEY&branch=master", result, "ToLanguageDownloadURL") |
||||
|
||||
examplePlugin.Branch = "" |
||||
result = examplePlugin.ToLanguageDownloadURL(examplePlugin.Languages[0]) |
||||
assert.Equal(t, "https://api.crowdin.com/api/project/test/download/all.zip?key=MYKEY", result, "ToLanguageDownloadURL") |
||||
} |
||||
|
||||
func TestConfig_ToProjectURL(t *testing.T) { |
||||
exampleConfig := setupExamplePlugin().Config |
||||
result := exampleConfig.ToProjectURL() |
||||
assert.Equal(t, "https://api.crowdin.com/api/project/test", result, "ToProjectURL") |
||||
} |
||||
|
@ -0,0 +1,61 @@ |
||||
package responses |
||||
|
||||
import ( |
||||
"bytes" |
||||
"github.com/stretchr/testify/assert" |
||||
"testing" |
||||
) |
||||
|
||||
var errorData = `<?xml version="1.0" encoding="ISO-8859-1"?> |
||||
<error> |
||||
<code>3</code> |
||||
<message>API key is not valid</message> |
||||
</error> |
||||
` |
||||
var invalidErrorData = `<?xml version="1.0" encoding="ISO-fdsfsdf-1"?> |
||||
<error> |
||||
<code>3</code> |
||||
<message>API key is not valid</message> |
||||
</error> |
||||
` |
||||
|
||||
var successData = `<?xml version="1.0" encoding="ISO-8859-1"?> |
||||
<success> |
||||
<stats> |
||||
<file status="skipped" name="demo.ini"></file> |
||||
</stats> |
||||
</success>` |
||||
var invalidSuccessData = `<?xml version="1.0" encoding="ISO-sefsgdfb-1"?> |
||||
<success> |
||||
<stats> |
||||
<file status="skipped" name="demo.ini"></file> |
||||
</stats> |
||||
</success>` |
||||
|
||||
func TestParseAsError(t *testing.T) { |
||||
result, err := ParseAsError(bytes.NewBufferString(errorData)) |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, 3, result.Code, "error code") |
||||
assert.Equal(t, "API key is not valid", result.Message, "error message") |
||||
|
||||
_, err = ParseAsSuccess(bytes.NewBufferString(invalidErrorData)) |
||||
assert.Error(t, err) |
||||
} |
||||
|
||||
func TestError_Error(t *testing.T) { |
||||
result, err := ParseAsError(bytes.NewBufferString(errorData)) |
||||
assert.NoError(t, err) |
||||
assert.Error(t, result) |
||||
assert.Equal(t, result.Error(), "Error from crowdin: API key is not valid (error code 3)", "error message") |
||||
} |
||||
|
||||
func TestParseAsSuccess(t *testing.T) { |
||||
result, err := ParseAsSuccess(bytes.NewBufferString(successData)) |
||||
assert.NoError(t, err) |
||||
assert.Len(t, result.Stats, 1, "files") |
||||
assert.Equal(t, result.Stats[0].Status, "skipped", "status of first file") |
||||
assert.Equal(t, result.Stats[0].Name, "demo.ini", "name of first file") |
||||
|
||||
_, err = ParseAsError(bytes.NewBufferString(invalidSuccessData)) |
||||
assert.Error(t, err) |
||||
} |
@ -0,0 +1,67 @@ |
||||
package utils |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"io" |
||||
"os" |
||||
"path/filepath" |
||||
) |
||||
|
||||
func Unzip(src, dest string) error { |
||||
r, err := zip.OpenReader(src) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer func() { |
||||
if err := r.Close(); err != nil { |
||||
panic(err) |
||||
} |
||||
}() |
||||
|
||||
os.MkdirAll(dest, 0755) |
||||
|
||||
// Closure to address file descriptors issue with all the deferred .Close() methods
|
||||
extractAndWriteFile := func(f *zip.File) error { |
||||
rc, err := f.Open() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer func() { |
||||
if err := rc.Close(); err != nil { |
||||
panic(err) |
||||
} |
||||
}() |
||||
|
||||
path := filepath.Join(dest, f.Name) |
||||
|
||||
if f.FileInfo().IsDir() { |
||||
os.MkdirAll(path, f.Mode()) |
||||
} else { |
||||
os.MkdirAll(filepath.Dir(path), f.Mode()) |
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer func() { |
||||
if err := f.Close(); err != nil { |
||||
panic(err) |
||||
} |
||||
}() |
||||
|
||||
_, err = io.Copy(f, rc) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
for _, f := range r.File { |
||||
err := extractAndWriteFile(f) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,15 @@ |
||||
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. |
@ -0,0 +1,152 @@ |
||||
// 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.
|
||||
// +build !js,!appengine,!safe,!disableunsafe
|
||||
|
||||
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)) |
||||
) |
||||
|
||||
var ( |
||||
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
||||
// internal reflect.Value fields. These values are valid before golang
|
||||
// commit ecccf07e7f9d which changed the format. The are also valid
|
||||
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||
// the original format. Code in the init function updates these offsets
|
||||
// as necessary.
|
||||
offsetPtr = ptrSize |
||||
offsetScalar = uintptr(0) |
||||
offsetFlag = ptrSize * 2 |
||||
|
||||
// flagKindWidth and flagKindShift indicate various bits that the
|
||||
// reflect package uses internally to track kind information.
|
||||
//
|
||||
// flagRO indicates whether or not the value field of a reflect.Value is
|
||||
// read-only.
|
||||
//
|
||||
// flagIndir indicates whether the value field of a reflect.Value is
|
||||
// the actual data or a pointer to the data.
|
||||
//
|
||||
// These values are valid before golang commit 90a7c3c86944 which
|
||||
// changed their positions. Code in the init function updates these
|
||||
// flags as necessary.
|
||||
flagKindWidth = uintptr(5) |
||||
flagKindShift = flagKindWidth - 1 |
||||
flagRO = uintptr(1 << 0) |
||||
flagIndir = uintptr(1 << 1) |
||||
) |
||||
|
||||
func init() { |
||||
// Older versions of reflect.Value stored small integers directly in the
|
||||
// ptr field (which is named val in the older versions). Versions
|
||||
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
||||
// scalar for this purpose which unfortunately came before the flag
|
||||
// field, so the offset of the flag field is different for those
|
||||
// versions.
|
||||
//
|
||||
// This code constructs a new reflect.Value from a known small integer
|
||||
// and checks if the size of the reflect.Value struct indicates it has
|
||||
// the scalar field. When it does, the offsets are updated accordingly.
|
||||
vv := reflect.ValueOf(0xf00) |
||||
if unsafe.Sizeof(vv) == (ptrSize * 4) { |
||||
offsetScalar = ptrSize * 2 |
||||
offsetFlag = ptrSize * 3 |
||||
} |
||||
|
||||
// Commit 90a7c3c86944 changed the flag positions such that the low
|
||||
// order bits are the kind. This code extracts the kind from the flags
|
||||
// field and ensures it's the correct type. When it's not, the flag
|
||||
// order has been changed to the newer format, so the flags are updated
|
||||
// accordingly.
|
||||
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) |
||||
upfv := *(*uintptr)(upf) |
||||
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift) |
||||
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) { |
||||
flagKindShift = 0 |
||||
flagRO = 1 << 5 |
||||
flagIndir = 1 << 6 |
||||
|
||||
// Commit adf9b30e5594 modified the flags to separate the
|
||||
// flagRO flag into two bits which specifies whether or not the
|
||||
// field is embedded. This causes flagIndir to move over a bit
|
||||
// and means that flagRO is the combination of either of the
|
||||
// original flagRO bit and the new bit.
|
||||
//
|
||||
// This code detects the change by extracting what used to be
|
||||
// the indirect bit to ensure it's set. When it's not, the flag
|
||||
// order has been changed to the newer format, so the flags are
|
||||
// updated accordingly.
|
||||
if upfv&flagIndir == 0 { |
||||
flagRO = 3 << 5 |
||||
flagIndir = 1 << 7 |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 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) (rv reflect.Value) { |
||||
indirects := 1 |
||||
vt := v.Type() |
||||
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) |
||||
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) |
||||
if rvf&flagIndir != 0 { |
||||
vt = reflect.PtrTo(v.Type()) |
||||
indirects++ |
||||
} else if offsetScalar != 0 { |
||||
// The value is in the scalar field when it's not one of the
|
||||
// reference types.
|
||||
switch vt.Kind() { |
||||
case reflect.Uintptr: |
||||
case reflect.Chan: |
||||
case reflect.Func: |
||||
case reflect.Map: |
||||
case reflect.Ptr: |
||||
case reflect.UnsafePointer: |
||||
default: |
||||
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + |
||||
offsetScalar) |
||||
} |
||||
} |
||||
|
||||
pv := reflect.NewAt(vt, upv) |
||||
rv = pv |
||||
for i := 0; i < indirects; i++ { |
||||
rv = rv.Elem() |
||||
} |
||||
return rv |
||||
} |
@ -0,0 +1,38 @@ |
||||
// 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
|
||||
|
||||
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 |
||||
} |
@ -0,0 +1,341 @@ |
||||
/* |
||||
* 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)) |
||||
} |
@ -0,0 +1,306 @@ |
||||
/* |
||||
* 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: " "} |
||||
} |