@ -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
|
// Use of this source code is governed by a MIT license that can
|
||||||
// be found in the LICENSE file.
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
package termui |
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,
|
// Block is a base struct for all other upper level widgets,
|
||||||
// consider it as css: display:block.
|
// consider it as css: display:block.
|
||||||
// Normally you do not need to create it manually.
|
// Normally you do not need to create it manually.
|
||||||
type Block struct { |
type Block struct { |
||||||
|
area image.Rectangle |
||||||
|
innerArea image.Rectangle |
||||||
X int |
X int |
||||||
Y int |
Y int |
||||||
Border labeledBorder |
Border bool |
||||||
IsDisplay bool |
BorderFg Attribute |
||||||
HasBorder bool |
BorderBg Attribute |
||||||
BgColor Attribute |
BorderLeft bool |
||||||
|
BorderRight bool |
||||||
|
BorderTop bool |
||||||
|
BorderBottom bool |
||||||
|
BorderLabel string |
||||||
|
BorderLabelFg Attribute |
||||||
|
BorderLabelBg Attribute |
||||||
|
Display bool |
||||||
|
Bg Attribute |
||||||
Width int |
Width int |
||||||
Height int |
Height int |
||||||
innerWidth int |
|
||||||
innerHeight int |
|
||||||
innerX int |
|
||||||
innerY int |
|
||||||
PaddingTop int |
PaddingTop int |
||||||
PaddingBottom int |
PaddingBottom int |
||||||
PaddingLeft int |
PaddingLeft int |
||||||
PaddingRight int |
PaddingRight int |
||||||
|
id string |
||||||
|
Float Align |
||||||
} |
} |
||||||
|
|
||||||
// NewBlock returns a *Block which inherits styles from current theme.
|
// NewBlock returns a *Block which inherits styles from current theme.
|
||||||
func NewBlock() *Block { |
func NewBlock() *Block { |
||||||
d := Block{} |
b := Block{} |
||||||
d.IsDisplay = true |
b.Display = true |
||||||
d.HasBorder = theme.HasBorder |
b.Border = true |
||||||
d.Border.BgColor = theme.BorderBg |
b.BorderLeft = true |
||||||
d.Border.FgColor = theme.BorderFg |
b.BorderRight = true |
||||||
d.Border.LabelBgColor = theme.BorderLabelTextBg |
b.BorderTop = true |
||||||
d.Border.LabelFgColor = theme.BorderLabelTextFg |
b.BorderBottom = true |
||||||
d.BgColor = theme.BlockBg |
b.BorderBg = ThemeAttr("border.bg") |
||||||
d.Width = 2 |
b.BorderFg = ThemeAttr("border.fg") |
||||||
d.Height = 2 |
b.BorderLabelBg = ThemeAttr("label.bg") |
||||||
return &d |
b.BorderLabelFg = ThemeAttr("label.fg") |
||||||
|
b.Bg = ThemeAttr("block.bg") |
||||||
|
b.Width = 2 |
||||||
|
b.Height = 2 |
||||||
|
b.id = GenId() |
||||||
|
b.Float = AlignNone |
||||||
|
return &b |
||||||
} |
} |
||||||
|
|
||||||
// compute box model
|
func (b Block) Id() string { |
||||||
func (d *Block) align() { |
return b.id |
||||||
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++ |
|
||||||
} |
} |
||||||
|
|
||||||
if d.innerHeight < 0 { |
// Align computes box model
|
||||||
d.innerHeight = 0 |
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-- |
||||||
} |
} |
||||||
if d.innerWidth < 0 { |
|
||||||
d.innerWidth = 0 |
|
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
// InnerBounds returns the internal bounds of the block after aligning and
|
// InnerBounds returns the internal bounds of the block after aligning and
|
||||||
// calculating the padding and border, if any.
|
// calculating the padding and border, if any.
|
||||||
func (d *Block) InnerBounds() (x, y, width, height int) { |
func (b *Block) InnerBounds() image.Rectangle { |
||||||
d.align() |
b.Align() |
||||||
return d.innerX, d.innerY, d.innerWidth, d.innerHeight |
return b.innerArea |
||||||
} |
} |
||||||
|
|
||||||
// Buffer implements Bufferer interface.
|
// Buffer implements Bufferer interface.
|
||||||
// Draw background and border (if any).
|
// Draw background and border (if any).
|
||||||
func (d *Block) Buffer() []Point { |
func (b *Block) Buffer() Buffer { |
||||||
d.align() |
b.Align() |
||||||
|
|
||||||
ps := []Point{} |
buf := NewBuffer() |
||||||
if !d.IsDisplay { |
buf.SetArea(b.area) |
||||||
return ps |
buf.Fill(' ', ColorDefault, b.Bg) |
||||||
} |
|
||||||
|
|
||||||
if d.HasBorder { |
b.drawBorder(buf) |
||||||
ps = d.Border.Buffer() |
b.drawBorderLabel(buf) |
||||||
} |
|
||||||
|
|
||||||
for i := 0; i < d.innerWidth; i++ { |
return buf |
||||||
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 |
|
||||||
} |
} |
||||||
|
|
||||||
// GetHeight implements GridBufferer.
|
// GetHeight implements GridBufferer.
|
||||||
// It returns current height of the block.
|
// It returns current height of the block.
|
||||||
func (d Block) GetHeight() int { |
func (b Block) GetHeight() int { |
||||||
return d.Height |
return b.Height |
||||||
} |
} |
||||||
|
|
||||||
// SetX implements GridBufferer interface, which sets block's x position.
|
// SetX implements GridBufferer interface, which sets block's x position.
|
||||||
func (d *Block) SetX(x int) { |
func (b *Block) SetX(x int) { |
||||||
d.X = x |
b.X = x |
||||||
} |
} |
||||||
|
|
||||||
// SetY implements GridBufferer interface, it sets y position for block.
|
// SetY implements GridBufferer interface, it sets y position for block.
|
||||||
func (d *Block) SetY(y int) { |
func (b *Block) SetY(y int) { |
||||||
d.Y = y |
b.Y = y |
||||||
} |
} |
||||||
|
|
||||||
// SetWidth implements GridBuffer interface, it sets block's width.
|
// SetWidth implements GridBuffer interface, it sets block's width.
|
||||||
func (d *Block) SetWidth(w int) { |
func (b *Block) SetWidth(w int) { |
||||||
d.Width = w |
b.Width = w |
||||||
} |
} |
||||||
|
|
||||||
// chop the overflow parts
|
func (b Block) InnerWidth() int { |
||||||
func (d *Block) chopOverflow(ps []Point) []Point { |
return b.innerArea.Dx() |
||||||
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) |
|
||||||
} |
} |
||||||
|
|
||||||
|
func (b Block) InnerHeight() int { |
||||||
|
return b.innerArea.Dy() |
||||||
} |
} |
||||||
return nps |
|
||||||
|
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
|
// Use of this source code is governed by a MIT license that can
|
||||||
// be found in the LICENSE file.
|
// 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
|
// Use of this source code is governed by a MIT license that can
|
||||||
// be found in the LICENSE file.
|
// 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
|
// Use of this source code is governed by a MIT license that can
|
||||||
// be found in the LICENSE file.
|
// 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! |