@ -1,254 +0,0 @@ |
||||
package cl |
||||
|
||||
import ( |
||||
"math/rand" |
||||
"reflect" |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
var kernelSource = ` |
||||
__kernel void square( |
||||
__global float* input, |
||||
__global float* output, |
||||
const unsigned int count) |
||||
{ |
||||
int i = get_global_id(0); |
||||
if(i < count) |
||||
output[i] = input[i] * input[i]; |
||||
} |
||||
` |
||||
|
||||
func getObjectStrings(object interface{}) map[string]string { |
||||
v := reflect.ValueOf(object) |
||||
t := reflect.TypeOf(object) |
||||
|
||||
strs := make(map[string]string) |
||||
|
||||
numMethods := t.NumMethod() |
||||
for i := 0; i < numMethods; i++ { |
||||
method := t.Method(i) |
||||
if method.Type.NumIn() == 1 && method.Type.NumOut() == 1 && method.Type.Out(0).Kind() == reflect.String { |
||||
// this is a string-returning method with (presumably) only a pointer receiver parameter
|
||||
// call it
|
||||
outs := v.Method(i).Call([]reflect.Value{}) |
||||
// put the result in our map
|
||||
strs[method.Name] = (outs[0].Interface()).(string) |
||||
} |
||||
} |
||||
|
||||
return strs |
||||
} |
||||
|
||||
func TestPlatformStringsContainNoNULs(t *testing.T) { |
||||
platforms, err := GetPlatforms() |
||||
if err != nil { |
||||
t.Fatalf("Failed to get platforms: %+v", err) |
||||
} |
||||
|
||||
for _, p := range platforms { |
||||
for key, value := range getObjectStrings(p) { |
||||
if strings.Contains(value, "\x00") { |
||||
t.Fatalf("platform string %q = %+q contains NUL", key, value) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestDeviceStringsContainNoNULs(t *testing.T) { |
||||
platforms, err := GetPlatforms() |
||||
if err != nil { |
||||
t.Fatalf("Failed to get platforms: %+v", err) |
||||
} |
||||
|
||||
for _, p := range platforms { |
||||
devs, err := p.GetDevices(DeviceTypeAll) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get devices for platform %q: %+v", p.Name(), err) |
||||
} |
||||
|
||||
for _, d := range devs { |
||||
for key, value := range getObjectStrings(d) { |
||||
if strings.Contains(value, "\x00") { |
||||
t.Fatalf("device string %q = %+q contains NUL", key, value) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestHello(t *testing.T) { |
||||
var data [1024]float32 |
||||
for i := 0; i < len(data); i++ { |
||||
data[i] = rand.Float32() |
||||
} |
||||
|
||||
platforms, err := GetPlatforms() |
||||
if err != nil { |
||||
t.Fatalf("Failed to get platforms: %+v", err) |
||||
} |
||||
for i, p := range platforms { |
||||
t.Logf("Platform %d:", i) |
||||
t.Logf(" Name: %s", p.Name()) |
||||
t.Logf(" Vendor: %s", p.Vendor()) |
||||
t.Logf(" Profile: %s", p.Profile()) |
||||
t.Logf(" Version: %s", p.Version()) |
||||
t.Logf(" Extensions: %s", p.Extensions()) |
||||
} |
||||
platform := platforms[0] |
||||
|
||||
devices, err := platform.GetDevices(DeviceTypeAll) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get devices: %+v", err) |
||||
} |
||||
if len(devices) == 0 { |
||||
t.Fatalf("GetDevices returned no devices") |
||||
} |
||||
deviceIndex := -1 |
||||
for i, d := range devices { |
||||
if deviceIndex < 0 && d.Type() == DeviceTypeGPU { |
||||
deviceIndex = i |
||||
} |
||||
t.Logf("Device %d (%s): %s", i, d.Type(), d.Name()) |
||||
t.Logf(" Address Bits: %d", d.AddressBits()) |
||||
t.Logf(" Available: %+v", d.Available()) |
||||
// t.Logf(" Built-In Kernels: %s", d.BuiltInKernels())
|
||||
t.Logf(" Compiler Available: %+v", d.CompilerAvailable()) |
||||
t.Logf(" Double FP Config: %s", d.DoubleFPConfig()) |
||||
t.Logf(" Driver Version: %s", d.DriverVersion()) |
||||
t.Logf(" Error Correction Supported: %+v", d.ErrorCorrectionSupport()) |
||||
t.Logf(" Execution Capabilities: %s", d.ExecutionCapabilities()) |
||||
t.Logf(" Extensions: %s", d.Extensions()) |
||||
t.Logf(" Global Memory Cache Type: %s", d.GlobalMemCacheType()) |
||||
t.Logf(" Global Memory Cacheline Size: %d KB", d.GlobalMemCachelineSize()/1024) |
||||
t.Logf(" Global Memory Size: %d MB", d.GlobalMemSize()/(1024*1024)) |
||||
t.Logf(" Half FP Config: %s", d.HalfFPConfig()) |
||||
t.Logf(" Host Unified Memory: %+v", d.HostUnifiedMemory()) |
||||
t.Logf(" Image Support: %+v", d.ImageSupport()) |
||||
t.Logf(" Image2D Max Dimensions: %d x %d", d.Image2DMaxWidth(), d.Image2DMaxHeight()) |
||||
t.Logf(" Image3D Max Dimenionns: %d x %d x %d", d.Image3DMaxWidth(), d.Image3DMaxHeight(), d.Image3DMaxDepth()) |
||||
// t.Logf(" Image Max Buffer Size: %d", d.ImageMaxBufferSize())
|
||||
// t.Logf(" Image Max Array Size: %d", d.ImageMaxArraySize())
|
||||
// t.Logf(" Linker Available: %+v", d.LinkerAvailable())
|
||||
t.Logf(" Little Endian: %+v", d.EndianLittle()) |
||||
t.Logf(" Local Mem Size Size: %d KB", d.LocalMemSize()/1024) |
||||
t.Logf(" Local Mem Type: %s", d.LocalMemType()) |
||||
t.Logf(" Max Clock Frequency: %d", d.MaxClockFrequency()) |
||||
t.Logf(" Max Compute Units: %d", d.MaxComputeUnits()) |
||||
t.Logf(" Max Constant Args: %d", d.MaxConstantArgs()) |
||||
t.Logf(" Max Constant Buffer Size: %d KB", d.MaxConstantBufferSize()/1024) |
||||
t.Logf(" Max Mem Alloc Size: %d KB", d.MaxMemAllocSize()/1024) |
||||
t.Logf(" Max Parameter Size: %d", d.MaxParameterSize()) |
||||
t.Logf(" Max Read-Image Args: %d", d.MaxReadImageArgs()) |
||||
t.Logf(" Max Samplers: %d", d.MaxSamplers()) |
||||
t.Logf(" Max Work Group Size: %d", d.MaxWorkGroupSize()) |
||||
t.Logf(" Max Work Item Dimensions: %d", d.MaxWorkItemDimensions()) |
||||
t.Logf(" Max Work Item Sizes: %d", d.MaxWorkItemSizes()) |
||||
t.Logf(" Max Write-Image Args: %d", d.MaxWriteImageArgs()) |
||||
t.Logf(" Memory Base Address Alignment: %d", d.MemBaseAddrAlign()) |
||||
t.Logf(" Native Vector Width Char: %d", d.NativeVectorWidthChar()) |
||||
t.Logf(" Native Vector Width Short: %d", d.NativeVectorWidthShort()) |
||||
t.Logf(" Native Vector Width Int: %d", d.NativeVectorWidthInt()) |
||||
t.Logf(" Native Vector Width Long: %d", d.NativeVectorWidthLong()) |
||||
t.Logf(" Native Vector Width Float: %d", d.NativeVectorWidthFloat()) |
||||
t.Logf(" Native Vector Width Double: %d", d.NativeVectorWidthDouble()) |
||||
t.Logf(" Native Vector Width Half: %d", d.NativeVectorWidthHalf()) |
||||
t.Logf(" OpenCL C Version: %s", d.OpenCLCVersion()) |
||||
// t.Logf(" Parent Device: %+v", d.ParentDevice())
|
||||
t.Logf(" Profile: %s", d.Profile()) |
||||
t.Logf(" Profiling Timer Resolution: %d", d.ProfilingTimerResolution()) |
||||
t.Logf(" Vendor: %s", d.Vendor()) |
||||
t.Logf(" Version: %s", d.Version()) |
||||
} |
||||
if deviceIndex < 0 { |
||||
deviceIndex = 0 |
||||
} |
||||
device := devices[deviceIndex] |
||||
t.Logf("Using device %d", deviceIndex) |
||||
context, err := CreateContext([]*Device{device}) |
||||
if err != nil { |
||||
t.Fatalf("CreateContext failed: %+v", err) |
||||
} |
||||
// imageFormats, err := context.GetSupportedImageFormats(0, MemObjectTypeImage2D)
|
||||
// if err != nil {
|
||||
// t.Fatalf("GetSupportedImageFormats failed: %+v", err)
|
||||
// }
|
||||
// t.Logf("Supported image formats: %+v", imageFormats)
|
||||
queue, err := context.CreateCommandQueue(device, 0) |
||||
if err != nil { |
||||
t.Fatalf("CreateCommandQueue failed: %+v", err) |
||||
} |
||||
program, err := context.CreateProgramWithSource([]string{kernelSource}) |
||||
if err != nil { |
||||
t.Fatalf("CreateProgramWithSource failed: %+v", err) |
||||
} |
||||
if err := program.BuildProgram(nil, ""); err != nil { |
||||
t.Fatalf("BuildProgram failed: %+v", err) |
||||
} |
||||
kernel, err := program.CreateKernel("square") |
||||
if err != nil { |
||||
t.Fatalf("CreateKernel failed: %+v", err) |
||||
} |
||||
for i := 0; i < 3; i++ { |
||||
name, err := kernel.ArgName(i) |
||||
if err == ErrUnsupported { |
||||
break |
||||
} else if err != nil { |
||||
t.Errorf("GetKernelArgInfo for name failed: %+v", err) |
||||
break |
||||
} else { |
||||
t.Logf("Kernel arg %d: %s", i, name) |
||||
} |
||||
} |
||||
input, err := context.CreateEmptyBuffer(MemReadOnly, 4*len(data)) |
||||
if err != nil { |
||||
t.Fatalf("CreateBuffer failed for input: %+v", err) |
||||
} |
||||
output, err := context.CreateEmptyBuffer(MemReadOnly, 4*len(data)) |
||||
if err != nil { |
||||
t.Fatalf("CreateBuffer failed for output: %+v", err) |
||||
} |
||||
if _, err := queue.EnqueueWriteBufferFloat32(input, true, 0, data[:], nil); err != nil { |
||||
t.Fatalf("EnqueueWriteBufferFloat32 failed: %+v", err) |
||||
} |
||||
if err := kernel.SetArgs(input, output, uint32(len(data))); err != nil { |
||||
t.Fatalf("SetKernelArgs failed: %+v", err) |
||||
} |
||||
|
||||
local, err := kernel.WorkGroupSize(device) |
||||
if err != nil { |
||||
t.Fatalf("WorkGroupSize failed: %+v", err) |
||||
} |
||||
t.Logf("Work group size: %d", local) |
||||
size, _ := kernel.PreferredWorkGroupSizeMultiple(nil) |
||||
t.Logf("Preferred Work Group Size Multiple: %d", size) |
||||
|
||||
global := len(data) |
||||
d := len(data) % local |
||||
if d != 0 { |
||||
global += local - d |
||||
} |
||||
if _, err := queue.EnqueueNDRangeKernel(kernel, nil, []int{global}, []int{local}, nil); err != nil { |
||||
t.Fatalf("EnqueueNDRangeKernel failed: %+v", err) |
||||
} |
||||
|
||||
if err := queue.Finish(); err != nil { |
||||
t.Fatalf("Finish failed: %+v", err) |
||||
} |
||||
|
||||
results := make([]float32, len(data)) |
||||
if _, err := queue.EnqueueReadBufferFloat32(output, true, 0, results, nil); err != nil { |
||||
t.Fatalf("EnqueueReadBufferFloat32 failed: %+v", err) |
||||
} |
||||
|
||||
correct := 0 |
||||
for i, v := range data { |
||||
if results[i] == v*v { |
||||
correct++ |
||||
} |
||||
} |
||||
|
||||
if correct != len(data) { |
||||
t.Fatalf("%d/%d correct values", correct, len(data)) |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
version: "{build}" |
||||
|
||||
os: Windows Server 2012 R2 |
||||
|
||||
install: |
||||
- go version |
||||
- go env |
||||
|
||||
build_script: |
||||
- cd %APPVEYOR_BUILD_FOLDER% |
||||
- go vet ./... |
||||
- go test -v ./... |
||||
|
||||
test: off |
||||
|
||||
deploy: off |
3
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
@ -0,0 +1,13 @@ |
||||
Copyright (c) 2012-2013 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. |
@ -0,0 +1,151 @@ |
||||
// Copyright (c) 2015 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 and "-tags disableunsafe"
|
||||
// is not added to the go build command line.
|
||||
// +build !appengine,!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 = uintptr(ptrSize) |
||||
offsetScalar = uintptr(0) |
||||
offsetFlag = uintptr(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 = uintptr(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,37 @@ |
||||
// Copyright (c) 2015 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 either the code is running on Google App Engine or "-tags disableunsafe"
|
||||
// is added to the go build command line.
|
||||
// +build appengine 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 |
||||
} |
@ -1,298 +0,0 @@ |
||||
/* |
||||
* Copyright (c) 2013 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_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
"testing" |
||||
|
||||
"github.com/davecgh/go-spew/spew" |
||||
) |
||||
|
||||
// custom type to test Stinger interface on non-pointer receiver.
|
||||
type stringer string |
||||
|
||||
// String implements the Stringer interface for testing invocation of custom
|
||||
// stringers on types with non-pointer receivers.
|
||||
func (s stringer) String() string { |
||||
return "stringer " + string(s) |
||||
} |
||||
|
||||
// custom type to test Stinger interface on pointer receiver.
|
||||
type pstringer string |
||||
|
||||
// String implements the Stringer interface for testing invocation of custom
|
||||
// stringers on types with only pointer receivers.
|
||||
func (s *pstringer) String() string { |
||||
return "stringer " + string(*s) |
||||
} |
||||
|
||||
// xref1 and xref2 are cross referencing structs for testing circular reference
|
||||
// detection.
|
||||
type xref1 struct { |
||||
ps2 *xref2 |
||||
} |
||||
type xref2 struct { |
||||
ps1 *xref1 |
||||
} |
||||
|
||||
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
|
||||
// reference for testing detection.
|
||||
type indirCir1 struct { |
||||
ps2 *indirCir2 |
||||
} |
||||
type indirCir2 struct { |
||||
ps3 *indirCir3 |
||||
} |
||||
type indirCir3 struct { |
||||
ps1 *indirCir1 |
||||
} |
||||
|
||||
// embed is used to test embedded structures.
|
||||
type embed struct { |
||||
a string |
||||
} |
||||
|
||||
// embedwrap is used to test embedded structures.
|
||||
type embedwrap struct { |
||||
*embed |
||||
e *embed |
||||
} |
||||
|
||||
// panicer is used to intentionally cause a panic for testing spew properly
|
||||
// handles them
|
||||
type panicer int |
||||
|
||||
func (p panicer) String() string { |
||||
panic("test panic") |
||||
} |
||||
|
||||
// customError is used to test custom error interface invocation.
|
||||
type customError int |
||||
|
||||
func (e customError) Error() string { |
||||
return fmt.Sprintf("error: %d", int(e)) |
||||
} |
||||
|
||||
// stringizeWants converts a slice of wanted test output into a format suitable
|
||||
// for a test error message.
|
||||
func stringizeWants(wants []string) string { |
||||
s := "" |
||||
for i, want := range wants { |
||||
if i > 0 { |
||||
s += fmt.Sprintf("want%d: %s", i+1, want) |
||||
} else { |
||||
s += "want: " + want |
||||
} |
||||
} |
||||
return s |
||||
} |
||||
|
||||
// testFailed returns whether or not a test failed by checking if the result
|
||||
// of the test is in the slice of wanted strings.
|
||||
func testFailed(result string, wants []string) bool { |
||||
for _, want := range wants { |
||||
if result == want { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
type sortableStruct struct { |
||||
x int |
||||
} |
||||
|
||||
func (ss sortableStruct) String() string { |
||||
return fmt.Sprintf("ss.%d", ss.x) |
||||
} |
||||
|
||||
type unsortableStruct struct { |
||||
x int |
||||
} |
||||
|
||||
type sortTestCase struct { |
||||
input []reflect.Value |
||||
expected []reflect.Value |
||||
} |
||||
|
||||
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) { |
||||
getInterfaces := func(values []reflect.Value) []interface{} { |
||||
interfaces := []interface{}{} |
||||
for _, v := range values { |
||||
interfaces = append(interfaces, v.Interface()) |
||||
} |
||||
return interfaces |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
spew.SortValues(test.input, cs) |
||||
// reflect.DeepEqual cannot really make sense of reflect.Value,
|
||||
// probably because of all the pointer tricks. For instance,
|
||||
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
|
||||
// instead.
|
||||
input := getInterfaces(test.input) |
||||
expected := getInterfaces(test.expected) |
||||
if !reflect.DeepEqual(input, expected) { |
||||
t.Errorf("Sort mismatch:\n %v != %v", input, expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// TestSortValues ensures the sort functionality for relect.Value based sorting
|
||||
// works as intended.
|
||||
func TestSortValues(t *testing.T) { |
||||
v := reflect.ValueOf |
||||
|
||||
a := v("a") |
||||
b := v("b") |
||||
c := v("c") |
||||
embedA := v(embed{"a"}) |
||||
embedB := v(embed{"b"}) |
||||
embedC := v(embed{"c"}) |
||||
tests := []sortTestCase{ |
||||
// No values.
|
||||
{ |
||||
[]reflect.Value{}, |
||||
[]reflect.Value{}, |
||||
}, |
||||
// Bools.
|
||||
{ |
||||
[]reflect.Value{v(false), v(true), v(false)}, |
||||
[]reflect.Value{v(false), v(false), v(true)}, |
||||
}, |
||||
// Ints.
|
||||
{ |
||||
[]reflect.Value{v(2), v(1), v(3)}, |
||||
[]reflect.Value{v(1), v(2), v(3)}, |
||||
}, |
||||
// Uints.
|
||||
{ |
||||
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))}, |
||||
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))}, |
||||
}, |
||||
// Floats.
|
||||
{ |
||||
[]reflect.Value{v(2.0), v(1.0), v(3.0)}, |
||||
[]reflect.Value{v(1.0), v(2.0), v(3.0)}, |
||||
}, |
||||
// Strings.
|
||||
{ |
||||
[]reflect.Value{b, a, c}, |
||||
[]reflect.Value{a, b, c}, |
||||
}, |
||||
// Array
|
||||
{ |
||||
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})}, |
||||
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})}, |
||||
}, |
||||
// Uintptrs.
|
||||
{ |
||||
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))}, |
||||
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))}, |
||||
}, |
||||
// SortableStructs.
|
||||
{ |
||||
// Note: not sorted - DisableMethods is set.
|
||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})}, |
||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})}, |
||||
}, |
||||
// UnsortableStructs.
|
||||
{ |
||||
// Note: not sorted - SpewKeys is false.
|
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, |
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, |
||||
}, |
||||
// Invalid.
|
||||
{ |
||||
[]reflect.Value{embedB, embedA, embedC}, |
||||
[]reflect.Value{embedB, embedA, embedC}, |
||||
}, |
||||
} |
||||
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false} |
||||
helpTestSortValues(tests, &cs, t) |
||||
} |
||||
|
||||
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
|
||||
// based sorting works as intended when using string methods.
|
||||
func TestSortValuesWithMethods(t *testing.T) { |
||||
v := reflect.ValueOf |
||||
|
||||
a := v("a") |
||||
b := v("b") |
||||
c := v("c") |
||||
tests := []sortTestCase{ |
||||
// Ints.
|
||||
{ |
||||
[]reflect.Value{v(2), v(1), v(3)}, |
||||
[]reflect.Value{v(1), v(2), v(3)}, |
||||
}, |
||||
// Strings.
|
||||
{ |
||||
[]reflect.Value{b, a, c}, |
||||
[]reflect.Value{a, b, c}, |
||||
}, |
||||
// SortableStructs.
|
||||
{ |
||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})}, |
||||
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})}, |
||||
}, |
||||
// UnsortableStructs.
|
||||
{ |
||||
// Note: not sorted - SpewKeys is false.
|
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, |
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, |
||||
}, |
||||
} |
||||
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false} |
||||
helpTestSortValues(tests, &cs, t) |
||||
} |
||||
|
||||
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
|
||||
// based sorting works as intended when using spew to stringify keys.
|
||||
func TestSortValuesWithSpew(t *testing.T) { |
||||
v := reflect.ValueOf |
||||
|
||||
a := v("a") |
||||
b := v("b") |
||||
c := v("c") |
||||
tests := []sortTestCase{ |
||||
// Ints.
|
||||
{ |
||||
[]reflect.Value{v(2), v(1), v(3)}, |
||||
[]reflect.Value{v(1), v(2), v(3)}, |
||||
}, |
||||
// Strings.
|
||||
{ |
||||
[]reflect.Value{b, a, c}, |
||||
[]reflect.Value{a, b, c}, |
||||
}, |
||||
// SortableStructs.
|
||||
{ |
||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})}, |
||||
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})}, |
||||
}, |
||||
// UnsortableStructs.
|
||||
{ |
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, |
||||
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})}, |
||||
}, |
||||
} |
||||
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true} |
||||
helpTestSortValues(tests, &cs, t) |
||||
} |
@ -1,97 +0,0 @@ |
||||
// Copyright (c) 2013 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 both cgo is supported and "-tags testcgo" is added to the go test
|
||||
// command line. This means the cgo tests are only added (and hence run) when
|
||||
// specifially requested. This configuration is used because spew itself
|
||||
// does not require cgo to run even though it does handle certain cgo types
|
||||
// specially. Rather than forcing all clients to require cgo and an external
|
||||
// C compiler just to run the tests, this scheme makes them optional.
|
||||
// +build cgo,testcgo
|
||||
|
||||
package spew_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/davecgh/go-spew/spew/testdata" |
||||
) |
||||
|
||||
func addCgoDumpTests() { |
||||
// C char pointer.
|
||||
v := testdata.GetCgoCharPointer() |
||||
nv := testdata.GetCgoNullCharPointer() |
||||
pv := &v |
||||
vcAddr := fmt.Sprintf("%p", v) |
||||
vAddr := fmt.Sprintf("%p", pv) |
||||
pvAddr := fmt.Sprintf("%p", &pv) |
||||
vt := "*testdata._Ctype_char" |
||||
vs := "116" |
||||
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n") |
||||
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n") |
||||
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n") |
||||
addDumpTest(nv, "("+vt+")(<nil>)\n") |
||||
|
||||
// C char array.
|
||||
v2, v2l, v2c := testdata.GetCgoCharArray() |
||||
v2Len := fmt.Sprintf("%d", v2l) |
||||
v2Cap := fmt.Sprintf("%d", v2c) |
||||
v2t := "[6]testdata._Ctype_char" |
||||
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " + |
||||
"{\n 00000000 74 65 73 74 32 00 " + |
||||
" |test2.|\n}" |
||||
addDumpTest(v2, "("+v2t+") "+v2s+"\n") |
||||
|
||||
// C unsigned char array.
|
||||
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray() |
||||
v3Len := fmt.Sprintf("%d", v3l) |
||||
v3Cap := fmt.Sprintf("%d", v3c) |
||||
v3t := "[6]testdata._Ctype_unsignedchar" |
||||
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " + |
||||
"{\n 00000000 74 65 73 74 33 00 " + |
||||
" |test3.|\n}" |
||||
addDumpTest(v3, "("+v3t+") "+v3s+"\n") |
||||
|
||||
// C signed char array.
|
||||
v4, v4l, v4c := testdata.GetCgoSignedCharArray() |
||||
v4Len := fmt.Sprintf("%d", v4l) |
||||
v4Cap := fmt.Sprintf("%d", v4c) |
||||
v4t := "[6]testdata._Ctype_schar" |
||||
v4t2 := "testdata._Ctype_schar" |
||||
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " + |
||||
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 + |
||||
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 + |
||||
") 0\n}" |
||||
addDumpTest(v4, "("+v4t+") "+v4s+"\n") |
||||
|
||||
// C uint8_t array.
|
||||
v5, v5l, v5c := testdata.GetCgoUint8tArray() |
||||
v5Len := fmt.Sprintf("%d", v5l) |
||||
v5Cap := fmt.Sprintf("%d", v5c) |
||||
v5t := "[6]testdata._Ctype_uint8_t" |
||||
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " + |
||||
"{\n 00000000 74 65 73 74 35 00 " + |
||||
" |test5.|\n}" |
||||
addDumpTest(v5, "("+v5t+") "+v5s+"\n") |
||||
|
||||
// C typedefed unsigned char array.
|
||||
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray() |
||||
v6Len := fmt.Sprintf("%d", v6l) |
||||
v6Cap := fmt.Sprintf("%d", v6c) |
||||
v6t := "[6]testdata._Ctype_custom_uchar_t" |
||||
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " + |
||||
"{\n 00000000 74 65 73 74 36 00 " + |
||||
" |test6.|\n}" |
||||
addDumpTest(v6, "("+v6t+") "+v6s+"\n") |
||||
} |
@ -1,26 +0,0 @@ |
||||
// Copyright (c) 2013 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 either cgo is not supported or "-tags testcgo" is not added to the go
|
||||
// test command line. This file intentionally does not setup any cgo tests in
|
||||
// this scenario.
|
||||
// +build !cgo !testcgo
|
||||
|
||||
package spew_test |
||||
|
||||
func addCgoDumpTests() { |
||||
// Don't add any tests for cgo since this file is only compiled when
|
||||
// there should not be any cgo tests.
|
||||
} |
@ -1,230 +0,0 @@ |
||||
/* |
||||
* Copyright (c) 2013 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_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/davecgh/go-spew/spew" |
||||
) |
||||
|
||||
type Flag int |
||||
|
||||
const ( |
||||
flagOne Flag = iota |
||||
flagTwo |
||||
) |
||||
|
||||
var flagStrings = map[Flag]string{ |
||||
flagOne: "flagOne", |
||||
flagTwo: "flagTwo", |
||||
} |
||||
|
||||
func (f Flag) String() string { |
||||
if s, ok := flagStrings[f]; ok { |
||||
return s |
||||
} |
||||
return fmt.Sprintf("Unknown flag (%d)", int(f)) |
||||
} |
||||
|
||||
type Bar struct { |
||||
flag Flag |
||||
data uintptr |
||||
} |
||||
|
||||
type Foo struct { |
||||
unexportedField Bar |
||||
ExportedField map[interface{}]interface{} |
||||
} |
||||
|
||||
// This example demonstrates how to use Dump to dump variables to stdout.
|
||||
func ExampleDump() { |
||||
// The following package level declarations are assumed for this example:
|
||||
/* |
||||
type Flag int |
||||
|
||||
const ( |
||||
flagOne Flag = iota |
||||
flagTwo |
||||
) |
||||
|
||||
var flagStrings = map[Flag]string{ |
||||
flagOne: "flagOne", |
||||
flagTwo: "flagTwo", |
||||
} |
||||
|
||||
func (f Flag) String() string { |
||||
if s, ok := flagStrings[f]; ok { |
||||
return s |
||||
} |
||||
return fmt.Sprintf("Unknown flag (%d)", int(f)) |
||||
} |
||||
|
||||
type Bar struct { |
||||
flag Flag |
||||
data uintptr |
||||
} |
||||
|
||||
type Foo struct { |
||||
unexportedField Bar |
||||
ExportedField map[interface{}]interface{} |
||||
} |
||||
*/ |
||||
|
||||
// Setup some sample data structures for the example.
|
||||
bar := Bar{Flag(flagTwo), uintptr(0)} |
||||
s1 := Foo{bar, map[interface{}]interface{}{"one": true}} |
||||
f := Flag(5) |
||||
b := []byte{ |
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, |
||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, |
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, |
||||
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, |
||||
0x31, 0x32, |
||||
} |
||||
|
||||
// Dump!
|
||||
spew.Dump(s1, f, b) |
||||
|
||||
// Output:
|
||||
// (spew_test.Foo) {
|
||||
// unexportedField: (spew_test.Bar) {
|
||||
// flag: (spew_test.Flag) flagTwo,
|
||||
// data: (uintptr) <nil>
|
||||
// },
|
||||
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
// (string) (len=3) "one": (bool) true
|
||||
// }
|
||||
// }
|
||||
// (spew_test.Flag) Unknown flag (5)
|
||||
// ([]uint8) (len=34 cap=34) {
|
||||
// 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|
|
||||
// }
|
||||
//
|
||||
} |
||||
|
||||
// This example demonstrates how to use Printf to display a variable with a
|
||||
// format string and inline formatting.
|
||||
func ExamplePrintf() { |
||||
// Create a double pointer to a uint 8.
|
||||
ui8 := uint8(5) |
||||
pui8 := &ui8 |
||||
ppui8 := &pui8 |
||||
|
||||
// Create a circular data type.
|
||||
type circular struct { |
||||
ui8 uint8 |
||||
c *circular |
||||
} |
||||
c := circular{ui8: 1} |
||||
c.c = &c |
||||
|
||||
// Print!
|
||||
spew.Printf("ppui8: %v\n", ppui8) |
||||
spew.Printf("circular: %v\n", c) |
||||
|
||||
// Output:
|
||||
// ppui8: <**>5
|
||||
// circular: {1 <*>{1 <*><shown>}}
|
||||
} |
||||
|
||||
// This example demonstrates how to use a ConfigState.
|
||||
func ExampleConfigState() { |
||||
// Modify the indent level of the ConfigState only. The global
|
||||
// configuration is not modified.
|
||||
scs := spew.ConfigState{Indent: "\t"} |
||||
|
||||
// Output using the ConfigState instance.
|
||||
v := map[string]int{"one": 1} |
||||
scs.Printf("v: %v\n", v) |
||||
scs.Dump(v) |
||||
|
||||
// Output:
|
||||
// v: map[one:1]
|
||||
// (map[string]int) (len=1) {
|
||||
// (string) (len=3) "one": (int) 1
|
||||
// }
|
||||
} |
||||
|
||||
// This example demonstrates how to use ConfigState.Dump to dump variables to
|
||||
// stdout
|
||||
func ExampleConfigState_Dump() { |
||||
// See the top-level Dump example for details on the types used in this
|
||||
// example.
|
||||
|
||||
// Create two ConfigState instances with different indentation.
|
||||
scs := spew.ConfigState{Indent: "\t"} |
||||
scs2 := spew.ConfigState{Indent: " "} |
||||
|
||||
// Setup some sample data structures for the example.
|
||||
bar := Bar{Flag(flagTwo), uintptr(0)} |
||||
s1 := Foo{bar, map[interface{}]interface{}{"one": true}} |
||||
|
||||
// Dump using the ConfigState instances.
|
||||
scs.Dump(s1) |
||||
scs2.Dump(s1) |
||||
|
||||
// Output:
|
||||
// (spew_test.Foo) {
|
||||
// unexportedField: (spew_test.Bar) {
|
||||
// flag: (spew_test.Flag) flagTwo,
|
||||
// data: (uintptr) <nil>
|
||||
// },
|
||||
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
// (string) (len=3) "one": (bool) true
|
||||
// }
|
||||
// }
|
||||
// (spew_test.Foo) {
|
||||
// unexportedField: (spew_test.Bar) {
|
||||
// flag: (spew_test.Flag) flagTwo,
|
||||
// data: (uintptr) <nil>
|
||||
// },
|
||||
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
// (string) (len=3) "one": (bool) true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
} |
||||
|
||||
// This example demonstrates how to use ConfigState.Printf to display a variable
|
||||
// with a format string and inline formatting.
|
||||
func ExampleConfigState_Printf() { |
||||
// See the top-level Dump example for details on the types used in this
|
||||
// example.
|
||||
|
||||
// Create two ConfigState instances and modify the method handling of the
|
||||
// first ConfigState only.
|
||||
scs := spew.NewDefaultConfig() |
||||
scs2 := spew.NewDefaultConfig() |
||||
scs.DisableMethods = true |
||||
|
||||
// Alternatively
|
||||
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||
// scs2 := spew.ConfigState{Indent: " "}
|
||||
|
||||
// This is of type Flag which implements a Stringer and has raw value 1.
|
||||
f := flagTwo |
||||
|
||||
// Dump using the ConfigState instances.
|
||||
scs.Printf("f: %v\n", f) |
||||
scs2.Printf("f: %v\n", f) |
||||
|
||||
// Output:
|
||||
// f: 1
|
||||
// f: flagTwo
|
||||
} |
@ -1,156 +0,0 @@ |
||||
/* |
||||
* Copyright (c) 2013 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. |
||||
*/ |
||||
|
||||
/* |
||||
This test file is part of the spew package rather than than the spew_test |
||||
package because it needs access to internals to properly test certain cases |
||||
which are not possible via the public interface since they should never happen. |
||||
*/ |
||||
|
||||
package spew |
||||
|
||||
import ( |
||||
"bytes" |
||||
"reflect" |
||||
"testing" |
||||
"unsafe" |
||||
) |
||||
|
||||
// dummyFmtState implements a fake fmt.State to use for testing invalid
|
||||
// reflect.Value handling. This is necessary because the fmt package catches
|
||||
// invalid values before invoking the formatter on them.
|
||||
type dummyFmtState struct { |
||||
bytes.Buffer |
||||
} |
||||
|
||||
func (dfs *dummyFmtState) Flag(f int) bool { |
||||
if f == int('+') { |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func (dfs *dummyFmtState) Precision() (int, bool) { |
||||
return 0, false |
||||
} |
||||
|
||||
func (dfs *dummyFmtState) Width() (int, bool) { |
||||
return 0, false |
||||
} |
||||
|
||||
// TestInvalidReflectValue ensures the dump and formatter code handles an
|
||||
// invalid reflect value properly. This needs access to internal state since it
|
||||
// should never happen in real code and therefore can't be tested via the public
|
||||
// API.
|
||||
func TestInvalidReflectValue(t *testing.T) { |
||||
i := 1 |
||||
|
||||
// Dump invalid reflect value.
|
||||
v := new(reflect.Value) |
||||
buf := new(bytes.Buffer) |
||||
d := dumpState{w: buf, cs: &Config} |
||||
d.dump(*v) |
||||
s := buf.String() |
||||
want := "<invalid>" |
||||
if s != want { |
||||
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want) |
||||
} |
||||
i++ |
||||
|
||||
// Formatter invalid reflect value.
|
||||
buf2 := new(dummyFmtState) |
||||
f := formatState{value: *v, cs: &Config, fs: buf2} |
||||
f.format(*v) |
||||
s = buf2.String() |
||||
want = "<invalid>" |
||||
if s != want { |
||||
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want) |
||||
} |
||||
} |
||||
|
||||
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
|
||||
// the maximum kind value which does not exist. This is needed to test the
|
||||
// fallback code which punts to the standard fmt library for new types that
|
||||
// might get added to the language.
|
||||
func changeKind(v *reflect.Value, readOnly bool) { |
||||
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag)) |
||||
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift) |
||||
if readOnly { |
||||
*rvf |= flagRO |
||||
} else { |
||||
*rvf &= ^uintptr(flagRO) |
||||
} |
||||
} |
||||
|
||||
// TestAddedReflectValue tests functionaly of the dump and formatter code which
|
||||
// falls back to the standard fmt library for new types that might get added to
|
||||
// the language.
|
||||
func TestAddedReflectValue(t *testing.T) { |
||||
i := 1 |
||||
|
||||
// Dump using a reflect.Value that is exported.
|
||||
v := reflect.ValueOf(int8(5)) |
||||
changeKind(&v, false) |
||||
buf := new(bytes.Buffer) |
||||
d := dumpState{w: buf, cs: &Config} |
||||
d.dump(v) |
||||
s := buf.String() |
||||
want := "(int8) 5" |
||||
if s != want { |
||||
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want) |
||||
} |
||||
i++ |
||||
|
||||
// Dump using a reflect.Value that is not exported.
|
||||
changeKind(&v, true) |
||||
buf.Reset() |
||||
d.dump(v) |
||||
s = buf.String() |
||||
want = "(int8) <int8 Value>" |
||||
if s != want { |
||||
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want) |
||||
} |
||||
i++ |
||||
|
||||
// Formatter using a reflect.Value that is exported.
|
||||
changeKind(&v, false) |
||||
buf2 := new(dummyFmtState) |
||||
f := formatState{value: v, cs: &Config, fs: buf2} |
||||
f.format(v) |
||||
s = buf2.String() |
||||
want = "5" |
||||
if s != want { |
||||
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want) |
||||
} |
||||
i++ |
||||
|
||||
// Formatter using a reflect.Value that is not exported.
|
||||
changeKind(&v, true) |
||||
buf2.Reset() |
||||
f = formatState{value: v, cs: &Config, fs: buf2} |
||||
f.format(v) |
||||
s = buf2.String() |
||||
want = "<int8 Value>" |
||||
if s != want { |
||||
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want) |
||||
} |
||||
} |
||||
|
||||
// SortValues makes the internal sortValues function available to the test
|
||||
// package.
|
||||
func SortValues(values []reflect.Value, cs *ConfigState) { |
||||
sortValues(values, cs) |
||||
} |
@ -1,308 +0,0 @@ |
||||
/* |
||||
* Copyright (c) 2013 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_test |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"github.com/davecgh/go-spew/spew" |
||||
"io/ioutil" |
||||
"os" |
||||
"testing" |
||||
) |
||||
|
||||
// spewFunc is used to identify which public function of the spew package or
|
||||
// ConfigState a test applies to.
|
||||
type spewFunc int |
||||
|
||||
const ( |
||||
fCSFdump spewFunc = iota |
||||
fCSFprint |
||||
fCSFprintf |
||||
fCSFprintln |
||||
fCSPrint |
||||
fCSPrintln |
||||
fCSSdump |
||||
fCSSprint |
||||
fCSSprintf |
||||
fCSSprintln |
||||
fCSErrorf |
||||
fCSNewFormatter |
||||
fErrorf |
||||
fFprint |
||||
fFprintln |
||||
fPrint |
||||
fPrintln |
||||
fSdump |
||||
fSprint |
||||
fSprintf |
||||
fSprintln |
||||
) |
||||
|
||||
// Map of spewFunc values to names for pretty printing.
|
||||
var spewFuncStrings = map[spewFunc]string{ |
||||
fCSFdump: "ConfigState.Fdump", |
||||
fCSFprint: "ConfigState.Fprint", |
||||
fCSFprintf: "ConfigState.Fprintf", |
||||
fCSFprintln: "ConfigState.Fprintln", |
||||
fCSSdump: "ConfigState.Sdump", |
||||
fCSPrint: "ConfigState.Print", |
||||
fCSPrintln: "ConfigState.Println", |
||||
fCSSprint: "ConfigState.Sprint", |
||||
fCSSprintf: "ConfigState.Sprintf", |
||||
fCSSprintln: "ConfigState.Sprintln", |
||||
fCSErrorf: "ConfigState.Errorf", |
||||
fCSNewFormatter: "ConfigState.NewFormatter", |
||||
fErrorf: "spew.Errorf", |
||||
fFprint: "spew.Fprint", |
||||
fFprintln: "spew.Fprintln", |
||||
fPrint: "spew.Print", |
||||
fPrintln: "spew.Println", |
||||
fSdump: "spew.Sdump", |
||||
fSprint: "spew.Sprint", |
||||
fSprintf: "spew.Sprintf", |
||||
fSprintln: "spew.Sprintln", |
||||
} |
||||
|
||||
func (f spewFunc) String() string { |
||||
if s, ok := spewFuncStrings[f]; ok { |
||||
return s |
||||
} |
||||
return fmt.Sprintf("Unknown spewFunc (%d)", int(f)) |
||||
} |
||||
|
||||
// spewTest is used to describe a test to be performed against the public
|
||||
// functions of the spew package or ConfigState.
|
||||
type spewTest struct { |
||||
cs *spew.ConfigState |
||||
f spewFunc |
||||
format string |
||||
in interface{} |
||||
want string |
||||
} |
||||
|
||||
// spewTests houses the tests to be performed against the public functions of
|
||||
// the spew package and ConfigState.
|
||||
//
|
||||
// These tests are only intended to ensure the public functions are exercised
|
||||
// and are intentionally not exhaustive of types. The exhaustive type
|
||||
// tests are handled in the dump and format tests.
|
||||
var spewTests []spewTest |
||||
|
||||
// redirStdout is a helper function to return the standard output from f as a
|
||||
// byte slice.
|
||||
func redirStdout(f func()) ([]byte, error) { |
||||
tempFile, err := ioutil.TempFile("", "ss-test") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
fileName := tempFile.Name() |
||||
defer os.Remove(fileName) // Ignore error
|
||||
|
||||
origStdout := os.Stdout |
||||
os.Stdout = tempFile |
||||
f() |
||||
os.Stdout = origStdout |
||||
tempFile.Close() |
||||
|
||||
return ioutil.ReadFile(fileName) |
||||
} |
||||
|
||||
func initSpewTests() { |
||||
// Config states with various settings.
|
||||
scsDefault := spew.NewDefaultConfig() |
||||
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true} |
||||
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true} |
||||
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1} |
||||
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true} |
||||
|
||||
// Variables for tests on types which implement Stringer interface with and
|
||||
// without a pointer receiver.
|
||||
ts := stringer("test") |
||||
tps := pstringer("test") |
||||
|
||||
// depthTester is used to test max depth handling for structs, array, slices
|
||||
// and maps.
|
||||
type depthTester struct { |
||||
ic indirCir1 |
||||
arr [1]string |
||||
slice []string |
||||
m map[string]int |
||||
} |
||||
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"}, |
||||
map[string]int{"one": 1}} |
||||
|
||||
// Variable for tests on types which implement error interface.
|
||||
te := customError(10) |
||||
|
||||
spewTests = []spewTest{ |
||||
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"}, |
||||
{scsDefault, fCSFprint, "", int16(32767), "32767"}, |
||||
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"}, |
||||
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"}, |
||||
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"}, |
||||
{scsDefault, fCSPrintln, "", uint8(255), "255\n"}, |
||||
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"}, |
||||
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"}, |
||||
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"}, |
||||
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"}, |
||||
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"}, |
||||
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"}, |
||||
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"}, |
||||
{scsDefault, fFprint, "", float32(3.14), "3.14"}, |
||||
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"}, |
||||
{scsDefault, fPrint, "", true, "true"}, |
||||
{scsDefault, fPrintln, "", false, "false\n"}, |
||||
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"}, |
||||
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"}, |
||||
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"}, |
||||
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"}, |
||||
{scsNoMethods, fCSFprint, "", ts, "test"}, |
||||
{scsNoMethods, fCSFprint, "", &ts, "<*>test"}, |
||||
{scsNoMethods, fCSFprint, "", tps, "test"}, |
||||
{scsNoMethods, fCSFprint, "", &tps, "<*>test"}, |
||||
{scsNoPmethods, fCSFprint, "", ts, "stringer test"}, |
||||
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"}, |
||||
{scsNoPmethods, fCSFprint, "", tps, "test"}, |
||||
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"}, |
||||
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"}, |
||||
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" + |
||||
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" + |
||||
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" + |
||||
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" + |
||||
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"}, |
||||
{scsContinue, fCSFprint, "", ts, "(stringer test) test"}, |
||||
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " + |
||||
"(len=4) (stringer test) \"test\"\n"}, |
||||
{scsContinue, fCSFprint, "", te, "(error: 10) 10"}, |
||||
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " + |
||||
"(error: 10) 10\n"}, |
||||
} |
||||
} |
||||
|
||||
// TestSpew executes all of the tests described by spewTests.
|
||||
func TestSpew(t *testing.T) { |
||||
initSpewTests() |
||||
|
||||
t.Logf("Running %d tests", len(spewTests)) |
||||
for i, test := range spewTests { |
||||
buf := new(bytes.Buffer) |
||||
switch test.f { |
||||
case fCSFdump: |
||||
test.cs.Fdump(buf, test.in) |
||||
|
||||
case fCSFprint: |
||||
test.cs.Fprint(buf, test.in) |
||||
|
||||
case fCSFprintf: |
||||
test.cs.Fprintf(buf, test.format, test.in) |
||||
|
||||
case fCSFprintln: |
||||
test.cs.Fprintln(buf, test.in) |
||||
|
||||
case fCSPrint: |
||||
b, err := redirStdout(func() { test.cs.Print(test.in) }) |
||||
if err != nil { |
||||
t.Errorf("%v #%d %v", test.f, i, err) |
||||
continue |
||||
} |
||||
buf.Write(b) |
||||
|
||||
case fCSPrintln: |
||||
b, err := redirStdout(func() { test.cs.Println(test.in) }) |
||||
if err != nil { |
||||
t.Errorf("%v #%d %v", test.f, i, err) |
||||
continue |
||||
} |
||||
buf.Write(b) |
||||
|
||||
case fCSSdump: |
||||
str := test.cs.Sdump(test.in) |
||||
buf.WriteString(str) |
||||
|
||||
case fCSSprint: |
||||
str := test.cs.Sprint(test.in) |
||||
buf.WriteString(str) |
||||
|
||||
case fCSSprintf: |
||||
str := test.cs.Sprintf(test.format, test.in) |
||||
buf.WriteString(str) |
||||
|
||||
case fCSSprintln: |
||||
str := test.cs.Sprintln(test.in) |
||||
buf.WriteString(str) |
||||
|
||||
case fCSErrorf: |
||||
err := test.cs.Errorf(test.format, test.in) |
||||
buf.WriteString(err.Error()) |
||||
|
||||
case fCSNewFormatter: |
||||
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in)) |
||||
|
||||
case fErrorf: |
||||
err := spew.Errorf(test.format, test.in) |
||||
buf.WriteString(err.Error()) |
||||
|
||||
case fFprint: |
||||
spew.Fprint(buf, test.in) |
||||
|
||||
case fFprintln: |
||||
spew.Fprintln(buf, test.in) |
||||
|
||||
case fPrint: |
||||
b, err := redirStdout(func() { spew.Print(test.in) }) |
||||
if err != nil { |
||||
t.Errorf("%v #%d %v", test.f, i, err) |
||||
continue |
||||
} |
||||
buf.Write(b) |
||||
|
||||
case fPrintln: |
||||
b, err := redirStdout(func() { spew.Println(test.in) }) |
||||
if err != nil { |
||||
t.Errorf("%v #%d %v", test.f, i, err) |
||||
continue |
||||
} |
||||
buf.Write(b) |
||||
|
||||
case fSdump: |
||||
str := spew.Sdump(test.in) |
||||
buf.WriteString(str) |
||||
|
||||
case fSprint: |
||||
str := spew.Sprint(test.in) |
||||
buf.WriteString(str) |
||||
|
||||
case fSprintf: |
||||
str := spew.Sprintf(test.format, test.in) |
||||
buf.WriteString(str) |
||||
|
||||
case fSprintln: |
||||
str := spew.Sprintln(test.in) |
||||
buf.WriteString(str) |
||||
|
||||
default: |
||||
t.Errorf("%v #%d unrecognized function", test.f, i) |
||||
continue |
||||
} |
||||
s := buf.String() |
||||
if test.want != s { |
||||
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want) |
||||
continue |
||||
} |
||||
} |
||||
} |
@ -1,82 +0,0 @@ |
||||
// Copyright (c) 2013 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 both cgo is supported and "-tags testcgo" is added to the go test
|
||||
// command line. This code should really only be in the dumpcgo_test.go file,
|
||||
// but unfortunately Go will not allow cgo in test files, so this is a
|
||||
// workaround to allow cgo types to be tested. This configuration is used
|
||||
// because spew itself does not require cgo to run even though it does handle
|
||||
// certain cgo types specially. Rather than forcing all clients to require cgo
|
||||
// and an external C compiler just to run the tests, this scheme makes them
|
||||
// optional.
|
||||
// +build cgo,testcgo
|
||||
|
||||
package testdata |
||||
|
||||
/* |
||||
#include <stdint.h> |
||||
typedef unsigned char custom_uchar_t; |
||||
|
||||
char *ncp = 0; |
||||
char *cp = "test"; |
||||
char ca[6] = {'t', 'e', 's', 't', '2', '\0'}; |
||||
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'}; |
||||
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'}; |
||||
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'}; |
||||
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'}; |
||||
*/ |
||||
import "C" |
||||
|
||||
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
|
||||
// used for tests.
|
||||
func GetCgoNullCharPointer() interface{} { |
||||
return C.ncp |
||||
} |
||||
|
||||
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
|
||||
// tests.
|
||||
func GetCgoCharPointer() interface{} { |
||||
return C.cp |
||||
} |
||||
|
||||
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
|
||||
// This is only used for tests.
|
||||
func GetCgoCharArray() (interface{}, int, int) { |
||||
return C.ca, len(C.ca), cap(C.ca) |
||||
} |
||||
|
||||
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
|
||||
// array's len and cap. This is only used for tests.
|
||||
func GetCgoUnsignedCharArray() (interface{}, int, int) { |
||||
return C.uca, len(C.uca), cap(C.uca) |
||||
} |
||||
|
||||
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
|
||||
// and cap. This is only used for tests.
|
||||
func GetCgoSignedCharArray() (interface{}, int, int) { |
||||
return C.sca, len(C.sca), cap(C.sca) |
||||
} |
||||
|
||||
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
|
||||
// cap. This is only used for tests.
|
||||
func GetCgoUint8tArray() (interface{}, int, int) { |
||||
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta) |
||||
} |
||||
|
||||
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
|
||||
// cgo and the array's len and cap. This is only used for tests.
|
||||
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) { |
||||
return C.tuca, len(C.tuca), cap(C.tuca) |
||||
} |
@ -1,221 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// Copyright 2015 Lefteris Karapetsas <lefteris@refu.co>
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethash |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/rand" |
||||
"encoding/hex" |
||||
"log" |
||||
"math/big" |
||||
"os" |
||||
"sync" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
) |
||||
|
||||
func init() { |
||||
// glog.SetV(6)
|
||||
// glog.SetToStderr(true)
|
||||
} |
||||
|
||||
type testBlock struct { |
||||
difficulty *big.Int |
||||
hashNoNonce common.Hash |
||||
nonce uint64 |
||||
mixDigest common.Hash |
||||
number uint64 |
||||
} |
||||
|
||||
func (b *testBlock) Difficulty() *big.Int { return b.difficulty } |
||||
func (b *testBlock) HashNoNonce() common.Hash { return b.hashNoNonce } |
||||
func (b *testBlock) Nonce() uint64 { return b.nonce } |
||||
func (b *testBlock) MixDigest() common.Hash { return b.mixDigest } |
||||
func (b *testBlock) NumberU64() uint64 { return b.number } |
||||
|
||||
var validBlocks = []*testBlock{ |
||||
// from proof of concept nine testnet, epoch 0
|
||||
{ |
||||
number: 22, |
||||
hashNoNonce: common.HexToHash("372eca2454ead349c3df0ab5d00b0b706b23e49d469387db91811cee0358fc6d"), |
||||
difficulty: big.NewInt(132416), |
||||
nonce: 0x495732e0ed7a801c, |
||||
mixDigest: common.HexToHash("2f74cdeb198af0b9abe65d22d372e22fb2d474371774a9583c1cc427a07939f5"), |
||||
}, |
||||
// from proof of concept nine testnet, epoch 1
|
||||
{ |
||||
number: 30001, |
||||
hashNoNonce: common.HexToHash("7e44356ee3441623bc72a683fd3708fdf75e971bbe294f33e539eedad4b92b34"), |
||||
difficulty: big.NewInt(1532671), |
||||
nonce: 0x318df1c8adef7e5e, |
||||
mixDigest: common.HexToHash("144b180aad09ae3c81fb07be92c8e6351b5646dda80e6844ae1b697e55ddde84"), |
||||
}, |
||||
// from proof of concept nine testnet, epoch 2
|
||||
{ |
||||
number: 60000, |
||||
hashNoNonce: common.HexToHash("5fc898f16035bf5ac9c6d9077ae1e3d5fc1ecc3c9fd5bee8bb00e810fdacbaa0"), |
||||
difficulty: big.NewInt(2467358), |
||||
nonce: 0x50377003e5d830ca, |
||||
mixDigest: common.HexToHash("ab546a5b73c452ae86dadd36f0ed83a6745226717d3798832d1b20b489e82063"), |
||||
}, |
||||
} |
||||
|
||||
var invalidZeroDiffBlock = testBlock{ |
||||
number: 61440000, |
||||
hashNoNonce: crypto.Sha3Hash([]byte("foo")), |
||||
difficulty: big.NewInt(0), |
||||
nonce: 0xcafebabec00000fe, |
||||
mixDigest: crypto.Sha3Hash([]byte("bar")), |
||||
} |
||||
|
||||
func TestEthashVerifyValid(t *testing.T) { |
||||
eth := New() |
||||
for i, block := range validBlocks { |
||||
if !eth.Verify(block) { |
||||
t.Errorf("block %d (%x) did not validate.", i, block.hashNoNonce[:6]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestEthashVerifyInvalid(t *testing.T) { |
||||
eth := New() |
||||
if eth.Verify(&invalidZeroDiffBlock) { |
||||
t.Errorf("should not validate - we just ensure it does not panic on this block") |
||||
} |
||||
} |
||||
|
||||
func TestEthashConcurrentVerify(t *testing.T) { |
||||
eth, err := NewForTesting() |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
defer os.RemoveAll(eth.Full.Dir) |
||||
|
||||
block := &testBlock{difficulty: big.NewInt(10)} |
||||
nonce, md := eth.Search(block, nil, 0) |
||||
block.nonce = nonce |
||||
block.mixDigest = common.BytesToHash(md) |
||||
|
||||
// Verify the block concurrently to check for data races.
|
||||
var wg sync.WaitGroup |
||||
wg.Add(100) |
||||
for i := 0; i < 100; i++ { |
||||
go func() { |
||||
if !eth.Verify(block) { |
||||
t.Error("Block could not be verified") |
||||
} |
||||
wg.Done() |
||||
}() |
||||
} |
||||
wg.Wait() |
||||
} |
||||
|
||||
func TestEthashConcurrentSearch(t *testing.T) { |
||||
eth, err := NewForTesting() |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
eth.Turbo(true) |
||||
defer os.RemoveAll(eth.Full.Dir) |
||||
|
||||
type searchRes struct { |
||||
n uint64 |
||||
md []byte |
||||
} |
||||
|
||||
var ( |
||||
block = &testBlock{difficulty: big.NewInt(35000)} |
||||
nsearch = 10 |
||||
wg = new(sync.WaitGroup) |
||||
found = make(chan searchRes) |
||||
stop = make(chan struct{}) |
||||
) |
||||
rand.Read(block.hashNoNonce[:]) |
||||
wg.Add(nsearch) |
||||
// launch n searches concurrently.
|
||||
for i := 0; i < nsearch; i++ { |
||||
go func() { |
||||
nonce, md := eth.Search(block, stop, 0) |
||||
select { |
||||
case found <- searchRes{n: nonce, md: md}: |
||||
case <-stop: |
||||
} |
||||
wg.Done() |
||||
}() |
||||
} |
||||
|
||||
// wait for one of them to find the nonce
|
||||
res := <-found |
||||
// stop the others
|
||||
close(stop) |
||||
wg.Wait() |
||||
|
||||
block.nonce = res.n |
||||
block.mixDigest = common.BytesToHash(res.md) |
||||
if !eth.Verify(block) { |
||||
t.Error("Block could not be verified") |
||||
} |
||||
} |
||||
|
||||
func TestEthashSearchAcrossEpoch(t *testing.T) { |
||||
eth, err := NewForTesting() |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
defer os.RemoveAll(eth.Full.Dir) |
||||
|
||||
for i := epochLength - 40; i < epochLength+40; i++ { |
||||
block := &testBlock{number: i, difficulty: big.NewInt(90)} |
||||
rand.Read(block.hashNoNonce[:]) |
||||
nonce, md := eth.Search(block, nil, 0) |
||||
block.nonce = nonce |
||||
block.mixDigest = common.BytesToHash(md) |
||||
if !eth.Verify(block) { |
||||
t.Fatalf("Block could not be verified") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestGetSeedHash(t *testing.T) { |
||||
seed0, err := GetSeedHash(0) |
||||
if err != nil { |
||||
t.Errorf("Failed to get seedHash for block 0: %v", err) |
||||
} |
||||
if bytes.Compare(seed0, make([]byte, 32)) != 0 { |
||||
log.Printf("seedHash for block 0 should be 0s, was: %v\n", seed0) |
||||
} |
||||
seed1, err := GetSeedHash(30000) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
// From python:
|
||||
// > from pyethash import get_seedhash
|
||||
// > get_seedhash(30000)
|
||||
expectedSeed1, err := hex.DecodeString("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
if bytes.Compare(seed1, expectedSeed1) != 0 { |
||||
log.Printf("seedHash for block 1 should be: %v,\nactual value: %v\n", expectedSeed1, seed1) |
||||
} |
||||
|
||||
} |
@ -1,176 +0,0 @@ |
||||
package color |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"os" |
||||
"testing" |
||||
|
||||
"github.com/shiena/ansicolor" |
||||
) |
||||
|
||||
// Testing colors is kinda different. First we test for given colors and their
|
||||
// escaped formatted results. Next we create some visual tests to be tested.
|
||||
// Each visual test includes the color name to be compared.
|
||||
func TestColor(t *testing.T) { |
||||
rb := new(bytes.Buffer) |
||||
Output = rb |
||||
|
||||
testColors := []struct { |
||||
text string |
||||
code Attribute |
||||
}{ |
||||
{text: "black", code: FgBlack}, |
||||
{text: "red", code: FgRed}, |
||||
{text: "green", code: FgGreen}, |
||||
{text: "yellow", code: FgYellow}, |
||||
{text: "blue", code: FgBlue}, |
||||
{text: "magent", code: FgMagenta}, |
||||
{text: "cyan", code: FgCyan}, |
||||
{text: "white", code: FgWhite}, |
||||
} |
||||
|
||||
for _, c := range testColors { |
||||
New(c.code).Print(c.text) |
||||
|
||||
line, _ := rb.ReadString('\n') |
||||
scannedLine := fmt.Sprintf("%q", line) |
||||
colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text) |
||||
escapedForm := fmt.Sprintf("%q", colored) |
||||
|
||||
fmt.Printf("%s\t: %s\n", c.text, line) |
||||
|
||||
if scannedLine != escapedForm { |
||||
t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestNoColor(t *testing.T) { |
||||
rb := new(bytes.Buffer) |
||||
Output = rb |
||||
|
||||
testColors := []struct { |
||||
text string |
||||
code Attribute |
||||
}{ |
||||
{text: "black", code: FgBlack}, |
||||
{text: "red", code: FgRed}, |
||||
{text: "green", code: FgGreen}, |
||||
{text: "yellow", code: FgYellow}, |
||||
{text: "blue", code: FgBlue}, |
||||
{text: "magent", code: FgMagenta}, |
||||
{text: "cyan", code: FgCyan}, |
||||
{text: "white", code: FgWhite}, |
||||
} |
||||
|
||||
for _, c := range testColors { |
||||
p := New(c.code) |
||||
p.DisableColor() |
||||
p.Print(c.text) |
||||
|
||||
line, _ := rb.ReadString('\n') |
||||
if line != c.text { |
||||
t.Errorf("Expecting %s, got '%s'\n", c.text, line) |
||||
} |
||||
} |
||||
|
||||
// global check
|
||||
NoColor = true |
||||
defer func() { |
||||
NoColor = false |
||||
}() |
||||
for _, c := range testColors { |
||||
p := New(c.code) |
||||
p.Print(c.text) |
||||
|
||||
line, _ := rb.ReadString('\n') |
||||
if line != c.text { |
||||
t.Errorf("Expecting %s, got '%s'\n", c.text, line) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestColorVisual(t *testing.T) { |
||||
// First Visual Test
|
||||
fmt.Println("") |
||||
Output = ansicolor.NewAnsiColorWriter(os.Stdout) |
||||
|
||||
New(FgRed).Printf("red\t") |
||||
New(BgRed).Print(" ") |
||||
New(FgRed, Bold).Println(" red") |
||||
|
||||
New(FgGreen).Printf("green\t") |
||||
New(BgGreen).Print(" ") |
||||
New(FgGreen, Bold).Println(" green") |
||||
|
||||
New(FgYellow).Printf("yellow\t") |
||||
New(BgYellow).Print(" ") |
||||
New(FgYellow, Bold).Println(" yellow") |
||||
|
||||
New(FgBlue).Printf("blue\t") |
||||
New(BgBlue).Print(" ") |
||||
New(FgBlue, Bold).Println(" blue") |
||||
|
||||
New(FgMagenta).Printf("magenta\t") |
||||
New(BgMagenta).Print(" ") |
||||
New(FgMagenta, Bold).Println(" magenta") |
||||
|
||||
New(FgCyan).Printf("cyan\t") |
||||
New(BgCyan).Print(" ") |
||||
New(FgCyan, Bold).Println(" cyan") |
||||
|
||||
New(FgWhite).Printf("white\t") |
||||
New(BgWhite).Print(" ") |
||||
New(FgWhite, Bold).Println(" white") |
||||
fmt.Println("") |
||||
|
||||
// Second Visual test
|
||||
Black("black") |
||||
Red("red") |
||||
Green("green") |
||||
Yellow("yellow") |
||||
Blue("blue") |
||||
Magenta("magenta") |
||||
Cyan("cyan") |
||||
White("white") |
||||
|
||||
// Third visual test
|
||||
fmt.Println() |
||||
Set(FgBlue) |
||||
fmt.Println("is this blue?") |
||||
Unset() |
||||
|
||||
Set(FgMagenta) |
||||
fmt.Println("and this magenta?") |
||||
Unset() |
||||
|
||||
// Fourth Visual test
|
||||
fmt.Println() |
||||
blue := New(FgBlue).PrintlnFunc() |
||||
blue("blue text with custom print func") |
||||
|
||||
red := New(FgRed).PrintfFunc() |
||||
red("red text with a printf func: %d\n", 123) |
||||
|
||||
put := New(FgYellow).SprintFunc() |
||||
warn := New(FgRed).SprintFunc() |
||||
|
||||
fmt.Fprintf(Output, "this is a %s and this is %s.\n", put("warning"), warn("error")) |
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc() |
||||
fmt.Fprintf(Output, "this %s rocks!\n", info("package")) |
||||
|
||||
// Fifth Visual Test
|
||||
fmt.Println() |
||||
|
||||
fmt.Fprintln(Output, BlackString("black")) |
||||
fmt.Fprintln(Output, RedString("red")) |
||||
fmt.Fprintln(Output, GreenString("green")) |
||||
fmt.Fprintln(Output, YellowString("yellow")) |
||||
fmt.Fprintln(Output, BlueString("blue")) |
||||
fmt.Fprintln(Output, MagentaString("magenta")) |
||||
fmt.Fprintln(Output, CyanString("cyan")) |
||||
fmt.Fprintln(Output, WhiteString("white")) |
||||
} |
@ -1,142 +1,240 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
import "image" |
||||
|
||||
// Hline is a horizontal line.
|
||||
type Hline struct { |
||||
X int |
||||
Y int |
||||
Len int |
||||
Fg Attribute |
||||
Bg Attribute |
||||
} |
||||
|
||||
// Vline is a vertical line.
|
||||
type Vline struct { |
||||
X int |
||||
Y int |
||||
Len int |
||||
Fg Attribute |
||||
Bg Attribute |
||||
} |
||||
|
||||
// Buffer draws a horizontal line.
|
||||
func (l Hline) Buffer() Buffer { |
||||
if l.Len <= 0 { |
||||
return NewBuffer() |
||||
} |
||||
return NewFilledBuffer(l.X, l.Y, l.X+l.Len, l.Y+1, HORIZONTAL_LINE, l.Fg, l.Bg) |
||||
} |
||||
|
||||
// Buffer draws a vertical line.
|
||||
func (l Vline) Buffer() Buffer { |
||||
if l.Len <= 0 { |
||||
return NewBuffer() |
||||
} |
||||
return NewFilledBuffer(l.X, l.Y, l.X+1, l.Y+l.Len, VERTICAL_LINE, l.Fg, l.Bg) |
||||
} |
||||
|
||||
// Buffer draws a box border.
|
||||
func (b Block) drawBorder(buf Buffer) { |
||||
if !b.Border { |
||||
return |
||||
} |
||||
|
||||
min := b.area.Min |
||||
max := b.area.Max |
||||
|
||||
x0 := min.X |
||||
y0 := min.Y |
||||
x1 := max.X - 1 |
||||
y1 := max.Y - 1 |
||||
|
||||
// draw lines
|
||||
if b.BorderTop { |
||||
buf.Merge(Hline{x0, y0, x1 - x0, b.BorderFg, b.BorderBg}.Buffer()) |
||||
} |
||||
if b.BorderBottom { |
||||
buf.Merge(Hline{x0, y1, x1 - x0, b.BorderFg, b.BorderBg}.Buffer()) |
||||
} |
||||
if b.BorderLeft { |
||||
buf.Merge(Vline{x0, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer()) |
||||
} |
||||
if b.BorderRight { |
||||
buf.Merge(Vline{x1, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer()) |
||||
} |
||||
|
||||
// draw corners
|
||||
if b.BorderTop && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 0 { |
||||
buf.Set(x0, y0, Cell{TOP_LEFT, b.BorderFg, b.BorderBg}) |
||||
} |
||||
if b.BorderTop && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 0 { |
||||
buf.Set(x1, y0, Cell{TOP_RIGHT, b.BorderFg, b.BorderBg}) |
||||
} |
||||
if b.BorderBottom && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 1 { |
||||
buf.Set(x0, y1, Cell{BOTTOM_LEFT, b.BorderFg, b.BorderBg}) |
||||
} |
||||
if b.BorderBottom && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 1 { |
||||
buf.Set(x1, y1, Cell{BOTTOM_RIGHT, b.BorderFg, b.BorderBg}) |
||||
} |
||||
} |
||||
|
||||
func (b Block) drawBorderLabel(buf Buffer) { |
||||
maxTxtW := b.area.Dx() - 2 |
||||
tx := DTrimTxCls(DefaultTxBuilder.Build(b.BorderLabel, b.BorderLabelFg, b.BorderLabelBg), maxTxtW) |
||||
|
||||
for i, w := 0, 0; i < len(tx); i++ { |
||||
buf.Set(b.area.Min.X+1+w, b.area.Min.Y, tx[i]) |
||||
w += tx[i].Width() |
||||
} |
||||
} |
||||
|
||||
// Block is a base struct for all other upper level widgets,
|
||||
// consider it as css: display:block.
|
||||
// Normally you do not need to create it manually.
|
||||
type Block struct { |
||||
area image.Rectangle |
||||
innerArea image.Rectangle |
||||
X int |
||||
Y int |
||||
Border labeledBorder |
||||
IsDisplay bool |
||||
HasBorder bool |
||||
BgColor Attribute |
||||
Border bool |
||||
BorderFg Attribute |
||||
BorderBg Attribute |
||||
BorderLeft bool |
||||
BorderRight bool |
||||
BorderTop bool |
||||
BorderBottom bool |
||||
BorderLabel string |
||||
BorderLabelFg Attribute |
||||
BorderLabelBg Attribute |
||||
Display bool |
||||
Bg Attribute |
||||
Width int |
||||
Height int |
||||
innerWidth int |
||||
innerHeight int |
||||
innerX int |
||||
innerY int |
||||
PaddingTop int |
||||
PaddingBottom int |
||||
PaddingLeft int |
||||
PaddingRight int |
||||
id string |
||||
Float Align |
||||
} |
||||
|
||||
// NewBlock returns a *Block which inherits styles from current theme.
|
||||
func NewBlock() *Block { |
||||
d := Block{} |
||||
d.IsDisplay = true |
||||
d.HasBorder = theme.HasBorder |
||||
d.Border.BgColor = theme.BorderBg |
||||
d.Border.FgColor = theme.BorderFg |
||||
d.Border.LabelBgColor = theme.BorderLabelTextBg |
||||
d.Border.LabelFgColor = theme.BorderLabelTextFg |
||||
d.BgColor = theme.BlockBg |
||||
d.Width = 2 |
||||
d.Height = 2 |
||||
return &d |
||||
} |
||||
|
||||
// compute box model
|
||||
func (d *Block) align() { |
||||
d.innerWidth = d.Width - d.PaddingLeft - d.PaddingRight |
||||
d.innerHeight = d.Height - d.PaddingTop - d.PaddingBottom |
||||
d.innerX = d.X + d.PaddingLeft |
||||
d.innerY = d.Y + d.PaddingTop |
||||
|
||||
if d.HasBorder { |
||||
d.innerHeight -= 2 |
||||
d.innerWidth -= 2 |
||||
d.Border.X = d.X |
||||
d.Border.Y = d.Y |
||||
d.Border.Width = d.Width |
||||
d.Border.Height = d.Height |
||||
d.innerX++ |
||||
d.innerY++ |
||||
} |
||||
b := Block{} |
||||
b.Display = true |
||||
b.Border = true |
||||
b.BorderLeft = true |
||||
b.BorderRight = true |
||||
b.BorderTop = true |
||||
b.BorderBottom = true |
||||
b.BorderBg = ThemeAttr("border.bg") |
||||
b.BorderFg = ThemeAttr("border.fg") |
||||
b.BorderLabelBg = ThemeAttr("label.bg") |
||||
b.BorderLabelFg = ThemeAttr("label.fg") |
||||
b.Bg = ThemeAttr("block.bg") |
||||
b.Width = 2 |
||||
b.Height = 2 |
||||
b.id = GenId() |
||||
b.Float = AlignNone |
||||
return &b |
||||
} |
||||
|
||||
if d.innerHeight < 0 { |
||||
d.innerHeight = 0 |
||||
} |
||||
if d.innerWidth < 0 { |
||||
d.innerWidth = 0 |
||||
} |
||||
func (b Block) Id() string { |
||||
return b.id |
||||
} |
||||
|
||||
// Align computes box model
|
||||
func (b *Block) Align() { |
||||
// outer
|
||||
b.area.Min.X = 0 |
||||
b.area.Min.Y = 0 |
||||
b.area.Max.X = b.Width |
||||
b.area.Max.Y = b.Height |
||||
|
||||
// float
|
||||
b.area = AlignArea(TermRect(), b.area, b.Float) |
||||
b.area = MoveArea(b.area, b.X, b.Y) |
||||
|
||||
// inner
|
||||
b.innerArea.Min.X = b.area.Min.X + b.PaddingLeft |
||||
b.innerArea.Min.Y = b.area.Min.Y + b.PaddingTop |
||||
b.innerArea.Max.X = b.area.Max.X - b.PaddingRight |
||||
b.innerArea.Max.Y = b.area.Max.Y - b.PaddingBottom |
||||
|
||||
if b.Border { |
||||
if b.BorderLeft { |
||||
b.innerArea.Min.X++ |
||||
} |
||||
if b.BorderRight { |
||||
b.innerArea.Max.X-- |
||||
} |
||||
if b.BorderTop { |
||||
b.innerArea.Min.Y++ |
||||
} |
||||
if b.BorderBottom { |
||||
b.innerArea.Max.Y-- |
||||
} |
||||
} |
||||
} |
||||
|
||||
// InnerBounds returns the internal bounds of the block after aligning and
|
||||
// calculating the padding and border, if any.
|
||||
func (d *Block) InnerBounds() (x, y, width, height int) { |
||||
d.align() |
||||
return d.innerX, d.innerY, d.innerWidth, d.innerHeight |
||||
func (b *Block) InnerBounds() image.Rectangle { |
||||
b.Align() |
||||
return b.innerArea |
||||
} |
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
// Draw background and border (if any).
|
||||
func (d *Block) Buffer() []Point { |
||||
d.align() |
||||
func (b *Block) Buffer() Buffer { |
||||
b.Align() |
||||
|
||||
ps := []Point{} |
||||
if !d.IsDisplay { |
||||
return ps |
||||
} |
||||
buf := NewBuffer() |
||||
buf.SetArea(b.area) |
||||
buf.Fill(' ', ColorDefault, b.Bg) |
||||
|
||||
if d.HasBorder { |
||||
ps = d.Border.Buffer() |
||||
} |
||||
b.drawBorder(buf) |
||||
b.drawBorderLabel(buf) |
||||
|
||||
for i := 0; i < d.innerWidth; i++ { |
||||
for j := 0; j < d.innerHeight; j++ { |
||||
p := Point{} |
||||
p.X = d.X + 1 + i |
||||
p.Y = d.Y + 1 + j |
||||
p.Ch = ' ' |
||||
p.Bg = d.BgColor |
||||
ps = append(ps, p) |
||||
} |
||||
} |
||||
return ps |
||||
return buf |
||||
} |
||||
|
||||
// GetHeight implements GridBufferer.
|
||||
// It returns current height of the block.
|
||||
func (d Block) GetHeight() int { |
||||
return d.Height |
||||
func (b Block) GetHeight() int { |
||||
return b.Height |
||||
} |
||||
|
||||
// SetX implements GridBufferer interface, which sets block's x position.
|
||||
func (d *Block) SetX(x int) { |
||||
d.X = x |
||||
func (b *Block) SetX(x int) { |
||||
b.X = x |
||||
} |
||||
|
||||
// SetY implements GridBufferer interface, it sets y position for block.
|
||||
func (d *Block) SetY(y int) { |
||||
d.Y = y |
||||
func (b *Block) SetY(y int) { |
||||
b.Y = y |
||||
} |
||||
|
||||
// SetWidth implements GridBuffer interface, it sets block's width.
|
||||
func (d *Block) SetWidth(w int) { |
||||
d.Width = w |
||||
} |
||||
|
||||
// chop the overflow parts
|
||||
func (d *Block) chopOverflow(ps []Point) []Point { |
||||
nps := make([]Point, 0, len(ps)) |
||||
x := d.X |
||||
y := d.Y |
||||
w := d.Width |
||||
h := d.Height |
||||
for _, v := range ps { |
||||
if v.X >= x && |
||||
v.X < x+w && |
||||
v.Y >= y && |
||||
v.Y < y+h { |
||||
nps = append(nps, v) |
||||
} |
||||
} |
||||
return nps |
||||
func (b *Block) SetWidth(w int) { |
||||
b.Width = w |
||||
} |
||||
|
||||
func (b Block) InnerWidth() int { |
||||
return b.innerArea.Dx() |
||||
} |
||||
|
||||
func (b Block) InnerHeight() int { |
||||
return b.innerArea.Dy() |
||||
} |
||||
|
||||
func (b Block) InnerX() int { |
||||
return b.innerArea.Min.X |
||||
} |
||||
|
||||
func (b Block) InnerY() int { return b.innerArea.Min.Y } |
||||
|
@ -1,46 +0,0 @@ |
||||
package termui |
||||
|
||||
import "testing" |
||||
|
||||
func TestBlock_InnerBounds(t *testing.T) { |
||||
b := NewBlock() |
||||
b.X = 10 |
||||
b.Y = 11 |
||||
b.Width = 12 |
||||
b.Height = 13 |
||||
|
||||
assert := func(name string, x, y, w, h int) { |
||||
t.Log(name) |
||||
cx, cy, cw, ch := b.InnerBounds() |
||||
if cx != x { |
||||
t.Errorf("expected x to be %d but got %d", x, cx) |
||||
} |
||||
if cy != y { |
||||
t.Errorf("expected y to be %d but got %d", y, cy) |
||||
} |
||||
if cw != w { |
||||
t.Errorf("expected width to be %d but got %d", w, cw) |
||||
} |
||||
if ch != h { |
||||
t.Errorf("expected height to be %d but got %d", h, ch) |
||||
} |
||||
} |
||||
|
||||
b.HasBorder = false |
||||
assert("no border, no padding", 10, 11, 12, 13) |
||||
|
||||
b.HasBorder = true |
||||
assert("border, no padding", 11, 12, 10, 11) |
||||
|
||||
b.PaddingBottom = 2 |
||||
assert("border, 2b padding", 11, 12, 10, 9) |
||||
|
||||
b.PaddingTop = 3 |
||||
assert("border, 2b 3t padding", 11, 15, 10, 6) |
||||
|
||||
b.PaddingLeft = 4 |
||||
assert("border, 2b 3t 4l padding", 15, 15, 6, 6) |
||||
|
||||
b.PaddingRight = 5 |
||||
assert("border, 2b 3t 4l 5r padding", 15, 15, 1, 6) |
||||
} |
@ -1,4 +1,4 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
@ -1,117 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
type border struct { |
||||
X int |
||||
Y int |
||||
Width int |
||||
Height int |
||||
FgColor Attribute |
||||
BgColor Attribute |
||||
} |
||||
|
||||
type hline struct { |
||||
X int |
||||
Y int |
||||
Length int |
||||
FgColor Attribute |
||||
BgColor Attribute |
||||
} |
||||
|
||||
type vline struct { |
||||
X int |
||||
Y int |
||||
Length int |
||||
FgColor Attribute |
||||
BgColor Attribute |
||||
} |
||||
|
||||
// Draw a horizontal line.
|
||||
func (l hline) Buffer() []Point { |
||||
pts := make([]Point, l.Length) |
||||
for i := 0; i < l.Length; i++ { |
||||
pts[i].X = l.X + i |
||||
pts[i].Y = l.Y |
||||
pts[i].Ch = HORIZONTAL_LINE |
||||
pts[i].Bg = l.BgColor |
||||
pts[i].Fg = l.FgColor |
||||
} |
||||
return pts |
||||
} |
||||
|
||||
// Draw a vertical line.
|
||||
func (l vline) Buffer() []Point { |
||||
pts := make([]Point, l.Length) |
||||
for i := 0; i < l.Length; i++ { |
||||
pts[i].X = l.X |
||||
pts[i].Y = l.Y + i |
||||
pts[i].Ch = VERTICAL_LINE |
||||
pts[i].Bg = l.BgColor |
||||
pts[i].Fg = l.FgColor |
||||
} |
||||
return pts |
||||
} |
||||
|
||||
// Draw a box border.
|
||||
func (b border) Buffer() []Point { |
||||
if b.Width < 2 || b.Height < 2 { |
||||
return nil |
||||
} |
||||
pts := make([]Point, 2*b.Width+2*b.Height-4) |
||||
|
||||
pts[0].X = b.X |
||||
pts[0].Y = b.Y |
||||
pts[0].Fg = b.FgColor |
||||
pts[0].Bg = b.BgColor |
||||
pts[0].Ch = TOP_LEFT |
||||
|
||||
pts[1].X = b.X + b.Width - 1 |
||||
pts[1].Y = b.Y |
||||
pts[1].Fg = b.FgColor |
||||
pts[1].Bg = b.BgColor |
||||
pts[1].Ch = TOP_RIGHT |
||||
|
||||
pts[2].X = b.X |
||||
pts[2].Y = b.Y + b.Height - 1 |
||||
pts[2].Fg = b.FgColor |
||||
pts[2].Bg = b.BgColor |
||||
pts[2].Ch = BOTTOM_LEFT |
||||
|
||||
pts[3].X = b.X + b.Width - 1 |
||||
pts[3].Y = b.Y + b.Height - 1 |
||||
pts[3].Fg = b.FgColor |
||||
pts[3].Bg = b.BgColor |
||||
pts[3].Ch = BOTTOM_RIGHT |
||||
|
||||
copy(pts[4:], (hline{b.X + 1, b.Y, b.Width - 2, b.FgColor, b.BgColor}).Buffer()) |
||||
copy(pts[4+b.Width-2:], (hline{b.X + 1, b.Y + b.Height - 1, b.Width - 2, b.FgColor, b.BgColor}).Buffer()) |
||||
copy(pts[4+2*b.Width-4:], (vline{b.X, b.Y + 1, b.Height - 2, b.FgColor, b.BgColor}).Buffer()) |
||||
copy(pts[4+2*b.Width-4+b.Height-2:], (vline{b.X + b.Width - 1, b.Y + 1, b.Height - 2, b.FgColor, b.BgColor}).Buffer()) |
||||
|
||||
return pts |
||||
} |
||||
|
||||
type labeledBorder struct { |
||||
border |
||||
Label string |
||||
LabelFgColor Attribute |
||||
LabelBgColor Attribute |
||||
} |
||||
|
||||
// Draw a box border with label.
|
||||
func (lb labeledBorder) Buffer() []Point { |
||||
ps := lb.border.Buffer() |
||||
maxTxtW := lb.Width - 2 |
||||
rs := trimStr2Runes(lb.Label, maxTxtW) |
||||
|
||||
for i, j, w := 0, 0, 0; i < len(rs); i++ { |
||||
w = charWidth(rs[i]) |
||||
ps = append(ps, newPointWithAttrs(rs[i], lb.X+1+j, lb.Y, lb.LabelFgColor, lb.LabelBgColor)) |
||||
j += w |
||||
} |
||||
|
||||
return ps |
||||
} |
@ -0,0 +1,106 @@ |
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
import "image" |
||||
|
||||
// Cell is a rune with assigned Fg and Bg
|
||||
type Cell struct { |
||||
Ch rune |
||||
Fg Attribute |
||||
Bg Attribute |
||||
} |
||||
|
||||
// Buffer is a renderable rectangle cell data container.
|
||||
type Buffer struct { |
||||
Area image.Rectangle // selected drawing area
|
||||
CellMap map[image.Point]Cell |
||||
} |
||||
|
||||
// At returns the cell at (x,y).
|
||||
func (b Buffer) At(x, y int) Cell { |
||||
return b.CellMap[image.Pt(x, y)] |
||||
} |
||||
|
||||
// Set assigns a char to (x,y)
|
||||
func (b Buffer) Set(x, y int, c Cell) { |
||||
b.CellMap[image.Pt(x, y)] = c |
||||
} |
||||
|
||||
// Bounds returns the domain for which At can return non-zero color.
|
||||
func (b Buffer) Bounds() image.Rectangle { |
||||
x0, y0, x1, y1 := 0, 0, 0, 0 |
||||
for p := range b.CellMap { |
||||
if p.X > x1 { |
||||
x1 = p.X |
||||
} |
||||
if p.X < x0 { |
||||
x0 = p.X |
||||
} |
||||
if p.Y > y1 { |
||||
y1 = p.Y |
||||
} |
||||
if p.Y < y0 { |
||||
y0 = p.Y |
||||
} |
||||
} |
||||
return image.Rect(x0, y0, x1, y1) |
||||
} |
||||
|
||||
// SetArea assigns a new rect area to Buffer b.
|
||||
func (b *Buffer) SetArea(r image.Rectangle) { |
||||
b.Area.Max = r.Max |
||||
b.Area.Min = r.Min |
||||
} |
||||
|
||||
// Sync sets drawing area to the buffer's bound
|
||||
func (b Buffer) Sync() { |
||||
b.SetArea(b.Bounds()) |
||||
} |
||||
|
||||
// NewCell returns a new cell
|
||||
func NewCell(ch rune, fg, bg Attribute) Cell { |
||||
return Cell{ch, fg, bg} |
||||
} |
||||
|
||||
// Merge merges bs Buffers onto b
|
||||
func (b *Buffer) Merge(bs ...Buffer) { |
||||
for _, buf := range bs { |
||||
for p, v := range buf.CellMap { |
||||
b.Set(p.X, p.Y, v) |
||||
} |
||||
b.SetArea(b.Area.Union(buf.Area)) |
||||
} |
||||
} |
||||
|
||||
// NewBuffer returns a new Buffer
|
||||
func NewBuffer() Buffer { |
||||
return Buffer{ |
||||
CellMap: make(map[image.Point]Cell), |
||||
Area: image.Rectangle{}} |
||||
} |
||||
|
||||
// Fill fills the Buffer b with ch,fg and bg.
|
||||
func (b Buffer) Fill(ch rune, fg, bg Attribute) { |
||||
for x := b.Area.Min.X; x < b.Area.Max.X; x++ { |
||||
for y := b.Area.Min.Y; y < b.Area.Max.Y; y++ { |
||||
b.Set(x, y, Cell{ch, fg, bg}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// NewFilledBuffer returns a new Buffer filled with ch, fb and bg.
|
||||
func NewFilledBuffer(x0, y0, x1, y1 int, ch rune, fg, bg Attribute) Buffer { |
||||
buf := NewBuffer() |
||||
buf.Area.Min = image.Pt(x0, y0) |
||||
buf.Area.Max = image.Pt(x1, y1) |
||||
|
||||
for x := buf.Area.Min.X; x < buf.Area.Max.X; x++ { |
||||
for y := buf.Area.Min.Y; y < buf.Area.Max.Y; y++ { |
||||
buf.Set(x, y, Cell{ch, fg, bg}) |
||||
} |
||||
} |
||||
return buf |
||||
} |
@ -1,55 +0,0 @@ |
||||
package termui |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/davecgh/go-spew/spew" |
||||
) |
||||
|
||||
func TestCanvasSet(t *testing.T) { |
||||
c := NewCanvas() |
||||
c.Set(0, 0) |
||||
c.Set(0, 1) |
||||
c.Set(0, 2) |
||||
c.Set(0, 3) |
||||
c.Set(1, 3) |
||||
c.Set(2, 3) |
||||
c.Set(3, 3) |
||||
c.Set(4, 3) |
||||
c.Set(5, 3) |
||||
spew.Dump(c) |
||||
} |
||||
|
||||
func TestCanvasUnset(t *testing.T) { |
||||
c := NewCanvas() |
||||
c.Set(0, 0) |
||||
c.Set(0, 1) |
||||
c.Set(0, 2) |
||||
c.Unset(0, 2) |
||||
spew.Dump(c) |
||||
c.Unset(0, 3) |
||||
spew.Dump(c) |
||||
} |
||||
|
||||
func TestCanvasBuffer(t *testing.T) { |
||||
c := NewCanvas() |
||||
c.Set(0, 0) |
||||
c.Set(0, 1) |
||||
c.Set(0, 2) |
||||
c.Set(0, 3) |
||||
c.Set(1, 3) |
||||
c.Set(2, 3) |
||||
c.Set(3, 3) |
||||
c.Set(4, 3) |
||||
c.Set(5, 3) |
||||
c.Set(6, 3) |
||||
c.Set(7, 2) |
||||
c.Set(8, 1) |
||||
c.Set(9, 0) |
||||
bufs := c.Buffer() |
||||
rs := make([]rune, len(bufs)) |
||||
for i, v := range bufs { |
||||
rs[i] = v.Ch |
||||
} |
||||
spew.Dump(string(rs)) |
||||
} |
@ -0,0 +1,26 @@ |
||||
#!/usr/bin/env perl6 |
||||
|
||||
use v6; |
||||
|
||||
my $copyright = '// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved. |
||||
// Use of this source code is governed by a MIT license that can |
||||
// be found in the LICENSE file. |
||||
|
||||
'; |
||||
|
||||
sub MAIN('update-docstr', Str $srcp) { |
||||
if $srcp.IO.f { |
||||
$_ = $srcp.IO.slurp; |
||||
if m/^ \/\/\s Copyright .+? \n\n/ { |
||||
unless ~$/ eq $copyright { |
||||
s/^ \/\/\s Copyright .+? \n\n /$copyright/; |
||||
spurt $srcp, $_; |
||||
say "[updated] doc string for:"~$srcp; |
||||
} |
||||
} else { |
||||
say "[added] doc string for "~$srcp~" (no match found)"; |
||||
$_ = $copyright ~ $_; |
||||
spurt $srcp, $_; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,117 @@ |
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package debug |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
|
||||
"golang.org/x/net/websocket" |
||||
) |
||||
|
||||
type Server struct { |
||||
Port string |
||||
Addr string |
||||
Path string |
||||
Msg chan string |
||||
chs []chan string |
||||
} |
||||
|
||||
type Client struct { |
||||
Port string |
||||
Addr string |
||||
Path string |
||||
ws *websocket.Conn |
||||
} |
||||
|
||||
var defaultPort = ":8080" |
||||
|
||||
func NewServer() *Server { |
||||
return &Server{ |
||||
Port: defaultPort, |
||||
Addr: "localhost", |
||||
Path: "/echo", |
||||
Msg: make(chan string), |
||||
chs: make([]chan string, 0), |
||||
} |
||||
} |
||||
|
||||
func NewClient() Client { |
||||
return Client{ |
||||
Port: defaultPort, |
||||
Addr: "localhost", |
||||
Path: "/echo", |
||||
} |
||||
} |
||||
|
||||
func (c Client) ConnectAndListen() error { |
||||
ws, err := websocket.Dial("ws://"+c.Addr+c.Port+c.Path, "", "http://"+c.Addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer ws.Close() |
||||
|
||||
var m string |
||||
for { |
||||
err := websocket.Message.Receive(ws, &m) |
||||
if err != nil { |
||||
fmt.Print(err) |
||||
return err |
||||
} |
||||
fmt.Print(m) |
||||
} |
||||
} |
||||
|
||||
func (s *Server) ListenAndServe() error { |
||||
http.Handle(s.Path, websocket.Handler(func(ws *websocket.Conn) { |
||||
defer ws.Close() |
||||
|
||||
mc := make(chan string) |
||||
s.chs = append(s.chs, mc) |
||||
|
||||
for m := range mc { |
||||
websocket.Message.Send(ws, m) |
||||
} |
||||
})) |
||||
|
||||
go func() { |
||||
for msg := range s.Msg { |
||||
for _, c := range s.chs { |
||||
go func(a chan string) { |
||||
a <- msg |
||||
}(c) |
||||
} |
||||
} |
||||
}() |
||||
|
||||
return http.ListenAndServe(s.Port, nil) |
||||
} |
||||
|
||||
func (s *Server) Log(msg string) { |
||||
go func() { s.Msg <- msg }() |
||||
} |
||||
|
||||
func (s *Server) Logf(format string, a ...interface{}) { |
||||
s.Log(fmt.Sprintf(format, a...)) |
||||
} |
||||
|
||||
var DefaultServer = NewServer() |
||||
var DefaultClient = NewClient() |
||||
|
||||
func ListenAndServe() error { |
||||
return DefaultServer.ListenAndServe() |
||||
} |
||||
|
||||
func ConnectAndListen() error { |
||||
return DefaultClient.ConnectAndListen() |
||||
} |
||||
|
||||
func Log(msg string) { |
||||
DefaultServer.Log(msg) |
||||
} |
||||
|
||||
func Logf(format string, a ...interface{}) { |
||||
DefaultServer.Logf(format, a...) |
||||
} |
@ -1,28 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
//
|
||||
// Portions of this file uses [termbox-go](https://github.com/nsf/termbox-go/blob/54b74d087b7c397c402d0e3b66d2ccb6eaf5c2b4/api_common.go)
|
||||
// by [authors](https://github.com/nsf/termbox-go/blob/master/AUTHORS)
|
||||
// under [license](https://github.com/nsf/termbox-go/blob/master/LICENSE)
|
||||
|
||||
package termui |
||||
|
||||
import ( |
||||
"errors" |
||||
"testing" |
||||
|
||||
termbox "github.com/nsf/termbox-go" |
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
type boxEvent termbox.Event |
||||
|
||||
func TestUiEvt(t *testing.T) { |
||||
err := errors.New("This is a mock error") |
||||
event := boxEvent{3, 5, 2, 'H', 200, 500, err, 50, 30, 2} |
||||
expetced := Event{3, 5, 2, 'H', 200, 500, err, 50, 30, 2} |
||||
|
||||
// We need to do that ugly casting so that vet does not complain
|
||||
assert.Equal(t, uiEvt(termbox.Event(event)), expetced) |
||||
} |
@ -1,35 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import "github.com/gizak/termui" |
||||
|
||||
func main() { |
||||
err := termui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer termui.Close() |
||||
|
||||
termui.UseTheme("helloworld") |
||||
|
||||
bc := termui.NewBarChart() |
||||
data := []int{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6} |
||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"} |
||||
bc.Border.Label = "Bar Chart" |
||||
bc.Data = data |
||||
bc.Width = 26 |
||||
bc.Height = 10 |
||||
bc.DataLabels = bclabels |
||||
bc.TextColor = termui.ColorGreen |
||||
bc.BarColor = termui.ColorRed |
||||
bc.NumColor = termui.ColorYellow |
||||
|
||||
termui.Render(bc) |
||||
|
||||
<-termui.EventCh() |
||||
} |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 443 KiB |
@ -1,148 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import ui "github.com/gizak/termui" |
||||
import "math" |
||||
|
||||
import "time" |
||||
|
||||
func main() { |
||||
err := ui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer ui.Close() |
||||
|
||||
p := ui.NewPar(":PRESS q TO QUIT DEMO") |
||||
p.Height = 3 |
||||
p.Width = 50 |
||||
p.TextFgColor = ui.ColorWhite |
||||
p.Border.Label = "Text Box" |
||||
p.Border.FgColor = ui.ColorCyan |
||||
|
||||
strs := []string{"[0] gizak/termui", "[1] editbox.go", "[2] iterrupt.go", "[3] keyboard.go", "[4] output.go", "[5] random_out.go", "[6] dashboard.go", "[7] nsf/termbox-go"} |
||||
list := ui.NewList() |
||||
list.Items = strs |
||||
list.ItemFgColor = ui.ColorYellow |
||||
list.Border.Label = "List" |
||||
list.Height = 7 |
||||
list.Width = 25 |
||||
list.Y = 4 |
||||
|
||||
g := ui.NewGauge() |
||||
g.Percent = 50 |
||||
g.Width = 50 |
||||
g.Height = 3 |
||||
g.Y = 11 |
||||
g.Border.Label = "Gauge" |
||||
g.BarColor = ui.ColorRed |
||||
g.Border.FgColor = ui.ColorWhite |
||||
g.Border.LabelFgColor = ui.ColorCyan |
||||
|
||||
spark := ui.Sparkline{} |
||||
spark.Height = 1 |
||||
spark.Title = "srv 0:" |
||||
spdata := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6} |
||||
spark.Data = spdata |
||||
spark.LineColor = ui.ColorCyan |
||||
spark.TitleColor = ui.ColorWhite |
||||
|
||||
spark1 := ui.Sparkline{} |
||||
spark1.Height = 1 |
||||
spark1.Title = "srv 1:" |
||||
spark1.Data = spdata |
||||
spark1.TitleColor = ui.ColorWhite |
||||
spark1.LineColor = ui.ColorRed |
||||
|
||||
sp := ui.NewSparklines(spark, spark1) |
||||
sp.Width = 25 |
||||
sp.Height = 7 |
||||
sp.Border.Label = "Sparkline" |
||||
sp.Y = 4 |
||||
sp.X = 25 |
||||
|
||||
sinps := (func() []float64 { |
||||
n := 220 |
||||
ps := make([]float64, n) |
||||
for i := range ps { |
||||
ps[i] = 1 + math.Sin(float64(i)/5) |
||||
} |
||||
return ps |
||||
})() |
||||
|
||||
lc := ui.NewLineChart() |
||||
lc.Border.Label = "dot-mode Line Chart" |
||||
lc.Data = sinps |
||||
lc.Width = 50 |
||||
lc.Height = 11 |
||||
lc.X = 0 |
||||
lc.Y = 14 |
||||
lc.AxesColor = ui.ColorWhite |
||||
lc.LineColor = ui.ColorRed | ui.AttrBold |
||||
lc.Mode = "dot" |
||||
|
||||
bc := ui.NewBarChart() |
||||
bcdata := []int{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6} |
||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"} |
||||
bc.Border.Label = "Bar Chart" |
||||
bc.Width = 26 |
||||
bc.Height = 10 |
||||
bc.X = 51 |
||||
bc.Y = 0 |
||||
bc.DataLabels = bclabels |
||||
bc.BarColor = ui.ColorGreen |
||||
bc.NumColor = ui.ColorBlack |
||||
|
||||
lc1 := ui.NewLineChart() |
||||
lc1.Border.Label = "braille-mode Line Chart" |
||||
lc1.Data = sinps |
||||
lc1.Width = 26 |
||||
lc1.Height = 11 |
||||
lc1.X = 51 |
||||
lc1.Y = 14 |
||||
lc1.AxesColor = ui.ColorWhite |
||||
lc1.LineColor = ui.ColorYellow | ui.AttrBold |
||||
|
||||
p1 := ui.NewPar("Hey!\nI am a borderless block!") |
||||
p1.HasBorder = false |
||||
p1.Width = 26 |
||||
p1.Height = 2 |
||||
p1.TextFgColor = ui.ColorMagenta |
||||
p1.X = 52 |
||||
p1.Y = 11 |
||||
|
||||
draw := func(t int) { |
||||
g.Percent = t % 101 |
||||
list.Items = strs[t%9:] |
||||
sp.Lines[0].Data = spdata[:30+t%50] |
||||
sp.Lines[1].Data = spdata[:35+t%50] |
||||
lc.Data = sinps[t/2:] |
||||
lc1.Data = sinps[2*t:] |
||||
bc.Data = bcdata[t/2%10:] |
||||
ui.Render(p, list, g, sp, lc, bc, lc1, p1) |
||||
} |
||||
|
||||
evt := ui.EventCh() |
||||
|
||||
i := 0 |
||||
for { |
||||
select { |
||||
case e := <-evt: |
||||
if e.Type == ui.EventKey && e.Ch == 'q' { |
||||
return |
||||
} |
||||
default: |
||||
draw(i) |
||||
i++ |
||||
if i == 102 { |
||||
return |
||||
} |
||||
time.Sleep(time.Second / 2) |
||||
} |
||||
} |
||||
} |
@ -1,62 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import "github.com/gizak/termui" |
||||
|
||||
func main() { |
||||
err := termui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer termui.Close() |
||||
|
||||
termui.UseTheme("helloworld") |
||||
|
||||
g0 := termui.NewGauge() |
||||
g0.Percent = 40 |
||||
g0.Width = 50 |
||||
g0.Height = 3 |
||||
g0.Border.Label = "Slim Gauge" |
||||
g0.BarColor = termui.ColorRed |
||||
g0.Border.FgColor = termui.ColorWhite |
||||
g0.Border.LabelFgColor = termui.ColorCyan |
||||
|
||||
g2 := termui.NewGauge() |
||||
g2.Percent = 60 |
||||
g2.Width = 50 |
||||
g2.Height = 3 |
||||
g2.PercentColor = termui.ColorBlue |
||||
g2.Y = 3 |
||||
g2.Border.Label = "Slim Gauge" |
||||
g2.BarColor = termui.ColorYellow |
||||
g2.Border.FgColor = termui.ColorWhite |
||||
|
||||
g1 := termui.NewGauge() |
||||
g1.Percent = 30 |
||||
g1.Width = 50 |
||||
g1.Height = 5 |
||||
g1.Y = 6 |
||||
g1.Border.Label = "Big Gauge" |
||||
g1.PercentColor = termui.ColorYellow |
||||
g1.BarColor = termui.ColorGreen |
||||
g1.Border.FgColor = termui.ColorWhite |
||||
g1.Border.LabelFgColor = termui.ColorMagenta |
||||
|
||||
g3 := termui.NewGauge() |
||||
g3.Percent = 50 |
||||
g3.Width = 50 |
||||
g3.Height = 3 |
||||
g3.Y = 11 |
||||
g3.Border.Label = "Gauge with custom label" |
||||
g3.Label = "{{percent}}% (100MBs free)" |
||||
g3.LabelAlign = termui.AlignRight |
||||
|
||||
termui.Render(g0, g1, g2, g3) |
||||
|
||||
<-termui.EventCh() |
||||
} |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 782 KiB |
@ -1,134 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import ui "github.com/gizak/termui" |
||||
import "math" |
||||
import "time" |
||||
|
||||
func main() { |
||||
err := ui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer ui.Close() |
||||
|
||||
sinps := (func() []float64 { |
||||
n := 400 |
||||
ps := make([]float64, n) |
||||
for i := range ps { |
||||
ps[i] = 1 + math.Sin(float64(i)/5) |
||||
} |
||||
return ps |
||||
})() |
||||
sinpsint := (func() []int { |
||||
ps := make([]int, len(sinps)) |
||||
for i, v := range sinps { |
||||
ps[i] = int(100*v + 10) |
||||
} |
||||
return ps |
||||
})() |
||||
|
||||
ui.UseTheme("helloworld") |
||||
|
||||
spark := ui.Sparkline{} |
||||
spark.Height = 8 |
||||
spdata := sinpsint |
||||
spark.Data = spdata[:100] |
||||
spark.LineColor = ui.ColorCyan |
||||
spark.TitleColor = ui.ColorWhite |
||||
|
||||
sp := ui.NewSparklines(spark) |
||||
sp.Height = 11 |
||||
sp.Border.Label = "Sparkline" |
||||
|
||||
lc := ui.NewLineChart() |
||||
lc.Border.Label = "braille-mode Line Chart" |
||||
lc.Data = sinps |
||||
lc.Height = 11 |
||||
lc.AxesColor = ui.ColorWhite |
||||
lc.LineColor = ui.ColorYellow | ui.AttrBold |
||||
|
||||
gs := make([]*ui.Gauge, 3) |
||||
for i := range gs { |
||||
gs[i] = ui.NewGauge() |
||||
gs[i].Height = 2 |
||||
gs[i].HasBorder = false |
||||
gs[i].Percent = i * 10 |
||||
gs[i].PaddingBottom = 1 |
||||
gs[i].BarColor = ui.ColorRed |
||||
} |
||||
|
||||
ls := ui.NewList() |
||||
ls.HasBorder = false |
||||
ls.Items = []string{ |
||||
"[1] Downloading File 1", |
||||
"", // == \newline
|
||||
"[2] Downloading File 2", |
||||
"", |
||||
"[3] Uploading File 3", |
||||
} |
||||
ls.Height = 5 |
||||
|
||||
par := ui.NewPar("<> This row has 3 columns\n<- Widgets can be stacked up like left side\n<- Stacked widgets are treated as a single widget") |
||||
par.Height = 5 |
||||
par.Border.Label = "Demonstration" |
||||
|
||||
// build layout
|
||||
ui.Body.AddRows( |
||||
ui.NewRow( |
||||
ui.NewCol(6, 0, sp), |
||||
ui.NewCol(6, 0, lc)), |
||||
ui.NewRow( |
||||
ui.NewCol(3, 0, ls), |
||||
ui.NewCol(3, 0, gs[0], gs[1], gs[2]), |
||||
ui.NewCol(6, 0, par))) |
||||
|
||||
// calculate layout
|
||||
ui.Body.Align() |
||||
|
||||
done := make(chan bool) |
||||
redraw := make(chan bool) |
||||
|
||||
update := func() { |
||||
for i := 0; i < 103; i++ { |
||||
for _, g := range gs { |
||||
g.Percent = (g.Percent + 3) % 100 |
||||
} |
||||
|
||||
sp.Lines[0].Data = spdata[:100+i] |
||||
lc.Data = sinps[2*i:] |
||||
|
||||
time.Sleep(time.Second / 2) |
||||
redraw <- true |
||||
} |
||||
done <- true |
||||
} |
||||
|
||||
evt := ui.EventCh() |
||||
|
||||
ui.Render(ui.Body) |
||||
go update() |
||||
|
||||
for { |
||||
select { |
||||
case e := <-evt: |
||||
if e.Type == ui.EventKey && e.Ch == 'q' { |
||||
return |
||||
} |
||||
if e.Type == ui.EventResize { |
||||
ui.Body.Width = ui.TermWidth() |
||||
ui.Body.Align() |
||||
go func() { redraw <- true }() |
||||
} |
||||
case <-done: |
||||
return |
||||
case <-redraw: |
||||
ui.Render(ui.Body) |
||||
} |
||||
} |
||||
} |
@ -1,68 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"math" |
||||
|
||||
"github.com/gizak/termui" |
||||
) |
||||
|
||||
func main() { |
||||
err := termui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer termui.Close() |
||||
|
||||
termui.UseTheme("helloworld") |
||||
|
||||
sinps := (func() []float64 { |
||||
n := 220 |
||||
ps := make([]float64, n) |
||||
for i := range ps { |
||||
ps[i] = 1 + math.Sin(float64(i)/5) |
||||
} |
||||
return ps |
||||
})() |
||||
|
||||
lc0 := termui.NewLineChart() |
||||
lc0.Border.Label = "braille-mode Line Chart" |
||||
lc0.Data = sinps |
||||
lc0.Width = 50 |
||||
lc0.Height = 12 |
||||
lc0.X = 0 |
||||
lc0.Y = 0 |
||||
lc0.AxesColor = termui.ColorWhite |
||||
lc0.LineColor = termui.ColorGreen | termui.AttrBold |
||||
|
||||
lc1 := termui.NewLineChart() |
||||
lc1.Border.Label = "dot-mode Line Chart" |
||||
lc1.Mode = "dot" |
||||
lc1.Data = sinps |
||||
lc1.Width = 26 |
||||
lc1.Height = 12 |
||||
lc1.X = 51 |
||||
lc1.DotStyle = '+' |
||||
lc1.AxesColor = termui.ColorWhite |
||||
lc1.LineColor = termui.ColorYellow | termui.AttrBold |
||||
|
||||
lc2 := termui.NewLineChart() |
||||
lc2.Border.Label = "dot-mode Line Chart" |
||||
lc2.Mode = "dot" |
||||
lc2.Data = sinps[4:] |
||||
lc2.Width = 77 |
||||
lc2.Height = 16 |
||||
lc2.X = 0 |
||||
lc2.Y = 12 |
||||
lc2.AxesColor = termui.ColorWhite |
||||
lc2.LineColor = termui.ColorCyan | termui.AttrBold |
||||
|
||||
termui.Render(lc0, lc1, lc2) |
||||
|
||||
<-termui.EventCh() |
||||
} |
Before Width: | Height: | Size: 136 KiB |
@ -1,41 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import "github.com/gizak/termui" |
||||
|
||||
func main() { |
||||
err := termui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer termui.Close() |
||||
|
||||
termui.UseTheme("helloworld") |
||||
|
||||
strs := []string{ |
||||
"[0] github.com/gizak/termui", |
||||
"[1] 你好,世界", |
||||
"[2] こんにちは世界", |
||||
"[3] keyboard.go", |
||||
"[4] output.go", |
||||
"[5] random_out.go", |
||||
"[6] dashboard.go", |
||||
"[7] nsf/termbox-go"} |
||||
|
||||
ls := termui.NewList() |
||||
ls.Items = strs |
||||
ls.ItemFgColor = termui.ColorYellow |
||||
ls.Border.Label = "List" |
||||
ls.Height = 7 |
||||
ls.Width = 25 |
||||
ls.Y = 0 |
||||
|
||||
termui.Render(ls) |
||||
|
||||
<-termui.EventCh() |
||||
} |
Before Width: | Height: | Size: 36 KiB |
@ -1,50 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import "github.com/gizak/termui" |
||||
|
||||
func main() { |
||||
err := termui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer termui.Close() |
||||
|
||||
termui.UseTheme("helloworld") |
||||
|
||||
bc := termui.NewMBarChart() |
||||
math := []int{90, 85, 90, 80} |
||||
english := []int{70, 85, 75, 60} |
||||
science := []int{75, 60, 80, 85} |
||||
compsci := []int{100, 100, 100, 100} |
||||
bc.Data[0] = math |
||||
bc.Data[1] = english |
||||
bc.Data[2] = science |
||||
bc.Data[3] = compsci |
||||
studentsName := []string{"Ken", "Rob", "Dennis", "Linus"} |
||||
bc.Border.Label = "Student's Marks X-Axis=Name Y-Axis=Marks[Math,English,Science,ComputerScience] in %" |
||||
bc.Width = 100 |
||||
bc.Height = 50 |
||||
bc.Y = 10 |
||||
bc.BarWidth = 10 |
||||
bc.DataLabels = studentsName |
||||
bc.ShowScale = true //Show y_axis scale value (min and max)
|
||||
bc.SetMax(400) |
||||
|
||||
bc.TextColor = termui.ColorGreen //this is color for label (x-axis)
|
||||
bc.BarColor[3] = termui.ColorGreen //BarColor for computerscience
|
||||
bc.BarColor[1] = termui.ColorYellow //Bar Color for english
|
||||
bc.NumColor[3] = termui.ColorRed // Num color for computerscience
|
||||
bc.NumColor[1] = termui.ColorRed // num color for english
|
||||
|
||||
//Other colors are automatically populated, btw All the students seems do well in computerscience. :p
|
||||
|
||||
termui.Render(bc) |
||||
|
||||
<-termui.EventCh() |
||||
} |
Before Width: | Height: | Size: 20 KiB |
@ -1,48 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import "github.com/gizak/termui" |
||||
|
||||
func main() { |
||||
err := termui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer termui.Close() |
||||
|
||||
termui.UseTheme("helloworld") |
||||
|
||||
par0 := termui.NewPar("Borderless Text") |
||||
par0.Height = 1 |
||||
par0.Width = 20 |
||||
par0.Y = 1 |
||||
par0.HasBorder = false |
||||
|
||||
par1 := termui.NewPar("你好,世界。") |
||||
par1.Height = 3 |
||||
par1.Width = 17 |
||||
par1.X = 20 |
||||
par1.Border.Label = "标签" |
||||
|
||||
par2 := termui.NewPar("Simple text\nwith label. It can be multilined with \\n or break automatically") |
||||
par2.Height = 5 |
||||
par2.Width = 37 |
||||
par2.Y = 4 |
||||
par2.Border.Label = "Multiline" |
||||
par2.Border.FgColor = termui.ColorYellow |
||||
|
||||
par3 := termui.NewPar("Long text with label and it is auto trimmed.") |
||||
par3.Height = 3 |
||||
par3.Width = 37 |
||||
par3.Y = 9 |
||||
par3.Border.Label = "Auto Trim" |
||||
|
||||
termui.Render(par0, par1, par2, par3) |
||||
|
||||
<-termui.EventCh() |
||||
} |
Before Width: | Height: | Size: 64 KiB |
@ -1,65 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import "github.com/gizak/termui" |
||||
|
||||
func main() { |
||||
err := termui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer termui.Close() |
||||
|
||||
termui.UseTheme("helloworld") |
||||
|
||||
data := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6} |
||||
spl0 := termui.NewSparkline() |
||||
spl0.Data = data[3:] |
||||
spl0.Title = "Sparkline 0" |
||||
spl0.LineColor = termui.ColorGreen |
||||
|
||||
// single
|
||||
spls0 := termui.NewSparklines(spl0) |
||||
spls0.Height = 2 |
||||
spls0.Width = 20 |
||||
spls0.HasBorder = false |
||||
|
||||
spl1 := termui.NewSparkline() |
||||
spl1.Data = data |
||||
spl1.Title = "Sparkline 1" |
||||
spl1.LineColor = termui.ColorRed |
||||
|
||||
spl2 := termui.NewSparkline() |
||||
spl2.Data = data[5:] |
||||
spl2.Title = "Sparkline 2" |
||||
spl2.LineColor = termui.ColorMagenta |
||||
|
||||
// group
|
||||
spls1 := termui.NewSparklines(spl0, spl1, spl2) |
||||
spls1.Height = 8 |
||||
spls1.Width = 20 |
||||
spls1.Y = 3 |
||||
spls1.Border.Label = "Group Sparklines" |
||||
|
||||
spl3 := termui.NewSparkline() |
||||
spl3.Data = data |
||||
spl3.Title = "Enlarged Sparkline" |
||||
spl3.Height = 8 |
||||
spl3.LineColor = termui.ColorYellow |
||||
|
||||
spls2 := termui.NewSparklines(spl3) |
||||
spls2.Height = 11 |
||||
spls2.Width = 30 |
||||
spls2.Border.FgColor = termui.ColorCyan |
||||
spls2.X = 21 |
||||
spls2.Border.Label = "Tweeked Sparkline" |
||||
|
||||
termui.Render(spls0, spls1, spls2) |
||||
|
||||
<-termui.EventCh() |
||||
} |
Before Width: | Height: | Size: 42 KiB |
@ -1,143 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main |
||||
|
||||
import ui "github.com/gizak/termui" |
||||
import "math" |
||||
|
||||
import "time" |
||||
|
||||
func main() { |
||||
err := ui.Init() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer ui.Close() |
||||
|
||||
ui.UseTheme("helloworld") |
||||
|
||||
p := ui.NewPar(":PRESS q TO QUIT DEMO") |
||||
p.Height = 3 |
||||
p.Width = 50 |
||||
p.Border.Label = "Text Box" |
||||
|
||||
strs := []string{"[0] gizak/termui", "[1] editbox.go", "[2] iterrupt.go", "[3] keyboard.go", "[4] output.go", "[5] random_out.go", "[6] dashboard.go", "[7] nsf/termbox-go"} |
||||
list := ui.NewList() |
||||
list.Items = strs |
||||
list.Border.Label = "List" |
||||
list.Height = 7 |
||||
list.Width = 25 |
||||
list.Y = 4 |
||||
|
||||
g := ui.NewGauge() |
||||
g.Percent = 50 |
||||
g.Width = 50 |
||||
g.Height = 3 |
||||
g.Y = 11 |
||||
g.Border.Label = "Gauge" |
||||
|
||||
spark := ui.NewSparkline() |
||||
spark.Title = "srv 0:" |
||||
spdata := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6} |
||||
spark.Data = spdata |
||||
|
||||
spark1 := ui.NewSparkline() |
||||
spark1.Title = "srv 1:" |
||||
spark1.Data = spdata |
||||
|
||||
sp := ui.NewSparklines(spark, spark1) |
||||
sp.Width = 25 |
||||
sp.Height = 7 |
||||
sp.Border.Label = "Sparkline" |
||||
sp.Y = 4 |
||||
sp.X = 25 |
||||
|
||||
lc := ui.NewLineChart() |
||||
sinps := (func() []float64 { |
||||
n := 100 |
||||
ps := make([]float64, n) |
||||
for i := range ps { |
||||
ps[i] = 1 + math.Sin(float64(i)/4) |
||||
} |
||||
return ps |
||||
})() |
||||
|
||||
lc.Border.Label = "Line Chart" |
||||
lc.Data = sinps |
||||
lc.Width = 50 |
||||
lc.Height = 11 |
||||
lc.X = 0 |
||||
lc.Y = 14 |
||||
lc.Mode = "dot" |
||||
|
||||
bc := ui.NewBarChart() |
||||
bcdata := []int{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6} |
||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"} |
||||
bc.Border.Label = "Bar Chart" |
||||
bc.Width = 26 |
||||
bc.Height = 10 |
||||
bc.X = 51 |
||||
bc.Y = 0 |
||||
bc.DataLabels = bclabels |
||||
|
||||
lc1 := ui.NewLineChart() |
||||
lc1.Border.Label = "Line Chart" |
||||
rndwalk := (func() []float64 { |
||||
n := 150 |
||||
d := make([]float64, n) |
||||
for i := 1; i < n; i++ { |
||||
if i < 20 { |
||||
d[i] = d[i-1] + 0.01 |
||||
} |
||||
if i > 20 { |
||||
d[i] = d[i-1] - 0.05 |
||||
} |
||||
} |
||||
return d |
||||
})() |
||||
lc1.Data = rndwalk |
||||
lc1.Width = 26 |
||||
lc1.Height = 11 |
||||
lc1.X = 51 |
||||
lc1.Y = 14 |
||||
|
||||
p1 := ui.NewPar("Hey!\nI am a borderless block!") |
||||
p1.HasBorder = false |
||||
p1.Width = 26 |
||||
p1.Height = 2 |
||||
p1.X = 52 |
||||
p1.Y = 11 |
||||
|
||||
draw := func(t int) { |
||||
g.Percent = t % 101 |
||||
list.Items = strs[t%9:] |
||||
sp.Lines[0].Data = spdata[t%10:] |
||||
sp.Lines[1].Data = spdata[t/2%10:] |
||||
lc.Data = sinps[t/2:] |
||||
lc1.Data = rndwalk[t:] |
||||
bc.Data = bcdata[t/2%10:] |
||||
ui.Render(p, list, g, sp, lc, bc, lc1, p1) |
||||
} |
||||
|
||||
evt := ui.EventCh() |
||||
i := 0 |
||||
for { |
||||
select { |
||||
case e := <-evt: |
||||
if e.Type == ui.EventKey && e.Ch == 'q' { |
||||
return |
||||
} |
||||
default: |
||||
draw(i) |
||||
i++ |
||||
if i == 102 { |
||||
return |
||||
} |
||||
time.Sleep(time.Second / 2) |
||||
} |
||||
} |
||||
} |
Before Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 88 KiB |
@ -1,98 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/davecgh/go-spew/spew" |
||||
) |
||||
|
||||
var r *Row |
||||
|
||||
func TestRowWidth(t *testing.T) { |
||||
p0 := NewPar("p0") |
||||
p0.Height = 1 |
||||
p1 := NewPar("p1") |
||||
p1.Height = 1 |
||||
p2 := NewPar("p2") |
||||
p2.Height = 1 |
||||
p3 := NewPar("p3") |
||||
p3.Height = 1 |
||||
|
||||
/* test against tree: |
||||
|
||||
r |
||||
/ \
|
||||
0:w 1 |
||||
/ \
|
||||
10:w 11 |
||||
/ |
||||
110:w |
||||
/ |
||||
1100:w |
||||
*/ |
||||
/* |
||||
r = &row{ |
||||
Span: 12, |
||||
Cols: []*row{ |
||||
&row{Widget: p0, Span: 6}, |
||||
&row{ |
||||
Span: 6, |
||||
Cols: []*row{ |
||||
&row{Widget: p1, Span: 6}, |
||||
&row{ |
||||
Span: 6, |
||||
Cols: []*row{ |
||||
&row{ |
||||
Span: 12, |
||||
Widget: p2, |
||||
Cols: []*row{ |
||||
&row{Span: 12, Widget: p3}}}}}}}}} |
||||
*/ |
||||
|
||||
r = NewRow( |
||||
NewCol(6, 0, p0), |
||||
NewCol(6, 0, |
||||
NewRow( |
||||
NewCol(6, 0, p1), |
||||
NewCol(6, 0, p2, p3)))) |
||||
|
||||
r.assignWidth(100) |
||||
if r.Width != 100 || |
||||
(r.Cols[0].Width) != 50 || |
||||
(r.Cols[1].Width) != 50 || |
||||
(r.Cols[1].Cols[0].Width) != 25 || |
||||
(r.Cols[1].Cols[1].Width) != 25 || |
||||
(r.Cols[1].Cols[1].Cols[0].Width) != 25 || |
||||
(r.Cols[1].Cols[1].Cols[0].Cols[0].Width) != 25 { |
||||
t.Error("assignWidth fails") |
||||
} |
||||
} |
||||
|
||||
func TestRowHeight(t *testing.T) { |
||||
spew.Dump() |
||||
|
||||
if (r.solveHeight()) != 2 || |
||||
(r.Cols[1].Cols[1].Height) != 2 || |
||||
(r.Cols[1].Cols[1].Cols[0].Height) != 2 || |
||||
(r.Cols[1].Cols[0].Height) != 1 { |
||||
t.Error("solveHeight fails") |
||||
} |
||||
} |
||||
|
||||
func TestAssignXY(t *testing.T) { |
||||
r.assignX(0) |
||||
r.assignY(0) |
||||
if (r.Cols[0].X) != 0 || |
||||
(r.Cols[1].Cols[0].X) != 50 || |
||||
(r.Cols[1].Cols[1].X) != 75 || |
||||
(r.Cols[1].Cols[1].Cols[0].X) != 75 || |
||||
(r.Cols[1].Cols[0].Y) != 0 || |
||||
(r.Cols[1].Cols[1].Cols[0].Y) != 0 || |
||||
(r.Cols[1].Cols[1].Cols[0].Cols[0].Y) != 1 { |
||||
t.Error("assignXY fails") |
||||
} |
||||
} |
@ -1,58 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/davecgh/go-spew/spew" |
||||
) |
||||
|
||||
func TestStr2Rune(t *testing.T) { |
||||
s := "你好,世界." |
||||
rs := str2runes(s) |
||||
if len(rs) != 6 { |
||||
t.Error() |
||||
} |
||||
} |
||||
|
||||
func TestWidth(t *testing.T) { |
||||
s0 := "つのだ☆HIRO" |
||||
s1 := "11111111111" |
||||
spew.Dump(s0) |
||||
spew.Dump(s1) |
||||
// above not align for setting East Asian Ambiguous to wide!!
|
||||
|
||||
if strWidth(s0) != strWidth(s1) { |
||||
t.Error("str len failed") |
||||
} |
||||
|
||||
len1 := []rune{'a', '2', '&', '「', 'オ', '。'} //will false: 'ᆵ', 'ᄚ', 'ᄒ'
|
||||
for _, v := range len1 { |
||||
if charWidth(v) != 1 { |
||||
t.Error("len1 failed") |
||||
} |
||||
} |
||||
|
||||
len2 := []rune{'漢', '字', '한', '자', '你', '好', 'だ', '。', '%', 's', 'E', 'ョ', '、', 'ヲ'} |
||||
for _, v := range len2 { |
||||
if charWidth(v) != 2 { |
||||
t.Error("len2 failed") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestTrim(t *testing.T) { |
||||
s := "つのだ☆HIRO" |
||||
if string(trimStr2Runes(s, 10)) != "つのだ☆HI"+dot { |
||||
t.Error("trim failed") |
||||
} |
||||
if string(trimStr2Runes(s, 11)) != "つのだ☆HIRO" { |
||||
t.Error("avoid tail trim failed") |
||||
} |
||||
if string(trimStr2Runes(s, 15)) != "つのだ☆HIRO" { |
||||
t.Error("avoid trim failed") |
||||
} |
||||
} |
@ -1,4 +1,4 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
@ -1,4 +1,4 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
@ -1,71 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
// Par displays a paragraph.
|
||||
/* |
||||
par := termui.NewPar("Simple Text") |
||||
par.Height = 3 |
||||
par.Width = 17 |
||||
par.Border.Label = "Label" |
||||
*/ |
||||
type Par struct { |
||||
Block |
||||
Text string |
||||
TextFgColor Attribute |
||||
TextBgColor Attribute |
||||
} |
||||
|
||||
// NewPar returns a new *Par with given text as its content.
|
||||
func NewPar(s string) *Par { |
||||
return &Par{ |
||||
Block: *NewBlock(), |
||||
Text: s, |
||||
TextFgColor: theme.ParTextFg, |
||||
TextBgColor: theme.ParTextBg} |
||||
} |
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (p *Par) Buffer() []Point { |
||||
ps := p.Block.Buffer() |
||||
|
||||
rs := str2runes(p.Text) |
||||
i, j, k := 0, 0, 0 |
||||
for i < p.innerHeight && k < len(rs) { |
||||
// the width of char is about to print
|
||||
w := charWidth(rs[k]) |
||||
|
||||
if rs[k] == '\n' || j+w > p.innerWidth { |
||||
i++ |
||||
j = 0 // set x = 0
|
||||
if rs[k] == '\n' { |
||||
k++ |
||||
} |
||||
|
||||
if i >= p.innerHeight { |
||||
ps = append(ps, newPointWithAttrs('…', |
||||
p.innerX+p.innerWidth-1, |
||||
p.innerY+p.innerHeight-1, |
||||
p.TextFgColor, p.TextBgColor)) |
||||
break |
||||
} |
||||
|
||||
continue |
||||
} |
||||
pi := Point{} |
||||
pi.X = p.innerX + j |
||||
pi.Y = p.innerY + i |
||||
|
||||
pi.Ch = rs[k] |
||||
pi.Bg = p.TextBgColor |
||||
pi.Fg = p.TextFgColor |
||||
|
||||
ps = append(ps, pi) |
||||
|
||||
k++ |
||||
j += w |
||||
} |
||||
return p.Block.chopOverflow(ps) |
||||
} |
@ -0,0 +1,64 @@ |
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
// Par displays a paragraph.
|
||||
/* |
||||
par := termui.NewPar("Simple Text") |
||||
par.Height = 3 |
||||
par.Width = 17 |
||||
par.Border.Label = "Label" |
||||
*/ |
||||
type Par struct { |
||||
Block |
||||
Text string |
||||
TextFgColor Attribute |
||||
TextBgColor Attribute |
||||
} |
||||
|
||||
// NewPar returns a new *Par with given text as its content.
|
||||
func NewPar(s string) *Par { |
||||
return &Par{ |
||||
Block: *NewBlock(), |
||||
Text: s, |
||||
TextFgColor: ThemeAttr("par.text.fg"), |
||||
TextBgColor: ThemeAttr("par.text.bg"), |
||||
} |
||||
} |
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (p *Par) Buffer() Buffer { |
||||
buf := p.Block.Buffer() |
||||
|
||||
fg, bg := p.TextFgColor, p.TextBgColor |
||||
cs := DefaultTxBuilder.Build(p.Text, fg, bg) |
||||
|
||||
y, x, n := 0, 0, 0 |
||||
for y < p.innerArea.Dy() && n < len(cs) { |
||||
w := cs[n].Width() |
||||
if cs[n].Ch == '\n' || x+w > p.innerArea.Dx() { |
||||
y++ |
||||
x = 0 // set x = 0
|
||||
if cs[n].Ch == '\n' { |
||||
n++ |
||||
} |
||||
|
||||
if y >= p.innerArea.Dy() { |
||||
buf.Set(p.innerArea.Min.X+p.innerArea.Dx()-1, |
||||
p.innerArea.Min.Y+p.innerArea.Dy()-1, |
||||
Cell{Ch: '…', Fg: p.TextFgColor, Bg: p.TextBgColor}) |
||||
break |
||||
} |
||||
continue |
||||
} |
||||
|
||||
buf.Set(p.innerArea.Min.X+x, p.innerArea.Min.Y+y, cs[n]) |
||||
|
||||
n++ |
||||
x += w |
||||
} |
||||
|
||||
return buf |
||||
} |
@ -1,28 +0,0 @@ |
||||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
// Point stands for a single cell in terminal.
|
||||
type Point struct { |
||||
Ch rune |
||||
Bg Attribute |
||||
Fg Attribute |
||||
X int |
||||
Y int |
||||
} |
||||
|
||||
func newPoint(c rune, x, y int) (p Point) { |
||||
p.Ch = c |
||||
p.X = x |
||||
p.Y = y |
||||
return |
||||
} |
||||
|
||||
func newPointWithAttrs(c rune, x, y int, fg, bg Attribute) Point { |
||||
p := newPoint(c, x, y) |
||||
p.Bg = bg |
||||
p.Fg = fg |
||||
return p |
||||
} |
@ -0,0 +1,78 @@ |
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
import "image" |
||||
|
||||
// Align is the position of the gauge's label.
|
||||
type Align uint |
||||
|
||||
// All supported positions.
|
||||
const ( |
||||
AlignNone Align = 0 |
||||
AlignLeft Align = 1 << iota |
||||
AlignRight |
||||
AlignBottom |
||||
AlignTop |
||||
AlignCenterVertical |
||||
AlignCenterHorizontal |
||||
AlignCenter = AlignCenterVertical | AlignCenterHorizontal |
||||
) |
||||
|
||||
func AlignArea(parent, child image.Rectangle, a Align) image.Rectangle { |
||||
w, h := child.Dx(), child.Dy() |
||||
|
||||
// parent center
|
||||
pcx, pcy := parent.Min.X+parent.Dx()/2, parent.Min.Y+parent.Dy()/2 |
||||
// child center
|
||||
ccx, ccy := child.Min.X+child.Dx()/2, child.Min.Y+child.Dy()/2 |
||||
|
||||
if a&AlignLeft == AlignLeft { |
||||
child.Min.X = parent.Min.X |
||||
child.Max.X = child.Min.X + w |
||||
} |
||||
|
||||
if a&AlignRight == AlignRight { |
||||
child.Max.X = parent.Max.X |
||||
child.Min.X = child.Max.X - w |
||||
} |
||||
|
||||
if a&AlignBottom == AlignBottom { |
||||
child.Max.Y = parent.Max.Y |
||||
child.Min.Y = child.Max.Y - h |
||||
} |
||||
|
||||
if a&AlignTop == AlignRight { |
||||
child.Min.Y = parent.Min.Y |
||||
child.Max.Y = child.Min.Y + h |
||||
} |
||||
|
||||
if a&AlignCenterHorizontal == AlignCenterHorizontal { |
||||
child.Min.X += pcx - ccx |
||||
child.Max.X = child.Min.X + w |
||||
} |
||||
|
||||
if a&AlignCenterVertical == AlignCenterVertical { |
||||
child.Min.Y += pcy - ccy |
||||
child.Max.Y = child.Min.Y + h |
||||
} |
||||
|
||||
return child |
||||
} |
||||
|
||||
func MoveArea(a image.Rectangle, dx, dy int) image.Rectangle { |
||||
a.Min.X += dx |
||||
a.Max.X += dx |
||||
a.Min.Y += dy |
||||
a.Max.Y += dy |
||||
return a |
||||
} |
||||
|
||||
var termWidth int |
||||
var termHeight int |
||||
|
||||
func TermRect() image.Rectangle { |
||||
return image.Rect(0, 0, termWidth, termHeight) |
||||
} |
@ -0,0 +1,66 @@ |
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
|
||||
"github.com/gizak/termui" |
||||
"github.com/gizak/termui/debug" |
||||
) |
||||
|
||||
func main() { |
||||
// run as client
|
||||
if len(os.Args) > 1 { |
||||
fmt.Print(debug.ConnectAndListen()) |
||||
return |
||||
} |
||||
|
||||
// run as server
|
||||
go func() { panic(debug.ListenAndServe()) }() |
||||
|
||||
if err := termui.Init(); err != nil { |
||||
panic(err) |
||||
} |
||||
defer termui.Close() |
||||
|
||||
//termui.UseTheme("helloworld")
|
||||
b := termui.NewBlock() |
||||
b.Width = 20 |
||||
b.Height = 20 |
||||
b.Float = termui.AlignCenter |
||||
b.BorderLabel = "[HELLO](fg-red,bg-white) [WORLD](fg-blue,bg-green)" |
||||
|
||||
termui.Render(b) |
||||
|
||||
termui.Handle("/sys", func(e termui.Event) { |
||||
k, ok := e.Data.(termui.EvtKbd) |
||||
debug.Logf("->%v\n", e) |
||||
if ok && k.KeyStr == "q" { |
||||
termui.StopLoop() |
||||
} |
||||
}) |
||||
|
||||
termui.Handle(("/usr"), func(e termui.Event) { |
||||
debug.Logf("->%v\n", e) |
||||
}) |
||||
|
||||
termui.Handle("/timer/1s", func(e termui.Event) { |
||||
t := e.Data.(termui.EvtTimer) |
||||
termui.SendCustomEvt("/usr/t", t.Count) |
||||
|
||||
if t.Count%2 == 0 { |
||||
b.BorderLabel = "[HELLO](fg-red,bg-green) [WORLD](fg-blue,bg-white)" |
||||
} else { |
||||
b.BorderLabel = "[HELLO](fg-blue,bg-white) [WORLD](fg-red,bg-green)" |
||||
} |
||||
|
||||
termui.Render(b) |
||||
|
||||
}) |
||||
|
||||
termui.Loop() |
||||
} |
@ -0,0 +1,215 @@ |
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
import ( |
||||
"regexp" |
||||
"strings" |
||||
) |
||||
|
||||
// TextBuilder is a minial interface to produce text []Cell using sepcific syntax (markdown).
|
||||
type TextBuilder interface { |
||||
Build(s string, fg, bg Attribute) []Cell |
||||
} |
||||
|
||||
// DefaultTxBuilder is set to be MarkdownTxBuilder.
|
||||
var DefaultTxBuilder = NewMarkdownTxBuilder() |
||||
|
||||
// MarkdownTxBuilder implements TextBuilder interface, using markdown syntax.
|
||||
type MarkdownTxBuilder struct { |
||||
baseFg Attribute |
||||
baseBg Attribute |
||||
plainTx []rune |
||||
markers []marker |
||||
} |
||||
|
||||
type marker struct { |
||||
st int |
||||
ed int |
||||
fg Attribute |
||||
bg Attribute |
||||
} |
||||
|
||||
var colorMap = map[string]Attribute{ |
||||
"red": ColorRed, |
||||
"blue": ColorBlue, |
||||
"black": ColorBlack, |
||||
"cyan": ColorCyan, |
||||
"yellow": ColorYellow, |
||||
"white": ColorWhite, |
||||
"default": ColorDefault, |
||||
"green": ColorGreen, |
||||
"magenta": ColorMagenta, |
||||
} |
||||
|
||||
var attrMap = map[string]Attribute{ |
||||
"bold": AttrBold, |
||||
"underline": AttrUnderline, |
||||
"reverse": AttrReverse, |
||||
} |
||||
|
||||
func rmSpc(s string) string { |
||||
reg := regexp.MustCompile(`\s+`) |
||||
return reg.ReplaceAllString(s, "") |
||||
} |
||||
|
||||
// readAttr translates strings like `fg-red,fg-bold,bg-white` to fg and bg Attribute
|
||||
func (mtb MarkdownTxBuilder) readAttr(s string) (Attribute, Attribute) { |
||||
fg := mtb.baseFg |
||||
bg := mtb.baseBg |
||||
|
||||
updateAttr := func(a Attribute, attrs []string) Attribute { |
||||
for _, s := range attrs { |
||||
// replace the color
|
||||
if c, ok := colorMap[s]; ok { |
||||
a &= 0xFF00 // erase clr 0 ~ 8 bits
|
||||
a |= c // set clr
|
||||
} |
||||
// add attrs
|
||||
if c, ok := attrMap[s]; ok { |
||||
a |= c |
||||
} |
||||
} |
||||
return a |
||||
} |
||||
|
||||
ss := strings.Split(s, ",") |
||||
fgs := []string{} |
||||
bgs := []string{} |
||||
for _, v := range ss { |
||||
subs := strings.Split(v, "-") |
||||
if len(subs) > 1 { |
||||
if subs[0] == "fg" { |
||||
fgs = append(fgs, subs[1]) |
||||
} |
||||
if subs[0] == "bg" { |
||||
bgs = append(bgs, subs[1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fg = updateAttr(fg, fgs) |
||||
bg = updateAttr(bg, bgs) |
||||
return fg, bg |
||||
} |
||||
|
||||
func (mtb *MarkdownTxBuilder) reset() { |
||||
mtb.plainTx = []rune{} |
||||
mtb.markers = []marker{} |
||||
} |
||||
|
||||
// parse streams and parses text into normalized text and render sequence.
|
||||
func (mtb *MarkdownTxBuilder) parse(str string) { |
||||
rs := str2runes(str) |
||||
normTx := []rune{} |
||||
square := []rune{} |
||||
brackt := []rune{} |
||||
accSquare := false |
||||
accBrackt := false |
||||
cntSquare := 0 |
||||
|
||||
reset := func() { |
||||
square = []rune{} |
||||
brackt = []rune{} |
||||
accSquare = false |
||||
accBrackt = false |
||||
cntSquare = 0 |
||||
} |
||||
// pipe stacks into normTx and clear
|
||||
rollback := func() { |
||||
normTx = append(normTx, square...) |
||||
normTx = append(normTx, brackt...) |
||||
reset() |
||||
} |
||||
// chop first and last
|
||||
chop := func(s []rune) []rune { |
||||
return s[1 : len(s)-1] |
||||
} |
||||
|
||||
for i, r := range rs { |
||||
switch { |
||||
// stacking brackt
|
||||
case accBrackt: |
||||
brackt = append(brackt, r) |
||||
if ')' == r { |
||||
fg, bg := mtb.readAttr(string(chop(brackt))) |
||||
st := len(normTx) |
||||
ed := len(normTx) + len(square) - 2 |
||||
mtb.markers = append(mtb.markers, marker{st, ed, fg, bg}) |
||||
normTx = append(normTx, chop(square)...) |
||||
reset() |
||||
} else if i+1 == len(rs) { |
||||
rollback() |
||||
} |
||||
// stacking square
|
||||
case accSquare: |
||||
switch { |
||||
// squares closed and followed by a '('
|
||||
case cntSquare == 0 && '(' == r: |
||||
accBrackt = true |
||||
brackt = append(brackt, '(') |
||||
// squares closed but not followed by a '('
|
||||
case cntSquare == 0: |
||||
rollback() |
||||
if '[' == r { |
||||
accSquare = true |
||||
cntSquare = 1 |
||||
brackt = append(brackt, '[') |
||||
} else { |
||||
normTx = append(normTx, r) |
||||
} |
||||
// hit the end
|
||||
case i+1 == len(rs): |
||||
square = append(square, r) |
||||
rollback() |
||||
case '[' == r: |
||||
cntSquare++ |
||||
square = append(square, '[') |
||||
case ']' == r: |
||||
cntSquare-- |
||||
square = append(square, ']') |
||||
// normal char
|
||||
default: |
||||
square = append(square, r) |
||||
} |
||||
// stacking normTx
|
||||
default: |
||||
if '[' == r { |
||||
accSquare = true |
||||
cntSquare = 1 |
||||
square = append(square, '[') |
||||
} else { |
||||
normTx = append(normTx, r) |
||||
} |
||||
} |
||||
} |
||||
|
||||
mtb.plainTx = normTx |
||||
} |
||||
|
||||
// Build implements TextBuilder interface.
|
||||
func (mtb MarkdownTxBuilder) Build(s string, fg, bg Attribute) []Cell { |
||||
mtb.baseFg = fg |
||||
mtb.baseBg = bg |
||||
mtb.reset() |
||||
mtb.parse(s) |
||||
cs := make([]Cell, len(mtb.plainTx)) |
||||
for i := range cs { |
||||
cs[i] = Cell{Ch: mtb.plainTx[i], Fg: fg, Bg: bg} |
||||
} |
||||
for _, mrk := range mtb.markers { |
||||
for i := mrk.st; i < mrk.ed; i++ { |
||||
cs[i].Fg = mrk.fg |
||||
cs[i].Bg = mrk.bg |
||||
} |
||||
} |
||||
|
||||
return cs |
||||
} |
||||
|
||||
// NewMarkdownTxBuilder returns a TextBuilder employing markdown syntax.
|
||||
func NewMarkdownTxBuilder() TextBuilder { |
||||
return MarkdownTxBuilder{} |
||||
} |
@ -0,0 +1,94 @@ |
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sync" |
||||
) |
||||
|
||||
// event mixins
|
||||
type WgtMgr map[string]WgtInfo |
||||
|
||||
type WgtInfo struct { |
||||
Handlers map[string]func(Event) |
||||
WgtRef Widget |
||||
Id string |
||||
} |
||||
|
||||
type Widget interface { |
||||
Id() string |
||||
} |
||||
|
||||
func NewWgtInfo(wgt Widget) WgtInfo { |
||||
return WgtInfo{ |
||||
Handlers: make(map[string]func(Event)), |
||||
WgtRef: wgt, |
||||
Id: wgt.Id(), |
||||
} |
||||
} |
||||
|
||||
func NewWgtMgr() WgtMgr { |
||||
wm := WgtMgr(make(map[string]WgtInfo)) |
||||
return wm |
||||
|
||||
} |
||||
|
||||
func (wm WgtMgr) AddWgt(wgt Widget) { |
||||
wm[wgt.Id()] = NewWgtInfo(wgt) |
||||
} |
||||
|
||||
func (wm WgtMgr) RmWgt(wgt Widget) { |
||||
wm.RmWgtById(wgt.Id()) |
||||
} |
||||
|
||||
func (wm WgtMgr) RmWgtById(id string) { |
||||
delete(wm, id) |
||||
} |
||||
|
||||
func (wm WgtMgr) AddWgtHandler(id, path string, h func(Event)) { |
||||
if w, ok := wm[id]; ok { |
||||
w.Handlers[path] = h |
||||
} |
||||
} |
||||
|
||||
func (wm WgtMgr) RmWgtHandler(id, path string) { |
||||
if w, ok := wm[id]; ok { |
||||
delete(w.Handlers, path) |
||||
} |
||||
} |
||||
|
||||
var counter struct { |
||||
sync.RWMutex |
||||
count int |
||||
} |
||||
|
||||
func GenId() string { |
||||
counter.Lock() |
||||
defer counter.Unlock() |
||||
|
||||
counter.count += 1 |
||||
return fmt.Sprintf("%d", counter.count) |
||||
} |
||||
|
||||
func (wm WgtMgr) WgtHandlersHook() func(Event) { |
||||
return func(e Event) { |
||||
for _, v := range wm { |
||||
if k := findMatch(v.Handlers, e.Path); k != "" { |
||||
v.Handlers[k](e) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
var DefaultWgtMgr WgtMgr |
||||
|
||||
func (b *Block) Handle(path string, handler func(Event)) { |
||||
if _, ok := DefaultWgtMgr[b.Id()]; !ok { |
||||
DefaultWgtMgr.AddWgt(b) |
||||
} |
||||
|
||||
DefaultWgtMgr.AddWgtHandler(b.Id(), path, handler) |
||||
} |
@ -0,0 +1,15 @@ |
||||
# This is the official list of Snappy-Go authors for copyright purposes. |
||||
# This file is distinct from the CONTRIBUTORS files. |
||||
# See the latter for an explanation. |
||||
|
||||
# Names should be added to this file as |
||||
# Name or Organization <email address> |
||||
# The email address is not required for organizations. |
||||
|
||||
# Please keep the list sorted. |
||||
|
||||
Damian Gryski <dgryski@gmail.com> |
||||
Google Inc. |
||||
Jan Mercl <0xjnml@gmail.com> |
||||
Rodolfo Carvalho <rhcarvalho@gmail.com> |
||||
Sebastien Binet <seb.binet@gmail.com> |
@ -0,0 +1,37 @@ |
||||
# This is the official list of people who can contribute |
||||
# (and typically have contributed) code to the Snappy-Go repository. |
||||
# The AUTHORS file lists the copyright holders; this file |
||||
# lists people. For example, Google employees are listed here |
||||
# but not in AUTHORS, because Google holds the copyright. |
||||
# |
||||
# The submission process automatically checks to make sure |
||||
# that people submitting code are listed in this file (by email address). |
||||
# |
||||
# Names should be added to this file only after verifying that |
||||
# the individual or the individual's organization has agreed to |
||||
# the appropriate Contributor License Agreement, found here: |
||||
# |
||||
# http://code.google.com/legal/individual-cla-v1.0.html |
||||
# http://code.google.com/legal/corporate-cla-v1.0.html |
||||
# |
||||
# The agreement for individuals can be filled out on the web. |
||||
# |
||||
# When adding J Random Contributor's name to this file, |
||||
# either J's name or J's organization's name should be |
||||
# added to the AUTHORS file, depending on whether the |
||||
# individual or corporate CLA was used. |
||||
|
||||
# Names should be added to this file like so: |
||||
# Name <email address> |
||||
|
||||
# Please keep the list sorted. |
||||
|
||||
Damian Gryski <dgryski@gmail.com> |
||||
Jan Mercl <0xjnml@gmail.com> |
||||
Kai Backman <kaib@golang.org> |
||||
Marc-Antoine Ruel <maruel@chromium.org> |
||||
Nigel Tao <nigeltao@golang.org> |
||||
Rob Pike <r@golang.org> |
||||
Rodolfo Carvalho <rhcarvalho@gmail.com> |
||||
Russ Cox <rsc@golang.org> |
||||
Sebastien Binet <seb.binet@gmail.com> |
@ -0,0 +1,27 @@ |
||||
Copyright (c) 2011 The Snappy-Go Authors. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following disclaimer |
||||
in the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Google Inc. nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,7 @@ |
||||
The Snappy compression format in the Go programming language. |
||||
|
||||
To download and install from source: |
||||
$ go get github.com/golang/snappy |
||||
|
||||
Unless otherwise noted, the Snappy-Go source files are distributed |
||||
under the BSD-style license found in the LICENSE file. |
@ -0,0 +1,212 @@ |
||||
package lru |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sync" |
||||
|
||||
"github.com/hashicorp/golang-lru/simplelru" |
||||
) |
||||
|
||||
const ( |
||||
// Default2QRecentRatio is the ratio of the 2Q cache dedicated
|
||||
// to recently added entries that have only been accessed once.
|
||||
Default2QRecentRatio = 0.25 |
||||
|
||||
// Default2QGhostEntries is the default ratio of ghost
|
||||
// entries kept to track entries recently evicted
|
||||
Default2QGhostEntries = 0.50 |
||||
) |
||||
|
||||
// TwoQueueCache is a thread-safe fixed size 2Q cache.
|
||||
// 2Q is an enhancement over the standard LRU cache
|
||||
// in that it tracks both frequently and recently used
|
||||
// entries separately. This avoids a burst in access to new
|
||||
// entries from evicting frequently used entries. It adds some
|
||||
// additional tracking overhead to the standard LRU cache, and is
|
||||
// computationally about 2x the cost, and adds some metadata over
|
||||
// head. The ARCCache is similar, but does not require setting any
|
||||
// parameters.
|
||||
type TwoQueueCache struct { |
||||
size int |
||||
recentSize int |
||||
|
||||
recent *simplelru.LRU |
||||
frequent *simplelru.LRU |
||||
recentEvict *simplelru.LRU |
||||
lock sync.RWMutex |
||||
} |
||||
|
||||
// New2Q creates a new TwoQueueCache using the default
|
||||
// values for the parameters.
|
||||
func New2Q(size int) (*TwoQueueCache, error) { |
||||
return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries) |
||||
} |
||||
|
||||
// New2QParams creates a new TwoQueueCache using the provided
|
||||
// parameter values.
|
||||
func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) { |
||||
if size <= 0 { |
||||
return nil, fmt.Errorf("invalid size") |
||||
} |
||||
if recentRatio < 0.0 || recentRatio > 1.0 { |
||||
return nil, fmt.Errorf("invalid recent ratio") |
||||
} |
||||
if ghostRatio < 0.0 || ghostRatio > 1.0 { |
||||
return nil, fmt.Errorf("invalid ghost ratio") |
||||
} |
||||
|
||||
// Determine the sub-sizes
|
||||
recentSize := int(float64(size) * recentRatio) |
||||
evictSize := int(float64(size) * ghostRatio) |
||||
|
||||
// Allocate the LRUs
|
||||
recent, err := simplelru.NewLRU(size, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
frequent, err := simplelru.NewLRU(size, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
recentEvict, err := simplelru.NewLRU(evictSize, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Initialize the cache
|
||||
c := &TwoQueueCache{ |
||||
size: size, |
||||
recentSize: recentSize, |
||||
recent: recent, |
||||
frequent: frequent, |
||||
recentEvict: recentEvict, |
||||
} |
||||
return c, nil |
||||
} |
||||
|
||||
func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
// Check if this is a frequent value
|
||||
if val, ok := c.frequent.Get(key); ok { |
||||
return val, ok |
||||
} |
||||
|
||||
// If the value is contained in recent, then we
|
||||
// promote it to frequent
|
||||
if val, ok := c.recent.Peek(key); ok { |
||||
c.recent.Remove(key) |
||||
c.frequent.Add(key, val) |
||||
return val, ok |
||||
} |
||||
|
||||
// No hit
|
||||
return nil, false |
||||
} |
||||
|
||||
func (c *TwoQueueCache) Add(key, value interface{}) { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
// Check if the value is frequently used already,
|
||||
// and just update the value
|
||||
if c.frequent.Contains(key) { |
||||
c.frequent.Add(key, value) |
||||
return |
||||
} |
||||
|
||||
// Check if the value is recently used, and promote
|
||||
// the value into the frequent list
|
||||
if c.recent.Contains(key) { |
||||
c.recent.Remove(key) |
||||
c.frequent.Add(key, value) |
||||
return |
||||
} |
||||
|
||||
// If the value was recently evicted, add it to the
|
||||
// frequently used list
|
||||
if c.recentEvict.Contains(key) { |
||||
c.ensureSpace(true) |
||||
c.recentEvict.Remove(key) |
||||
c.frequent.Add(key, value) |
||||
return |
||||
} |
||||
|
||||
// Add to the recently seen list
|
||||
c.ensureSpace(false) |
||||
c.recent.Add(key, value) |
||||
return |
||||
} |
||||
|
||||
// ensureSpace is used to ensure we have space in the cache
|
||||
func (c *TwoQueueCache) ensureSpace(recentEvict bool) { |
||||
// If we have space, nothing to do
|
||||
recentLen := c.recent.Len() |
||||
freqLen := c.frequent.Len() |
||||
if recentLen+freqLen < c.size { |
||||
return |
||||
} |
||||
|
||||
// If the recent buffer is larger than
|
||||
// the target, evict from there
|
||||
if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) { |
||||
k, _, _ := c.recent.RemoveOldest() |
||||
c.recentEvict.Add(k, nil) |
||||
return |
||||
} |
||||
|
||||
// Remove from the frequent list otherwise
|
||||
c.frequent.RemoveOldest() |
||||
} |
||||
|
||||
func (c *TwoQueueCache) Len() int { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
return c.recent.Len() + c.frequent.Len() |
||||
} |
||||
|
||||
func (c *TwoQueueCache) Keys() []interface{} { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
k1 := c.frequent.Keys() |
||||
k2 := c.recent.Keys() |
||||
return append(k1, k2...) |
||||
} |
||||
|
||||
func (c *TwoQueueCache) Remove(key interface{}) { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
if c.frequent.Remove(key) { |
||||
return |
||||
} |
||||
if c.recent.Remove(key) { |
||||
return |
||||
} |
||||
if c.recentEvict.Remove(key) { |
||||
return |
||||
} |
||||
} |
||||
|
||||
func (c *TwoQueueCache) Purge() { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
c.recent.Purge() |
||||
c.frequent.Purge() |
||||
c.recentEvict.Purge() |
||||
} |
||||
|
||||
func (c *TwoQueueCache) Contains(key interface{}) bool { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
return c.frequent.Contains(key) || c.recent.Contains(key) |
||||
} |
||||
|
||||
func (c *TwoQueueCache) Peek(key interface{}) (interface{}, bool) { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
if val, ok := c.frequent.Peek(key); ok { |
||||
return val, ok |
||||
} |
||||
return c.recent.Peek(key) |
||||
} |
@ -0,0 +1,257 @@ |
||||
package lru |
||||
|
||||
import ( |
||||
"sync" |
||||
|
||||
"github.com/hashicorp/golang-lru/simplelru" |
||||
) |
||||
|
||||
// ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC).
|
||||
// ARC is an enhancement over the standard LRU cache in that tracks both
|
||||
// frequency and recency of use. This avoids a burst in access to new
|
||||
// entries from evicting the frequently used older entries. It adds some
|
||||
// additional tracking overhead to a standard LRU cache, computationally
|
||||
// it is roughly 2x the cost, and the extra memory overhead is linear
|
||||
// with the size of the cache. ARC has been patented by IBM, but is
|
||||
// similar to the TwoQueueCache (2Q) which requires setting parameters.
|
||||
type ARCCache struct { |
||||
size int // Size is the total capacity of the cache
|
||||
p int // P is the dynamic preference towards T1 or T2
|
||||
|
||||
t1 *simplelru.LRU // T1 is the LRU for recently accessed items
|
||||
b1 *simplelru.LRU // B1 is the LRU for evictions from t1
|
||||
|
||||
t2 *simplelru.LRU // T2 is the LRU for frequently accessed items
|
||||
b2 *simplelru.LRU // B2 is the LRU for evictions from t2
|
||||
|
||||
lock sync.RWMutex |
||||
} |
||||
|
||||
// NewARC creates an ARC of the given size
|
||||
func NewARC(size int) (*ARCCache, error) { |
||||
// Create the sub LRUs
|
||||
b1, err := simplelru.NewLRU(size, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
b2, err := simplelru.NewLRU(size, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
t1, err := simplelru.NewLRU(size, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
t2, err := simplelru.NewLRU(size, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Initialize the ARC
|
||||
c := &ARCCache{ |
||||
size: size, |
||||
p: 0, |
||||
t1: t1, |
||||
b1: b1, |
||||
t2: t2, |
||||
b2: b2, |
||||
} |
||||
return c, nil |
||||
} |
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *ARCCache) Get(key interface{}) (interface{}, bool) { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
// Ff the value is contained in T1 (recent), then
|
||||
// promote it to T2 (frequent)
|
||||
if val, ok := c.t1.Peek(key); ok { |
||||
c.t1.Remove(key) |
||||
c.t2.Add(key, val) |
||||
return val, ok |
||||
} |
||||
|
||||
// Check if the value is contained in T2 (frequent)
|
||||
if val, ok := c.t2.Get(key); ok { |
||||
return val, ok |
||||
} |
||||
|
||||
// No hit
|
||||
return nil, false |
||||
} |
||||
|
||||
// Add adds a value to the cache.
|
||||
func (c *ARCCache) Add(key, value interface{}) { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
|
||||
// Check if the value is contained in T1 (recent), and potentially
|
||||
// promote it to frequent T2
|
||||
if c.t1.Contains(key) { |
||||
c.t1.Remove(key) |
||||
c.t2.Add(key, value) |
||||
return |
||||
} |
||||
|
||||
// Check if the value is already in T2 (frequent) and update it
|
||||
if c.t2.Contains(key) { |
||||
c.t2.Add(key, value) |
||||
return |
||||
} |
||||
|
||||
// Check if this value was recently evicted as part of the
|
||||
// recently used list
|
||||
if c.b1.Contains(key) { |
||||
// T1 set is too small, increase P appropriately
|
||||
delta := 1 |
||||
b1Len := c.b1.Len() |
||||
b2Len := c.b2.Len() |
||||
if b2Len > b1Len { |
||||
delta = b2Len / b1Len |
||||
} |
||||
if c.p+delta >= c.size { |
||||
c.p = c.size |
||||
} else { |
||||
c.p += delta |
||||
} |
||||
|
||||
// Potentially need to make room in the cache
|
||||
if c.t1.Len()+c.t2.Len() >= c.size { |
||||
c.replace(false) |
||||
} |
||||
|
||||
// Remove from B1
|
||||
c.b1.Remove(key) |
||||
|
||||
// Add the key to the frequently used list
|
||||
c.t2.Add(key, value) |
||||
return |
||||
} |
||||
|
||||
// Check if this value was recently evicted as part of the
|
||||
// frequently used list
|
||||
if c.b2.Contains(key) { |
||||
// T2 set is too small, decrease P appropriately
|
||||
delta := 1 |
||||
b1Len := c.b1.Len() |
||||
b2Len := c.b2.Len() |
||||
if b1Len > b2Len { |
||||
delta = b1Len / b2Len |
||||
} |
||||
if delta >= c.p { |
||||
c.p = 0 |
||||
} else { |
||||
c.p -= delta |
||||
} |
||||
|
||||
// Potentially need to make room in the cache
|
||||
if c.t1.Len()+c.t2.Len() >= c.size { |
||||
c.replace(true) |
||||
} |
||||
|
||||
// Remove from B2
|
||||
c.b2.Remove(key) |
||||
|
||||
// Add the key to the frequntly used list
|
||||
c.t2.Add(key, value) |
||||
return |
||||
} |
||||
|
||||
// Potentially need to make room in the cache
|
||||
if c.t1.Len()+c.t2.Len() >= c.size { |
||||
c.replace(false) |
||||
} |
||||
|
||||
// Keep the size of the ghost buffers trim
|
||||
if c.b1.Len() > c.size-c.p { |
||||
c.b1.RemoveOldest() |
||||
} |
||||
if c.b2.Len() > c.p { |
||||
c.b2.RemoveOldest() |
||||
} |
||||
|
||||
// Add to the recently seen list
|
||||
c.t1.Add(key, value) |
||||
return |
||||
} |
||||
|
||||
// replace is used to adaptively evict from either T1 or T2
|
||||
// based on the current learned value of P
|
||||
func (c *ARCCache) replace(b2ContainsKey bool) { |
||||
t1Len := c.t1.Len() |
||||
if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) { |
||||
k, _, ok := c.t1.RemoveOldest() |
||||
if ok { |
||||
c.b1.Add(k, nil) |
||||
} |
||||
} else { |
||||
k, _, ok := c.t2.RemoveOldest() |
||||
if ok { |
||||
c.b2.Add(k, nil) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Len returns the number of cached entries
|
||||
func (c *ARCCache) Len() int { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
return c.t1.Len() + c.t2.Len() |
||||
} |
||||
|
||||
// Keys returns all the cached keys
|
||||
func (c *ARCCache) Keys() []interface{} { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
k1 := c.t1.Keys() |
||||
k2 := c.t2.Keys() |
||||
return append(k1, k2...) |
||||
} |
||||
|
||||
// Remove is used to purge a key from the cache
|
||||
func (c *ARCCache) Remove(key interface{}) { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
if c.t1.Remove(key) { |
||||
return |
||||
} |
||||
if c.t2.Remove(key) { |
||||
return |
||||
} |
||||
if c.b1.Remove(key) { |
||||
return |
||||
} |
||||
if c.b2.Remove(key) { |
||||
return |
||||
} |
||||
} |
||||
|
||||
// Purge is used to clear the cache
|
||||
func (c *ARCCache) Purge() { |
||||
c.lock.Lock() |
||||
defer c.lock.Unlock() |
||||
c.t1.Purge() |
||||
c.t2.Purge() |
||||
c.b1.Purge() |
||||
c.b2.Purge() |
||||
} |
||||
|
||||
// Contains is used to check if the cache contains a key
|
||||
// without updating recency or frequency.
|
||||
func (c *ARCCache) Contains(key interface{}) bool { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
return c.t1.Contains(key) || c.t2.Contains(key) |
||||
} |
||||
|
||||
// Peek is used to inspect the cache value of a key
|
||||
// without updating recency or frequency.
|
||||
func (c *ARCCache) Peek(key interface{}) (interface{}, bool) { |
||||
c.lock.RLock() |
||||
defer c.lock.RUnlock() |
||||
if val, ok := c.t1.Peek(key); ok { |
||||
return val, ok |
||||
} |
||||
return c.t2.Peek(key) |
||||
} |
@ -1,127 +0,0 @@ |
||||
package lru |
||||
|
||||
import "testing" |
||||
|
||||
func TestLRU(t *testing.T) { |
||||
evictCounter := 0 |
||||
onEvicted := func(k interface{}, v interface{}) { |
||||
if k != v { |
||||
t.Fatalf("Evict values not equal (%v!=%v)", k, v) |
||||
} |
||||
evictCounter += 1 |
||||
} |
||||
l, err := NewWithEvict(128, onEvicted) |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
|
||||
for i := 0; i < 256; i++ { |
||||
l.Add(i, i) |
||||
} |
||||
if l.Len() != 128 { |
||||
t.Fatalf("bad len: %v", l.Len()) |
||||
} |
||||
|
||||
if evictCounter != 128 { |
||||
t.Fatalf("bad evict count: %v", evictCounter) |
||||
} |
||||
|
||||
for i, k := range l.Keys() { |
||||
if v, ok := l.Get(k); !ok || v != k || v != i+128 { |
||||
t.Fatalf("bad key: %v", k) |
||||
} |
||||
} |
||||
for i := 0; i < 128; i++ { |
||||
_, ok := l.Get(i) |
||||
if ok { |
||||
t.Fatalf("should be evicted") |
||||
} |
||||
} |
||||
for i := 128; i < 256; i++ { |
||||
_, ok := l.Get(i) |
||||
if !ok { |
||||
t.Fatalf("should not be evicted") |
||||
} |
||||
} |
||||
for i := 128; i < 192; i++ { |
||||
l.Remove(i) |
||||
_, ok := l.Get(i) |
||||
if ok { |
||||
t.Fatalf("should be deleted") |
||||
} |
||||
} |
||||
|
||||
l.Get(192) // expect 192 to be last key in l.Keys()
|
||||
|
||||
for i, k := range l.Keys() { |
||||
if (i < 63 && k != i+193) || (i == 63 && k != 192) { |
||||
t.Fatalf("out of order key: %v", k) |
||||
} |
||||
} |
||||
|
||||
l.Purge() |
||||
if l.Len() != 0 { |
||||
t.Fatalf("bad len: %v", l.Len()) |
||||
} |
||||
if _, ok := l.Get(200); ok { |
||||
t.Fatalf("should contain nothing") |
||||
} |
||||
} |
||||
|
||||
// test that Add returns true/false if an eviction occured
|
||||
func TestLRUAdd(t *testing.T) { |
||||
evictCounter := 0 |
||||
onEvicted := func(k interface{}, v interface{}) { |
||||
evictCounter += 1 |
||||
} |
||||
|
||||
l, err := NewWithEvict(1, onEvicted) |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
|
||||
if l.Add(1, 1) == true || evictCounter != 0 { |
||||
t.Errorf("should not have an eviction") |
||||
} |
||||
if l.Add(2, 2) == false || evictCounter != 1 { |
||||
t.Errorf("should have an eviction") |
||||
} |
||||
} |
||||
|
||||
// test that Contains doesn't update recent-ness
|
||||
func TestLRUContains(t *testing.T) { |
||||
l, err := New(2) |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
|
||||
l.Add(1, 1) |
||||
l.Add(2, 2) |
||||
if !l.Contains(1) { |
||||
t.Errorf("1 should be contained") |
||||
} |
||||
|
||||
l.Add(3, 3) |
||||
if l.Contains(1) { |
||||
t.Errorf("Contains should not have updated recent-ness of 1") |
||||
} |
||||
} |
||||
|
||||
// test that Peek doesn't update recent-ness
|
||||
func TestLRUPeek(t *testing.T) { |
||||
l, err := New(2) |
||||
if err != nil { |
||||
t.Fatalf("err: %v", err) |
||||
} |
||||
|
||||
l.Add(1, 1) |
||||
l.Add(2, 2) |
||||
if v, ok := l.Peek(1); !ok || v != 1 { |
||||
t.Errorf("1 should be set to 1: %v, %v", v, ok) |
||||
} |
||||
|
||||
l.Add(3, 3) |
||||
if l.Contains(1) { |
||||
t.Errorf("should not have updated recent-ness of 1") |
||||
} |
||||
} |
@ -0,0 +1,160 @@ |
||||
package simplelru |
||||
|
||||
import ( |
||||
"container/list" |
||||
"errors" |
||||
) |
||||
|
||||
// EvictCallback is used to get a callback when a cache entry is evicted
|
||||
type EvictCallback func(key interface{}, value interface{}) |
||||
|
||||
// LRU implements a non-thread safe fixed size LRU cache
|
||||
type LRU struct { |
||||
size int |
||||
evictList *list.List |
||||
items map[interface{}]*list.Element |
||||
onEvict EvictCallback |
||||
} |
||||
|
||||
// entry is used to hold a value in the evictList
|
||||
type entry struct { |
||||
key interface{} |
||||
value interface{} |
||||
} |
||||
|
||||
// NewLRU constructs an LRU of the given size
|
||||
func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { |
||||
if size <= 0 { |
||||
return nil, errors.New("Must provide a positive size") |
||||
} |
||||
c := &LRU{ |
||||
size: size, |
||||
evictList: list.New(), |
||||
items: make(map[interface{}]*list.Element), |
||||
onEvict: onEvict, |
||||
} |
||||
return c, nil |
||||
} |
||||
|
||||
// Purge is used to completely clear the cache
|
||||
func (c *LRU) Purge() { |
||||
for k, v := range c.items { |
||||
if c.onEvict != nil { |
||||
c.onEvict(k, v.Value.(*entry).value) |
||||
} |
||||
delete(c.items, k) |
||||
} |
||||
c.evictList.Init() |
||||
} |
||||
|
||||
// Add adds a value to the cache. Returns true if an eviction occured.
|
||||
func (c *LRU) Add(key, value interface{}) bool { |
||||
// Check for existing item
|
||||
if ent, ok := c.items[key]; ok { |
||||
c.evictList.MoveToFront(ent) |
||||
ent.Value.(*entry).value = value |
||||
return false |
||||
} |
||||
|
||||
// Add new item
|
||||
ent := &entry{key, value} |
||||
entry := c.evictList.PushFront(ent) |
||||
c.items[key] = entry |
||||
|
||||
evict := c.evictList.Len() > c.size |
||||
// Verify size not exceeded
|
||||
if evict { |
||||
c.removeOldest() |
||||
} |
||||
return evict |
||||
} |
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { |
||||
if ent, ok := c.items[key]; ok { |
||||
c.evictList.MoveToFront(ent) |
||||
return ent.Value.(*entry).value, true |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Check if a key is in the cache, without updating the recent-ness
|
||||
// or deleting it for being stale.
|
||||
func (c *LRU) Contains(key interface{}) (ok bool) { |
||||
_, ok = c.items[key] |
||||
return ok |
||||
} |
||||
|
||||
// Returns the key value (or undefined if not found) without updating
|
||||
// the "recently used"-ness of the key.
|
||||
func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { |
||||
if ent, ok := c.items[key]; ok { |
||||
return ent.Value.(*entry).value, true |
||||
} |
||||
return nil, ok |
||||
} |
||||
|
||||
// Remove removes the provided key from the cache, returning if the
|
||||
// key was contained.
|
||||
func (c *LRU) Remove(key interface{}) bool { |
||||
if ent, ok := c.items[key]; ok { |
||||
c.removeElement(ent) |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// RemoveOldest removes the oldest item from the cache.
|
||||
func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { |
||||
ent := c.evictList.Back() |
||||
if ent != nil { |
||||
c.removeElement(ent) |
||||
kv := ent.Value.(*entry) |
||||
return kv.key, kv.value, true |
||||
} |
||||
return nil, nil, false |
||||
} |
||||
|
||||
// GetOldest returns the oldest entry
|
||||
func (c *LRU) GetOldest() (interface{}, interface{}, bool) { |
||||
ent := c.evictList.Back() |
||||
if ent != nil { |
||||
kv := ent.Value.(*entry) |
||||
return kv.key, kv.value, true |
||||
} |
||||
return nil, nil, false |
||||
} |
||||
|
||||
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||
func (c *LRU) Keys() []interface{} { |
||||
keys := make([]interface{}, len(c.items)) |
||||
i := 0 |
||||
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { |
||||
keys[i] = ent.Value.(*entry).key |
||||
i++ |
||||
} |
||||
return keys |
||||
} |
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *LRU) Len() int { |
||||
return c.evictList.Len() |
||||
} |
||||
|
||||
// removeOldest removes the oldest item from the cache.
|
||||
func (c *LRU) removeOldest() { |
||||
ent := c.evictList.Back() |
||||
if ent != nil { |
||||
c.removeElement(ent) |
||||
} |
||||
} |
||||
|
||||
// removeElement is used to remove a given list element from the cache
|
||||
func (c *LRU) removeElement(e *list.Element) { |
||||
c.evictList.Remove(e) |
||||
kv := e.Value.(*entry) |
||||
delete(c.items, kv.key) |
||||
if c.onEvict != nil { |
||||
c.onEvict(kv.key, kv.value) |
||||
} |
||||
} |
@ -0,0 +1,27 @@ |
||||
// Copyright (c) 2010 Jack Palevich. All rights reserved. |
||||
// |
||||
// Redistribution and use in source and binary forms, with or without |
||||
// modification, are permitted provided that the following conditions are |
||||
// met: |
||||
// |
||||
// * Redistributions of source code must retain the above copyright |
||||
// notice, this list of conditions and the following disclaimer. |
||||
// * Redistributions in binary form must reproduce the above |
||||
// copyright notice, this list of conditions and the following disclaimer |
||||
// in the documentation and/or other materials provided with the |
||||
// distribution. |
||||
// * Neither the name of Google Inc. nor the names of its |
||||
// contributors may be used to endorse or promote products derived from |
||||
// this software without specific prior written permission. |
||||
// |
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,7 @@ |
||||
# gateway |
||||
|
||||
A very simple library for discovering the IP address of the local LAN gateway. |
||||
|
||||
Provides implementations for Linux, OS X (Darwin) and Windows. |
||||
|
||||
Pull requests for other OSs happily considered! |