Merge pull request #2206 from fjl/update-deps
Godeps: update all dependencies
|
@ -5,14 +5,18 @@
|
|||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/Gustav-Simonsson/go-opencl/cl",
|
||||
"Rev": "593e01cfc4f3353585015321e01951d4a907d3ef"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/cli",
|
||||
"Comment": "1.2.0-161-gf445c89",
|
||||
"Rev": "f445c894402839580d30de47551cedc152dad814"
|
||||
"Comment": "1.2.0-215-g0ab42fd",
|
||||
"Rev": "0ab42fd482c27cf2c95e7794ad3bb2082c2ab2d7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||
"Rev": "3e6e67c4dcea3ac2f25fd4731abc0e1deaf36216"
|
||||
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/ethash",
|
||||
|
@ -21,101 +25,126 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/fatih/color",
|
||||
"Comment": "v0.1-5-gf773d4c",
|
||||
"Rev": "f773d4c806cc8e4a5749d6a35e2a4bbcd71443d6"
|
||||
"Comment": "v0.1-12-g9aae6aa",
|
||||
"Rev": "9aae6aaa22315390f03959adca2c4d395b02fcef"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gizak/termui",
|
||||
"Rev": "bab8dce01c193d82bc04888a0a9a7814d505f532"
|
||||
"Rev": "08a5d3f67b7d9ec87830ea39c48e570a1f18531f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/golang/snappy",
|
||||
"Rev": "799c780093d646c1b79d30894e22512c319fa137"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hashicorp/golang-lru",
|
||||
"Rev": "7f9ef20a0256f494e24126014135cf893ab71e9e"
|
||||
"Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/huin/goupnp",
|
||||
"Rev": "90f71cb5dd6d4606388666d2cda4ce2f563d2185"
|
||||
"Rev": "46bde78b11f3f021f2a511df138be9e2fc7506e8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jackpal/gateway",
|
||||
"Rev": "192609c58b8985e645cbe82ddcb28a4362ca0fdc"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jackpal/go-nat-pmp",
|
||||
"Rev": "a45aa3d54aef73b504e15eb71bea0e5565b5e6e1"
|
||||
"Rev": "46523a463303c6ede3ddfe45bde1c7ed52ebaacd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-colorable",
|
||||
"Rev": "9fdad7c47650b7d2e1da50644c1f4ba7f172f252"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-isatty",
|
||||
"Rev": "7fcbc72f853b92b5720db4a6b8482be612daef24"
|
||||
"Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-runewidth",
|
||||
"Comment": "travisish-33-g5890272",
|
||||
"Rev": "5890272cd41c5103531cd7b79e428d99c9e97f76"
|
||||
"Comment": "travisish-44-ge882a96",
|
||||
"Rev": "e882a96ec18dd43fa283187b66af74497c9101c0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/nsf/termbox-go",
|
||||
"Rev": "ca2931516914070bb7f934c83e408689cea8dfb7"
|
||||
"Rev": "362329b0aa6447eadd52edd8d660ec1dff470295"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pborman/uuid",
|
||||
"Rev": "cccd189d45f7ac3368a0d127efb7f4d08ae0b655"
|
||||
"Comment": "v1.0-6-g0f1a469",
|
||||
"Rev": "0f1a46960a86dcdf5dd30d3e6568a497a997909f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/peterh/liner",
|
||||
"Rev": "29f6a646557d83e2b6e9ba05c45fbea9c006dbe8"
|
||||
"Rev": "ad1edfd30321d8f006ccf05f1e0524adeb943060"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rcrowley/go-metrics",
|
||||
"Rev": "a5cfc242a56ba7fa70b785f678d6214837bf93b9"
|
||||
"Rev": "51425a2415d21afadfd55cd93432c0bc69e9598d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/robertkrimen/otto",
|
||||
"Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/shiena/ansicolor",
|
||||
"Rev": "a5e2b567a4dd6cc74545b8a4f27c9d63b9e7735b"
|
||||
"Rev": "c21072f61b64b51ea58138ccacf0a85d54b9f07c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
||||
"Rev": "4875955338b0a434238a31165cb87255ab6e9e4a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syndtr/gosnappy/snappy",
|
||||
"Rev": "156a073208e131d7d2e212cb749feae7c339e846"
|
||||
"Rev": "e7e6f5b5ef25adb580feac515f9ccec514d0bda8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
||||
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ripemd160",
|
||||
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/scrypt",
|
||||
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "e0403b4e005737430c05a57aac078479844f919c"
|
||||
"Rev": "8968c61983e8f51a91b8c0ef25bf739278c89634"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/html",
|
||||
"Rev": "e0403b4e005737430c05a57aac078479844f919c"
|
||||
"Rev": "8968c61983e8f51a91b8c0ef25bf739278c89634"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/websocket",
|
||||
"Rev": "e0403b4e005737430c05a57aac078479844f919c"
|
||||
"Rev": "8968c61983e8f51a91b8c0ef25bf739278c89634"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/sys/unix",
|
||||
"Rev": "50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text/encoding",
|
||||
"Rev": "c93e7c9fff19fb9139b5ab04ce041833add0134e"
|
||||
"Rev": "09761194ac5034a97b2bfad4f5b896b0ac350b3e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text/internal/tag",
|
||||
"Rev": "09761194ac5034a97b2bfad4f5b896b0ac350b3e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text/internal/utf8internal",
|
||||
"Rev": "09761194ac5034a97b2bfad4f5b896b0ac350b3e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text/language",
|
||||
"Rev": "09761194ac5034a97b2bfad4f5b896b0ac350b3e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text/runes",
|
||||
"Rev": "09761194ac5034a97b2bfad4f5b896b0ac350b3e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/text/transform",
|
||||
"Rev": "c93e7c9fff19fb9139b5ab04ce041833add0134e"
|
||||
"Rev": "09761194ac5034a97b2bfad4f5b896b0ac350b3e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/check.v1",
|
||||
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"
|
||||
"Rev": "4f90aeace3a26ad7021961c297b22c42160c7b25"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/fatih/set.v0",
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -7,6 +7,12 @@ go:
|
|||
- 1.2.2
|
||||
- 1.3.3
|
||||
- 1.4.2
|
||||
- 1.5.1
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- go vet ./...
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli)
|
||||
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli)
|
||||
[![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli)
|
||||
[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli)
|
||||
|
||||
# cli.go
|
||||
|
||||
`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
|
||||
|
||||
## Overview
|
||||
|
||||
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
|
||||
|
||||
**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive!
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
|
||||
|
||||
To install `cli.go`, simply run:
|
||||
|
@ -24,6 +27,7 @@ export PATH=$PATH:$GOPATH/bin
|
|||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`.
|
||||
|
||||
``` go
|
||||
|
@ -123,6 +127,7 @@ GLOBAL OPTIONS
|
|||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
You can lookup arguments by calling the `Args` function on `cli.Context`.
|
||||
|
||||
``` go
|
||||
|
@ -134,7 +139,9 @@ app.Action = func(c *cli.Context) {
|
|||
```
|
||||
|
||||
### Flags
|
||||
|
||||
Setting and querying flags is simple.
|
||||
|
||||
``` go
|
||||
...
|
||||
app.Flags = []cli.Flag {
|
||||
|
@ -158,6 +165,33 @@ app.Action = func(c *cli.Context) {
|
|||
...
|
||||
```
|
||||
|
||||
You can also set a destination variable for a flag, to which the content will be scanned.
|
||||
|
||||
``` go
|
||||
...
|
||||
var language string
|
||||
app.Flags = []cli.Flag {
|
||||
cli.StringFlag{
|
||||
Name: "lang",
|
||||
Value: "english",
|
||||
Usage: "language for the greeting",
|
||||
Destination: &language,
|
||||
},
|
||||
}
|
||||
app.Action = func(c *cli.Context) {
|
||||
name := "someone"
|
||||
if len(c.Args()) > 0 {
|
||||
name = c.Args()[0]
|
||||
}
|
||||
if language == "spanish" {
|
||||
println("Hola", name)
|
||||
} else {
|
||||
println("Hello", name)
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
See full list of flags at http://godoc.org/github.com/codegangsta/cli
|
||||
|
||||
#### Alternate Names
|
||||
|
@ -207,6 +241,7 @@ app.Flags = []cli.Flag {
|
|||
### Subcommands
|
||||
|
||||
Subcommands can be defined for a more git-like command line app.
|
||||
|
||||
```go
|
||||
...
|
||||
app.Commands = []cli.Command{
|
||||
|
@ -257,6 +292,7 @@ You can enable completion commands by setting the `EnableBashCompletion`
|
|||
flag on the `App` object. By default, this setting will only auto-complete to
|
||||
show an app's subcommands, but you can write your own completion methods for
|
||||
the App or its subcommands.
|
||||
|
||||
```go
|
||||
...
|
||||
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
|
||||
|
@ -299,8 +335,8 @@ automatically install it there if you are distributing a package). Don't forget
|
|||
to source the file to make it active in the current shell.
|
||||
|
||||
```
|
||||
sudo cp src/bash_autocomplete /etc/bash_completion.d/<myprogram>
|
||||
source /etc/bash_completion.d/<myprogram>
|
||||
sudo cp src/bash_autocomplete /etc/bash_completion.d/<myprogram>
|
||||
source /etc/bash_completion.d/<myprogram>
|
||||
```
|
||||
|
||||
Alternatively, you can just document that users should source the generic
|
||||
|
@ -308,6 +344,7 @@ Alternatively, you can just document that users should source the generic
|
|||
to the name of their program (as above).
|
||||
|
||||
## Contribution Guidelines
|
||||
|
||||
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
|
||||
|
||||
If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
|
||||
|
|
|
@ -5,18 +5,21 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// App is the main structure of a cli application. It is recomended that
|
||||
// App is the main structure of a cli application. It is recommended that
|
||||
// an app be created with the cli.NewApp() function
|
||||
type App struct {
|
||||
// The name of the program. Defaults to os.Args[0]
|
||||
// The name of the program. Defaults to path.Base(os.Args[0])
|
||||
Name string
|
||||
// Full name of command for help, defaults to Name
|
||||
HelpName string
|
||||
// Description of the program.
|
||||
Usage string
|
||||
// Text to override the USAGE section of help
|
||||
UsageText string
|
||||
// Description of the program argument format.
|
||||
ArgsUsage string
|
||||
// Version of the program
|
||||
|
@ -43,6 +46,10 @@ type App struct {
|
|||
Action func(context *Context)
|
||||
// Execute this function if the proper command cannot be found
|
||||
CommandNotFound func(context *Context, command string)
|
||||
// Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages.
|
||||
// This function is able to replace the original error messages.
|
||||
// If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted.
|
||||
OnUsageError func(context *Context, err error, isSubcommand bool) error
|
||||
// Compilation date
|
||||
Compiled time.Time
|
||||
// List of all authors who contributed
|
||||
|
@ -70,9 +77,10 @@ func compileTime() time.Time {
|
|||
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
|
||||
func NewApp() *App {
|
||||
return &App{
|
||||
Name: os.Args[0],
|
||||
HelpName: os.Args[0],
|
||||
Name: path.Base(os.Args[0]),
|
||||
HelpName: path.Base(os.Args[0]),
|
||||
Usage: "A new cli application",
|
||||
UsageText: "",
|
||||
Version: "0.0.0",
|
||||
BashComplete: DefaultAppComplete,
|
||||
Action: helpCommand.Action,
|
||||
|
@ -118,25 +126,28 @@ func (a *App) Run(arguments []string) (err error) {
|
|||
set.SetOutput(ioutil.Discard)
|
||||
err = set.Parse(arguments[1:])
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, nil)
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(a.Writer, nerr)
|
||||
context := NewContext(a, set, nil)
|
||||
ShowAppHelp(context)
|
||||
return nerr
|
||||
}
|
||||
context := NewContext(a, set, nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(a.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(a.Writer)
|
||||
ShowAppHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err := a.OnUsageError(context, err, false)
|
||||
return err
|
||||
} else {
|
||||
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
||||
ShowAppHelp(context)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !a.HideHelp && checkHelp(context) {
|
||||
ShowAppHelp(context)
|
||||
return nil
|
||||
|
@ -149,8 +160,7 @@ func (a *App) Run(arguments []string) (err error) {
|
|||
|
||||
if a.After != nil {
|
||||
defer func() {
|
||||
afterErr := a.After(context)
|
||||
if afterErr != nil {
|
||||
if afterErr := a.After(context); afterErr != nil {
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
|
@ -161,8 +171,10 @@ func (a *App) Run(arguments []string) (err error) {
|
|||
}
|
||||
|
||||
if a.Before != nil {
|
||||
err := a.Before(context)
|
||||
err = a.Before(context)
|
||||
if err != nil {
|
||||
fmt.Fprintf(a.Writer, "%v\n\n", err)
|
||||
ShowAppHelp(context)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -233,17 +245,21 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|||
return nerr
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(a.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(a.Writer)
|
||||
ShowSubcommandHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err = a.OnUsageError(context, err, true)
|
||||
return err
|
||||
} else {
|
||||
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
||||
ShowSubcommandHelp(context)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(a.Commands) > 0 {
|
||||
if checkSubcommandHelp(context) {
|
||||
return nil
|
||||
|
|
|
@ -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
|
5
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
|
@ -3,13 +3,12 @@
|
|||
: ${PROG:=$(basename ${BASH_SOURCE})}
|
||||
|
||||
_cli_bash_autocomplete() {
|
||||
local cur prev opts base
|
||||
local cur opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _cli_bash_autocomplete $PROG
|
||||
complete -F _cli_bash_autocomplete $PROG
|
||||
|
|
|
@ -16,6 +16,8 @@ type Command struct {
|
|||
Aliases []string
|
||||
// A short description of the usage of this command
|
||||
Usage string
|
||||
// Custom text to show on USAGE section of help
|
||||
UsageText string
|
||||
// A longer explanation of how the command works
|
||||
Description string
|
||||
// A short description of the arguments of this command
|
||||
|
@ -30,6 +32,10 @@ type Command struct {
|
|||
After func(context *Context) error
|
||||
// The function to call when this command is invoked
|
||||
Action func(context *Context)
|
||||
// Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages.
|
||||
// This function is able to replace the original error messages.
|
||||
// If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted.
|
||||
OnUsageError func(context *Context, err error) error
|
||||
// List of child commands
|
||||
Subcommands []Command
|
||||
// List of flags to parse
|
||||
|
@ -54,8 +60,8 @@ func (c Command) FullName() string {
|
|||
}
|
||||
|
||||
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||
func (c Command) Run(ctx *Context) error {
|
||||
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
|
||||
func (c Command) Run(ctx *Context) (err error) {
|
||||
if len(c.Subcommands) > 0 {
|
||||
return c.startApp(ctx)
|
||||
}
|
||||
|
||||
|
@ -74,41 +80,54 @@ func (c Command) Run(ctx *Context) error {
|
|||
set := flagSet(c.Name, c.Flags)
|
||||
set.SetOutput(ioutil.Discard)
|
||||
|
||||
firstFlagIndex := -1
|
||||
terminatorIndex := -1
|
||||
for index, arg := range ctx.Args() {
|
||||
if arg == "--" {
|
||||
terminatorIndex = index
|
||||
break
|
||||
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
||||
firstFlagIndex = index
|
||||
if !c.SkipFlagParsing {
|
||||
firstFlagIndex := -1
|
||||
terminatorIndex := -1
|
||||
for index, arg := range ctx.Args() {
|
||||
if arg == "--" {
|
||||
terminatorIndex = index
|
||||
break
|
||||
} else if arg == "-" {
|
||||
// Do nothing. A dash alone is not really a flag.
|
||||
continue
|
||||
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
||||
firstFlagIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if firstFlagIndex > -1 && !c.SkipFlagParsing {
|
||||
args := ctx.Args()
|
||||
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
||||
copy(regularArgs, args[1:firstFlagIndex])
|
||||
if firstFlagIndex > -1 {
|
||||
args := ctx.Args()
|
||||
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
||||
copy(regularArgs, args[1:firstFlagIndex])
|
||||
|
||||
var flagArgs []string
|
||||
if terminatorIndex > -1 {
|
||||
flagArgs = args[firstFlagIndex:terminatorIndex]
|
||||
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
||||
var flagArgs []string
|
||||
if terminatorIndex > -1 {
|
||||
flagArgs = args[firstFlagIndex:terminatorIndex]
|
||||
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
||||
} else {
|
||||
flagArgs = args[firstFlagIndex:]
|
||||
}
|
||||
|
||||
err = set.Parse(append(flagArgs, regularArgs...))
|
||||
} else {
|
||||
flagArgs = args[firstFlagIndex:]
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
}
|
||||
|
||||
err = set.Parse(append(flagArgs, regularArgs...))
|
||||
} else {
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
if c.SkipFlagParsing {
|
||||
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return err
|
||||
if c.OnUsageError != nil {
|
||||
err := c.OnUsageError(ctx, err)
|
||||
return err
|
||||
} else {
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
nerr := normalizeFlags(c.Flags, set)
|
||||
|
@ -127,6 +146,30 @@ func (c Command) Run(ctx *Context) error {
|
|||
if checkCommandHelp(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.After != nil {
|
||||
defer func() {
|
||||
afterErr := c.After(context)
|
||||
if afterErr != nil {
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if c.Before != nil {
|
||||
err := c.Before(context)
|
||||
if err != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, err)
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
context.Command = c
|
||||
c.Action(context)
|
||||
return nil
|
||||
|
@ -160,7 +203,7 @@ func (c Command) startApp(ctx *Context) error {
|
|||
if c.HelpName == "" {
|
||||
app.HelpName = c.HelpName
|
||||
} else {
|
||||
app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
||||
app.HelpName = app.Name
|
||||
}
|
||||
|
||||
if c.Description != "" {
|
||||
|
@ -199,12 +242,9 @@ func (c Command) startApp(ctx *Context) error {
|
|||
app.Action = helpSubcommand.Action
|
||||
}
|
||||
|
||||
var newCmds []Command
|
||||
for _, cc := range app.Commands {
|
||||
cc.commandNamePath = []string{c.Name, cc.Name}
|
||||
newCmds = append(newCmds, cc)
|
||||
for index, cc := range app.Commands {
|
||||
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
||||
}
|
||||
app.Commands = newCmds
|
||||
|
||||
return app.RunAsSubcommand(ctx)
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ func (c *Context) GlobalIsSet(name string) bool {
|
|||
// Returns a slice of flag names used in this context.
|
||||
func (c *Context) FlagNames() (names []string) {
|
||||
for _, flag := range c.Command.Flags {
|
||||
name := strings.Split(flag.getName(), ",")[0]
|
||||
name := strings.Split(flag.GetName(), ",")[0]
|
||||
if name == "help" {
|
||||
continue
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ func (c *Context) FlagNames() (names []string) {
|
|||
// Returns a slice of global flag names used by the app.
|
||||
func (c *Context) GlobalFlagNames() (names []string) {
|
||||
for _, flag := range c.App.Flags {
|
||||
name := strings.Split(flag.getName(), ",")[0]
|
||||
name := strings.Split(flag.GetName(), ",")[0]
|
||||
if name == "help" || name == "version" {
|
||||
continue
|
||||
}
|
||||
|
@ -360,7 +360,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
|||
visited[f.Name] = true
|
||||
})
|
||||
for _, f := range flags {
|
||||
parts := strings.Split(f.getName(), ",")
|
||||
parts := strings.Split(f.GetName(), ",")
|
||||
if len(parts) == 1 {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -29,13 +30,13 @@ var HelpFlag = BoolFlag{
|
|||
}
|
||||
|
||||
// Flag is a common interface related to parsing flags in cli.
|
||||
// For more advanced flag parsing techniques, it is recomended that
|
||||
// For more advanced flag parsing techniques, it is recommended that
|
||||
// this interface be implemented.
|
||||
type Flag interface {
|
||||
fmt.Stringer
|
||||
// Apply Flag settings to the given flag set
|
||||
Apply(*flag.FlagSet)
|
||||
getName() string
|
||||
GetName() string
|
||||
}
|
||||
|
||||
func flagSet(name string, flags []Flag) *flag.FlagSet {
|
||||
|
@ -73,7 +74,18 @@ type GenericFlag struct {
|
|||
// help text to the user (uses the String() method of the generic flag to show
|
||||
// the value)
|
||||
func (f GenericFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage))
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage))
|
||||
}
|
||||
|
||||
func (f GenericFlag) FormatValueHelp() string {
|
||||
if f.Value == nil {
|
||||
return ""
|
||||
}
|
||||
s := f.Value.String()
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("\"%s\"", s)
|
||||
}
|
||||
|
||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||
|
@ -95,7 +107,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) {
|
|||
})
|
||||
}
|
||||
|
||||
func (f GenericFlag) getName() string {
|
||||
func (f GenericFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
|
@ -159,7 +171,7 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
|||
})
|
||||
}
|
||||
|
||||
func (f StringSliceFlag) getName() string {
|
||||
func (f StringSliceFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
|
@ -231,15 +243,16 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
|||
})
|
||||
}
|
||||
|
||||
func (f IntSliceFlag) getName() string {
|
||||
func (f IntSliceFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// BoolFlag is a switch that defaults to false
|
||||
type BoolFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
|
@ -264,20 +277,25 @@ func (f BoolFlag) Apply(set *flag.FlagSet) {
|
|||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f BoolFlag) getName() string {
|
||||
func (f BoolFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// BoolTFlag this represents a boolean flag that is true by default, but can
|
||||
// still be set to false by --some-flag=false
|
||||
type BoolTFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
|
@ -302,34 +320,38 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
|||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.BoolVar(f.Destination, name, val, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f BoolTFlag) getName() string {
|
||||
func (f BoolTFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// StringFlag represents a flag that takes as string value
|
||||
type StringFlag struct {
|
||||
Name string
|
||||
Value string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Name string
|
||||
Value string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *string
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f StringFlag) String() string {
|
||||
var fmtString string
|
||||
fmtString = "%s %v\t%v"
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage))
|
||||
}
|
||||
|
||||
if len(f.Value) > 0 {
|
||||
fmtString = "%s \"%v\"\t%v"
|
||||
} else {
|
||||
fmtString = "%s %v\t%v"
|
||||
func (f StringFlag) FormatValueHelp() string {
|
||||
s := f.Value
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
|
||||
return fmt.Sprintf("\"%s\"", s)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
|
@ -345,21 +367,26 @@ func (f StringFlag) Apply(set *flag.FlagSet) {
|
|||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.StringVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.String(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f StringFlag) getName() string {
|
||||
func (f StringFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IntFlag is a flag that takes an integer
|
||||
// Errors if the value provided cannot be parsed
|
||||
type IntFlag struct {
|
||||
Name string
|
||||
Value int
|
||||
Usage string
|
||||
EnvVar string
|
||||
Name string
|
||||
Value int
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *int
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
|
@ -383,21 +410,26 @@ func (f IntFlag) Apply(set *flag.FlagSet) {
|
|||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.IntVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Int(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f IntFlag) getName() string {
|
||||
func (f IntFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// DurationFlag is a flag that takes a duration specified in Go's duration
|
||||
// format: https://golang.org/pkg/time/#ParseDuration
|
||||
type DurationFlag struct {
|
||||
Name string
|
||||
Value time.Duration
|
||||
Usage string
|
||||
EnvVar string
|
||||
Name string
|
||||
Value time.Duration
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *time.Duration
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
|
@ -421,21 +453,26 @@ func (f DurationFlag) Apply(set *flag.FlagSet) {
|
|||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.DurationVar(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Duration(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f DurationFlag) getName() string {
|
||||
func (f DurationFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// Float64Flag is a flag that takes an float value
|
||||
// Errors if the value provided cannot be parsed
|
||||
type Float64Flag struct {
|
||||
Name string
|
||||
Value float64
|
||||
Usage string
|
||||
EnvVar string
|
||||
Name string
|
||||
Value float64
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *float64
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
|
@ -458,11 +495,15 @@ func (f Float64Flag) Apply(set *flag.FlagSet) {
|
|||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Destination != nil {
|
||||
set.Float64Var(f.Destination, name, f.Value, f.Usage)
|
||||
return
|
||||
}
|
||||
set.Float64(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f Float64Flag) getName() string {
|
||||
func (f Float64Flag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
|
@ -491,7 +532,15 @@ func prefixedNames(fullName string) (prefixed string) {
|
|||
func withEnvHint(envVar, str string) string {
|
||||
envText := ""
|
||||
if envVar != "" {
|
||||
envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $"))
|
||||
prefix := "$"
|
||||
suffix := ""
|
||||
sep := ", $"
|
||||
if runtime.GOOS == "windows" {
|
||||
prefix = "%"
|
||||
suffix = "%"
|
||||
sep = "%, %"
|
||||
}
|
||||
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
|
||||
}
|
||||
return str + envText
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ var AppHelpTemplate = `NAME:
|
|||
{{.Name}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
||||
{{if .Version}}
|
||||
VERSION:
|
||||
{{.Version}}
|
||||
|
@ -180,7 +180,9 @@ func printHelp(out io.Writer, templ string, data interface{}) {
|
|||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
||||
err := t.Execute(w, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
||||
// we can do to recover. We could send this to os.Stderr if we need.
|
||||
return
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
37
Godeps/_workspace/src/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
|
@ -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
|
||||
}
|
|
@ -23,116 +23,8 @@ import (
|
|||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||
// the technique used in the fmt package.
|
||||
var (
|
||||
|
@ -194,9 +86,14 @@ func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool)
|
|||
// We need an interface to check if the type implements the error or
|
||||
// Stringer interface. However, the reflect package won't give us an
|
||||
// interface on certain things like unexported struct fields in order
|
||||
// to enforce visibility rules. We use unsafe to bypass these restrictions
|
||||
// since this package does not mutate the values.
|
||||
// to enforce visibility rules. We use unsafe, when it's available,
|
||||
// to bypass these restrictions since this package does not mutate the
|
||||
// values.
|
||||
if !v.CanInterface() {
|
||||
if UnsafeDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
|
||||
|
@ -206,21 +103,15 @@ func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool)
|
|||
// mutate the value, however, types which choose to satisify an error or
|
||||
// Stringer interface with a pointer receiver should not be mutating their
|
||||
// state inside these interface methods.
|
||||
var viface interface{}
|
||||
if !cs.DisablePointerMethods {
|
||||
if !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
viface = v.Addr().Interface()
|
||||
} else {
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
viface = v.Interface()
|
||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
// Is it an error or Stringer?
|
||||
switch iface := viface.(type) {
|
||||
switch iface := v.Interface().(type) {
|
||||
case error:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -61,7 +61,10 @@ type ConfigState struct {
|
|||
// with a pointer receiver could technically mutate the value, however,
|
||||
// in practice, types which choose to satisify an error or Stringer
|
||||
// interface with a pointer receiver should not be mutating their state
|
||||
// inside these interface methods.
|
||||
// inside these interface methods. As a result, this option relies on
|
||||
// access to the unsafe package, so it will not have any effect when
|
||||
// running in environments without access to the unsafe package such as
|
||||
// Google App Engine or with the "disableunsafe" build tag specified.
|
||||
DisablePointerMethods bool
|
||||
|
||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||
|
|
|
@ -181,25 +181,28 @@ func (d *dumpState) dumpSlice(v reflect.Value) {
|
|||
// Try to use existing uint8 slices and fall back to converting
|
||||
// and copying if that fails.
|
||||
case vt.Kind() == reflect.Uint8:
|
||||
// We need an addressable interface to convert the type back
|
||||
// into a byte slice. However, the reflect package won't give
|
||||
// us an interface on certain things like unexported struct
|
||||
// fields in order to enforce visibility rules. We use unsafe
|
||||
// to bypass these restrictions since this package does not
|
||||
// We need an addressable interface to convert the type
|
||||
// to a byte slice. However, the reflect package won't
|
||||
// give us an interface on certain things like
|
||||
// unexported struct fields in order to enforce
|
||||
// visibility rules. We use unsafe, when available, to
|
||||
// bypass these restrictions since this package does not
|
||||
// mutate the values.
|
||||
vs := v
|
||||
if !vs.CanInterface() || !vs.CanAddr() {
|
||||
vs = unsafeReflectValue(vs)
|
||||
}
|
||||
vs = vs.Slice(0, numEntries)
|
||||
if !UnsafeDisabled {
|
||||
vs = vs.Slice(0, numEntries)
|
||||
|
||||
// Use the existing uint8 slice if it can be type
|
||||
// asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
// Use the existing uint8 slice if it can be
|
||||
// type asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying data needs to be converted if it can't
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -6,8 +6,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/shiena/ansicolor"
|
||||
)
|
||||
|
||||
// NoColor defines if the output is colorized or not. It's dynamically set to
|
||||
|
@ -53,6 +53,18 @@ const (
|
|||
FgWhite
|
||||
)
|
||||
|
||||
// Foreground Hi-Intensity text colors
|
||||
const (
|
||||
FgHiBlack Attribute = iota + 90
|
||||
FgHiRed
|
||||
FgHiGreen
|
||||
FgHiYellow
|
||||
FgHiBlue
|
||||
FgHiMagenta
|
||||
FgHiCyan
|
||||
FgHiWhite
|
||||
)
|
||||
|
||||
// Background text colors
|
||||
const (
|
||||
BgBlack Attribute = iota + 40
|
||||
|
@ -65,6 +77,18 @@ const (
|
|||
BgWhite
|
||||
)
|
||||
|
||||
// Background Hi-Intensity text colors
|
||||
const (
|
||||
BgHiBlack Attribute = iota + 100
|
||||
BgHiRed
|
||||
BgHiGreen
|
||||
BgHiYellow
|
||||
BgHiBlue
|
||||
BgHiMagenta
|
||||
BgHiCyan
|
||||
BgHiWhite
|
||||
)
|
||||
|
||||
// New returns a newly created color object.
|
||||
func New(value ...Attribute) *Color {
|
||||
c := &Color{params: make([]Attribute, 0)}
|
||||
|
@ -123,7 +147,7 @@ func (c *Color) prepend(value Attribute) {
|
|||
|
||||
// Output defines the standard output of the print functions. By default
|
||||
// os.Stdout is used.
|
||||
var Output = ansicolor.NewAnsiColorWriter(os.Stdout)
|
||||
var Output = colorable.NewColorableStdout()
|
||||
|
||||
// Print formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are added between operands when neither is a
|
||||
|
@ -259,6 +283,31 @@ func (c *Color) isNoColorSet() bool {
|
|||
return NoColor
|
||||
}
|
||||
|
||||
// Equals returns a boolean value indicating whether two colors are equal.
|
||||
func (c *Color) Equals(c2 *Color) bool {
|
||||
if len(c.params) != len(c2.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, attr := range c.params {
|
||||
if !c2.attrExists(attr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Color) attrExists(a Attribute) bool {
|
||||
for _, attr := range c.params {
|
||||
if attr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
|
@ -22,3 +22,4 @@ _testmain.go
|
|||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
.DS_Store
|
||||
|
|
|
@ -1,23 +1,72 @@
|
|||
# termui [![Build Status](https://travis-ci.org/gizak/termui.svg?branch=master)](https://travis-ci.org/gizak/termui) [![Doc Status](https://godoc.org/github.com/gizak/termui?status.png)](https://godoc.org/github.com/gizak/termui)
|
||||
|
||||
## Update 23/06/2015
|
||||
Pull requests and master branch are freezing, waiting for merging from `refactoring` branch.
|
||||
<img src="./_example/dashboard.gif" alt="demo cast under osx 10.10; Terminal.app; Menlo Regular 12pt.)" width="80%">
|
||||
|
||||
## Notice
|
||||
termui comes with ABSOLUTELY NO WARRANTY, and there is a breaking change coming up (see refactoring branch) which will change the `Bufferer` interface and many others. These changes reduce calculation overhead and introduce a new drawing buffer with better capacibilities. We will step into the next stage (call it beta) after merging these changes.
|
||||
`termui` is a cross-platform, easy-to-compile, and fully-customizable terminal dashboard. It is inspired by [blessed-contrib](https://github.com/yaronn/blessed-contrib), but purely in Go.
|
||||
|
||||
## Introduction
|
||||
Go terminal dashboard. Inspired by [blessed-contrib](https://github.com/yaronn/blessed-contrib), but purely in Go.
|
||||
Now version v2 has arrived! It brings new event system, new theme system, new `Buffer` interface and specific colour text rendering. (some docs are missing, but it will be completed soon!)
|
||||
|
||||
Cross-platform, easy to compile, and fully-customizable.
|
||||
## Installation
|
||||
|
||||
__Demo:__ (cast under osx 10.10; Terminal.app; Menlo Regular 12pt.)
|
||||
`master` mirrors v2 branch, to install:
|
||||
|
||||
<img src="./example/dashboard.gif" alt="demo" width="600">
|
||||
go get -u github.com/gizak/termui
|
||||
|
||||
For the compatible reason, you can choose to install the legacy version of `termui`:
|
||||
|
||||
go get gopkg.in/gizak/termui.v1
|
||||
|
||||
## Usage
|
||||
|
||||
### Layout
|
||||
|
||||
To use `termui`, the very first thing you may want to know is how to manage layout. `termui` offers two ways of doing this, known as absolute layout and grid layout.
|
||||
|
||||
__Absolute layout__
|
||||
|
||||
Each widget has an underlying block structure which basically is a box model. It has border, label and padding properties. A border of a widget can be chosen to hide or display (with its border label), you can pick a different front/back colour for the border as well. To display such a widget at a specific location in terminal window, you need to assign `.X`, `.Y`, `.Height`, `.Width` values for each widget before send it to `.Render`. Let's demonstrate these by a code snippet:
|
||||
|
||||
`````go
|
||||
import ui "github.com/gizak/termui" // <- ui shortcut, optional
|
||||
|
||||
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.BorderLabel = "Text Box"
|
||||
p.BorderFg = ui.ColorCyan
|
||||
|
||||
g := ui.NewGauge()
|
||||
g.Percent = 50
|
||||
g.Width = 50
|
||||
g.Height = 3
|
||||
g.Y = 11
|
||||
g.BorderLabel = "Gauge"
|
||||
g.BarColor = ui.ColorRed
|
||||
g.BorderFg = ui.ColorWhite
|
||||
g.BorderLabelFg = ui.ColorCyan
|
||||
|
||||
ui.Render(p, g) // feel free to call Render, it's async and non-block
|
||||
|
||||
// event handler...
|
||||
}
|
||||
`````
|
||||
|
||||
Note that components can be overlapped (I'd rather call this a feature...), `Render(rs ...Renderer)` renders its args from left to right (i.e. each component's weight is arising from left to right).
|
||||
|
||||
__Grid layout:__
|
||||
|
||||
Expressive syntax, using [12 columns grid system](http://www.w3schools.com/bootstrap/bootstrap_grid_system.asp)
|
||||
<img src="./_example/grid.gif" alt="grid" width="60%">
|
||||
|
||||
Grid layout uses [12 columns grid system](http://www.w3schools.com/bootstrap/bootstrap_grid_system.asp) with expressive syntax. To use `Grid`, all we need to do is build a widget tree consisting of `Row`s and Cols (Actually a Col is also a `Row` but with a widget endpoint attached).
|
||||
|
||||
```go
|
||||
import ui "github.com/gizak/termui"
|
||||
// init and create widgets...
|
||||
|
@ -37,111 +86,49 @@ Expressive syntax, using [12 columns grid system](http://www.w3schools.com/boots
|
|||
|
||||
ui.Render(ui.Body)
|
||||
```
|
||||
[demo code:](https://github.com/gizak/termui/blob/master/example/grid.go)
|
||||
|
||||
<img src="./example/grid.gif" alt="grid" width="500">
|
||||
### Events
|
||||
|
||||
## Installation
|
||||
|
||||
go get github.com/gizak/termui
|
||||
|
||||
## Usage
|
||||
|
||||
Each component's layout is a bit like HTML block (box model), which has border and padding.
|
||||
|
||||
The `Border` property can be chosen to hide or display (with its border label), when it comes to display, the label takes 1 padding space (i.e. in css: `padding: 1;`, innerHeight and innerWidth therefore shrunk by 1).
|
||||
|
||||
`````go
|
||||
import ui "github.com/gizak/termui" // <- ui shortcut, optional
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
ui.Render(p, g)
|
||||
|
||||
// event handler...
|
||||
}
|
||||
`````
|
||||
|
||||
Note that components can be overlapped (I'd rather call this a feature...), `Render(rs ...Renderer)` renders its args from left to right (i.e. each component's weight is arising from left to right).
|
||||
|
||||
## Themes
|
||||
|
||||
_All_ colors in _all_ components can be changed at _any_ time, while there provides some predefined color schemes:
|
||||
`termui` ships with a http-like event mux handling system. All events are channeled up from different sources (typing, click, windows resize, custom event) and then encoded as universal `Event` object. `Event.Path` indicates the event type and `Event.Data` stores the event data struct. Add a handler to a certain event is easy as below:
|
||||
|
||||
```go
|
||||
// for now there are only two themes: default and helloworld
|
||||
termui.UseTheme("helloworld")
|
||||
// handle key q pressing
|
||||
ui.Handle("/sys/kbd/q", func(ui.Event) {
|
||||
// press q to quit
|
||||
ui.StopLoop()
|
||||
})
|
||||
|
||||
// create components...
|
||||
ui.Handle("/sys/kbd/C-x", func(ui.Event) {
|
||||
// handle Ctrl + x combination
|
||||
})
|
||||
|
||||
ui.Handle("/sys/kbd", func(ui.Event) {
|
||||
// handle all other key pressing
|
||||
})
|
||||
|
||||
// handle a 1s timer
|
||||
ui.Handle("/timer/1s", func(e ui.Event) {
|
||||
t := e.Data.(ui.EvtTimer)
|
||||
// t is a EvtTimer
|
||||
if t.Count%2 ==0 {
|
||||
// do something
|
||||
}
|
||||
})
|
||||
|
||||
ui.Loop() // block until StopLoop is called
|
||||
```
|
||||
The `default ` theme's settings depend on the user's terminal color scheme, which is saying if your terminal default font color is white and background is white, it will be like:
|
||||
|
||||
<img src="./example/themedefault.png" alt="default" type="image/png" width="600">
|
||||
### Widgets
|
||||
|
||||
The `helloworld` color scheme drops in some colors!
|
||||
|
||||
<img src="./example/themehelloworld.png" alt="helloworld" type="image/png" width="600">
|
||||
|
||||
## Widgets
|
||||
|
||||
#### Par
|
||||
|
||||
[demo code](https://github.com/gizak/termui/blob/master/example/par.go)
|
||||
|
||||
<img src="./example/par.png" alt="par" type="image/png" width="300">
|
||||
|
||||
#### List
|
||||
[demo code](https://github.com/gizak/termui/blob/master/example/list.go)
|
||||
|
||||
<img src="./example/list.png" alt="list" type="image/png" width="200">
|
||||
|
||||
#### Gauge
|
||||
[demo code](https://github.com/gizak/termui/blob/master/example/gauge.go)
|
||||
|
||||
<img src="./example/gauge.png" alt="gauge" type="image/png" width="350">
|
||||
|
||||
#### Line Chart
|
||||
[demo code](https://github.com/gizak/termui/blob/master/example/linechart.go)
|
||||
|
||||
<img src="./example/linechart.png" alt="linechart" type="image/png" width="450">
|
||||
|
||||
#### Bar Chart
|
||||
[demo code](https://github.com/gizak/termui/blob/master/example/barchart.go)
|
||||
|
||||
<img src="./example/barchart.png" alt="barchart" type="image/png" width="150">
|
||||
|
||||
#### Mult-Bar / Stacked-Bar Chart
|
||||
[demo code](https://github.com/gizak/termui/blob/master/example/mbarchart.go)
|
||||
|
||||
<img src="./example/mbarchart.png" alt="barchart" type="image/png" width="150">
|
||||
|
||||
#### Sparklines
|
||||
[demo code](https://github.com/gizak/termui/blob/master/example/sparklines.go)
|
||||
|
||||
<img src="./example/sparklines.png" alt="sparklines" type="image/png" width="350">
|
||||
Click image to see the corresponding demo codes.
|
||||
|
||||
[<img src="./_example/par.png" alt="par" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/par.go)
|
||||
[<img src="./_example/list.png" alt="list" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/list.go)
|
||||
[<img src="./_example/gauge.png" alt="gauge" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/gauge.go)
|
||||
[<img src="./_example/linechart.png" alt="linechart" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/linechart.go)
|
||||
[<img src="./_example/barchart.png" alt="barchart" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/barchart.go)
|
||||
[<img src="./_example/mbarchart.png" alt="barchart" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/mbarchart.go)
|
||||
[<img src="./_example/sparklines.png" alt="sparklines" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/sparklines.go)
|
||||
|
||||
## GoDoc
|
||||
|
||||
|
@ -150,10 +137,12 @@ The `helloworld` color scheme drops in some colors!
|
|||
## TODO
|
||||
|
||||
- [x] Grid layout
|
||||
- [ ] Event system
|
||||
- [ ] Canvas widget
|
||||
- [ ] Refine APIs
|
||||
- [x] Event system
|
||||
- [x] Canvas widget
|
||||
- [x] Refine APIs
|
||||
- [ ] Focusable widgets
|
||||
|
||||
## Changelog
|
||||
|
||||
## License
|
||||
This library is under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
|
@ -39,16 +39,16 @@ type BarChart struct {
|
|||
// NewBarChart returns a new *BarChart with current theme.
|
||||
func NewBarChart() *BarChart {
|
||||
bc := &BarChart{Block: *NewBlock()}
|
||||
bc.BarColor = theme.BarChartBar
|
||||
bc.NumColor = theme.BarChartNum
|
||||
bc.TextColor = theme.BarChartText
|
||||
bc.BarColor = ThemeAttr("barchart.bar.bg")
|
||||
bc.NumColor = ThemeAttr("barchart.num.fg")
|
||||
bc.TextColor = ThemeAttr("barchart.text.fg")
|
||||
bc.BarGap = 1
|
||||
bc.BarWidth = 3
|
||||
return bc
|
||||
}
|
||||
|
||||
func (bc *BarChart) layout() {
|
||||
bc.numBar = bc.innerWidth / (bc.BarGap + bc.BarWidth)
|
||||
bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
|
||||
bc.labels = make([][]rune, bc.numBar)
|
||||
bc.dataNum = make([][]rune, len(bc.Data))
|
||||
|
||||
|
@ -69,7 +69,7 @@ func (bc *BarChart) layout() {
|
|||
bc.max = bc.Data[i]
|
||||
}
|
||||
}
|
||||
bc.scale = float64(bc.max) / float64(bc.innerHeight-1)
|
||||
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
|
||||
}
|
||||
|
||||
func (bc *BarChart) SetMax(max int) {
|
||||
|
@ -80,8 +80,8 @@ func (bc *BarChart) SetMax(max int) {
|
|||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (bc *BarChart) Buffer() []Point {
|
||||
ps := bc.Block.Buffer()
|
||||
func (bc *BarChart) Buffer() Buffer {
|
||||
buf := bc.Block.Buffer()
|
||||
bc.layout()
|
||||
|
||||
for i := 0; i < bc.numBar && i < len(bc.Data) && i < len(bc.DataLabels); i++ {
|
||||
|
@ -90,46 +90,49 @@ func (bc *BarChart) Buffer() []Point {
|
|||
// plot bar
|
||||
for j := 0; j < bc.BarWidth; j++ {
|
||||
for k := 0; k < h; k++ {
|
||||
p := Point{}
|
||||
p.Ch = ' '
|
||||
p.Bg = bc.BarColor
|
||||
if bc.BarColor == ColorDefault { // when color is default, space char treated as transparent!
|
||||
p.Bg |= AttrReverse
|
||||
c := Cell{
|
||||
Ch: ' ',
|
||||
Bg: bc.BarColor,
|
||||
}
|
||||
p.X = bc.innerX + i*(bc.BarWidth+bc.BarGap) + j
|
||||
p.Y = bc.innerY + bc.innerHeight - 2 - k
|
||||
ps = append(ps, p)
|
||||
if bc.BarColor == ColorDefault { // when color is default, space char treated as transparent!
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
}
|
||||
// plot text
|
||||
for j, k := 0, 0; j < len(bc.labels[i]); j++ {
|
||||
w := charWidth(bc.labels[i][j])
|
||||
p := Point{}
|
||||
p.Ch = bc.labels[i][j]
|
||||
p.Bg = bc.BgColor
|
||||
p.Fg = bc.TextColor
|
||||
p.Y = bc.innerY + bc.innerHeight - 1
|
||||
p.X = bc.innerX + oftX + k
|
||||
ps = append(ps, p)
|
||||
c := Cell{
|
||||
Ch: bc.labels[i][j],
|
||||
Bg: bc.Bg,
|
||||
Fg: bc.TextColor,
|
||||
}
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
|
||||
x := bc.innerArea.Min.X + oftX + k
|
||||
buf.Set(x, y, c)
|
||||
k += w
|
||||
}
|
||||
// plot num
|
||||
for j := 0; j < len(bc.dataNum[i]); j++ {
|
||||
p := Point{}
|
||||
p.Ch = bc.dataNum[i][j]
|
||||
p.Fg = bc.NumColor
|
||||
p.Bg = bc.BarColor
|
||||
c := Cell{
|
||||
Ch: bc.dataNum[i][j],
|
||||
Fg: bc.NumColor,
|
||||
Bg: bc.BarColor,
|
||||
}
|
||||
if bc.BarColor == ColorDefault { // the same as above
|
||||
p.Bg |= AttrReverse
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
if h == 0 {
|
||||
p.Bg = bc.BgColor
|
||||
c.Bg = bc.Bg
|
||||
}
|
||||
p.X = bc.innerX + oftX + (bc.BarWidth-len(bc.dataNum[i]))/2 + j
|
||||
p.Y = bc.innerY + bc.innerHeight - 2
|
||||
ps = append(ps, p)
|
||||
x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i]))/2 + j
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
}
|
||||
|
||||
return bc.Block.chopOverflow(ps)
|
||||
return buf
|
||||
}
|
|
@ -1,142 +1,240 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import "image"
|
||||
|
||||
// Hline is a horizontal line.
|
||||
type Hline struct {
|
||||
X int
|
||||
Y int
|
||||
Len int
|
||||
Fg Attribute
|
||||
Bg Attribute
|
||||
}
|
||||
|
||||
// Vline is a vertical line.
|
||||
type Vline struct {
|
||||
X int
|
||||
Y int
|
||||
Len int
|
||||
Fg Attribute
|
||||
Bg Attribute
|
||||
}
|
||||
|
||||
// Buffer draws a horizontal line.
|
||||
func (l Hline) Buffer() Buffer {
|
||||
if l.Len <= 0 {
|
||||
return NewBuffer()
|
||||
}
|
||||
return NewFilledBuffer(l.X, l.Y, l.X+l.Len, l.Y+1, HORIZONTAL_LINE, l.Fg, l.Bg)
|
||||
}
|
||||
|
||||
// Buffer draws a vertical line.
|
||||
func (l Vline) Buffer() Buffer {
|
||||
if l.Len <= 0 {
|
||||
return NewBuffer()
|
||||
}
|
||||
return NewFilledBuffer(l.X, l.Y, l.X+1, l.Y+l.Len, VERTICAL_LINE, l.Fg, l.Bg)
|
||||
}
|
||||
|
||||
// Buffer draws a box border.
|
||||
func (b Block) drawBorder(buf Buffer) {
|
||||
if !b.Border {
|
||||
return
|
||||
}
|
||||
|
||||
min := b.area.Min
|
||||
max := b.area.Max
|
||||
|
||||
x0 := min.X
|
||||
y0 := min.Y
|
||||
x1 := max.X - 1
|
||||
y1 := max.Y - 1
|
||||
|
||||
// draw lines
|
||||
if b.BorderTop {
|
||||
buf.Merge(Hline{x0, y0, x1 - x0, b.BorderFg, b.BorderBg}.Buffer())
|
||||
}
|
||||
if b.BorderBottom {
|
||||
buf.Merge(Hline{x0, y1, x1 - x0, b.BorderFg, b.BorderBg}.Buffer())
|
||||
}
|
||||
if b.BorderLeft {
|
||||
buf.Merge(Vline{x0, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer())
|
||||
}
|
||||
if b.BorderRight {
|
||||
buf.Merge(Vline{x1, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer())
|
||||
}
|
||||
|
||||
// draw corners
|
||||
if b.BorderTop && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 0 {
|
||||
buf.Set(x0, y0, Cell{TOP_LEFT, b.BorderFg, b.BorderBg})
|
||||
}
|
||||
if b.BorderTop && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 0 {
|
||||
buf.Set(x1, y0, Cell{TOP_RIGHT, b.BorderFg, b.BorderBg})
|
||||
}
|
||||
if b.BorderBottom && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 1 {
|
||||
buf.Set(x0, y1, Cell{BOTTOM_LEFT, b.BorderFg, b.BorderBg})
|
||||
}
|
||||
if b.BorderBottom && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 1 {
|
||||
buf.Set(x1, y1, Cell{BOTTOM_RIGHT, b.BorderFg, b.BorderBg})
|
||||
}
|
||||
}
|
||||
|
||||
func (b Block) drawBorderLabel(buf Buffer) {
|
||||
maxTxtW := b.area.Dx() - 2
|
||||
tx := DTrimTxCls(DefaultTxBuilder.Build(b.BorderLabel, b.BorderLabelFg, b.BorderLabelBg), maxTxtW)
|
||||
|
||||
for i, w := 0, 0; i < len(tx); i++ {
|
||||
buf.Set(b.area.Min.X+1+w, b.area.Min.Y, tx[i])
|
||||
w += tx[i].Width()
|
||||
}
|
||||
}
|
||||
|
||||
// Block is a base struct for all other upper level widgets,
|
||||
// consider it as css: display:block.
|
||||
// Normally you do not need to create it manually.
|
||||
type Block struct {
|
||||
area image.Rectangle
|
||||
innerArea image.Rectangle
|
||||
X int
|
||||
Y int
|
||||
Border labeledBorder
|
||||
IsDisplay bool
|
||||
HasBorder bool
|
||||
BgColor Attribute
|
||||
Border bool
|
||||
BorderFg Attribute
|
||||
BorderBg Attribute
|
||||
BorderLeft bool
|
||||
BorderRight bool
|
||||
BorderTop bool
|
||||
BorderBottom bool
|
||||
BorderLabel string
|
||||
BorderLabelFg Attribute
|
||||
BorderLabelBg Attribute
|
||||
Display bool
|
||||
Bg Attribute
|
||||
Width int
|
||||
Height int
|
||||
innerWidth int
|
||||
innerHeight int
|
||||
innerX int
|
||||
innerY int
|
||||
PaddingTop int
|
||||
PaddingBottom int
|
||||
PaddingLeft int
|
||||
PaddingRight int
|
||||
id string
|
||||
Float Align
|
||||
}
|
||||
|
||||
// NewBlock returns a *Block which inherits styles from current theme.
|
||||
func NewBlock() *Block {
|
||||
d := Block{}
|
||||
d.IsDisplay = true
|
||||
d.HasBorder = theme.HasBorder
|
||||
d.Border.BgColor = theme.BorderBg
|
||||
d.Border.FgColor = theme.BorderFg
|
||||
d.Border.LabelBgColor = theme.BorderLabelTextBg
|
||||
d.Border.LabelFgColor = theme.BorderLabelTextFg
|
||||
d.BgColor = theme.BlockBg
|
||||
d.Width = 2
|
||||
d.Height = 2
|
||||
return &d
|
||||
b := Block{}
|
||||
b.Display = true
|
||||
b.Border = true
|
||||
b.BorderLeft = true
|
||||
b.BorderRight = true
|
||||
b.BorderTop = true
|
||||
b.BorderBottom = true
|
||||
b.BorderBg = ThemeAttr("border.bg")
|
||||
b.BorderFg = ThemeAttr("border.fg")
|
||||
b.BorderLabelBg = ThemeAttr("label.bg")
|
||||
b.BorderLabelFg = ThemeAttr("label.fg")
|
||||
b.Bg = ThemeAttr("block.bg")
|
||||
b.Width = 2
|
||||
b.Height = 2
|
||||
b.id = GenId()
|
||||
b.Float = AlignNone
|
||||
return &b
|
||||
}
|
||||
|
||||
// compute box model
|
||||
func (d *Block) align() {
|
||||
d.innerWidth = d.Width - d.PaddingLeft - d.PaddingRight
|
||||
d.innerHeight = d.Height - d.PaddingTop - d.PaddingBottom
|
||||
d.innerX = d.X + d.PaddingLeft
|
||||
d.innerY = d.Y + d.PaddingTop
|
||||
func (b Block) Id() string {
|
||||
return b.id
|
||||
}
|
||||
|
||||
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++
|
||||
}
|
||||
// Align computes box model
|
||||
func (b *Block) Align() {
|
||||
// outer
|
||||
b.area.Min.X = 0
|
||||
b.area.Min.Y = 0
|
||||
b.area.Max.X = b.Width
|
||||
b.area.Max.Y = b.Height
|
||||
|
||||
if d.innerHeight < 0 {
|
||||
d.innerHeight = 0
|
||||
}
|
||||
if d.innerWidth < 0 {
|
||||
d.innerWidth = 0
|
||||
}
|
||||
// float
|
||||
b.area = AlignArea(TermRect(), b.area, b.Float)
|
||||
b.area = MoveArea(b.area, b.X, b.Y)
|
||||
|
||||
// inner
|
||||
b.innerArea.Min.X = b.area.Min.X + b.PaddingLeft
|
||||
b.innerArea.Min.Y = b.area.Min.Y + b.PaddingTop
|
||||
b.innerArea.Max.X = b.area.Max.X - b.PaddingRight
|
||||
b.innerArea.Max.Y = b.area.Max.Y - b.PaddingBottom
|
||||
|
||||
if b.Border {
|
||||
if b.BorderLeft {
|
||||
b.innerArea.Min.X++
|
||||
}
|
||||
if b.BorderRight {
|
||||
b.innerArea.Max.X--
|
||||
}
|
||||
if b.BorderTop {
|
||||
b.innerArea.Min.Y++
|
||||
}
|
||||
if b.BorderBottom {
|
||||
b.innerArea.Max.Y--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InnerBounds returns the internal bounds of the block after aligning and
|
||||
// calculating the padding and border, if any.
|
||||
func (d *Block) InnerBounds() (x, y, width, height int) {
|
||||
d.align()
|
||||
return d.innerX, d.innerY, d.innerWidth, d.innerHeight
|
||||
func (b *Block) InnerBounds() image.Rectangle {
|
||||
b.Align()
|
||||
return b.innerArea
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
// Draw background and border (if any).
|
||||
func (d *Block) Buffer() []Point {
|
||||
d.align()
|
||||
func (b *Block) Buffer() Buffer {
|
||||
b.Align()
|
||||
|
||||
ps := []Point{}
|
||||
if !d.IsDisplay {
|
||||
return ps
|
||||
}
|
||||
buf := NewBuffer()
|
||||
buf.SetArea(b.area)
|
||||
buf.Fill(' ', ColorDefault, b.Bg)
|
||||
|
||||
if d.HasBorder {
|
||||
ps = d.Border.Buffer()
|
||||
}
|
||||
b.drawBorder(buf)
|
||||
b.drawBorderLabel(buf)
|
||||
|
||||
for i := 0; i < d.innerWidth; i++ {
|
||||
for j := 0; j < d.innerHeight; j++ {
|
||||
p := Point{}
|
||||
p.X = d.X + 1 + i
|
||||
p.Y = d.Y + 1 + j
|
||||
p.Ch = ' '
|
||||
p.Bg = d.BgColor
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
return ps
|
||||
return buf
|
||||
}
|
||||
|
||||
// GetHeight implements GridBufferer.
|
||||
// It returns current height of the block.
|
||||
func (d Block) GetHeight() int {
|
||||
return d.Height
|
||||
func (b Block) GetHeight() int {
|
||||
return b.Height
|
||||
}
|
||||
|
||||
// SetX implements GridBufferer interface, which sets block's x position.
|
||||
func (d *Block) SetX(x int) {
|
||||
d.X = x
|
||||
func (b *Block) SetX(x int) {
|
||||
b.X = x
|
||||
}
|
||||
|
||||
// SetY implements GridBufferer interface, it sets y position for block.
|
||||
func (d *Block) SetY(y int) {
|
||||
d.Y = y
|
||||
func (b *Block) SetY(y int) {
|
||||
b.Y = y
|
||||
}
|
||||
|
||||
// SetWidth implements GridBuffer interface, it sets block's width.
|
||||
func (d *Block) SetWidth(w int) {
|
||||
d.Width = w
|
||||
func (b *Block) SetWidth(w int) {
|
||||
b.Width = w
|
||||
}
|
||||
|
||||
// chop the overflow parts
|
||||
func (d *Block) chopOverflow(ps []Point) []Point {
|
||||
nps := make([]Point, 0, len(ps))
|
||||
x := d.X
|
||||
y := d.Y
|
||||
w := d.Width
|
||||
h := d.Height
|
||||
for _, v := range ps {
|
||||
if v.X >= x &&
|
||||
v.X < x+w &&
|
||||
v.Y >= y &&
|
||||
v.Y < y+h {
|
||||
nps = append(nps, v)
|
||||
}
|
||||
}
|
||||
return nps
|
||||
func (b Block) InnerWidth() int {
|
||||
return b.innerArea.Dx()
|
||||
}
|
||||
|
||||
func (b Block) InnerHeight() int {
|
||||
return b.innerArea.Dy()
|
||||
}
|
||||
|
||||
func (b Block) InnerX() int {
|
||||
return b.innerArea.Min.X
|
||||
}
|
||||
|
||||
func (b Block) InnerY() int { return b.innerArea.Min.Y }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
|
@ -12,3 +12,9 @@ const HORIZONTAL_LINE = '─'
|
|||
const TOP_LEFT = '┌'
|
||||
const BOTTOM_RIGHT = '┘'
|
||||
const BOTTOM_LEFT = '└'
|
||||
const VERTICAL_LEFT = '┤'
|
||||
const VERTICAL_RIGHT = '├'
|
||||
const HORIZONTAL_DOWN = '┬'
|
||||
const HORIZONTAL_UP = '┴'
|
||||
const QUOTA_LEFT = '«'
|
||||
const QUOTA_RIGHT = '»'
|
|
@ -1,46 +0,0 @@
|
|||
package termui
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBlock_InnerBounds(t *testing.T) {
|
||||
b := NewBlock()
|
||||
b.X = 10
|
||||
b.Y = 11
|
||||
b.Width = 12
|
||||
b.Height = 13
|
||||
|
||||
assert := func(name string, x, y, w, h int) {
|
||||
t.Log(name)
|
||||
cx, cy, cw, ch := b.InnerBounds()
|
||||
if cx != x {
|
||||
t.Errorf("expected x to be %d but got %d", x, cx)
|
||||
}
|
||||
if cy != y {
|
||||
t.Errorf("expected y to be %d but got %d", y, cy)
|
||||
}
|
||||
if cw != w {
|
||||
t.Errorf("expected width to be %d but got %d", w, cw)
|
||||
}
|
||||
if ch != h {
|
||||
t.Errorf("expected height to be %d but got %d", h, ch)
|
||||
}
|
||||
}
|
||||
|
||||
b.HasBorder = false
|
||||
assert("no border, no padding", 10, 11, 12, 13)
|
||||
|
||||
b.HasBorder = true
|
||||
assert("border, no padding", 11, 12, 10, 11)
|
||||
|
||||
b.PaddingBottom = 2
|
||||
assert("border, 2b padding", 11, 12, 10, 9)
|
||||
|
||||
b.PaddingTop = 3
|
||||
assert("border, 2b 3t padding", 11, 15, 10, 6)
|
||||
|
||||
b.PaddingLeft = 4
|
||||
assert("border, 2b 3t 4l padding", 15, 15, 6, 6)
|
||||
|
||||
b.PaddingRight = 5
|
||||
assert("border, 2b 3t 4l 5r padding", 15, 15, 1, 6)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
type border struct {
|
||||
X int
|
||||
Y int
|
||||
Width int
|
||||
Height int
|
||||
FgColor Attribute
|
||||
BgColor Attribute
|
||||
}
|
||||
|
||||
type hline struct {
|
||||
X int
|
||||
Y int
|
||||
Length int
|
||||
FgColor Attribute
|
||||
BgColor Attribute
|
||||
}
|
||||
|
||||
type vline struct {
|
||||
X int
|
||||
Y int
|
||||
Length int
|
||||
FgColor Attribute
|
||||
BgColor Attribute
|
||||
}
|
||||
|
||||
// Draw a horizontal line.
|
||||
func (l hline) Buffer() []Point {
|
||||
pts := make([]Point, l.Length)
|
||||
for i := 0; i < l.Length; i++ {
|
||||
pts[i].X = l.X + i
|
||||
pts[i].Y = l.Y
|
||||
pts[i].Ch = HORIZONTAL_LINE
|
||||
pts[i].Bg = l.BgColor
|
||||
pts[i].Fg = l.FgColor
|
||||
}
|
||||
return pts
|
||||
}
|
||||
|
||||
// Draw a vertical line.
|
||||
func (l vline) Buffer() []Point {
|
||||
pts := make([]Point, l.Length)
|
||||
for i := 0; i < l.Length; i++ {
|
||||
pts[i].X = l.X
|
||||
pts[i].Y = l.Y + i
|
||||
pts[i].Ch = VERTICAL_LINE
|
||||
pts[i].Bg = l.BgColor
|
||||
pts[i].Fg = l.FgColor
|
||||
}
|
||||
return pts
|
||||
}
|
||||
|
||||
// Draw a box border.
|
||||
func (b border) Buffer() []Point {
|
||||
if b.Width < 2 || b.Height < 2 {
|
||||
return nil
|
||||
}
|
||||
pts := make([]Point, 2*b.Width+2*b.Height-4)
|
||||
|
||||
pts[0].X = b.X
|
||||
pts[0].Y = b.Y
|
||||
pts[0].Fg = b.FgColor
|
||||
pts[0].Bg = b.BgColor
|
||||
pts[0].Ch = TOP_LEFT
|
||||
|
||||
pts[1].X = b.X + b.Width - 1
|
||||
pts[1].Y = b.Y
|
||||
pts[1].Fg = b.FgColor
|
||||
pts[1].Bg = b.BgColor
|
||||
pts[1].Ch = TOP_RIGHT
|
||||
|
||||
pts[2].X = b.X
|
||||
pts[2].Y = b.Y + b.Height - 1
|
||||
pts[2].Fg = b.FgColor
|
||||
pts[2].Bg = b.BgColor
|
||||
pts[2].Ch = BOTTOM_LEFT
|
||||
|
||||
pts[3].X = b.X + b.Width - 1
|
||||
pts[3].Y = b.Y + b.Height - 1
|
||||
pts[3].Fg = b.FgColor
|
||||
pts[3].Bg = b.BgColor
|
||||
pts[3].Ch = BOTTOM_RIGHT
|
||||
|
||||
copy(pts[4:], (hline{b.X + 1, b.Y, b.Width - 2, b.FgColor, b.BgColor}).Buffer())
|
||||
copy(pts[4+b.Width-2:], (hline{b.X + 1, b.Y + b.Height - 1, b.Width - 2, b.FgColor, b.BgColor}).Buffer())
|
||||
copy(pts[4+2*b.Width-4:], (vline{b.X, b.Y + 1, b.Height - 2, b.FgColor, b.BgColor}).Buffer())
|
||||
copy(pts[4+2*b.Width-4+b.Height-2:], (vline{b.X + b.Width - 1, b.Y + 1, b.Height - 2, b.FgColor, b.BgColor}).Buffer())
|
||||
|
||||
return pts
|
||||
}
|
||||
|
||||
type labeledBorder struct {
|
||||
border
|
||||
Label string
|
||||
LabelFgColor Attribute
|
||||
LabelBgColor Attribute
|
||||
}
|
||||
|
||||
// Draw a box border with label.
|
||||
func (lb labeledBorder) Buffer() []Point {
|
||||
ps := lb.border.Buffer()
|
||||
maxTxtW := lb.Width - 2
|
||||
rs := trimStr2Runes(lb.Label, maxTxtW)
|
||||
|
||||
for i, j, w := 0, 0, 0; i < len(rs); i++ {
|
||||
w = charWidth(rs[i])
|
||||
ps = append(ps, newPointWithAttrs(rs[i], lb.X+1+j, lb.Y, lb.LabelFgColor, lb.LabelBgColor))
|
||||
j += w
|
||||
}
|
||||
|
||||
return ps
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import "image"
|
||||
|
||||
// Cell is a rune with assigned Fg and Bg
|
||||
type Cell struct {
|
||||
Ch rune
|
||||
Fg Attribute
|
||||
Bg Attribute
|
||||
}
|
||||
|
||||
// Buffer is a renderable rectangle cell data container.
|
||||
type Buffer struct {
|
||||
Area image.Rectangle // selected drawing area
|
||||
CellMap map[image.Point]Cell
|
||||
}
|
||||
|
||||
// At returns the cell at (x,y).
|
||||
func (b Buffer) At(x, y int) Cell {
|
||||
return b.CellMap[image.Pt(x, y)]
|
||||
}
|
||||
|
||||
// Set assigns a char to (x,y)
|
||||
func (b Buffer) Set(x, y int, c Cell) {
|
||||
b.CellMap[image.Pt(x, y)] = c
|
||||
}
|
||||
|
||||
// Bounds returns the domain for which At can return non-zero color.
|
||||
func (b Buffer) Bounds() image.Rectangle {
|
||||
x0, y0, x1, y1 := 0, 0, 0, 0
|
||||
for p := range b.CellMap {
|
||||
if p.X > x1 {
|
||||
x1 = p.X
|
||||
}
|
||||
if p.X < x0 {
|
||||
x0 = p.X
|
||||
}
|
||||
if p.Y > y1 {
|
||||
y1 = p.Y
|
||||
}
|
||||
if p.Y < y0 {
|
||||
y0 = p.Y
|
||||
}
|
||||
}
|
||||
return image.Rect(x0, y0, x1, y1)
|
||||
}
|
||||
|
||||
// SetArea assigns a new rect area to Buffer b.
|
||||
func (b *Buffer) SetArea(r image.Rectangle) {
|
||||
b.Area.Max = r.Max
|
||||
b.Area.Min = r.Min
|
||||
}
|
||||
|
||||
// Sync sets drawing area to the buffer's bound
|
||||
func (b Buffer) Sync() {
|
||||
b.SetArea(b.Bounds())
|
||||
}
|
||||
|
||||
// NewCell returns a new cell
|
||||
func NewCell(ch rune, fg, bg Attribute) Cell {
|
||||
return Cell{ch, fg, bg}
|
||||
}
|
||||
|
||||
// Merge merges bs Buffers onto b
|
||||
func (b *Buffer) Merge(bs ...Buffer) {
|
||||
for _, buf := range bs {
|
||||
for p, v := range buf.CellMap {
|
||||
b.Set(p.X, p.Y, v)
|
||||
}
|
||||
b.SetArea(b.Area.Union(buf.Area))
|
||||
}
|
||||
}
|
||||
|
||||
// NewBuffer returns a new Buffer
|
||||
func NewBuffer() Buffer {
|
||||
return Buffer{
|
||||
CellMap: make(map[image.Point]Cell),
|
||||
Area: image.Rectangle{}}
|
||||
}
|
||||
|
||||
// Fill fills the Buffer b with ch,fg and bg.
|
||||
func (b Buffer) Fill(ch rune, fg, bg Attribute) {
|
||||
for x := b.Area.Min.X; x < b.Area.Max.X; x++ {
|
||||
for y := b.Area.Min.Y; y < b.Area.Max.Y; y++ {
|
||||
b.Set(x, y, Cell{ch, fg, bg})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewFilledBuffer returns a new Buffer filled with ch, fb and bg.
|
||||
func NewFilledBuffer(x0, y0, x1, y1 int, ch rune, fg, bg Attribute) Buffer {
|
||||
buf := NewBuffer()
|
||||
buf.Area.Min = image.Pt(x0, y0)
|
||||
buf.Area.Max = image.Pt(x1, y1)
|
||||
|
||||
for x := buf.Area.Min.X; x < buf.Area.Max.X; x++ {
|
||||
for y := buf.Area.Min.Y; y < buf.Area.Max.Y; y++ {
|
||||
buf.Set(x, y, Cell{ch, fg, bg})
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
|
@ -63,12 +63,10 @@ func (c Canvas) Unset(x, y int) {
|
|||
}
|
||||
|
||||
// Buffer returns un-styled points
|
||||
func (c Canvas) Buffer() []Point {
|
||||
ps := make([]Point, len(c))
|
||||
i := 0
|
||||
func (c Canvas) Buffer() Buffer {
|
||||
buf := NewBuffer()
|
||||
for k, v := range c {
|
||||
ps[i] = newPoint(v+brailleBase, k[0], k[1])
|
||||
i++
|
||||
buf.Set(k[0], k[1], Cell{Ch: v + brailleBase})
|
||||
}
|
||||
return ps
|
||||
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,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
|
|
|
@ -1,219 +1,316 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
//
|
||||
// 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 "github.com/nsf/termbox-go"
|
||||
import (
|
||||
"path"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
/***********************************termbox-go**************************************/
|
||||
|
||||
type (
|
||||
EventType uint8
|
||||
Modifier uint8
|
||||
Key uint16
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
// This type represents a termbox event. The 'Mod', 'Key' and 'Ch' fields are
|
||||
// valid if 'Type' is EventKey. The 'Width' and 'Height' fields are valid if
|
||||
// 'Type' is EventResize. The 'Err' field is valid if 'Type' is EventError.
|
||||
type Event struct {
|
||||
Type EventType // one of Event* constants
|
||||
Mod Modifier // one of Mod* constants or 0
|
||||
Key Key // one of Key* constants, invalid if 'Ch' is not 0
|
||||
Ch rune // a unicode character
|
||||
Width int // width of the screen
|
||||
Height int // height of the screen
|
||||
Err error // error in case if input failed
|
||||
MouseX int // x coord of mouse
|
||||
MouseY int // y coord of mouse
|
||||
N int // number of bytes written when getting a raw event
|
||||
Type string
|
||||
Path string
|
||||
From string
|
||||
To string
|
||||
Data interface{}
|
||||
Time int64
|
||||
}
|
||||
|
||||
const (
|
||||
KeyF1 Key = 0xFFFF - iota
|
||||
KeyF2
|
||||
KeyF3
|
||||
KeyF4
|
||||
KeyF5
|
||||
KeyF6
|
||||
KeyF7
|
||||
KeyF8
|
||||
KeyF9
|
||||
KeyF10
|
||||
KeyF11
|
||||
KeyF12
|
||||
KeyInsert
|
||||
KeyDelete
|
||||
KeyHome
|
||||
KeyEnd
|
||||
KeyPgup
|
||||
KeyPgdn
|
||||
KeyArrowUp
|
||||
KeyArrowDown
|
||||
KeyArrowLeft
|
||||
KeyArrowRight
|
||||
key_min // see terminfo
|
||||
MouseLeft
|
||||
MouseMiddle
|
||||
MouseRight
|
||||
)
|
||||
var sysEvtChs []chan Event
|
||||
|
||||
const (
|
||||
KeyCtrlTilde Key = 0x00
|
||||
KeyCtrl2 Key = 0x00
|
||||
KeyCtrlSpace Key = 0x00
|
||||
KeyCtrlA Key = 0x01
|
||||
KeyCtrlB Key = 0x02
|
||||
KeyCtrlC Key = 0x03
|
||||
KeyCtrlD Key = 0x04
|
||||
KeyCtrlE Key = 0x05
|
||||
KeyCtrlF Key = 0x06
|
||||
KeyCtrlG Key = 0x07
|
||||
KeyBackspace Key = 0x08
|
||||
KeyCtrlH Key = 0x08
|
||||
KeyTab Key = 0x09
|
||||
KeyCtrlI Key = 0x09
|
||||
KeyCtrlJ Key = 0x0A
|
||||
KeyCtrlK Key = 0x0B
|
||||
KeyCtrlL Key = 0x0C
|
||||
KeyEnter Key = 0x0D
|
||||
KeyCtrlM Key = 0x0D
|
||||
KeyCtrlN Key = 0x0E
|
||||
KeyCtrlO Key = 0x0F
|
||||
KeyCtrlP Key = 0x10
|
||||
KeyCtrlQ Key = 0x11
|
||||
KeyCtrlR Key = 0x12
|
||||
KeyCtrlS Key = 0x13
|
||||
KeyCtrlT Key = 0x14
|
||||
KeyCtrlU Key = 0x15
|
||||
KeyCtrlV Key = 0x16
|
||||
KeyCtrlW Key = 0x17
|
||||
KeyCtrlX Key = 0x18
|
||||
KeyCtrlY Key = 0x19
|
||||
KeyCtrlZ Key = 0x1A
|
||||
KeyEsc Key = 0x1B
|
||||
KeyCtrlLsqBracket Key = 0x1B
|
||||
KeyCtrl3 Key = 0x1B
|
||||
KeyCtrl4 Key = 0x1C
|
||||
KeyCtrlBackslash Key = 0x1C
|
||||
KeyCtrl5 Key = 0x1D
|
||||
KeyCtrlRsqBracket Key = 0x1D
|
||||
KeyCtrl6 Key = 0x1E
|
||||
KeyCtrl7 Key = 0x1F
|
||||
KeyCtrlSlash Key = 0x1F
|
||||
KeyCtrlUnderscore Key = 0x1F
|
||||
KeySpace Key = 0x20
|
||||
KeyBackspace2 Key = 0x7F
|
||||
KeyCtrl8 Key = 0x7F
|
||||
)
|
||||
|
||||
// Alt modifier constant, see Event.Mod field and SetInputMode function.
|
||||
const (
|
||||
ModAlt Modifier = 0x01
|
||||
)
|
||||
|
||||
// Event type. See Event.Type field.
|
||||
const (
|
||||
EventKey EventType = iota
|
||||
EventResize
|
||||
EventMouse
|
||||
EventError
|
||||
EventInterrupt
|
||||
EventRaw
|
||||
EventNone
|
||||
)
|
||||
|
||||
/**************************************end**************************************/
|
||||
|
||||
// convert termbox.Event to termui.Event
|
||||
func uiEvt(e termbox.Event) Event {
|
||||
event := Event{}
|
||||
event.Type = EventType(e.Type)
|
||||
event.Mod = Modifier(e.Mod)
|
||||
event.Key = Key(e.Key)
|
||||
event.Ch = e.Ch
|
||||
event.Width = e.Width
|
||||
event.Height = e.Height
|
||||
event.Err = e.Err
|
||||
event.MouseX = e.MouseX
|
||||
event.MouseY = e.MouseY
|
||||
event.N = e.N
|
||||
|
||||
return event
|
||||
type EvtKbd struct {
|
||||
KeyStr string
|
||||
}
|
||||
|
||||
var evtChs = make([]chan Event, 0)
|
||||
func evtKbd(e termbox.Event) EvtKbd {
|
||||
ek := EvtKbd{}
|
||||
|
||||
// EventCh returns an output-only event channel.
|
||||
// This function can be called many times (multiplexer).
|
||||
func EventCh() <-chan Event {
|
||||
out := make(chan Event)
|
||||
evtChs = append(evtChs, out)
|
||||
return out
|
||||
}
|
||||
k := string(e.Ch)
|
||||
pre := ""
|
||||
mod := ""
|
||||
|
||||
// turn on event listener
|
||||
func evtListen() {
|
||||
go func() {
|
||||
for {
|
||||
e := termbox.PollEvent()
|
||||
// dispatch
|
||||
for _, c := range evtChs {
|
||||
go func(ch chan Event) {
|
||||
ch <- uiEvt(e)
|
||||
}(c)
|
||||
if e.Mod == termbox.ModAlt {
|
||||
mod = "M-"
|
||||
}
|
||||
if e.Ch == 0 {
|
||||
if e.Key > 0xFFFF-12 {
|
||||
k = "<f" + strconv.Itoa(0xFFFF-int(e.Key)+1) + ">"
|
||||
} else if e.Key > 0xFFFF-25 {
|
||||
ks := []string{"<insert>", "<delete>", "<home>", "<end>", "<previous>", "<next>", "<up>", "<down>", "<left>", "<right>"}
|
||||
k = ks[0xFFFF-int(e.Key)-12]
|
||||
}
|
||||
|
||||
if e.Key <= 0x7F {
|
||||
pre = "C-"
|
||||
k = string('a' - 1 + int(e.Key))
|
||||
kmap := map[termbox.Key][2]string{
|
||||
termbox.KeyCtrlSpace: {"C-", "<space>"},
|
||||
termbox.KeyBackspace: {"", "<backspace>"},
|
||||
termbox.KeyTab: {"", "<tab>"},
|
||||
termbox.KeyEnter: {"", "<enter>"},
|
||||
termbox.KeyEsc: {"", "<escape>"},
|
||||
termbox.KeyCtrlBackslash: {"C-", "\\"},
|
||||
termbox.KeyCtrlSlash: {"C-", "/"},
|
||||
termbox.KeySpace: {"", "<space>"},
|
||||
termbox.KeyCtrl8: {"C-", "8"},
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
/*
|
||||
// EventHandlers is a handler sequence
|
||||
var EventHandlers []func(Event)
|
||||
|
||||
var signalQuit = make(chan bool)
|
||||
|
||||
// Quit sends quit signal to terminate termui
|
||||
func Quit() {
|
||||
signalQuit <- true
|
||||
}
|
||||
|
||||
// Wait listening to signalQuit, block operation.
|
||||
func Wait() {
|
||||
<-signalQuit
|
||||
}
|
||||
|
||||
// RegEvtHandler register function into TSEventHandler sequence.
|
||||
func RegEvtHandler(fn func(Event)) {
|
||||
EventHandlers = append(EventHandlers, fn)
|
||||
}
|
||||
|
||||
// EventLoop handles all events and
|
||||
// redirects every event to callbacks in EventHandlers
|
||||
func EventLoop() {
|
||||
evt := make(chan termbox.Event)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
evt <- termbox.PollEvent()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case c := <-signalQuit:
|
||||
defer func() { signalQuit <- c }()
|
||||
return
|
||||
case e := <-evt:
|
||||
for _, fn := range EventHandlers {
|
||||
fn(uiEvt(e))
|
||||
if sk, ok := kmap[e.Key]; ok {
|
||||
pre = sk[0]
|
||||
k = sk[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ek.KeyStr = pre + mod + k
|
||||
return ek
|
||||
}
|
||||
|
||||
func crtTermboxEvt(e termbox.Event) Event {
|
||||
systypemap := map[termbox.EventType]string{
|
||||
termbox.EventKey: "keyboard",
|
||||
termbox.EventResize: "window",
|
||||
termbox.EventMouse: "mouse",
|
||||
termbox.EventError: "error",
|
||||
termbox.EventInterrupt: "interrupt",
|
||||
}
|
||||
ne := Event{From: "/sys", Time: time.Now().Unix()}
|
||||
typ := e.Type
|
||||
ne.Type = systypemap[typ]
|
||||
|
||||
switch typ {
|
||||
case termbox.EventKey:
|
||||
kbd := evtKbd(e)
|
||||
ne.Path = "/sys/kbd/" + kbd.KeyStr
|
||||
ne.Data = kbd
|
||||
case termbox.EventResize:
|
||||
wnd := EvtWnd{}
|
||||
wnd.Width = e.Width
|
||||
wnd.Height = e.Height
|
||||
ne.Path = "/sys/wnd/resize"
|
||||
ne.Data = wnd
|
||||
case termbox.EventError:
|
||||
err := EvtErr(e.Err)
|
||||
ne.Path = "/sys/err"
|
||||
ne.Data = err
|
||||
case termbox.EventMouse:
|
||||
m := EvtMouse{}
|
||||
m.X = e.MouseX
|
||||
m.Y = e.MouseY
|
||||
ne.Path = "/sys/mouse"
|
||||
ne.Data = m
|
||||
}
|
||||
return ne
|
||||
}
|
||||
|
||||
type EvtWnd struct {
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
type EvtMouse struct {
|
||||
X int
|
||||
Y int
|
||||
Press string
|
||||
}
|
||||
|
||||
type EvtErr error
|
||||
|
||||
func hookTermboxEvt() {
|
||||
for {
|
||||
e := termbox.PollEvent()
|
||||
|
||||
for _, c := range sysEvtChs {
|
||||
go func(ch chan Event) {
|
||||
ch <- crtTermboxEvt(e)
|
||||
}(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewSysEvtCh() chan Event {
|
||||
ec := make(chan Event)
|
||||
sysEvtChs = append(sysEvtChs, ec)
|
||||
return ec
|
||||
}
|
||||
|
||||
var DefaultEvtStream = NewEvtStream()
|
||||
|
||||
type EvtStream struct {
|
||||
sync.RWMutex
|
||||
srcMap map[string]chan Event
|
||||
stream chan Event
|
||||
wg sync.WaitGroup
|
||||
sigStopLoop chan Event
|
||||
Handlers map[string]func(Event)
|
||||
hook func(Event)
|
||||
}
|
||||
|
||||
func NewEvtStream() *EvtStream {
|
||||
return &EvtStream{
|
||||
srcMap: make(map[string]chan Event),
|
||||
stream: make(chan Event),
|
||||
Handlers: make(map[string]func(Event)),
|
||||
sigStopLoop: make(chan Event),
|
||||
}
|
||||
}
|
||||
|
||||
func (es *EvtStream) Init() {
|
||||
es.Merge("internal", es.sigStopLoop)
|
||||
go func() {
|
||||
es.wg.Wait()
|
||||
close(es.stream)
|
||||
}()
|
||||
}
|
||||
|
||||
func cleanPath(p string) string {
|
||||
if p == "" {
|
||||
return "/"
|
||||
}
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
return path.Clean(p)
|
||||
}
|
||||
|
||||
func isPathMatch(pattern, path string) bool {
|
||||
if len(pattern) == 0 {
|
||||
return false
|
||||
}
|
||||
n := len(pattern)
|
||||
return len(path) >= n && path[0:n] == pattern
|
||||
}
|
||||
|
||||
func (es *EvtStream) Merge(name string, ec chan Event) {
|
||||
es.Lock()
|
||||
defer es.Unlock()
|
||||
|
||||
es.wg.Add(1)
|
||||
es.srcMap[name] = ec
|
||||
|
||||
go func(a chan Event) {
|
||||
for n := range a {
|
||||
n.From = name
|
||||
es.stream <- n
|
||||
}
|
||||
es.wg.Done()
|
||||
}(ec)
|
||||
}
|
||||
|
||||
func (es *EvtStream) Handle(path string, handler func(Event)) {
|
||||
es.Handlers[cleanPath(path)] = handler
|
||||
}
|
||||
|
||||
func findMatch(mux map[string]func(Event), path string) string {
|
||||
n := -1
|
||||
pattern := ""
|
||||
for m := range mux {
|
||||
if !isPathMatch(m, path) {
|
||||
continue
|
||||
}
|
||||
if len(m) > n {
|
||||
pattern = m
|
||||
n = len(m)
|
||||
}
|
||||
}
|
||||
return pattern
|
||||
|
||||
}
|
||||
|
||||
func (es *EvtStream) match(path string) string {
|
||||
return findMatch(es.Handlers, path)
|
||||
}
|
||||
|
||||
func (es *EvtStream) Hook(f func(Event)) {
|
||||
es.hook = f
|
||||
}
|
||||
|
||||
func (es *EvtStream) Loop() {
|
||||
for e := range es.stream {
|
||||
switch e.Path {
|
||||
case "/sig/stoploop":
|
||||
return
|
||||
}
|
||||
go func(a Event) {
|
||||
es.RLock()
|
||||
defer es.RUnlock()
|
||||
if pattern := es.match(a.Path); pattern != "" {
|
||||
es.Handlers[pattern](a)
|
||||
}
|
||||
}(e)
|
||||
if es.hook != nil {
|
||||
es.hook(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (es *EvtStream) StopLoop() {
|
||||
go func() {
|
||||
e := Event{
|
||||
Path: "/sig/stoploop",
|
||||
}
|
||||
es.sigStopLoop <- e
|
||||
}()
|
||||
}
|
||||
|
||||
func Merge(name string, ec chan Event) {
|
||||
DefaultEvtStream.Merge(name, ec)
|
||||
}
|
||||
|
||||
func Handle(path string, handler func(Event)) {
|
||||
DefaultEvtStream.Handle(path, handler)
|
||||
}
|
||||
|
||||
func Loop() {
|
||||
DefaultEvtStream.Loop()
|
||||
}
|
||||
|
||||
func StopLoop() {
|
||||
DefaultEvtStream.StopLoop()
|
||||
}
|
||||
|
||||
type EvtTimer struct {
|
||||
Duration time.Duration
|
||||
Count uint64
|
||||
}
|
||||
|
||||
func NewTimerCh(du time.Duration) chan Event {
|
||||
t := make(chan Event)
|
||||
|
||||
go func(a chan Event) {
|
||||
n := uint64(0)
|
||||
for {
|
||||
n++
|
||||
time.Sleep(du)
|
||||
e := Event{}
|
||||
e.Type = "timer"
|
||||
e.Path = "/timer/" + du.String()
|
||||
e.Time = time.Now().Unix()
|
||||
e.Data = EvtTimer{
|
||||
Duration: du,
|
||||
Count: n,
|
||||
}
|
||||
t <- e
|
||||
|
||||
}
|
||||
}(t)
|
||||
return t
|
||||
}
|
||||
|
||||
var DefualtHandler = func(e Event) {
|
||||
}
|
||||
|
||||
var usrEvtCh = make(chan Event)
|
||||
|
||||
func SendCustomEvt(path string, data interface{}) {
|
||||
e := Event{}
|
||||
e.Path = path
|
||||
e.Data = data
|
||||
e.Time = time.Now().Unix()
|
||||
usrEvtCh <- e
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -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,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
|
@ -21,33 +21,27 @@ import (
|
|||
g.PercentColor = termui.ColorBlue
|
||||
*/
|
||||
|
||||
// Align is the position of the gauge's label.
|
||||
type Align int
|
||||
|
||||
// All supported positions.
|
||||
const (
|
||||
AlignLeft Align = iota
|
||||
AlignCenter
|
||||
AlignRight
|
||||
)
|
||||
const ColorUndef Attribute = Attribute(^uint16(0))
|
||||
|
||||
type Gauge struct {
|
||||
Block
|
||||
Percent int
|
||||
BarColor Attribute
|
||||
PercentColor Attribute
|
||||
Label string
|
||||
LabelAlign Align
|
||||
Percent int
|
||||
BarColor Attribute
|
||||
PercentColor Attribute
|
||||
PercentColorHighlighted Attribute
|
||||
Label string
|
||||
LabelAlign Align
|
||||
}
|
||||
|
||||
// NewGauge return a new gauge with current theme.
|
||||
func NewGauge() *Gauge {
|
||||
g := &Gauge{
|
||||
Block: *NewBlock(),
|
||||
PercentColor: theme.GaugePercent,
|
||||
BarColor: theme.GaugeBar,
|
||||
Label: "{{percent}}%",
|
||||
LabelAlign: AlignCenter,
|
||||
Block: *NewBlock(),
|
||||
PercentColor: ThemeAttr("gauge.percent.fg"),
|
||||
BarColor: ThemeAttr("gauge.bar.bg"),
|
||||
Label: "{{percent}}%",
|
||||
LabelAlign: AlignCenter,
|
||||
PercentColorHighlighted: ColorUndef,
|
||||
}
|
||||
|
||||
g.Width = 12
|
||||
|
@ -56,28 +50,26 @@ func NewGauge() *Gauge {
|
|||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (g *Gauge) Buffer() []Point {
|
||||
ps := g.Block.Buffer()
|
||||
func (g *Gauge) Buffer() Buffer {
|
||||
buf := g.Block.Buffer()
|
||||
|
||||
// plot bar
|
||||
w := g.Percent * g.innerWidth / 100
|
||||
for i := 0; i < g.innerHeight; i++ {
|
||||
w := g.Percent * g.innerArea.Dx() / 100
|
||||
for i := 0; i < g.innerArea.Dy(); i++ {
|
||||
for j := 0; j < w; j++ {
|
||||
p := Point{}
|
||||
p.X = g.innerX + j
|
||||
p.Y = g.innerY + i
|
||||
p.Ch = ' '
|
||||
p.Bg = g.BarColor
|
||||
if p.Bg == ColorDefault {
|
||||
p.Bg |= AttrReverse
|
||||
c := Cell{}
|
||||
c.Ch = ' '
|
||||
c.Bg = g.BarColor
|
||||
if c.Bg == ColorDefault {
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
ps = append(ps, p)
|
||||
buf.Set(g.innerArea.Min.X+j, g.innerArea.Min.Y+i, c)
|
||||
}
|
||||
}
|
||||
|
||||
// plot percentage
|
||||
s := strings.Replace(g.Label, "{{percent}}", strconv.Itoa(g.Percent), -1)
|
||||
pry := g.innerY + g.innerHeight/2
|
||||
pry := g.innerArea.Min.Y + g.innerArea.Dy()/2
|
||||
rs := str2runes(s)
|
||||
var pos int
|
||||
switch g.LabelAlign {
|
||||
|
@ -85,29 +77,33 @@ func (g *Gauge) Buffer() []Point {
|
|||
pos = 0
|
||||
|
||||
case AlignCenter:
|
||||
pos = (g.innerWidth - strWidth(s)) / 2
|
||||
pos = (g.innerArea.Dx() - strWidth(s)) / 2
|
||||
|
||||
case AlignRight:
|
||||
pos = g.innerWidth - strWidth(s)
|
||||
pos = g.innerArea.Dx() - strWidth(s) - 1
|
||||
}
|
||||
pos += g.innerArea.Min.X
|
||||
|
||||
for i, v := range rs {
|
||||
p := Point{}
|
||||
p.X = 1 + pos + i
|
||||
p.Y = pry
|
||||
p.Ch = v
|
||||
p.Fg = g.PercentColor
|
||||
if w+g.innerX > pos+i {
|
||||
p.Bg = g.BarColor
|
||||
if p.Bg == ColorDefault {
|
||||
p.Bg |= AttrReverse
|
||||
}
|
||||
|
||||
} else {
|
||||
p.Bg = g.Block.BgColor
|
||||
c := Cell{
|
||||
Ch: v,
|
||||
Fg: g.PercentColor,
|
||||
}
|
||||
|
||||
ps = append(ps, p)
|
||||
if w+g.innerArea.Min.X > pos+i {
|
||||
c.Bg = g.BarColor
|
||||
if c.Bg == ColorDefault {
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
|
||||
if g.PercentColorHighlighted != ColorUndef {
|
||||
c.Fg = g.PercentColorHighlighted
|
||||
}
|
||||
} else {
|
||||
c.Bg = g.Block.Bg
|
||||
}
|
||||
|
||||
buf.Set(1+pos+i, pry, c)
|
||||
}
|
||||
return g.Block.chopOverflow(ps)
|
||||
return buf
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
|
@ -160,8 +160,8 @@ func (r *Row) SetWidth(w int) {
|
|||
|
||||
// Buffer implements Bufferer interface,
|
||||
// recursively merge all widgets buffer
|
||||
func (r *Row) Buffer() []Point {
|
||||
merged := []Point{}
|
||||
func (r *Row) Buffer() Buffer {
|
||||
merged := NewBuffer()
|
||||
|
||||
if r.isRenderableLeaf() {
|
||||
return r.Widget.Buffer()
|
||||
|
@ -169,13 +169,13 @@ func (r *Row) Buffer() []Point {
|
|||
|
||||
// for those are not leaves but have a renderable widget
|
||||
if r.Widget != nil {
|
||||
merged = append(merged, r.Widget.Buffer()...)
|
||||
merged.Merge(r.Widget.Buffer())
|
||||
}
|
||||
|
||||
// collect buffer from children
|
||||
if !r.isLeaf() {
|
||||
for _, c := range r.Cols {
|
||||
merged = append(merged, c.Buffer()...)
|
||||
merged.Merge(c.Buffer())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,13 +267,13 @@ func (g *Grid) Align() {
|
|||
}
|
||||
|
||||
// Buffer implments Bufferer interface.
|
||||
func (g Grid) Buffer() []Point {
|
||||
ps := []Point{}
|
||||
func (g Grid) Buffer() Buffer {
|
||||
buf := NewBuffer()
|
||||
|
||||
for _, r := range g.Rows {
|
||||
ps = append(ps, r.Buffer()...)
|
||||
buf.Merge(r.Buffer())
|
||||
}
|
||||
return ps
|
||||
return buf
|
||||
}
|
||||
|
||||
// Body corresponds to the entire terminal display region.
|
||||
var Body *Grid
|
||||
|
|
|
@ -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,10 +1,15 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import tm "github.com/nsf/termbox-go"
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
tm "github.com/nsf/termbox-go"
|
||||
)
|
||||
import rw "github.com/mattn/go-runewidth"
|
||||
|
||||
/* ---------------Port from termbox-go --------------------- */
|
||||
|
@ -12,6 +17,7 @@ import rw "github.com/mattn/go-runewidth"
|
|||
// Attribute is printable cell's color and style.
|
||||
type Attribute uint16
|
||||
|
||||
// 8 basic clolrs
|
||||
const (
|
||||
ColorDefault Attribute = iota
|
||||
ColorBlack
|
||||
|
@ -24,7 +30,10 @@ const (
|
|||
ColorWhite
|
||||
)
|
||||
|
||||
const NumberofColors = 8 //Have a constant that defines number of colors
|
||||
//Have a constant that defines number of colors
|
||||
const NumberofColors = 8
|
||||
|
||||
// Text style
|
||||
const (
|
||||
AttrBold Attribute = 1 << (iota + 9)
|
||||
AttrUnderline
|
||||
|
@ -46,15 +55,39 @@ func str2runes(s string) []rune {
|
|||
return []rune(s)
|
||||
}
|
||||
|
||||
// Here for backwards-compatibility.
|
||||
func trimStr2Runes(s string, w int) []rune {
|
||||
return TrimStr2Runes(s, w)
|
||||
}
|
||||
|
||||
// TrimStr2Runes trims string to w[-1 rune], appends …, and returns the runes
|
||||
// of that string if string is grather then n. If string is small then w,
|
||||
// return the runes.
|
||||
func TrimStr2Runes(s string, w int) []rune {
|
||||
if w <= 0 {
|
||||
return []rune{}
|
||||
}
|
||||
|
||||
sw := rw.StringWidth(s)
|
||||
if sw > w {
|
||||
return []rune(rw.Truncate(s, w, dot))
|
||||
}
|
||||
return str2runes(s) //[]rune(rw.Truncate(s, w, ""))
|
||||
return str2runes(s)
|
||||
}
|
||||
|
||||
// TrimStrIfAppropriate trim string to "s[:-1] + …"
|
||||
// if string > width otherwise return string
|
||||
func TrimStrIfAppropriate(s string, w int) string {
|
||||
if w <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
sw := rw.StringWidth(s)
|
||||
if sw > w {
|
||||
return rw.Truncate(s, w, dot)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func strWidth(s string) int {
|
||||
|
@ -64,3 +97,118 @@ func strWidth(s string) int {
|
|||
func charWidth(ch rune) int {
|
||||
return rw.RuneWidth(ch)
|
||||
}
|
||||
|
||||
var whiteSpaceRegex = regexp.MustCompile(`\s`)
|
||||
|
||||
// StringToAttribute converts text to a termui attribute. You may specifiy more
|
||||
// then one attribute like that: "BLACK, BOLD, ...". All whitespaces
|
||||
// are ignored.
|
||||
func StringToAttribute(text string) Attribute {
|
||||
text = whiteSpaceRegex.ReplaceAllString(strings.ToLower(text), "")
|
||||
attributes := strings.Split(text, ",")
|
||||
result := Attribute(0)
|
||||
|
||||
for _, theAttribute := range attributes {
|
||||
var match Attribute
|
||||
switch theAttribute {
|
||||
case "reset", "default":
|
||||
match = ColorDefault
|
||||
|
||||
case "black":
|
||||
match = ColorBlack
|
||||
|
||||
case "red":
|
||||
match = ColorRed
|
||||
|
||||
case "green":
|
||||
match = ColorGreen
|
||||
|
||||
case "yellow":
|
||||
match = ColorYellow
|
||||
|
||||
case "blue":
|
||||
match = ColorBlue
|
||||
|
||||
case "magenta":
|
||||
match = ColorMagenta
|
||||
|
||||
case "cyan":
|
||||
match = ColorCyan
|
||||
|
||||
case "white":
|
||||
match = ColorWhite
|
||||
|
||||
case "bold":
|
||||
match = AttrBold
|
||||
|
||||
case "underline":
|
||||
match = AttrUnderline
|
||||
|
||||
case "reverse":
|
||||
match = AttrReverse
|
||||
}
|
||||
|
||||
result |= match
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// TextCells returns a coloured text cells []Cell
|
||||
func TextCells(s string, fg, bg Attribute) []Cell {
|
||||
cs := make([]Cell, 0, len(s))
|
||||
|
||||
// sequence := MarkdownTextRendererFactory{}.TextRenderer(s).Render(fg, bg)
|
||||
// runes := []rune(sequence.NormalizedText)
|
||||
runes := str2runes(s)
|
||||
|
||||
for n := range runes {
|
||||
// point, _ := sequence.PointAt(n, 0, 0)
|
||||
// cs = append(cs, Cell{point.Ch, point.Fg, point.Bg})
|
||||
cs = append(cs, Cell{runes[n], fg, bg})
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
// Width returns the actual screen space the cell takes (usually 1 or 2).
|
||||
func (c Cell) Width() int {
|
||||
return charWidth(c.Ch)
|
||||
}
|
||||
|
||||
// Copy return a copy of c
|
||||
func (c Cell) Copy() Cell {
|
||||
return c
|
||||
}
|
||||
|
||||
// TrimTxCells trims the overflowed text cells sequence.
|
||||
func TrimTxCells(cs []Cell, w int) []Cell {
|
||||
if len(cs) <= w {
|
||||
return cs
|
||||
}
|
||||
return cs[:w]
|
||||
}
|
||||
|
||||
// DTrimTxCls trims the overflowed text cells sequence and append dots at the end.
|
||||
func DTrimTxCls(cs []Cell, w int) []Cell {
|
||||
l := len(cs)
|
||||
if l <= 0 {
|
||||
return []Cell{}
|
||||
}
|
||||
|
||||
rt := make([]Cell, 0, w)
|
||||
csw := 0
|
||||
for i := 0; i < l && csw <= w; i++ {
|
||||
c := cs[i]
|
||||
cw := c.Width()
|
||||
|
||||
if cw+csw < w {
|
||||
rt = append(rt, c)
|
||||
csw += cw
|
||||
} else {
|
||||
rt = append(rt, Cell{'…', c.Fg, c.Bg})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return rt
|
||||
}
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func TestStr2Rune(t *testing.T) {
|
||||
s := "你好,世界."
|
||||
rs := str2runes(s)
|
||||
if len(rs) != 6 {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func TestWidth(t *testing.T) {
|
||||
s0 := "つのだ☆HIRO"
|
||||
s1 := "11111111111"
|
||||
spew.Dump(s0)
|
||||
spew.Dump(s1)
|
||||
// above not align for setting East Asian Ambiguous to wide!!
|
||||
|
||||
if strWidth(s0) != strWidth(s1) {
|
||||
t.Error("str len failed")
|
||||
}
|
||||
|
||||
len1 := []rune{'a', '2', '&', '「', 'オ', '。'} //will false: 'ᆵ', 'ᄚ', 'ᄒ'
|
||||
for _, v := range len1 {
|
||||
if charWidth(v) != 1 {
|
||||
t.Error("len1 failed")
|
||||
}
|
||||
}
|
||||
|
||||
len2 := []rune{'漢', '字', '한', '자', '你', '好', 'だ', '。', '%', 's', 'E', 'ョ', '、', 'ヲ'}
|
||||
for _, v := range len2 {
|
||||
if charWidth(v) != 2 {
|
||||
t.Error("len2 failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrim(t *testing.T) {
|
||||
s := "つのだ☆HIRO"
|
||||
if string(trimStr2Runes(s, 10)) != "つのだ☆HI"+dot {
|
||||
t.Error("trim failed")
|
||||
}
|
||||
if string(trimStr2Runes(s, 11)) != "つのだ☆HIRO" {
|
||||
t.Error("avoid tail trim failed")
|
||||
}
|
||||
if string(trimStr2Runes(s, 15)) != "つのだ☆HIRO" {
|
||||
t.Error("avoid trim failed")
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
|
@ -74,8 +74,8 @@ type LineChart struct {
|
|||
// NewLineChart returns a new LineChart with current theme.
|
||||
func NewLineChart() *LineChart {
|
||||
lc := &LineChart{Block: *NewBlock()}
|
||||
lc.AxesColor = theme.LineChartAxes
|
||||
lc.LineColor = theme.LineChartLine
|
||||
lc.AxesColor = ThemeAttr("linechart.axes.fg")
|
||||
lc.LineColor = ThemeAttr("linechart.line.fg")
|
||||
lc.Mode = "braille"
|
||||
lc.DotStyle = '•'
|
||||
lc.axisXLebelGap = 2
|
||||
|
@ -87,8 +87,8 @@ func NewLineChart() *LineChart {
|
|||
|
||||
// one cell contains two data points
|
||||
// so the capicity is 2x as dot-mode
|
||||
func (lc *LineChart) renderBraille() []Point {
|
||||
ps := []Point{}
|
||||
func (lc *LineChart) renderBraille() Buffer {
|
||||
buf := NewBuffer()
|
||||
|
||||
// return: b -> which cell should the point be in
|
||||
// m -> in the cell, divided into 4 equal height levels, which subcell?
|
||||
|
@ -104,44 +104,48 @@ func (lc *LineChart) renderBraille() []Point {
|
|||
b1, m1 := getPos(lc.Data[2*i+1])
|
||||
|
||||
if b0 == b1 {
|
||||
p := Point{}
|
||||
p.Ch = braillePatterns[[2]int{m0, m1}]
|
||||
p.Bg = lc.BgColor
|
||||
p.Fg = lc.LineColor
|
||||
p.Y = lc.innerY + lc.innerHeight - 3 - b0
|
||||
p.X = lc.innerX + lc.labelYSpace + 1 + i
|
||||
ps = append(ps, p)
|
||||
c := Cell{
|
||||
Ch: braillePatterns[[2]int{m0, m1}],
|
||||
Bg: lc.Bg,
|
||||
Fg: lc.LineColor,
|
||||
}
|
||||
y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - b0
|
||||
x := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
|
||||
buf.Set(x, y, c)
|
||||
} else {
|
||||
p0 := newPointWithAttrs(lSingleBraille[m0],
|
||||
lc.innerX+lc.labelYSpace+1+i,
|
||||
lc.innerY+lc.innerHeight-3-b0,
|
||||
lc.LineColor,
|
||||
lc.BgColor)
|
||||
p1 := newPointWithAttrs(rSingleBraille[m1],
|
||||
lc.innerX+lc.labelYSpace+1+i,
|
||||
lc.innerY+lc.innerHeight-3-b1,
|
||||
lc.LineColor,
|
||||
lc.BgColor)
|
||||
ps = append(ps, p0, p1)
|
||||
c0 := Cell{Ch: lSingleBraille[m0],
|
||||
Fg: lc.LineColor,
|
||||
Bg: lc.Bg}
|
||||
x0 := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
|
||||
y0 := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - b0
|
||||
buf.Set(x0, y0, c0)
|
||||
|
||||
c1 := Cell{Ch: rSingleBraille[m1],
|
||||
Fg: lc.LineColor,
|
||||
Bg: lc.Bg}
|
||||
x1 := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
|
||||
y1 := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - b1
|
||||
buf.Set(x1, y1, c1)
|
||||
}
|
||||
|
||||
}
|
||||
return ps
|
||||
return buf
|
||||
}
|
||||
|
||||
func (lc *LineChart) renderDot() []Point {
|
||||
ps := []Point{}
|
||||
func (lc *LineChart) renderDot() Buffer {
|
||||
buf := NewBuffer()
|
||||
for i := 0; i < len(lc.Data) && i < lc.axisXWidth; i++ {
|
||||
p := Point{}
|
||||
p.Ch = lc.DotStyle
|
||||
p.Fg = lc.LineColor
|
||||
p.Bg = lc.BgColor
|
||||
p.X = lc.innerX + lc.labelYSpace + 1 + i
|
||||
p.Y = lc.innerY + lc.innerHeight - 3 - int((lc.Data[i]-lc.bottomValue)/lc.scale+0.5)
|
||||
ps = append(ps, p)
|
||||
c := Cell{
|
||||
Ch: lc.DotStyle,
|
||||
Fg: lc.LineColor,
|
||||
Bg: lc.Bg,
|
||||
}
|
||||
x := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
|
||||
y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - int((lc.Data[i]-lc.bottomValue)/lc.scale+0.5)
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
|
||||
return ps
|
||||
return buf
|
||||
}
|
||||
|
||||
func (lc *LineChart) calcLabelX() {
|
||||
|
@ -220,9 +224,9 @@ func (lc *LineChart) calcLayout() {
|
|||
lc.maxY = lc.Data[0]
|
||||
|
||||
// valid visible range
|
||||
vrange := lc.innerWidth
|
||||
vrange := lc.innerArea.Dx()
|
||||
if lc.Mode == "braille" {
|
||||
vrange = 2 * lc.innerWidth
|
||||
vrange = 2 * lc.innerArea.Dx()
|
||||
}
|
||||
if vrange > len(lc.Data) {
|
||||
vrange = len(lc.Data)
|
||||
|
@ -247,40 +251,30 @@ func (lc *LineChart) calcLayout() {
|
|||
lc.topValue = lc.maxY + 0.2*span
|
||||
}
|
||||
|
||||
lc.axisYHeight = lc.innerHeight - 2
|
||||
lc.axisYHeight = lc.innerArea.Dy() - 2
|
||||
lc.calcLabelY()
|
||||
|
||||
lc.axisXWidth = lc.innerWidth - 1 - lc.labelYSpace
|
||||
lc.axisXWidth = lc.innerArea.Dx() - 1 - lc.labelYSpace
|
||||
lc.calcLabelX()
|
||||
|
||||
lc.drawingX = lc.innerX + 1 + lc.labelYSpace
|
||||
lc.drawingY = lc.innerY
|
||||
lc.drawingX = lc.innerArea.Min.X + 1 + lc.labelYSpace
|
||||
lc.drawingY = lc.innerArea.Min.Y
|
||||
}
|
||||
|
||||
func (lc *LineChart) plotAxes() []Point {
|
||||
origY := lc.innerY + lc.innerHeight - 2
|
||||
origX := lc.innerX + lc.labelYSpace
|
||||
func (lc *LineChart) plotAxes() Buffer {
|
||||
buf := NewBuffer()
|
||||
|
||||
ps := []Point{newPointWithAttrs(ORIGIN, origX, origY, lc.AxesColor, lc.BgColor)}
|
||||
origY := lc.innerArea.Min.Y + lc.innerArea.Dy() - 2
|
||||
origX := lc.innerArea.Min.X + lc.labelYSpace
|
||||
|
||||
buf.Set(origX, origY, Cell{Ch: ORIGIN, Fg: lc.AxesColor, Bg: lc.Bg})
|
||||
|
||||
for x := origX + 1; x < origX+lc.axisXWidth; x++ {
|
||||
p := Point{}
|
||||
p.X = x
|
||||
p.Y = origY
|
||||
p.Bg = lc.BgColor
|
||||
p.Fg = lc.AxesColor
|
||||
p.Ch = HDASH
|
||||
ps = append(ps, p)
|
||||
buf.Set(x, origY, Cell{Ch: HDASH, Fg: lc.AxesColor, Bg: lc.Bg})
|
||||
}
|
||||
|
||||
for dy := 1; dy <= lc.axisYHeight; dy++ {
|
||||
p := Point{}
|
||||
p.X = origX
|
||||
p.Y = origY - dy
|
||||
p.Bg = lc.BgColor
|
||||
p.Fg = lc.AxesColor
|
||||
p.Ch = VDASH
|
||||
ps = append(ps, p)
|
||||
buf.Set(origX, origY-dy, Cell{Ch: VDASH, Fg: lc.AxesColor, Bg: lc.Bg})
|
||||
}
|
||||
|
||||
// x label
|
||||
|
@ -290,13 +284,14 @@ func (lc *LineChart) plotAxes() []Point {
|
|||
break
|
||||
}
|
||||
for j, r := range rs {
|
||||
p := Point{}
|
||||
p.Ch = r
|
||||
p.Fg = lc.AxesColor
|
||||
p.Bg = lc.BgColor
|
||||
p.X = origX + oft + j
|
||||
p.Y = lc.innerY + lc.innerHeight - 1
|
||||
ps = append(ps, p)
|
||||
c := Cell{
|
||||
Ch: r,
|
||||
Fg: lc.AxesColor,
|
||||
Bg: lc.Bg,
|
||||
}
|
||||
x := origX + oft + j
|
||||
y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 1
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
oft += len(rs) + lc.axisXLebelGap
|
||||
}
|
||||
|
@ -304,33 +299,31 @@ func (lc *LineChart) plotAxes() []Point {
|
|||
// y labels
|
||||
for i, rs := range lc.labelY {
|
||||
for j, r := range rs {
|
||||
p := Point{}
|
||||
p.Ch = r
|
||||
p.Fg = lc.AxesColor
|
||||
p.Bg = lc.BgColor
|
||||
p.X = lc.innerX + j
|
||||
p.Y = origY - i*(lc.axisYLebelGap+1)
|
||||
ps = append(ps, p)
|
||||
buf.Set(
|
||||
lc.innerArea.Min.X+j,
|
||||
origY-i*(lc.axisYLebelGap+1),
|
||||
Cell{Ch: r, Fg: lc.AxesColor, Bg: lc.Bg})
|
||||
}
|
||||
}
|
||||
|
||||
return ps
|
||||
return buf
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (lc *LineChart) Buffer() []Point {
|
||||
ps := lc.Block.Buffer()
|
||||
func (lc *LineChart) Buffer() Buffer {
|
||||
buf := lc.Block.Buffer()
|
||||
|
||||
if lc.Data == nil || len(lc.Data) == 0 {
|
||||
return ps
|
||||
return buf
|
||||
}
|
||||
lc.calcLayout()
|
||||
ps = append(ps, lc.plotAxes()...)
|
||||
buf.Merge(lc.plotAxes())
|
||||
|
||||
if lc.Mode == "dot" {
|
||||
ps = append(ps, lc.renderDot()...)
|
||||
buf.Merge(lc.renderDot())
|
||||
} else {
|
||||
ps = append(ps, lc.renderBraille()...)
|
||||
buf.Merge(lc.renderBraille())
|
||||
}
|
||||
|
||||
return lc.Block.chopOverflow(ps)
|
||||
return buf
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
|
@ -41,64 +41,49 @@ type List struct {
|
|||
func NewList() *List {
|
||||
l := &List{Block: *NewBlock()}
|
||||
l.Overflow = "hidden"
|
||||
l.ItemFgColor = theme.ListItemFg
|
||||
l.ItemBgColor = theme.ListItemBg
|
||||
l.ItemFgColor = ThemeAttr("list.item.fg")
|
||||
l.ItemBgColor = ThemeAttr("list.item.bg")
|
||||
return l
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (l *List) Buffer() []Point {
|
||||
ps := l.Block.Buffer()
|
||||
func (l *List) Buffer() Buffer {
|
||||
buf := l.Block.Buffer()
|
||||
|
||||
switch l.Overflow {
|
||||
case "wrap":
|
||||
rs := str2runes(strings.Join(l.Items, "\n"))
|
||||
cs := DefaultTxBuilder.Build(strings.Join(l.Items, "\n"), l.ItemFgColor, l.ItemBgColor)
|
||||
i, j, k := 0, 0, 0
|
||||
for i < l.innerHeight && k < len(rs) {
|
||||
w := charWidth(rs[k])
|
||||
if rs[k] == '\n' || j+w > l.innerWidth {
|
||||
for i < l.innerArea.Dy() && k < len(cs) {
|
||||
w := cs[k].Width()
|
||||
if cs[k].Ch == '\n' || j+w > l.innerArea.Dx() {
|
||||
i++
|
||||
j = 0
|
||||
if rs[k] == '\n' {
|
||||
if cs[k].Ch == '\n' {
|
||||
k++
|
||||
}
|
||||
continue
|
||||
}
|
||||
pi := Point{}
|
||||
pi.X = l.innerX + j
|
||||
pi.Y = l.innerY + i
|
||||
buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, cs[k])
|
||||
|
||||
pi.Ch = rs[k]
|
||||
pi.Bg = l.ItemBgColor
|
||||
pi.Fg = l.ItemFgColor
|
||||
|
||||
ps = append(ps, pi)
|
||||
k++
|
||||
j++
|
||||
}
|
||||
|
||||
case "hidden":
|
||||
trimItems := l.Items
|
||||
if len(trimItems) > l.innerHeight {
|
||||
trimItems = trimItems[:l.innerHeight]
|
||||
if len(trimItems) > l.innerArea.Dy() {
|
||||
trimItems = trimItems[:l.innerArea.Dy()]
|
||||
}
|
||||
for i, v := range trimItems {
|
||||
rs := trimStr2Runes(v, l.innerWidth)
|
||||
|
||||
cs := DTrimTxCls(DefaultTxBuilder.Build(v, l.ItemFgColor, l.ItemBgColor), l.innerArea.Dx())
|
||||
j := 0
|
||||
for _, vv := range rs {
|
||||
w := charWidth(vv)
|
||||
p := Point{}
|
||||
p.X = l.innerX + j
|
||||
p.Y = l.innerY + i
|
||||
|
||||
p.Ch = vv
|
||||
p.Bg = l.ItemBgColor
|
||||
p.Fg = l.ItemFgColor
|
||||
|
||||
ps = append(ps, p)
|
||||
for _, vv := range cs {
|
||||
w := vv.Width()
|
||||
buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, vv)
|
||||
j += w
|
||||
}
|
||||
}
|
||||
}
|
||||
return l.Block.chopOverflow(ps)
|
||||
return buf
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
|
@ -48,16 +48,16 @@ type MBarChart struct {
|
|||
// NewBarChart returns a new *BarChart with current theme.
|
||||
func NewMBarChart() *MBarChart {
|
||||
bc := &MBarChart{Block: *NewBlock()}
|
||||
bc.BarColor[0] = theme.MBarChartBar
|
||||
bc.NumColor[0] = theme.MBarChartNum
|
||||
bc.TextColor = theme.MBarChartText
|
||||
bc.BarColor[0] = ThemeAttr("mbarchart.bar.bg")
|
||||
bc.NumColor[0] = ThemeAttr("mbarchart.num.fg")
|
||||
bc.TextColor = ThemeAttr("mbarchart.text.fg")
|
||||
bc.BarGap = 1
|
||||
bc.BarWidth = 3
|
||||
return bc
|
||||
}
|
||||
|
||||
func (bc *MBarChart) layout() {
|
||||
bc.numBar = bc.innerWidth / (bc.BarGap + bc.BarWidth)
|
||||
bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
|
||||
bc.labels = make([][]rune, bc.numBar)
|
||||
DataLen := 0
|
||||
LabelLen := len(bc.DataLabels)
|
||||
|
@ -129,9 +129,9 @@ func (bc *MBarChart) layout() {
|
|||
if bc.ShowScale {
|
||||
s := fmt.Sprintf("%d", bc.max)
|
||||
bc.maxScale = trimStr2Runes(s, len(s))
|
||||
bc.scale = float64(bc.max) / float64(bc.innerHeight-2)
|
||||
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-2)
|
||||
} else {
|
||||
bc.scale = float64(bc.max) / float64(bc.innerHeight-1)
|
||||
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -144,8 +144,8 @@ func (bc *MBarChart) SetMax(max int) {
|
|||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (bc *MBarChart) Buffer() []Point {
|
||||
ps := bc.Block.Buffer()
|
||||
func (bc *MBarChart) Buffer() Buffer {
|
||||
buf := bc.Block.Buffer()
|
||||
bc.layout()
|
||||
var oftX int
|
||||
|
||||
|
@ -157,15 +157,17 @@ func (bc *MBarChart) Buffer() []Point {
|
|||
// plot bars
|
||||
for j := 0; j < bc.BarWidth; j++ {
|
||||
for k := 0; k < h; k++ {
|
||||
p := Point{}
|
||||
p.Ch = ' '
|
||||
p.Bg = bc.BarColor[i1]
|
||||
if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
|
||||
p.Bg |= AttrReverse
|
||||
c := Cell{
|
||||
Ch: ' ',
|
||||
Bg: bc.BarColor[i1],
|
||||
}
|
||||
p.X = bc.innerX + i*(bc.BarWidth+bc.BarGap) + j
|
||||
p.Y = bc.innerY + bc.innerHeight - 2 - k - ph
|
||||
ps = append(ps, p)
|
||||
if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k - ph
|
||||
buf.Set(x, y, c)
|
||||
|
||||
}
|
||||
}
|
||||
ph += h
|
||||
|
@ -173,13 +175,14 @@ func (bc *MBarChart) Buffer() []Point {
|
|||
// plot text
|
||||
for j, k := 0, 0; j < len(bc.labels[i]); j++ {
|
||||
w := charWidth(bc.labels[i][j])
|
||||
p := Point{}
|
||||
p.Ch = bc.labels[i][j]
|
||||
p.Bg = bc.BgColor
|
||||
p.Fg = bc.TextColor
|
||||
p.Y = bc.innerY + bc.innerHeight - 1
|
||||
p.X = bc.innerX + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
|
||||
ps = append(ps, p)
|
||||
c := Cell{
|
||||
Ch: bc.labels[i][j],
|
||||
Bg: bc.Bg,
|
||||
Fg: bc.TextColor,
|
||||
}
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
|
||||
x := bc.innerArea.Max.X + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
|
||||
buf.Set(x, y, c)
|
||||
k += w
|
||||
}
|
||||
// plot num
|
||||
|
@ -187,19 +190,20 @@ func (bc *MBarChart) Buffer() []Point {
|
|||
for i1 := 0; i1 < bc.numStack; i1++ {
|
||||
h := int(float64(bc.Data[i1][i]) / bc.scale)
|
||||
for j := 0; j < len(bc.dataNum[i1][i]) && h > 0; j++ {
|
||||
p := Point{}
|
||||
p.Ch = bc.dataNum[i1][i][j]
|
||||
p.Fg = bc.NumColor[i1]
|
||||
p.Bg = bc.BarColor[i1]
|
||||
c := Cell{
|
||||
Ch: bc.dataNum[i1][i][j],
|
||||
Fg: bc.NumColor[i1],
|
||||
Bg: bc.BarColor[i1],
|
||||
}
|
||||
if bc.BarColor[i1] == ColorDefault { // the same as above
|
||||
p.Bg |= AttrReverse
|
||||
c.Bg |= AttrReverse
|
||||
}
|
||||
if h == 0 {
|
||||
p.Bg = bc.BgColor
|
||||
c.Bg = bc.Bg
|
||||
}
|
||||
p.X = bc.innerX + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
|
||||
p.Y = bc.innerY + bc.innerHeight - 2 - ph
|
||||
ps = append(ps, p)
|
||||
x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - ph
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
ph += h
|
||||
}
|
||||
|
@ -208,26 +212,31 @@ func (bc *MBarChart) Buffer() []Point {
|
|||
if bc.ShowScale {
|
||||
//Currently bar graph only supprts data range from 0 to MAX
|
||||
//Plot 0
|
||||
p := Point{}
|
||||
p.Ch = '0'
|
||||
p.Bg = bc.BgColor
|
||||
p.Fg = bc.TextColor
|
||||
p.Y = bc.innerY + bc.innerHeight - 2
|
||||
p.X = bc.X
|
||||
ps = append(ps, p)
|
||||
c := Cell{
|
||||
Ch: '0',
|
||||
Bg: bc.Bg,
|
||||
Fg: bc.TextColor,
|
||||
}
|
||||
|
||||
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
|
||||
x := bc.X
|
||||
buf.Set(x, y, c)
|
||||
|
||||
//Plot the maximum sacle value
|
||||
for i := 0; i < len(bc.maxScale); i++ {
|
||||
p := Point{}
|
||||
p.Ch = bc.maxScale[i]
|
||||
p.Bg = bc.BgColor
|
||||
p.Fg = bc.TextColor
|
||||
p.Y = bc.innerY
|
||||
p.X = bc.X + i
|
||||
ps = append(ps, p)
|
||||
c := Cell{
|
||||
Ch: bc.maxScale[i],
|
||||
Bg: bc.Bg,
|
||||
Fg: bc.TextColor,
|
||||
}
|
||||
|
||||
y := bc.innerArea.Min.Y
|
||||
x := bc.X + i
|
||||
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bc.Block.chopOverflow(ps)
|
||||
return buf
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,29 +1,62 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import tm "github.com/nsf/termbox-go"
|
||||
import (
|
||||
"image"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
tm "github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
// Bufferer should be implemented by all renderable components.
|
||||
type Bufferer interface {
|
||||
Buffer() []Point
|
||||
Buffer() Buffer
|
||||
}
|
||||
|
||||
// Init initializes termui library. This function should be called before any others.
|
||||
// After initialization, the library must be finalized by 'Close' function.
|
||||
func Init() error {
|
||||
if err := tm.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sysEvtChs = make([]chan Event, 0)
|
||||
go hookTermboxEvt()
|
||||
|
||||
renderJobs = make(chan []Bufferer)
|
||||
//renderLock = new(sync.RWMutex)
|
||||
|
||||
Body = NewGrid()
|
||||
Body.X = 0
|
||||
Body.Y = 0
|
||||
Body.BgColor = theme.BodyBg
|
||||
defer func() {
|
||||
w, _ := tm.Size()
|
||||
Body.Width = w
|
||||
evtListen()
|
||||
Body.BgColor = ThemeAttr("bg")
|
||||
Body.Width = TermWidth()
|
||||
|
||||
DefaultEvtStream.Init()
|
||||
DefaultEvtStream.Merge("termbox", NewSysEvtCh())
|
||||
DefaultEvtStream.Merge("timer", NewTimerCh(time.Second))
|
||||
DefaultEvtStream.Merge("custom", usrEvtCh)
|
||||
|
||||
DefaultEvtStream.Handle("/", DefualtHandler)
|
||||
DefaultEvtStream.Handle("/sys/wnd/resize", func(e Event) {
|
||||
w := e.Data.(EvtWnd)
|
||||
Body.Width = w.Width
|
||||
})
|
||||
|
||||
DefaultWgtMgr = NewWgtMgr()
|
||||
DefaultEvtStream.Hook(DefaultWgtMgr.WgtHandlersHook())
|
||||
|
||||
go func() {
|
||||
for bs := range renderJobs {
|
||||
render(bs...)
|
||||
}
|
||||
}()
|
||||
return tm.Init()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close finalizes termui library,
|
||||
|
@ -32,29 +65,71 @@ func Close() {
|
|||
tm.Close()
|
||||
}
|
||||
|
||||
var renderLock sync.Mutex
|
||||
|
||||
func termSync() {
|
||||
renderLock.Lock()
|
||||
tm.Sync()
|
||||
termWidth, termHeight = tm.Size()
|
||||
renderLock.Unlock()
|
||||
}
|
||||
|
||||
// TermWidth returns the current terminal's width.
|
||||
func TermWidth() int {
|
||||
tm.Sync()
|
||||
w, _ := tm.Size()
|
||||
return w
|
||||
termSync()
|
||||
return termWidth
|
||||
}
|
||||
|
||||
// TermHeight returns the current terminal's height.
|
||||
func TermHeight() int {
|
||||
tm.Sync()
|
||||
_, h := tm.Size()
|
||||
return h
|
||||
termSync()
|
||||
return termHeight
|
||||
}
|
||||
|
||||
// Render renders all Bufferer in the given order from left to right,
|
||||
// right could overlap on left ones.
|
||||
func Render(rs ...Bufferer) {
|
||||
tm.Clear(tm.ColorDefault, toTmAttr(theme.BodyBg))
|
||||
for _, r := range rs {
|
||||
buf := r.Buffer()
|
||||
for _, v := range buf {
|
||||
tm.SetCell(v.X, v.Y, v.Ch, toTmAttr(v.Fg), toTmAttr(v.Bg))
|
||||
func render(bs ...Bufferer) {
|
||||
|
||||
for _, b := range bs {
|
||||
|
||||
buf := b.Buffer()
|
||||
// set cels in buf
|
||||
for p, c := range buf.CellMap {
|
||||
if p.In(buf.Area) {
|
||||
|
||||
tm.SetCell(p.X, p.Y, c.Ch, toTmAttr(c.Fg), toTmAttr(c.Bg))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
renderLock.Lock()
|
||||
// render
|
||||
tm.Flush()
|
||||
renderLock.Unlock()
|
||||
}
|
||||
|
||||
func Clear() {
|
||||
tm.Clear(tm.ColorDefault, toTmAttr(ThemeAttr("bg")))
|
||||
}
|
||||
|
||||
func clearArea(r image.Rectangle, bg Attribute) {
|
||||
for i := r.Min.X; i < r.Max.X; i++ {
|
||||
for j := r.Min.Y; j < r.Max.Y; j++ {
|
||||
tm.SetCell(i, j, ' ', tm.ColorDefault, toTmAttr(bg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ClearArea(r image.Rectangle, bg Attribute) {
|
||||
clearArea(r, bg)
|
||||
tm.Flush()
|
||||
}
|
||||
|
||||
var renderJobs chan []Bufferer
|
||||
|
||||
func Render(bs ...Bufferer) {
|
||||
//go func() { renderJobs <- bs }()
|
||||
renderJobs <- bs
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import "math"
|
||||
|
||||
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃
|
||||
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
|
||||
/*
|
||||
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}
|
||||
spl := termui.NewSparkline()
|
||||
|
@ -49,8 +47,8 @@ func (s *Sparklines) Add(sl Sparkline) {
|
|||
func NewSparkline() Sparkline {
|
||||
return Sparkline{
|
||||
Height: 1,
|
||||
TitleColor: theme.SparklineTitle,
|
||||
LineColor: theme.SparklineLine}
|
||||
TitleColor: ThemeAttr("sparkline.title.fg"),
|
||||
LineColor: ThemeAttr("sparkline.line.fg")}
|
||||
}
|
||||
|
||||
// NewSparklines return a new *Spaklines with given Sparkline(s), you can always add a new Sparkline later.
|
||||
|
@ -67,13 +65,13 @@ func (sl *Sparklines) update() {
|
|||
sl.Lines[i].displayHeight = v.Height + 1
|
||||
}
|
||||
}
|
||||
sl.displayWidth = sl.innerWidth
|
||||
sl.displayWidth = sl.innerArea.Dx()
|
||||
|
||||
// get how many lines gotta display
|
||||
h := 0
|
||||
sl.displayLines = 0
|
||||
for _, v := range sl.Lines {
|
||||
if h+v.displayHeight <= sl.innerHeight {
|
||||
if h+v.displayHeight <= sl.innerArea.Dy() {
|
||||
sl.displayLines++
|
||||
} else {
|
||||
break
|
||||
|
@ -84,20 +82,24 @@ func (sl *Sparklines) update() {
|
|||
for i := 0; i < sl.displayLines; i++ {
|
||||
data := sl.Lines[i].Data
|
||||
|
||||
max := math.MinInt32
|
||||
max := 0
|
||||
for _, v := range data {
|
||||
if max < v {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
sl.Lines[i].max = max
|
||||
sl.Lines[i].scale = float32(8*sl.Lines[i].Height) / float32(max)
|
||||
if max != 0 {
|
||||
sl.Lines[i].scale = float32(8*sl.Lines[i].Height) / float32(max)
|
||||
} else { // when all negative
|
||||
sl.Lines[i].scale = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer implements Bufferer interface.
|
||||
func (sl *Sparklines) Buffer() []Point {
|
||||
ps := sl.Block.Buffer()
|
||||
func (sl *Sparklines) Buffer() Buffer {
|
||||
buf := sl.Block.Buffer()
|
||||
sl.update()
|
||||
|
||||
oftY := 0
|
||||
|
@ -105,52 +107,61 @@ func (sl *Sparklines) Buffer() []Point {
|
|||
l := sl.Lines[i]
|
||||
data := l.Data
|
||||
|
||||
if len(data) > sl.innerWidth {
|
||||
data = data[len(data)-sl.innerWidth:]
|
||||
if len(data) > sl.innerArea.Dx() {
|
||||
data = data[len(data)-sl.innerArea.Dx():]
|
||||
}
|
||||
|
||||
if l.Title != "" {
|
||||
rs := trimStr2Runes(l.Title, sl.innerWidth)
|
||||
rs := trimStr2Runes(l.Title, sl.innerArea.Dx())
|
||||
oftX := 0
|
||||
for _, v := range rs {
|
||||
w := charWidth(v)
|
||||
p := Point{}
|
||||
p.Ch = v
|
||||
p.Fg = l.TitleColor
|
||||
p.Bg = sl.BgColor
|
||||
p.X = sl.innerX + oftX
|
||||
p.Y = sl.innerY + oftY
|
||||
ps = append(ps, p)
|
||||
c := Cell{
|
||||
Ch: v,
|
||||
Fg: l.TitleColor,
|
||||
Bg: sl.Bg,
|
||||
}
|
||||
x := sl.innerArea.Min.X + oftX
|
||||
y := sl.innerArea.Min.Y + oftY
|
||||
buf.Set(x, y, c)
|
||||
oftX += w
|
||||
}
|
||||
}
|
||||
|
||||
for j, v := range data {
|
||||
// display height of the data point, zero when data is negative
|
||||
h := int(float32(v)*l.scale + 0.5)
|
||||
if v < 0 {
|
||||
h = 0
|
||||
}
|
||||
|
||||
barCnt := h / 8
|
||||
barMod := h % 8
|
||||
for jj := 0; jj < barCnt; jj++ {
|
||||
p := Point{}
|
||||
p.X = sl.innerX + j
|
||||
p.Y = sl.innerY + oftY + l.Height - jj
|
||||
p.Ch = ' ' // => sparks[7]
|
||||
p.Bg = l.LineColor
|
||||
c := Cell{
|
||||
Ch: ' ', // => sparks[7]
|
||||
Bg: l.LineColor,
|
||||
}
|
||||
x := sl.innerArea.Min.X + j
|
||||
y := sl.innerArea.Min.Y + oftY + l.Height - jj
|
||||
|
||||
//p.Bg = sl.BgColor
|
||||
ps = append(ps, p)
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
if barMod != 0 {
|
||||
p := Point{}
|
||||
p.X = sl.innerX + j
|
||||
p.Y = sl.innerY + oftY + l.Height - barCnt
|
||||
p.Ch = sparks[barMod-1]
|
||||
p.Fg = l.LineColor
|
||||
p.Bg = sl.BgColor
|
||||
ps = append(ps, p)
|
||||
c := Cell{
|
||||
Ch: sparks[barMod-1],
|
||||
Fg: l.LineColor,
|
||||
Bg: sl.Bg,
|
||||
}
|
||||
x := sl.innerArea.Min.X + j
|
||||
y := sl.innerArea.Min.Y + oftY + l.Height - barCnt
|
||||
buf.Set(x, y, c)
|
||||
}
|
||||
}
|
||||
|
||||
oftY += l.displayHeight
|
||||
}
|
||||
|
||||
return sl.Block.chopOverflow(ps)
|
||||
return buf
|
||||
}
|
||||
|
|
|
@ -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{}
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
package termui
|
||||
|
||||
import "strings"
|
||||
|
||||
/*
|
||||
// A ColorScheme represents the current look-and-feel of the dashboard.
|
||||
type ColorScheme struct {
|
||||
BodyBg Attribute
|
||||
|
@ -29,6 +32,7 @@ type ColorScheme struct {
|
|||
MBarChartBar Attribute
|
||||
MBarChartText Attribute
|
||||
MBarChartNum Attribute
|
||||
TabActiveBg Attribute
|
||||
}
|
||||
|
||||
// default color scheme depends on the user's terminal setting.
|
||||
|
@ -58,6 +62,7 @@ var themeHelloWorld = ColorScheme{
|
|||
MBarChartBar: ColorRed,
|
||||
MBarChartNum: ColorWhite,
|
||||
MBarChartText: ColorCyan,
|
||||
TabActiveBg: ColorMagenta,
|
||||
}
|
||||
|
||||
var theme = themeDefault // global dep
|
||||
|
@ -82,3 +87,54 @@ func UseTheme(th string) {
|
|||
theme = themeDefault
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
var ColorMap = map[string]Attribute{
|
||||
"fg": ColorWhite,
|
||||
"bg": ColorDefault,
|
||||
"border.fg": ColorWhite,
|
||||
"label.fg": ColorGreen,
|
||||
"par.fg": ColorYellow,
|
||||
"par.label.bg": ColorWhite,
|
||||
}
|
||||
|
||||
func ThemeAttr(name string) Attribute {
|
||||
return lookUpAttr(ColorMap, name)
|
||||
}
|
||||
|
||||
func lookUpAttr(clrmap map[string]Attribute, name string) Attribute {
|
||||
|
||||
a, ok := clrmap[name]
|
||||
if ok {
|
||||
return a
|
||||
}
|
||||
|
||||
ns := strings.Split(name, ".")
|
||||
for i := range ns {
|
||||
nn := strings.Join(ns[i:len(ns)], ".")
|
||||
a, ok = ColorMap[nn]
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// 0<=r,g,b <= 5
|
||||
func ColorRGB(r, g, b int) Attribute {
|
||||
within := func(n int) int {
|
||||
if n < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if n > 5 {
|
||||
return 5
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
r, b, g = within(r), within(b), within(g)
|
||||
return Attribute(0x0f + 36*r + 6*g + b)
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -13,6 +13,8 @@ import (
|
|||
var (
|
||||
// ErrCorrupt reports that the input is invalid.
|
||||
ErrCorrupt = errors.New("snappy: corrupt input")
|
||||
// ErrTooLarge reports that the uncompressed length is too large.
|
||||
ErrTooLarge = errors.New("snappy: decoded block is too large")
|
||||
// ErrUnsupported reports that the input isn't supported.
|
||||
ErrUnsupported = errors.New("snappy: unsupported input")
|
||||
)
|
||||
|
@ -27,11 +29,13 @@ func DecodedLen(src []byte) (int, error) {
|
|||
// that the length header occupied.
|
||||
func decodedLen(src []byte) (blockLen, headerLen int, err error) {
|
||||
v, n := binary.Uvarint(src)
|
||||
if n == 0 {
|
||||
if n <= 0 || v > 0xffffffff {
|
||||
return 0, 0, ErrCorrupt
|
||||
}
|
||||
if uint64(int(v)) != v {
|
||||
return 0, 0, errors.New("snappy: decoded block is too large")
|
||||
|
||||
const wordSize = 32 << (^uint(0) >> 32 & 1)
|
||||
if wordSize == 32 && v > 0x7fffffff {
|
||||
return 0, 0, ErrTooLarge
|
||||
}
|
||||
return int(v), n, nil
|
||||
}
|
||||
|
@ -56,7 +60,7 @@ func Decode(dst, src []byte) ([]byte, error) {
|
|||
x := uint(src[s] >> 2)
|
||||
switch {
|
||||
case x < 60:
|
||||
s += 1
|
||||
s++
|
||||
case x == 60:
|
||||
s += 2
|
||||
if s > len(src) {
|
||||
|
@ -130,7 +134,7 @@ func Decode(dst, src []byte) ([]byte, error) {
|
|||
|
||||
// NewReader returns a new Reader that decompresses from r, using the framing
|
||||
// format described at
|
||||
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
|
@ -139,7 +143,7 @@ func NewReader(r io.Reader) *Reader {
|
|||
}
|
||||
}
|
||||
|
||||
// Reader is an io.Reader than can read Snappy-compressed bytes.
|
||||
// Reader is an io.Reader that can read Snappy-compressed bytes.
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
err error
|
||||
|
@ -200,7 +204,7 @@ func (r *Reader) Read(p []byte) (int, error) {
|
|||
}
|
||||
|
||||
// The chunk types are specified at
|
||||
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||
switch chunkType {
|
||||
case chunkTypeCompressedData:
|
||||
// Section 4.2. Compressed data (chunk type 0x00).
|
||||
|
@ -280,13 +284,11 @@ func (r *Reader) Read(p []byte) (int, error) {
|
|||
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
|
||||
r.err = ErrUnsupported
|
||||
return 0, r.err
|
||||
|
||||
} else {
|
||||
// Section 4.4 Padding (chunk type 0xfe).
|
||||
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
|
||||
if !r.readFull(r.buf[:chunkLen]) {
|
||||
return 0, r.err
|
||||
}
|
||||
}
|
||||
// Section 4.4 Padding (chunk type 0xfe).
|
||||
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
|
||||
if !r.readFull(r.buf[:chunkLen]) {
|
||||
return 0, r.err
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ package snappy
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -79,7 +80,7 @@ func emitCopy(dst []byte, offset, length int) int {
|
|||
// slice of dst if dst was large enough to hold the entire encoded block.
|
||||
// Otherwise, a newly allocated slice will be returned.
|
||||
// It is valid to pass a nil dst.
|
||||
func Encode(dst, src []byte) ([]byte, error) {
|
||||
func Encode(dst, src []byte) []byte {
|
||||
if n := MaxEncodedLen(len(src)); len(dst) < n {
|
||||
dst = make([]byte, n)
|
||||
}
|
||||
|
@ -92,7 +93,7 @@ func Encode(dst, src []byte) ([]byte, error) {
|
|||
if len(src) != 0 {
|
||||
d += emitLiteral(dst[d:], src)
|
||||
}
|
||||
return dst[:d], nil
|
||||
return dst[:d]
|
||||
}
|
||||
|
||||
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
|
||||
|
@ -122,7 +123,8 @@ func Encode(dst, src []byte) ([]byte, error) {
|
|||
t, *p = *p-1, s+1
|
||||
// If t is invalid or src[s:s+4] differs from src[t:t+4], accumulate a literal byte.
|
||||
if t < 0 || s-t >= maxOffset || b0 != src[t] || b1 != src[t+1] || b2 != src[t+2] || b3 != src[t+3] {
|
||||
s++
|
||||
// Skip multiple bytes if the last match was >= 32 bytes prior.
|
||||
s += 1 + (s-lit)>>5
|
||||
continue
|
||||
}
|
||||
// Otherwise, we have a match. First, emit any pending literal bytes.
|
||||
|
@ -145,7 +147,7 @@ func Encode(dst, src []byte) ([]byte, error) {
|
|||
if lit != len(src) {
|
||||
d += emitLiteral(dst[d:], src[lit:])
|
||||
}
|
||||
return dst[:d], nil
|
||||
return dst[:d]
|
||||
}
|
||||
|
||||
// MaxEncodedLen returns the maximum length of a snappy block, given its
|
||||
|
@ -174,23 +176,56 @@ func MaxEncodedLen(srcLen int) int {
|
|||
return 32 + srcLen + srcLen/6
|
||||
}
|
||||
|
||||
// NewWriter returns a new Writer that compresses to w, using the framing
|
||||
// format described at
|
||||
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
|
||||
var errClosed = errors.New("snappy: Writer is closed")
|
||||
|
||||
// NewWriter returns a new Writer that compresses to w.
|
||||
//
|
||||
// The Writer returned does not buffer writes. There is no need to Flush or
|
||||
// Close such a Writer.
|
||||
//
|
||||
// Deprecated: the Writer returned is not suitable for many small writes, only
|
||||
// for few large writes. Use NewBufferedWriter instead, which is efficient
|
||||
// regardless of the frequency and shape of the writes, and remember to Close
|
||||
// that Writer when done.
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{
|
||||
w: w,
|
||||
enc: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)),
|
||||
w: w,
|
||||
obuf: make([]byte, obufLen),
|
||||
}
|
||||
}
|
||||
|
||||
// NewBufferedWriter returns a new Writer that compresses to w, using the
|
||||
// framing format described at
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||
//
|
||||
// The Writer returned buffers writes. Users must call Close to guarantee all
|
||||
// data has been forwarded to the underlying io.Writer. They may also call
|
||||
// Flush zero or more times before calling Close.
|
||||
func NewBufferedWriter(w io.Writer) *Writer {
|
||||
return &Writer{
|
||||
w: w,
|
||||
ibuf: make([]byte, 0, maxUncompressedChunkLen),
|
||||
obuf: make([]byte, obufLen),
|
||||
}
|
||||
}
|
||||
|
||||
// Writer is an io.Writer than can write Snappy-compressed bytes.
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
err error
|
||||
enc []byte
|
||||
buf [checksumSize + chunkHeaderSize]byte
|
||||
wroteHeader bool
|
||||
w io.Writer
|
||||
err error
|
||||
|
||||
// ibuf is a buffer for the incoming (uncompressed) bytes.
|
||||
//
|
||||
// Its use is optional. For backwards compatibility, Writers created by the
|
||||
// NewWriter function have ibuf == nil, do not buffer incoming bytes, and
|
||||
// therefore do not need to be Flush'ed or Close'd.
|
||||
ibuf []byte
|
||||
|
||||
// obuf is a buffer for the outgoing (compressed) bytes.
|
||||
obuf []byte
|
||||
|
||||
// wroteStreamHeader is whether we have written the stream header.
|
||||
wroteStreamHeader bool
|
||||
}
|
||||
|
||||
// Reset discards the writer's state and switches the Snappy writer to write to
|
||||
|
@ -198,23 +233,60 @@ type Writer struct {
|
|||
func (w *Writer) Reset(writer io.Writer) {
|
||||
w.w = writer
|
||||
w.err = nil
|
||||
w.wroteHeader = false
|
||||
if w.ibuf != nil {
|
||||
w.ibuf = w.ibuf[:0]
|
||||
}
|
||||
w.wroteStreamHeader = false
|
||||
}
|
||||
|
||||
// Write satisfies the io.Writer interface.
|
||||
func (w *Writer) Write(p []byte) (n int, errRet error) {
|
||||
func (w *Writer) Write(p []byte) (nRet int, errRet error) {
|
||||
if w.ibuf == nil {
|
||||
// Do not buffer incoming bytes. This does not perform or compress well
|
||||
// if the caller of Writer.Write writes many small slices. This
|
||||
// behavior is therefore deprecated, but still supported for backwards
|
||||
// compatibility with code that doesn't explicitly Flush or Close.
|
||||
return w.write(p)
|
||||
}
|
||||
|
||||
// The remainder of this method is based on bufio.Writer.Write from the
|
||||
// standard library.
|
||||
|
||||
for len(p) > (cap(w.ibuf)-len(w.ibuf)) && w.err == nil {
|
||||
var n int
|
||||
if len(w.ibuf) == 0 {
|
||||
// Large write, empty buffer.
|
||||
// Write directly from p to avoid copy.
|
||||
n, _ = w.write(p)
|
||||
} else {
|
||||
n = copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
||||
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
||||
w.Flush()
|
||||
}
|
||||
nRet += n
|
||||
p = p[n:]
|
||||
}
|
||||
if w.err != nil {
|
||||
return nRet, w.err
|
||||
}
|
||||
n := copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
||||
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
||||
nRet += n
|
||||
return nRet, nil
|
||||
}
|
||||
|
||||
func (w *Writer) write(p []byte) (nRet int, errRet error) {
|
||||
if w.err != nil {
|
||||
return 0, w.err
|
||||
}
|
||||
if !w.wroteHeader {
|
||||
copy(w.enc, magicChunk)
|
||||
if _, err := w.w.Write(w.enc[:len(magicChunk)]); err != nil {
|
||||
w.err = err
|
||||
return n, err
|
||||
}
|
||||
w.wroteHeader = true
|
||||
}
|
||||
for len(p) > 0 {
|
||||
obufStart := len(magicChunk)
|
||||
if !w.wroteStreamHeader {
|
||||
w.wroteStreamHeader = true
|
||||
copy(w.obuf, magicChunk)
|
||||
obufStart = 0
|
||||
}
|
||||
|
||||
var uncompressed []byte
|
||||
if len(p) > maxUncompressedChunkLen {
|
||||
uncompressed, p = p[:maxUncompressedChunkLen], p[maxUncompressedChunkLen:]
|
||||
|
@ -225,34 +297,60 @@ func (w *Writer) Write(p []byte) (n int, errRet error) {
|
|||
|
||||
// Compress the buffer, discarding the result if the improvement
|
||||
// isn't at least 12.5%.
|
||||
compressed := Encode(w.obuf[obufHeaderLen:], uncompressed)
|
||||
chunkType := uint8(chunkTypeCompressedData)
|
||||
chunkBody, err := Encode(w.enc, uncompressed)
|
||||
if err != nil {
|
||||
w.err = err
|
||||
return n, err
|
||||
}
|
||||
if len(chunkBody) >= len(uncompressed)-len(uncompressed)/8 {
|
||||
chunkType, chunkBody = chunkTypeUncompressedData, uncompressed
|
||||
chunkLen := 4 + len(compressed)
|
||||
obufEnd := obufHeaderLen + len(compressed)
|
||||
if len(compressed) >= len(uncompressed)-len(uncompressed)/8 {
|
||||
chunkType = chunkTypeUncompressedData
|
||||
chunkLen = 4 + len(uncompressed)
|
||||
obufEnd = obufHeaderLen
|
||||
}
|
||||
|
||||
chunkLen := 4 + len(chunkBody)
|
||||
w.buf[0] = chunkType
|
||||
w.buf[1] = uint8(chunkLen >> 0)
|
||||
w.buf[2] = uint8(chunkLen >> 8)
|
||||
w.buf[3] = uint8(chunkLen >> 16)
|
||||
w.buf[4] = uint8(checksum >> 0)
|
||||
w.buf[5] = uint8(checksum >> 8)
|
||||
w.buf[6] = uint8(checksum >> 16)
|
||||
w.buf[7] = uint8(checksum >> 24)
|
||||
if _, err = w.w.Write(w.buf[:]); err != nil {
|
||||
// Fill in the per-chunk header that comes before the body.
|
||||
w.obuf[len(magicChunk)+0] = chunkType
|
||||
w.obuf[len(magicChunk)+1] = uint8(chunkLen >> 0)
|
||||
w.obuf[len(magicChunk)+2] = uint8(chunkLen >> 8)
|
||||
w.obuf[len(magicChunk)+3] = uint8(chunkLen >> 16)
|
||||
w.obuf[len(magicChunk)+4] = uint8(checksum >> 0)
|
||||
w.obuf[len(magicChunk)+5] = uint8(checksum >> 8)
|
||||
w.obuf[len(magicChunk)+6] = uint8(checksum >> 16)
|
||||
w.obuf[len(magicChunk)+7] = uint8(checksum >> 24)
|
||||
|
||||
if _, err := w.w.Write(w.obuf[obufStart:obufEnd]); err != nil {
|
||||
w.err = err
|
||||
return n, err
|
||||
return nRet, err
|
||||
}
|
||||
if _, err = w.w.Write(chunkBody); err != nil {
|
||||
w.err = err
|
||||
return n, err
|
||||
if chunkType == chunkTypeUncompressedData {
|
||||
if _, err := w.w.Write(uncompressed); err != nil {
|
||||
w.err = err
|
||||
return nRet, err
|
||||
}
|
||||
}
|
||||
n += len(uncompressed)
|
||||
nRet += len(uncompressed)
|
||||
}
|
||||
return n, nil
|
||||
return nRet, nil
|
||||
}
|
||||
|
||||
// Flush flushes the Writer to its underlying io.Writer.
|
||||
func (w *Writer) Flush() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
if len(w.ibuf) == 0 {
|
||||
return nil
|
||||
}
|
||||
w.write(w.ibuf)
|
||||
w.ibuf = w.ibuf[:0]
|
||||
return w.err
|
||||
}
|
||||
|
||||
// Close calls Flush and then closes the Writer.
|
||||
func (w *Writer) Close() error {
|
||||
w.Flush()
|
||||
ret := w.err
|
||||
if w.err == nil {
|
||||
w.err = errClosed
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
// Package snappy implements the snappy block-based compression format.
|
||||
// It aims for very high speeds and reasonable compression.
|
||||
//
|
||||
// The C++ snappy implementation is at http://code.google.com/p/snappy/
|
||||
// The C++ snappy implementation is at https://github.com/google/snappy
|
||||
package snappy
|
||||
|
||||
import (
|
||||
|
@ -46,9 +46,18 @@ const (
|
|||
chunkHeaderSize = 4
|
||||
magicChunk = "\xff\x06\x00\x00" + magicBody
|
||||
magicBody = "sNaPpY"
|
||||
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt says
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt says
|
||||
// that "the uncompressed data in a chunk must be no longer than 65536 bytes".
|
||||
maxUncompressedChunkLen = 65536
|
||||
|
||||
// maxEncodedLenOfMaxUncompressedChunkLen equals
|
||||
// MaxEncodedLen(maxUncompressedChunkLen), but is hard coded to be a const
|
||||
// instead of a variable, so that obufLen can also be a const. Their
|
||||
// equivalence is confirmed by TestMaxEncodedLenOfMaxUncompressedChunkLen.
|
||||
maxEncodedLenOfMaxUncompressedChunkLen = 76490
|
||||
|
||||
obufHeaderLen = len(magicChunk) + checksumSize + chunkHeaderSize
|
||||
obufLen = obufHeaderLen + maxEncodedLenOfMaxUncompressedChunkLen
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -61,7 +70,7 @@ const (
|
|||
var crcTable = crc32.MakeTable(crc32.Castagnoli)
|
||||
|
||||
// crc implements the checksum specified in section 3 of
|
||||
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||
func crc(b []byte) uint32 {
|
||||
c := crc32.Update(0, crcTable, b)
|
||||
return uint32(c>>15|c<<17) + 0xa282ead8
|
|
@ -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)
|
||||
}
|
|
@ -4,24 +4,15 @@
|
|||
package lru
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/golang-lru/simplelru"
|
||||
)
|
||||
|
||||
// Cache is a thread-safe fixed size LRU cache.
|
||||
type Cache struct {
|
||||
size int
|
||||
evictList *list.List
|
||||
items map[interface{}]*list.Element
|
||||
lock sync.RWMutex
|
||||
onEvicted func(key interface{}, value interface{})
|
||||
}
|
||||
|
||||
// entry is used to hold a value in the evictList
|
||||
type entry struct {
|
||||
key interface{}
|
||||
value interface{}
|
||||
lru *simplelru.LRU
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates an LRU of the given size
|
||||
|
@ -29,15 +20,15 @@ func New(size int) (*Cache, error) {
|
|||
return NewWithEvict(size, nil)
|
||||
}
|
||||
|
||||
// NewWithEvict constructs a fixed size cache with the given eviction
|
||||
// callback.
|
||||
func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
|
||||
if size <= 0 {
|
||||
return nil, errors.New("Must provide a positive size")
|
||||
lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Cache{
|
||||
size: size,
|
||||
evictList: list.New(),
|
||||
items: make(map[interface{}]*list.Element, size),
|
||||
onEvicted: onEvicted,
|
||||
lru: lru,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
@ -45,131 +36,79 @@ func NewWithEvict(size int, onEvicted func(key interface{}, value interface{}))
|
|||
// Purge is used to completely clear the cache
|
||||
func (c *Cache) Purge() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if c.onEvicted != nil {
|
||||
for k, v := range c.items {
|
||||
c.onEvicted(k, v.Value.(*entry).value)
|
||||
}
|
||||
}
|
||||
|
||||
c.evictList = list.New()
|
||||
c.items = make(map[interface{}]*list.Element, c.size)
|
||||
c.lru.Purge()
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
// Add adds a value to the cache. Returns true if an eviction occured.
|
||||
// Add adds a value to the cache. Returns true if an eviction occurred.
|
||||
func (c *Cache) Add(key, value interface{}) bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// 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
|
||||
return c.lru.Add(key, value)
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
|
||||
func (c *Cache) Get(key interface{}) (interface{}, bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
return c.lru.Get(key)
|
||||
}
|
||||
|
||||
// Check if a key is in the cache, without updating the recent-ness
|
||||
// or deleting it for being stale.
|
||||
func (c *Cache) Contains(key interface{}) bool {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.lru.Contains(key)
|
||||
}
|
||||
|
||||
// Returns the key value (or undefined if not found) without updating
|
||||
// the "recently used"-ness of the key.
|
||||
func (c *Cache) Peek(key interface{}) (interface{}, bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.lru.Peek(key)
|
||||
}
|
||||
|
||||
// ContainsOrAdd checks if a key is in the cache without updating the
|
||||
// recent-ness or deleting it for being stale, and if not, adds the value.
|
||||
// Returns whether found and whether an eviction occurred.
|
||||
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if ent, ok := c.items[key]; ok {
|
||||
c.evictList.MoveToFront(ent)
|
||||
return ent.Value.(*entry).value, true
|
||||
if c.lru.Contains(key) {
|
||||
return true, false
|
||||
} else {
|
||||
evict := c.lru.Add(key, value)
|
||||
return false, evict
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if a key is in the cache, without updating the recent-ness or deleting it for being stale.
|
||||
func (c *Cache) Contains(key interface{}) (ok bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
_, ok = c.items[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Returns the key value (or undefined if not found) without updating the "recently used"-ness of the key.
|
||||
// (If you find yourself using this a lot, you might be using the wrong sort of data structure, but there are some use cases where it's handy.)
|
||||
func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
if ent, ok := c.items[key]; ok {
|
||||
return ent.Value.(*entry).value, true
|
||||
}
|
||||
return nil, ok
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *Cache) Remove(key interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if ent, ok := c.items[key]; ok {
|
||||
c.removeElement(ent)
|
||||
}
|
||||
c.lru.Remove(key)
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
// RemoveOldest removes the oldest item from the cache.
|
||||
func (c *Cache) RemoveOldest() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.removeOldest()
|
||||
c.lru.RemoveOldest()
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||
func (c *Cache) Keys() []interface{} {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
keys := make([]interface{}, len(c.items))
|
||||
ent := c.evictList.Back()
|
||||
i := 0
|
||||
for ent != nil {
|
||||
keys[i] = ent.Value.(*entry).key
|
||||
ent = ent.Prev()
|
||||
i++
|
||||
}
|
||||
|
||||
return keys
|
||||
return c.lru.Keys()
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *Cache) Len() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.evictList.Len()
|
||||
}
|
||||
|
||||
// removeOldest removes the oldest item from the cache.
|
||||
func (c *Cache) 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 *Cache) removeElement(e *list.Element) {
|
||||
c.evictList.Remove(e)
|
||||
kv := e.Value.(*entry)
|
||||
delete(c.items, kv.key)
|
||||
if c.onEvicted != nil {
|
||||
c.onEvicted(kv.key, kv.value)
|
||||
}
|
||||
return c.lru.Len()
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
160
Godeps/_workspace/src/github.com/hashicorp/golang-lru/simplelru/lru.go
generated
vendored
Normal file
|
@ -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!
|