Merge pull request #2206 from fjl/update-deps

Godeps: update all dependencies
This commit is contained in:
Jeffrey Wilcke 2016-02-17 13:46:30 +01:00
commit aa36a6ae4f
664 changed files with 164402 additions and 67007 deletions

99
Godeps/Godeps.json generated
View File

@ -5,14 +5,18 @@
"./..." "./..."
], ],
"Deps": [ "Deps": [
{
"ImportPath": "github.com/Gustav-Simonsson/go-opencl/cl",
"Rev": "593e01cfc4f3353585015321e01951d4a907d3ef"
},
{ {
"ImportPath": "github.com/codegangsta/cli", "ImportPath": "github.com/codegangsta/cli",
"Comment": "1.2.0-161-gf445c89", "Comment": "1.2.0-215-g0ab42fd",
"Rev": "f445c894402839580d30de47551cedc152dad814" "Rev": "0ab42fd482c27cf2c95e7794ad3bb2082c2ab2d7"
}, },
{ {
"ImportPath": "github.com/davecgh/go-spew/spew", "ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "3e6e67c4dcea3ac2f25fd4731abc0e1deaf36216" "Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
}, },
{ {
"ImportPath": "github.com/ethereum/ethash", "ImportPath": "github.com/ethereum/ethash",
@ -21,101 +25,126 @@
}, },
{ {
"ImportPath": "github.com/fatih/color", "ImportPath": "github.com/fatih/color",
"Comment": "v0.1-5-gf773d4c", "Comment": "v0.1-12-g9aae6aa",
"Rev": "f773d4c806cc8e4a5749d6a35e2a4bbcd71443d6" "Rev": "9aae6aaa22315390f03959adca2c4d395b02fcef"
}, },
{ {
"ImportPath": "github.com/gizak/termui", "ImportPath": "github.com/gizak/termui",
"Rev": "bab8dce01c193d82bc04888a0a9a7814d505f532" "Rev": "08a5d3f67b7d9ec87830ea39c48e570a1f18531f"
},
{
"ImportPath": "github.com/golang/snappy",
"Rev": "799c780093d646c1b79d30894e22512c319fa137"
}, },
{ {
"ImportPath": "github.com/hashicorp/golang-lru", "ImportPath": "github.com/hashicorp/golang-lru",
"Rev": "7f9ef20a0256f494e24126014135cf893ab71e9e" "Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"
}, },
{ {
"ImportPath": "github.com/huin/goupnp", "ImportPath": "github.com/huin/goupnp",
"Rev": "90f71cb5dd6d4606388666d2cda4ce2f563d2185" "Rev": "46bde78b11f3f021f2a511df138be9e2fc7506e8"
},
{
"ImportPath": "github.com/jackpal/gateway",
"Rev": "192609c58b8985e645cbe82ddcb28a4362ca0fdc"
}, },
{ {
"ImportPath": "github.com/jackpal/go-nat-pmp", "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", "ImportPath": "github.com/mattn/go-isatty",
"Rev": "7fcbc72f853b92b5720db4a6b8482be612daef24" "Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639"
}, },
{ {
"ImportPath": "github.com/mattn/go-runewidth", "ImportPath": "github.com/mattn/go-runewidth",
"Comment": "travisish-33-g5890272", "Comment": "travisish-44-ge882a96",
"Rev": "5890272cd41c5103531cd7b79e428d99c9e97f76" "Rev": "e882a96ec18dd43fa283187b66af74497c9101c0"
}, },
{ {
"ImportPath": "github.com/nsf/termbox-go", "ImportPath": "github.com/nsf/termbox-go",
"Rev": "ca2931516914070bb7f934c83e408689cea8dfb7" "Rev": "362329b0aa6447eadd52edd8d660ec1dff470295"
}, },
{ {
"ImportPath": "github.com/pborman/uuid", "ImportPath": "github.com/pborman/uuid",
"Rev": "cccd189d45f7ac3368a0d127efb7f4d08ae0b655" "Comment": "v1.0-6-g0f1a469",
"Rev": "0f1a46960a86dcdf5dd30d3e6568a497a997909f"
}, },
{ {
"ImportPath": "github.com/peterh/liner", "ImportPath": "github.com/peterh/liner",
"Rev": "29f6a646557d83e2b6e9ba05c45fbea9c006dbe8" "Rev": "ad1edfd30321d8f006ccf05f1e0524adeb943060"
}, },
{ {
"ImportPath": "github.com/rcrowley/go-metrics", "ImportPath": "github.com/rcrowley/go-metrics",
"Rev": "a5cfc242a56ba7fa70b785f678d6214837bf93b9" "Rev": "51425a2415d21afadfd55cd93432c0bc69e9598d"
}, },
{ {
"ImportPath": "github.com/robertkrimen/otto", "ImportPath": "github.com/robertkrimen/otto",
"Rev": "dea31a3d392779af358ec41f77a07fcc7e9d04ba" "Rev": "c21072f61b64b51ea58138ccacf0a85d54b9f07c"
},
{
"ImportPath": "github.com/shiena/ansicolor",
"Rev": "a5e2b567a4dd6cc74545b8a4f27c9d63b9e7735b"
}, },
{ {
"ImportPath": "github.com/syndtr/goleveldb/leveldb", "ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "4875955338b0a434238a31165cb87255ab6e9e4a" "Rev": "e7e6f5b5ef25adb580feac515f9ccec514d0bda8"
},
{
"ImportPath": "github.com/syndtr/gosnappy/snappy",
"Rev": "156a073208e131d7d2e212cb749feae7c339e846"
}, },
{ {
"ImportPath": "golang.org/x/crypto/pbkdf2", "ImportPath": "golang.org/x/crypto/pbkdf2",
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300" "Rev": "1f22c0103821b9390939b6776727195525381532"
}, },
{ {
"ImportPath": "golang.org/x/crypto/ripemd160", "ImportPath": "golang.org/x/crypto/ripemd160",
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300" "Rev": "1f22c0103821b9390939b6776727195525381532"
}, },
{ {
"ImportPath": "golang.org/x/crypto/scrypt", "ImportPath": "golang.org/x/crypto/scrypt",
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300" "Rev": "1f22c0103821b9390939b6776727195525381532"
}, },
{ {
"ImportPath": "golang.org/x/net/context", "ImportPath": "golang.org/x/net/context",
"Rev": "e0403b4e005737430c05a57aac078479844f919c" "Rev": "8968c61983e8f51a91b8c0ef25bf739278c89634"
}, },
{ {
"ImportPath": "golang.org/x/net/html", "ImportPath": "golang.org/x/net/html",
"Rev": "e0403b4e005737430c05a57aac078479844f919c" "Rev": "8968c61983e8f51a91b8c0ef25bf739278c89634"
}, },
{ {
"ImportPath": "golang.org/x/net/websocket", "ImportPath": "golang.org/x/net/websocket",
"Rev": "e0403b4e005737430c05a57aac078479844f919c" "Rev": "8968c61983e8f51a91b8c0ef25bf739278c89634"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e"
}, },
{ {
"ImportPath": "golang.org/x/text/encoding", "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", "ImportPath": "golang.org/x/text/transform",
"Rev": "c93e7c9fff19fb9139b5ab04ce041833add0134e" "Rev": "09761194ac5034a97b2bfad4f5b896b0ac350b3e"
}, },
{ {
"ImportPath": "gopkg.in/check.v1", "ImportPath": "gopkg.in/check.v1",
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673" "Rev": "4f90aeace3a26ad7021961c297b22c42160c7b25"
}, },
{ {
"ImportPath": "gopkg.in/fatih/set.v0", "ImportPath": "gopkg.in/fatih/set.v0",

View File

@ -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))
}
}

View File

@ -7,6 +7,12 @@ go:
- 1.2.2 - 1.2.2
- 1.3.3 - 1.3.3
- 1.4.2 - 1.4.2
- 1.5.1
- tip
matrix:
allow_failures:
- go: tip
script: script:
- go vet ./... - go vet ./...

View File

@ -1,16 +1,19 @@
[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) [![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) [![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli)
# cli.go # 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. `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 ## 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. 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! **This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive!
## Installation ## Installation
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html). 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: To install `cli.go`, simply run:
@ -24,6 +27,7 @@ export PATH=$PATH:$GOPATH/bin
``` ```
## Getting Started ## 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()`. 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 ``` go
@ -123,6 +127,7 @@ GLOBAL OPTIONS
``` ```
### Arguments ### Arguments
You can lookup arguments by calling the `Args` function on `cli.Context`. You can lookup arguments by calling the `Args` function on `cli.Context`.
``` go ``` go
@ -134,7 +139,9 @@ app.Action = func(c *cli.Context) {
``` ```
### Flags ### Flags
Setting and querying flags is simple. Setting and querying flags is simple.
``` go ``` go
... ...
app.Flags = []cli.Flag { 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 See full list of flags at http://godoc.org/github.com/codegangsta/cli
#### Alternate Names #### Alternate Names
@ -207,6 +241,7 @@ app.Flags = []cli.Flag {
### Subcommands ### Subcommands
Subcommands can be defined for a more git-like command line app. Subcommands can be defined for a more git-like command line app.
```go ```go
... ...
app.Commands = []cli.Command{ 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 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 show an app's subcommands, but you can write your own completion methods for
the App or its subcommands. the App or its subcommands.
```go ```go
... ...
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
@ -308,6 +344,7 @@ Alternatively, you can just document that users should source the generic
to the name of their program (as above). to the name of their program (as above).
## Contribution Guidelines ## 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. 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. 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.

View File

@ -5,18 +5,21 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"time" "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 // an app be created with the cli.NewApp() function
type App struct { 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 Name string
// Full name of command for help, defaults to Name // Full name of command for help, defaults to Name
HelpName string HelpName string
// Description of the program. // Description of the program.
Usage string Usage string
// Text to override the USAGE section of help
UsageText string
// Description of the program argument format. // Description of the program argument format.
ArgsUsage string ArgsUsage string
// Version of the program // Version of the program
@ -43,6 +46,10 @@ type App struct {
Action func(context *Context) Action func(context *Context)
// Execute this function if the proper command cannot be found // Execute this function if the proper command cannot be found
CommandNotFound func(context *Context, command string) 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 // Compilation date
Compiled time.Time Compiled time.Time
// List of all authors who contributed // 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. // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App { func NewApp() *App {
return &App{ return &App{
Name: os.Args[0], Name: path.Base(os.Args[0]),
HelpName: os.Args[0], HelpName: path.Base(os.Args[0]),
Usage: "A new cli application", Usage: "A new cli application",
UsageText: "",
Version: "0.0.0", Version: "0.0.0",
BashComplete: DefaultAppComplete, BashComplete: DefaultAppComplete,
Action: helpCommand.Action, Action: helpCommand.Action,
@ -118,25 +126,28 @@ func (a *App) Run(arguments []string) (err error) {
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:]) err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil)
if nerr != nil { if nerr != nil {
fmt.Fprintln(a.Writer, nerr) fmt.Fprintln(a.Writer, nerr)
context := NewContext(a, set, nil)
ShowAppHelp(context) ShowAppHelp(context)
return nerr 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) { if checkCompletions(context) {
return nil 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) { if !a.HideHelp && checkHelp(context) {
ShowAppHelp(context) ShowAppHelp(context)
return nil return nil
@ -149,8 +160,7 @@ func (a *App) Run(arguments []string) (err error) {
if a.After != nil { if a.After != nil {
defer func() { defer func() {
afterErr := a.After(context) if afterErr := a.After(context); afterErr != nil {
if afterErr != nil {
if err != nil { if err != nil {
err = NewMultiError(err, afterErr) err = NewMultiError(err, afterErr)
} else { } else {
@ -161,8 +171,10 @@ func (a *App) Run(arguments []string) (err error) {
} }
if a.Before != nil { if a.Before != nil {
err := a.Before(context) err = a.Before(context)
if err != nil { if err != nil {
fmt.Fprintf(a.Writer, "%v\n\n", err)
ShowAppHelp(context)
return err return err
} }
} }
@ -233,15 +245,19 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
return nerr return nerr
} }
if checkCompletions(context) {
return nil
}
if err != nil { if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.") if a.OnUsageError != nil {
fmt.Fprintln(a.Writer) err = a.OnUsageError(context, err, true)
return err
} else {
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
ShowSubcommandHelp(context) ShowSubcommandHelp(context)
return err return err
} }
if checkCompletions(context) {
return nil
} }
if len(a.Commands) > 0 { if len(a.Commands) > 0 {

View File

@ -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

View File

@ -3,10 +3,9 @@
: ${PROG:=$(basename ${BASH_SOURCE})} : ${PROG:=$(basename ${BASH_SOURCE})}
_cli_bash_autocomplete() { _cli_bash_autocomplete() {
local cur prev opts base local cur opts base
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0 return 0

View File

@ -16,6 +16,8 @@ type Command struct {
Aliases []string Aliases []string
// A short description of the usage of this command // A short description of the usage of this command
Usage string Usage string
// Custom text to show on USAGE section of help
UsageText string
// A longer explanation of how the command works // A longer explanation of how the command works
Description string Description string
// A short description of the arguments of this command // A short description of the arguments of this command
@ -30,6 +32,10 @@ type Command struct {
After func(context *Context) error After func(context *Context) error
// The function to call when this command is invoked // The function to call when this command is invoked
Action func(context *Context) 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 // List of child commands
Subcommands []Command Subcommands []Command
// List of flags to parse // 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 // Invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) error { func (c Command) Run(ctx *Context) (err error) {
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { if len(c.Subcommands) > 0 {
return c.startApp(ctx) return c.startApp(ctx)
} }
@ -74,19 +80,22 @@ func (c Command) Run(ctx *Context) error {
set := flagSet(c.Name, c.Flags) set := flagSet(c.Name, c.Flags)
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
if !c.SkipFlagParsing {
firstFlagIndex := -1 firstFlagIndex := -1
terminatorIndex := -1 terminatorIndex := -1
for index, arg := range ctx.Args() { for index, arg := range ctx.Args() {
if arg == "--" { if arg == "--" {
terminatorIndex = index terminatorIndex = index
break break
} else if arg == "-" {
// Do nothing. A dash alone is not really a flag.
continue
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index firstFlagIndex = index
} }
} }
var err error if firstFlagIndex > -1 {
if firstFlagIndex > -1 && !c.SkipFlagParsing {
args := ctx.Args() args := ctx.Args()
regularArgs := make([]string, len(args[1:firstFlagIndex])) regularArgs := make([]string, len(args[1:firstFlagIndex]))
copy(regularArgs, args[1:firstFlagIndex]) copy(regularArgs, args[1:firstFlagIndex])
@ -103,13 +112,23 @@ func (c Command) Run(ctx *Context) error {
} else { } else {
err = set.Parse(ctx.Args().Tail()) err = set.Parse(ctx.Args().Tail())
} }
} else {
if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
}
}
if err != nil { if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(ctx, err)
return err
} else {
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
fmt.Fprintln(ctx.App.Writer) fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name) ShowCommandHelp(ctx, c.Name)
return err return err
} }
}
nerr := normalizeFlags(c.Flags, set) nerr := normalizeFlags(c.Flags, set)
if nerr != nil { if nerr != nil {
@ -127,6 +146,30 @@ func (c Command) Run(ctx *Context) error {
if checkCommandHelp(context, c.Name) { if checkCommandHelp(context, c.Name) {
return nil 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 context.Command = c
c.Action(context) c.Action(context)
return nil return nil
@ -160,7 +203,7 @@ func (c Command) startApp(ctx *Context) error {
if c.HelpName == "" { if c.HelpName == "" {
app.HelpName = c.HelpName app.HelpName = c.HelpName
} else { } else {
app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) app.HelpName = app.Name
} }
if c.Description != "" { if c.Description != "" {
@ -199,12 +242,9 @@ func (c Command) startApp(ctx *Context) error {
app.Action = helpSubcommand.Action app.Action = helpSubcommand.Action
} }
var newCmds []Command for index, cc := range app.Commands {
for _, cc := range app.Commands { app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
cc.commandNamePath = []string{c.Name, cc.Name}
newCmds = append(newCmds, cc)
} }
app.Commands = newCmds
return app.RunAsSubcommand(ctx) return app.RunAsSubcommand(ctx)
} }

View File

@ -163,7 +163,7 @@ func (c *Context) GlobalIsSet(name string) bool {
// Returns a slice of flag names used in this context. // Returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) { func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags { for _, flag := range c.Command.Flags {
name := strings.Split(flag.getName(), ",")[0] name := strings.Split(flag.GetName(), ",")[0]
if name == "help" { if name == "help" {
continue continue
} }
@ -175,7 +175,7 @@ func (c *Context) FlagNames() (names []string) {
// Returns a slice of global flag names used by the app. // Returns a slice of global flag names used by the app.
func (c *Context) GlobalFlagNames() (names []string) { func (c *Context) GlobalFlagNames() (names []string) {
for _, flag := range c.App.Flags { for _, flag := range c.App.Flags {
name := strings.Split(flag.getName(), ",")[0] name := strings.Split(flag.GetName(), ",")[0]
if name == "help" || name == "version" { if name == "help" || name == "version" {
continue continue
} }
@ -360,7 +360,7 @@ func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited[f.Name] = true visited[f.Name] = true
}) })
for _, f := range flags { for _, f := range flags {
parts := strings.Split(f.getName(), ",") parts := strings.Split(f.GetName(), ",")
if len(parts) == 1 { if len(parts) == 1 {
continue continue
} }

View File

@ -4,6 +4,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -29,13 +30,13 @@ var HelpFlag = BoolFlag{
} }
// Flag is a common interface related to parsing flags in cli. // 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. // this interface be implemented.
type Flag interface { type Flag interface {
fmt.Stringer fmt.Stringer
// Apply Flag settings to the given flag set // Apply Flag settings to the given flag set
Apply(*flag.FlagSet) Apply(*flag.FlagSet)
getName() string GetName() string
} }
func flagSet(name string, flags []Flag) *flag.FlagSet { 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 // help text to the user (uses the String() method of the generic flag to show
// the value) // the value)
func (f GenericFlag) String() string { 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 // 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 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 return f.Name
} }
@ -231,7 +243,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
}) })
} }
func (f IntSliceFlag) getName() string { func (f IntSliceFlag) GetName() string {
return f.Name return f.Name
} }
@ -240,6 +252,7 @@ type BoolFlag struct {
Name string Name string
Usage string Usage string
EnvVar string EnvVar string
Destination *bool
} }
// String returns a readable representation of this value (for usage defaults) // String returns a readable representation of this value (for usage defaults)
@ -264,11 +277,15 @@ func (f BoolFlag) Apply(set *flag.FlagSet) {
} }
eachName(f.Name, func(name string) { 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) set.Bool(name, val, f.Usage)
}) })
} }
func (f BoolFlag) getName() string { func (f BoolFlag) GetName() string {
return f.Name return f.Name
} }
@ -278,6 +295,7 @@ type BoolTFlag struct {
Name string Name string
Usage string Usage string
EnvVar string EnvVar string
Destination *bool
} }
// String returns a readable representation of this value (for usage defaults) // String returns a readable representation of this value (for usage defaults)
@ -302,11 +320,15 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) {
} }
eachName(f.Name, func(name string) { 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) set.Bool(name, val, f.Usage)
}) })
} }
func (f BoolTFlag) getName() string { func (f BoolTFlag) GetName() string {
return f.Name return f.Name
} }
@ -316,20 +338,20 @@ type StringFlag struct {
Value string Value string
Usage string Usage string
EnvVar string EnvVar string
Destination *string
} }
// String returns the usage // String returns the usage
func (f StringFlag) String() string { func (f StringFlag) String() string {
var fmtString string return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage))
fmtString = "%s %v\t%v"
if len(f.Value) > 0 {
fmtString = "%s \"%v\"\t%v"
} else {
fmtString = "%s %v\t%v"
} }
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) func (f StringFlag) FormatValueHelp() string {
s := f.Value
if len(s) == 0 {
return ""
}
return fmt.Sprintf("\"%s\"", s)
} }
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
@ -345,11 +367,15 @@ func (f StringFlag) Apply(set *flag.FlagSet) {
} }
eachName(f.Name, func(name string) { 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) set.String(name, f.Value, f.Usage)
}) })
} }
func (f StringFlag) getName() string { func (f StringFlag) GetName() string {
return f.Name return f.Name
} }
@ -360,6 +386,7 @@ type IntFlag struct {
Value int Value int
Usage string Usage string
EnvVar string EnvVar string
Destination *int
} }
// String returns the usage // String returns the usage
@ -383,11 +410,15 @@ func (f IntFlag) Apply(set *flag.FlagSet) {
} }
eachName(f.Name, func(name string) { 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) set.Int(name, f.Value, f.Usage)
}) })
} }
func (f IntFlag) getName() string { func (f IntFlag) GetName() string {
return f.Name return f.Name
} }
@ -398,6 +429,7 @@ type DurationFlag struct {
Value time.Duration Value time.Duration
Usage string Usage string
EnvVar string EnvVar string
Destination *time.Duration
} }
// String returns a readable representation of this value (for usage defaults) // String returns a readable representation of this value (for usage defaults)
@ -421,11 +453,15 @@ func (f DurationFlag) Apply(set *flag.FlagSet) {
} }
eachName(f.Name, func(name string) { 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) set.Duration(name, f.Value, f.Usage)
}) })
} }
func (f DurationFlag) getName() string { func (f DurationFlag) GetName() string {
return f.Name return f.Name
} }
@ -436,6 +472,7 @@ type Float64Flag struct {
Value float64 Value float64
Usage string Usage string
EnvVar string EnvVar string
Destination *float64
} }
// String returns the usage // String returns the usage
@ -458,11 +495,15 @@ func (f Float64Flag) Apply(set *flag.FlagSet) {
} }
eachName(f.Name, func(name string) { 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) set.Float64(name, f.Value, f.Usage)
}) })
} }
func (f Float64Flag) getName() string { func (f Float64Flag) GetName() string {
return f.Name return f.Name
} }
@ -491,7 +532,15 @@ func prefixedNames(fullName string) (prefixed string) {
func withEnvHint(envVar, str string) string { func withEnvHint(envVar, str string) string {
envText := "" envText := ""
if envVar != "" { 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 return str + envText
} }

View File

@ -15,7 +15,7 @@ var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
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}} {{if .Version}}
VERSION: 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)) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data) err := t.Execute(w, data)
if err != nil { 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() w.Flush()
} }

View File

@ -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.

View File

@ -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
}

View 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
}

View File

@ -23,116 +23,8 @@ import (
"reflect" "reflect"
"sort" "sort"
"strconv" "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 // Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package. // the technique used in the fmt package.
var ( 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 // We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an // Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order // interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe to bypass these restrictions // to enforce visibility rules. We use unsafe, when it's available,
// since this package does not mutate the values. // to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() { if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v) 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 // mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their // Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods. // state inside these interface methods.
var viface interface{} if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
if !cs.DisablePointerMethods {
if !v.CanAddr() {
v = unsafeReflectValue(v) v = unsafeReflectValue(v)
} }
viface = v.Addr().Interface()
} else {
if v.CanAddr() { if v.CanAddr() {
v = v.Addr() v = v.Addr()
} }
viface = v.Interface()
}
// Is it an error or Stringer? // Is it an error or Stringer?
switch iface := viface.(type) { switch iface := v.Interface().(type) {
case error: case error:
defer catchPanic(w, v) defer catchPanic(w, v)
if cs.ContinueOnMethod { if cs.ContinueOnMethod {

View File

@ -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)
}

View File

@ -61,7 +61,10 @@ type ConfigState struct {
// with a pointer receiver could technically mutate the value, however, // with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer // in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state // 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 DisablePointerMethods bool
// ContinueOnMethod specifies whether or not recursion should continue once // ContinueOnMethod specifies whether or not recursion should continue once

View File

@ -181,26 +181,29 @@ func (d *dumpState) dumpSlice(v reflect.Value) {
// Try to use existing uint8 slices and fall back to converting // Try to use existing uint8 slices and fall back to converting
// and copying if that fails. // and copying if that fails.
case vt.Kind() == reflect.Uint8: case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type back // We need an addressable interface to convert the type
// into a byte slice. However, the reflect package won't give // to a byte slice. However, the reflect package won't
// us an interface on certain things like unexported struct // give us an interface on certain things like
// fields in order to enforce visibility rules. We use unsafe // unexported struct fields in order to enforce
// to bypass these restrictions since this package does not // visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values. // mutate the values.
vs := v vs := v
if !vs.CanInterface() || !vs.CanAddr() { if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs) vs = unsafeReflectValue(vs)
} }
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries) vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be type // Use the existing uint8 slice if it can be
// asserted. // type asserted.
iface := vs.Interface() iface := vs.Interface()
if slice, ok := iface.([]uint8); ok { if slice, ok := iface.([]uint8); ok {
buf = slice buf = slice
doHexDump = true doHexDump = true
break break
} }
}
// The underlying data needs to be converted if it can't // The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice. // be type asserted to a uint8 slice.

File diff suppressed because it is too large Load Diff

View File

@ -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")
}

View File

@ -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.
}

View File

@ -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
}

File diff suppressed because it is too large Load Diff

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -6,8 +6,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/shiena/ansicolor"
) )
// NoColor defines if the output is colorized or not. It's dynamically set to // NoColor defines if the output is colorized or not. It's dynamically set to
@ -53,6 +53,18 @@ const (
FgWhite FgWhite
) )
// Foreground Hi-Intensity text colors
const (
FgHiBlack Attribute = iota + 90
FgHiRed
FgHiGreen
FgHiYellow
FgHiBlue
FgHiMagenta
FgHiCyan
FgHiWhite
)
// Background text colors // Background text colors
const ( const (
BgBlack Attribute = iota + 40 BgBlack Attribute = iota + 40
@ -65,6 +77,18 @@ const (
BgWhite 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. // New returns a newly created color object.
func New(value ...Attribute) *Color { func New(value ...Attribute) *Color {
c := &Color{params: make([]Attribute, 0)} 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 // Output defines the standard output of the print functions. By default
// os.Stdout is used. // 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 // Print formats using the default formats for its operands and writes to
// standard output. Spaces are added between operands when neither is a // standard output. Spaces are added between operands when neither is a
@ -259,6 +283,31 @@ func (c *Color) isNoColorSet() bool {
return NoColor 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 { func boolPtr(v bool) *bool {
return &v return &v
} }

View File

@ -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"))
}

View File

@ -22,3 +22,4 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof *.prof
.DS_Store

View File

@ -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) # 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 <img src="./_example/dashboard.gif" alt="demo cast under osx 10.10; Terminal.app; Menlo Regular 12pt.)" width="80%">
Pull requests and master branch are freezing, waiting for merging from `refactoring` branch.
## Notice `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.
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.
## Introduction 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!)
Go terminal dashboard. Inspired by [blessed-contrib](https://github.com/yaronn/blessed-contrib), but purely in Go.
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:__ __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 ```go
import ui "github.com/gizak/termui" import ui "github.com/gizak/termui"
// init and create widgets... // init and create widgets...
@ -37,111 +86,49 @@ Expressive syntax, using [12 columns grid system](http://www.w3schools.com/boots
ui.Render(ui.Body) 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 `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 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:
```go ```go
// for now there are only two themes: default and helloworld // handle key q pressing
termui.UseTheme("helloworld") 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! Click image to see the corresponding demo codes.
<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">
[<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 ## GoDoc
@ -150,10 +137,12 @@ The `helloworld` color scheme drops in some colors!
## TODO ## TODO
- [x] Grid layout - [x] Grid layout
- [ ] Event system - [x] Event system
- [ ] Canvas widget - [x] Canvas widget
- [ ] Refine APIs - [x] Refine APIs
- [ ] Focusable widgets - [ ] Focusable widgets
## Changelog
## License ## License
This library is under the [MIT License](http://opensource.org/licenses/MIT) This library is under the [MIT License](http://opensource.org/licenses/MIT)

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -39,16 +39,16 @@ type BarChart struct {
// NewBarChart returns a new *BarChart with current theme. // NewBarChart returns a new *BarChart with current theme.
func NewBarChart() *BarChart { func NewBarChart() *BarChart {
bc := &BarChart{Block: *NewBlock()} bc := &BarChart{Block: *NewBlock()}
bc.BarColor = theme.BarChartBar bc.BarColor = ThemeAttr("barchart.bar.bg")
bc.NumColor = theme.BarChartNum bc.NumColor = ThemeAttr("barchart.num.fg")
bc.TextColor = theme.BarChartText bc.TextColor = ThemeAttr("barchart.text.fg")
bc.BarGap = 1 bc.BarGap = 1
bc.BarWidth = 3 bc.BarWidth = 3
return bc return bc
} }
func (bc *BarChart) layout() { 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.labels = make([][]rune, bc.numBar)
bc.dataNum = make([][]rune, len(bc.Data)) bc.dataNum = make([][]rune, len(bc.Data))
@ -69,7 +69,7 @@ func (bc *BarChart) layout() {
bc.max = bc.Data[i] 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) { func (bc *BarChart) SetMax(max int) {
@ -80,8 +80,8 @@ func (bc *BarChart) SetMax(max int) {
} }
// Buffer implements Bufferer interface. // Buffer implements Bufferer interface.
func (bc *BarChart) Buffer() []Point { func (bc *BarChart) Buffer() Buffer {
ps := bc.Block.Buffer() buf := bc.Block.Buffer()
bc.layout() bc.layout()
for i := 0; i < bc.numBar && i < len(bc.Data) && i < len(bc.DataLabels); i++ { 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 // plot bar
for j := 0; j < bc.BarWidth; j++ { for j := 0; j < bc.BarWidth; j++ {
for k := 0; k < h; k++ { for k := 0; k < h; k++ {
p := Point{} c := Cell{
p.Ch = ' ' Ch: ' ',
p.Bg = bc.BarColor Bg: bc.BarColor,
if bc.BarColor == ColorDefault { // when color is default, space char treated as transparent!
p.Bg |= AttrReverse
} }
p.X = bc.innerX + i*(bc.BarWidth+bc.BarGap) + j if bc.BarColor == ColorDefault { // when color is default, space char treated as transparent!
p.Y = bc.innerY + bc.innerHeight - 2 - k c.Bg |= AttrReverse
ps = append(ps, p) }
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 // plot text
for j, k := 0, 0; j < len(bc.labels[i]); j++ { for j, k := 0, 0; j < len(bc.labels[i]); j++ {
w := charWidth(bc.labels[i][j]) w := charWidth(bc.labels[i][j])
p := Point{} c := Cell{
p.Ch = bc.labels[i][j] Ch: bc.labels[i][j],
p.Bg = bc.BgColor Bg: bc.Bg,
p.Fg = bc.TextColor Fg: bc.TextColor,
p.Y = bc.innerY + bc.innerHeight - 1 }
p.X = bc.innerX + oftX + k y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
ps = append(ps, p) x := bc.innerArea.Min.X + oftX + k
buf.Set(x, y, c)
k += w k += w
} }
// plot num // plot num
for j := 0; j < len(bc.dataNum[i]); j++ { for j := 0; j < len(bc.dataNum[i]); j++ {
p := Point{} c := Cell{
p.Ch = bc.dataNum[i][j] Ch: bc.dataNum[i][j],
p.Fg = bc.NumColor Fg: bc.NumColor,
p.Bg = bc.BarColor Bg: bc.BarColor,
}
if bc.BarColor == ColorDefault { // the same as above if bc.BarColor == ColorDefault { // the same as above
p.Bg |= AttrReverse c.Bg |= AttrReverse
} }
if h == 0 { if h == 0 {
p.Bg = bc.BgColor c.Bg = bc.Bg
} }
p.X = bc.innerX + oftX + (bc.BarWidth-len(bc.dataNum[i]))/2 + j x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i]))/2 + j
p.Y = bc.innerY + bc.innerHeight - 2 y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
ps = append(ps, p) buf.Set(x, y, c)
} }
} }
return bc.Block.chopOverflow(ps) return buf
} }

View File

@ -1,142 +1,240 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
package termui package termui
import "image"
// Hline is a horizontal line.
type Hline struct {
X int
Y int
Len int
Fg Attribute
Bg Attribute
}
// Vline is a vertical line.
type Vline struct {
X int
Y int
Len int
Fg Attribute
Bg Attribute
}
// Buffer draws a horizontal line.
func (l Hline) Buffer() Buffer {
if l.Len <= 0 {
return NewBuffer()
}
return NewFilledBuffer(l.X, l.Y, l.X+l.Len, l.Y+1, HORIZONTAL_LINE, l.Fg, l.Bg)
}
// Buffer draws a vertical line.
func (l Vline) Buffer() Buffer {
if l.Len <= 0 {
return NewBuffer()
}
return NewFilledBuffer(l.X, l.Y, l.X+1, l.Y+l.Len, VERTICAL_LINE, l.Fg, l.Bg)
}
// Buffer draws a box border.
func (b Block) drawBorder(buf Buffer) {
if !b.Border {
return
}
min := b.area.Min
max := b.area.Max
x0 := min.X
y0 := min.Y
x1 := max.X - 1
y1 := max.Y - 1
// draw lines
if b.BorderTop {
buf.Merge(Hline{x0, y0, x1 - x0, b.BorderFg, b.BorderBg}.Buffer())
}
if b.BorderBottom {
buf.Merge(Hline{x0, y1, x1 - x0, b.BorderFg, b.BorderBg}.Buffer())
}
if b.BorderLeft {
buf.Merge(Vline{x0, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer())
}
if b.BorderRight {
buf.Merge(Vline{x1, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer())
}
// draw corners
if b.BorderTop && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 0 {
buf.Set(x0, y0, Cell{TOP_LEFT, b.BorderFg, b.BorderBg})
}
if b.BorderTop && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 0 {
buf.Set(x1, y0, Cell{TOP_RIGHT, b.BorderFg, b.BorderBg})
}
if b.BorderBottom && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 1 {
buf.Set(x0, y1, Cell{BOTTOM_LEFT, b.BorderFg, b.BorderBg})
}
if b.BorderBottom && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 1 {
buf.Set(x1, y1, Cell{BOTTOM_RIGHT, b.BorderFg, b.BorderBg})
}
}
func (b Block) drawBorderLabel(buf Buffer) {
maxTxtW := b.area.Dx() - 2
tx := DTrimTxCls(DefaultTxBuilder.Build(b.BorderLabel, b.BorderLabelFg, b.BorderLabelBg), maxTxtW)
for i, w := 0, 0; i < len(tx); i++ {
buf.Set(b.area.Min.X+1+w, b.area.Min.Y, tx[i])
w += tx[i].Width()
}
}
// Block is a base struct for all other upper level widgets, // Block is a base struct for all other upper level widgets,
// consider it as css: display:block. // consider it as css: display:block.
// Normally you do not need to create it manually. // Normally you do not need to create it manually.
type Block struct { type Block struct {
area image.Rectangle
innerArea image.Rectangle
X int X int
Y int Y int
Border labeledBorder Border bool
IsDisplay bool BorderFg Attribute
HasBorder bool BorderBg Attribute
BgColor Attribute BorderLeft bool
BorderRight bool
BorderTop bool
BorderBottom bool
BorderLabel string
BorderLabelFg Attribute
BorderLabelBg Attribute
Display bool
Bg Attribute
Width int Width int
Height int Height int
innerWidth int
innerHeight int
innerX int
innerY int
PaddingTop int PaddingTop int
PaddingBottom int PaddingBottom int
PaddingLeft int PaddingLeft int
PaddingRight int PaddingRight int
id string
Float Align
} }
// NewBlock returns a *Block which inherits styles from current theme. // NewBlock returns a *Block which inherits styles from current theme.
func NewBlock() *Block { func NewBlock() *Block {
d := Block{} b := Block{}
d.IsDisplay = true b.Display = true
d.HasBorder = theme.HasBorder b.Border = true
d.Border.BgColor = theme.BorderBg b.BorderLeft = true
d.Border.FgColor = theme.BorderFg b.BorderRight = true
d.Border.LabelBgColor = theme.BorderLabelTextBg b.BorderTop = true
d.Border.LabelFgColor = theme.BorderLabelTextFg b.BorderBottom = true
d.BgColor = theme.BlockBg b.BorderBg = ThemeAttr("border.bg")
d.Width = 2 b.BorderFg = ThemeAttr("border.fg")
d.Height = 2 b.BorderLabelBg = ThemeAttr("label.bg")
return &d b.BorderLabelFg = ThemeAttr("label.fg")
b.Bg = ThemeAttr("block.bg")
b.Width = 2
b.Height = 2
b.id = GenId()
b.Float = AlignNone
return &b
} }
// compute box model func (b Block) Id() string {
func (d *Block) align() { return b.id
d.innerWidth = d.Width - d.PaddingLeft - d.PaddingRight
d.innerHeight = d.Height - d.PaddingTop - d.PaddingBottom
d.innerX = d.X + d.PaddingLeft
d.innerY = d.Y + d.PaddingTop
if d.HasBorder {
d.innerHeight -= 2
d.innerWidth -= 2
d.Border.X = d.X
d.Border.Y = d.Y
d.Border.Width = d.Width
d.Border.Height = d.Height
d.innerX++
d.innerY++
} }
if d.innerHeight < 0 { // Align computes box model
d.innerHeight = 0 func (b *Block) Align() {
} // outer
if d.innerWidth < 0 { b.area.Min.X = 0
d.innerWidth = 0 b.area.Min.Y = 0
} b.area.Max.X = b.Width
b.area.Max.Y = b.Height
// float
b.area = AlignArea(TermRect(), b.area, b.Float)
b.area = MoveArea(b.area, b.X, b.Y)
// inner
b.innerArea.Min.X = b.area.Min.X + b.PaddingLeft
b.innerArea.Min.Y = b.area.Min.Y + b.PaddingTop
b.innerArea.Max.X = b.area.Max.X - b.PaddingRight
b.innerArea.Max.Y = b.area.Max.Y - b.PaddingBottom
if b.Border {
if b.BorderLeft {
b.innerArea.Min.X++
}
if b.BorderRight {
b.innerArea.Max.X--
}
if b.BorderTop {
b.innerArea.Min.Y++
}
if b.BorderBottom {
b.innerArea.Max.Y--
}
}
} }
// InnerBounds returns the internal bounds of the block after aligning and // InnerBounds returns the internal bounds of the block after aligning and
// calculating the padding and border, if any. // calculating the padding and border, if any.
func (d *Block) InnerBounds() (x, y, width, height int) { func (b *Block) InnerBounds() image.Rectangle {
d.align() b.Align()
return d.innerX, d.innerY, d.innerWidth, d.innerHeight return b.innerArea
} }
// Buffer implements Bufferer interface. // Buffer implements Bufferer interface.
// Draw background and border (if any). // Draw background and border (if any).
func (d *Block) Buffer() []Point { func (b *Block) Buffer() Buffer {
d.align() b.Align()
ps := []Point{} buf := NewBuffer()
if !d.IsDisplay { buf.SetArea(b.area)
return ps buf.Fill(' ', ColorDefault, b.Bg)
}
if d.HasBorder { b.drawBorder(buf)
ps = d.Border.Buffer() b.drawBorderLabel(buf)
}
for i := 0; i < d.innerWidth; i++ { return buf
for j := 0; j < d.innerHeight; j++ {
p := Point{}
p.X = d.X + 1 + i
p.Y = d.Y + 1 + j
p.Ch = ' '
p.Bg = d.BgColor
ps = append(ps, p)
}
}
return ps
} }
// GetHeight implements GridBufferer. // GetHeight implements GridBufferer.
// It returns current height of the block. // It returns current height of the block.
func (d Block) GetHeight() int { func (b Block) GetHeight() int {
return d.Height return b.Height
} }
// SetX implements GridBufferer interface, which sets block's x position. // SetX implements GridBufferer interface, which sets block's x position.
func (d *Block) SetX(x int) { func (b *Block) SetX(x int) {
d.X = x b.X = x
} }
// SetY implements GridBufferer interface, it sets y position for block. // SetY implements GridBufferer interface, it sets y position for block.
func (d *Block) SetY(y int) { func (b *Block) SetY(y int) {
d.Y = y b.Y = y
} }
// SetWidth implements GridBuffer interface, it sets block's width. // SetWidth implements GridBuffer interface, it sets block's width.
func (d *Block) SetWidth(w int) { func (b *Block) SetWidth(w int) {
d.Width = w b.Width = w
} }
// chop the overflow parts func (b Block) InnerWidth() int {
func (d *Block) chopOverflow(ps []Point) []Point { return b.innerArea.Dx()
nps := make([]Point, 0, len(ps))
x := d.X
y := d.Y
w := d.Width
h := d.Height
for _, v := range ps {
if v.X >= x &&
v.X < x+w &&
v.Y >= y &&
v.Y < y+h {
nps = append(nps, v)
} }
func (b Block) InnerHeight() int {
return b.innerArea.Dy()
} }
return nps
func (b Block) InnerX() int {
return b.innerArea.Min.X
} }
func (b Block) InnerY() int { return b.innerArea.Min.Y }

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -12,3 +12,9 @@ const HORIZONTAL_LINE = '─'
const TOP_LEFT = '┌' const TOP_LEFT = '┌'
const BOTTOM_RIGHT = '┘' const BOTTOM_RIGHT = '┘'
const BOTTOM_LEFT = '└' const BOTTOM_LEFT = '└'
const VERTICAL_LEFT = '┤'
const VERTICAL_RIGHT = '├'
const HORIZONTAL_DOWN = '┬'
const HORIZONTAL_UP = '┴'
const QUOTA_LEFT = '«'
const QUOTA_RIGHT = '»'

View File

@ -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)
}

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.

View 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
}

106
Godeps/_workspace/src/github.com/gizak/termui/buffer.go generated vendored Normal file
View File

@ -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
}

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -63,12 +63,10 @@ func (c Canvas) Unset(x, y int) {
} }
// Buffer returns un-styled points // Buffer returns un-styled points
func (c Canvas) Buffer() []Point { func (c Canvas) Buffer() Buffer {
ps := make([]Point, len(c)) buf := NewBuffer()
i := 0
for k, v := range c { for k, v := range c {
ps[i] = newPoint(v+brailleBase, k[0], k[1]) buf.Set(k[0], k[1], Cell{Ch: v + brailleBase})
i++
} }
return ps return buf
} }

View File

@ -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))
}

26
Godeps/_workspace/src/github.com/gizak/termui/config generated vendored Normal file
View File

@ -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, $_;
}
}
}

View File

@ -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...)
}

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.

View 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 // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
//
// 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 package termui
import "github.com/nsf/termbox-go" import (
"path"
"strconv"
"sync"
"time"
/***********************************termbox-go**************************************/ "github.com/nsf/termbox-go"
type (
EventType uint8
Modifier uint8
Key uint16
) )
// 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 Event struct {
Type EventType // one of Event* constants Type string
Mod Modifier // one of Mod* constants or 0 Path string
Key Key // one of Key* constants, invalid if 'Ch' is not 0 From string
Ch rune // a unicode character To string
Width int // width of the screen Data interface{}
Height int // height of the screen Time int64
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
} }
const ( var sysEvtChs []chan Event
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
)
const ( type EvtKbd struct {
KeyCtrlTilde Key = 0x00 KeyStr string
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
} }
var evtChs = make([]chan Event, 0) func evtKbd(e termbox.Event) EvtKbd {
ek := EvtKbd{}
// EventCh returns an output-only event channel. k := string(e.Ch)
// This function can be called many times (multiplexer). pre := ""
func EventCh() <-chan Event { mod := ""
out := make(chan Event)
evtChs = append(evtChs, out) if e.Mod == termbox.ModAlt {
return out 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]
} }
// turn on event listener if e.Key <= 0x7F {
func evtListen() { pre = "C-"
go func() { 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"},
}
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 { for {
e := termbox.PollEvent() e := termbox.PollEvent()
// dispatch
for _, c := range evtChs { for _, c := range sysEvtChs {
go func(ch chan Event) { go func(ch chan Event) {
ch <- uiEvt(e) ch <- crtTermboxEvt(e)
}(c) }(c)
} }
} }
}()
} }
/* func NewSysEvtCh() chan Event {
// EventHandlers is a handler sequence ec := make(chan Event)
var EventHandlers []func(Event) sysEvtChs = append(sysEvtChs, ec)
return ec
var signalQuit = make(chan bool)
// Quit sends quit signal to terminate termui
func Quit() {
signalQuit <- true
} }
// Wait listening to signalQuit, block operation. var DefaultEvtStream = NewEvtStream()
func Wait() {
<-signalQuit 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)
} }
// RegEvtHandler register function into TSEventHandler sequence. func NewEvtStream() *EvtStream {
func RegEvtHandler(fn func(Event)) { return &EvtStream{
EventHandlers = append(EventHandlers, fn) srcMap: make(map[string]chan Event),
stream: make(chan Event),
Handlers: make(map[string]func(Event)),
sigStopLoop: make(chan Event),
}
} }
// EventLoop handles all events and func (es *EvtStream) Init() {
// redirects every event to callbacks in EventHandlers es.Merge("internal", es.sigStopLoop)
func EventLoop() {
evt := make(chan termbox.Event)
go func() { go func() {
for { es.wg.Wait()
evt <- termbox.PollEvent() close(es.stream)
}
}() }()
}
for { func cleanPath(p string) string {
select { if p == "" {
case c := <-signalQuit: return "/"
defer func() { signalQuit <- c }() }
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 return
case e := <-evt: }
for _, fn := range EventHandlers { go func(a Event) {
fn(uiEvt(e)) 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
} }
*/

View File

@ -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)
}

View File

@ -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()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 KiB

View File

@ -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)
}
}
}

View File

@ -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()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 KiB

View File

@ -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)
}
}
}

View File

@ -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()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -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()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@ -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()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -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()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

View File

@ -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()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

@ -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)
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -21,21 +21,14 @@ import (
g.PercentColor = termui.ColorBlue g.PercentColor = termui.ColorBlue
*/ */
// Align is the position of the gauge's label. const ColorUndef Attribute = Attribute(^uint16(0))
type Align int
// All supported positions.
const (
AlignLeft Align = iota
AlignCenter
AlignRight
)
type Gauge struct { type Gauge struct {
Block Block
Percent int Percent int
BarColor Attribute BarColor Attribute
PercentColor Attribute PercentColor Attribute
PercentColorHighlighted Attribute
Label string Label string
LabelAlign Align LabelAlign Align
} }
@ -44,10 +37,11 @@ type Gauge struct {
func NewGauge() *Gauge { func NewGauge() *Gauge {
g := &Gauge{ g := &Gauge{
Block: *NewBlock(), Block: *NewBlock(),
PercentColor: theme.GaugePercent, PercentColor: ThemeAttr("gauge.percent.fg"),
BarColor: theme.GaugeBar, BarColor: ThemeAttr("gauge.bar.bg"),
Label: "{{percent}}%", Label: "{{percent}}%",
LabelAlign: AlignCenter, LabelAlign: AlignCenter,
PercentColorHighlighted: ColorUndef,
} }
g.Width = 12 g.Width = 12
@ -56,28 +50,26 @@ func NewGauge() *Gauge {
} }
// Buffer implements Bufferer interface. // Buffer implements Bufferer interface.
func (g *Gauge) Buffer() []Point { func (g *Gauge) Buffer() Buffer {
ps := g.Block.Buffer() buf := g.Block.Buffer()
// plot bar // plot bar
w := g.Percent * g.innerWidth / 100 w := g.Percent * g.innerArea.Dx() / 100
for i := 0; i < g.innerHeight; i++ { for i := 0; i < g.innerArea.Dy(); i++ {
for j := 0; j < w; j++ { for j := 0; j < w; j++ {
p := Point{} c := Cell{}
p.X = g.innerX + j c.Ch = ' '
p.Y = g.innerY + i c.Bg = g.BarColor
p.Ch = ' ' if c.Bg == ColorDefault {
p.Bg = g.BarColor c.Bg |= AttrReverse
if p.Bg == ColorDefault {
p.Bg |= AttrReverse
} }
ps = append(ps, p) buf.Set(g.innerArea.Min.X+j, g.innerArea.Min.Y+i, c)
} }
} }
// plot percentage // plot percentage
s := strings.Replace(g.Label, "{{percent}}", strconv.Itoa(g.Percent), -1) 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) rs := str2runes(s)
var pos int var pos int
switch g.LabelAlign { switch g.LabelAlign {
@ -85,29 +77,33 @@ func (g *Gauge) Buffer() []Point {
pos = 0 pos = 0
case AlignCenter: case AlignCenter:
pos = (g.innerWidth - strWidth(s)) / 2 pos = (g.innerArea.Dx() - strWidth(s)) / 2
case AlignRight: case AlignRight:
pos = g.innerWidth - strWidth(s) pos = g.innerArea.Dx() - strWidth(s) - 1
} }
pos += g.innerArea.Min.X
for i, v := range rs { for i, v := range rs {
p := Point{} c := Cell{
p.X = 1 + pos + i Ch: v,
p.Y = pry Fg: g.PercentColor,
p.Ch = v
p.Fg = g.PercentColor
if w+g.innerX > pos+i {
p.Bg = g.BarColor
if p.Bg == ColorDefault {
p.Bg |= AttrReverse
} }
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 { } else {
p.Bg = g.Block.BgColor c.Bg = g.Block.Bg
} }
ps = append(ps, p) buf.Set(1+pos+i, pry, c)
} }
return g.Block.chopOverflow(ps) return buf
} }

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -160,8 +160,8 @@ func (r *Row) SetWidth(w int) {
// Buffer implements Bufferer interface, // Buffer implements Bufferer interface,
// recursively merge all widgets buffer // recursively merge all widgets buffer
func (r *Row) Buffer() []Point { func (r *Row) Buffer() Buffer {
merged := []Point{} merged := NewBuffer()
if r.isRenderableLeaf() { if r.isRenderableLeaf() {
return r.Widget.Buffer() return r.Widget.Buffer()
@ -169,13 +169,13 @@ func (r *Row) Buffer() []Point {
// for those are not leaves but have a renderable widget // for those are not leaves but have a renderable widget
if r.Widget != nil { if r.Widget != nil {
merged = append(merged, r.Widget.Buffer()...) merged.Merge(r.Widget.Buffer())
} }
// collect buffer from children // collect buffer from children
if !r.isLeaf() { if !r.isLeaf() {
for _, c := range r.Cols { 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. // Buffer implments Bufferer interface.
func (g Grid) Buffer() []Point { func (g Grid) Buffer() Buffer {
ps := []Point{} buf := NewBuffer()
for _, r := range g.Rows { 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 var Body *Grid

View File

@ -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")
}
}

View File

@ -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 // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
package termui package termui
import tm "github.com/nsf/termbox-go" import (
"regexp"
"strings"
tm "github.com/nsf/termbox-go"
)
import rw "github.com/mattn/go-runewidth" import rw "github.com/mattn/go-runewidth"
/* ---------------Port from termbox-go --------------------- */ /* ---------------Port from termbox-go --------------------- */
@ -12,6 +17,7 @@ import rw "github.com/mattn/go-runewidth"
// Attribute is printable cell's color and style. // Attribute is printable cell's color and style.
type Attribute uint16 type Attribute uint16
// 8 basic clolrs
const ( const (
ColorDefault Attribute = iota ColorDefault Attribute = iota
ColorBlack ColorBlack
@ -24,7 +30,10 @@ const (
ColorWhite 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 ( const (
AttrBold Attribute = 1 << (iota + 9) AttrBold Attribute = 1 << (iota + 9)
AttrUnderline AttrUnderline
@ -46,15 +55,39 @@ func str2runes(s string) []rune {
return []rune(s) return []rune(s)
} }
// Here for backwards-compatibility.
func trimStr2Runes(s string, w int) []rune { 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 { if w <= 0 {
return []rune{} return []rune{}
} }
sw := rw.StringWidth(s) sw := rw.StringWidth(s)
if sw > w { if sw > w {
return []rune(rw.Truncate(s, w, dot)) 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 { func strWidth(s string) int {
@ -64,3 +97,118 @@ func strWidth(s string) int {
func charWidth(ch rune) int { func charWidth(ch rune) int {
return rw.RuneWidth(ch) 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
}

View File

@ -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{'漢', '字', '한', '자', '你', '好', 'だ', '。', '', '', '', 'ョ', '、', 'ヲ'}
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")
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -74,8 +74,8 @@ type LineChart struct {
// NewLineChart returns a new LineChart with current theme. // NewLineChart returns a new LineChart with current theme.
func NewLineChart() *LineChart { func NewLineChart() *LineChart {
lc := &LineChart{Block: *NewBlock()} lc := &LineChart{Block: *NewBlock()}
lc.AxesColor = theme.LineChartAxes lc.AxesColor = ThemeAttr("linechart.axes.fg")
lc.LineColor = theme.LineChartLine lc.LineColor = ThemeAttr("linechart.line.fg")
lc.Mode = "braille" lc.Mode = "braille"
lc.DotStyle = '•' lc.DotStyle = '•'
lc.axisXLebelGap = 2 lc.axisXLebelGap = 2
@ -87,8 +87,8 @@ func NewLineChart() *LineChart {
// one cell contains two data points // one cell contains two data points
// so the capicity is 2x as dot-mode // so the capicity is 2x as dot-mode
func (lc *LineChart) renderBraille() []Point { func (lc *LineChart) renderBraille() Buffer {
ps := []Point{} buf := NewBuffer()
// return: b -> which cell should the point be in // return: b -> which cell should the point be in
// m -> in the cell, divided into 4 equal height levels, which subcell? // 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]) b1, m1 := getPos(lc.Data[2*i+1])
if b0 == b1 { if b0 == b1 {
p := Point{} c := Cell{
p.Ch = braillePatterns[[2]int{m0, m1}] Ch: braillePatterns[[2]int{m0, m1}],
p.Bg = lc.BgColor Bg: lc.Bg,
p.Fg = lc.LineColor Fg: lc.LineColor,
p.Y = lc.innerY + lc.innerHeight - 3 - b0 }
p.X = lc.innerX + lc.labelYSpace + 1 + i y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - b0
ps = append(ps, p) x := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
buf.Set(x, y, c)
} else { } else {
p0 := newPointWithAttrs(lSingleBraille[m0], c0 := Cell{Ch: lSingleBraille[m0],
lc.innerX+lc.labelYSpace+1+i, Fg: lc.LineColor,
lc.innerY+lc.innerHeight-3-b0, Bg: lc.Bg}
lc.LineColor, x0 := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
lc.BgColor) y0 := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - b0
p1 := newPointWithAttrs(rSingleBraille[m1], buf.Set(x0, y0, c0)
lc.innerX+lc.labelYSpace+1+i,
lc.innerY+lc.innerHeight-3-b1, c1 := Cell{Ch: rSingleBraille[m1],
lc.LineColor, Fg: lc.LineColor,
lc.BgColor) Bg: lc.Bg}
ps = append(ps, p0, p1) 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 { func (lc *LineChart) renderDot() Buffer {
ps := []Point{} buf := NewBuffer()
for i := 0; i < len(lc.Data) && i < lc.axisXWidth; i++ { for i := 0; i < len(lc.Data) && i < lc.axisXWidth; i++ {
p := Point{} c := Cell{
p.Ch = lc.DotStyle Ch: lc.DotStyle,
p.Fg = lc.LineColor Fg: lc.LineColor,
p.Bg = lc.BgColor Bg: lc.Bg,
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) x := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
ps = append(ps, p) 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() { func (lc *LineChart) calcLabelX() {
@ -220,9 +224,9 @@ func (lc *LineChart) calcLayout() {
lc.maxY = lc.Data[0] lc.maxY = lc.Data[0]
// valid visible range // valid visible range
vrange := lc.innerWidth vrange := lc.innerArea.Dx()
if lc.Mode == "braille" { if lc.Mode == "braille" {
vrange = 2 * lc.innerWidth vrange = 2 * lc.innerArea.Dx()
} }
if vrange > len(lc.Data) { if vrange > len(lc.Data) {
vrange = len(lc.Data) vrange = len(lc.Data)
@ -247,40 +251,30 @@ func (lc *LineChart) calcLayout() {
lc.topValue = lc.maxY + 0.2*span lc.topValue = lc.maxY + 0.2*span
} }
lc.axisYHeight = lc.innerHeight - 2 lc.axisYHeight = lc.innerArea.Dy() - 2
lc.calcLabelY() lc.calcLabelY()
lc.axisXWidth = lc.innerWidth - 1 - lc.labelYSpace lc.axisXWidth = lc.innerArea.Dx() - 1 - lc.labelYSpace
lc.calcLabelX() lc.calcLabelX()
lc.drawingX = lc.innerX + 1 + lc.labelYSpace lc.drawingX = lc.innerArea.Min.X + 1 + lc.labelYSpace
lc.drawingY = lc.innerY lc.drawingY = lc.innerArea.Min.Y
} }
func (lc *LineChart) plotAxes() []Point { func (lc *LineChart) plotAxes() Buffer {
origY := lc.innerY + lc.innerHeight - 2 buf := NewBuffer()
origX := lc.innerX + lc.labelYSpace
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++ { for x := origX + 1; x < origX+lc.axisXWidth; x++ {
p := Point{} buf.Set(x, origY, Cell{Ch: HDASH, Fg: lc.AxesColor, Bg: lc.Bg})
p.X = x
p.Y = origY
p.Bg = lc.BgColor
p.Fg = lc.AxesColor
p.Ch = HDASH
ps = append(ps, p)
} }
for dy := 1; dy <= lc.axisYHeight; dy++ { for dy := 1; dy <= lc.axisYHeight; dy++ {
p := Point{} buf.Set(origX, origY-dy, Cell{Ch: VDASH, Fg: lc.AxesColor, Bg: lc.Bg})
p.X = origX
p.Y = origY - dy
p.Bg = lc.BgColor
p.Fg = lc.AxesColor
p.Ch = VDASH
ps = append(ps, p)
} }
// x label // x label
@ -290,13 +284,14 @@ func (lc *LineChart) plotAxes() []Point {
break break
} }
for j, r := range rs { for j, r := range rs {
p := Point{} c := Cell{
p.Ch = r Ch: r,
p.Fg = lc.AxesColor Fg: lc.AxesColor,
p.Bg = lc.BgColor Bg: lc.Bg,
p.X = origX + oft + j }
p.Y = lc.innerY + lc.innerHeight - 1 x := origX + oft + j
ps = append(ps, p) y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 1
buf.Set(x, y, c)
} }
oft += len(rs) + lc.axisXLebelGap oft += len(rs) + lc.axisXLebelGap
} }
@ -304,33 +299,31 @@ func (lc *LineChart) plotAxes() []Point {
// y labels // y labels
for i, rs := range lc.labelY { for i, rs := range lc.labelY {
for j, r := range rs { for j, r := range rs {
p := Point{} buf.Set(
p.Ch = r lc.innerArea.Min.X+j,
p.Fg = lc.AxesColor origY-i*(lc.axisYLebelGap+1),
p.Bg = lc.BgColor Cell{Ch: r, Fg: lc.AxesColor, Bg: lc.Bg})
p.X = lc.innerX + j
p.Y = origY - i*(lc.axisYLebelGap+1)
ps = append(ps, p)
} }
} }
return ps return buf
} }
// Buffer implements Bufferer interface. // Buffer implements Bufferer interface.
func (lc *LineChart) Buffer() []Point { func (lc *LineChart) Buffer() Buffer {
ps := lc.Block.Buffer() buf := lc.Block.Buffer()
if lc.Data == nil || len(lc.Data) == 0 { if lc.Data == nil || len(lc.Data) == 0 {
return ps return buf
} }
lc.calcLayout() lc.calcLayout()
ps = append(ps, lc.plotAxes()...) buf.Merge(lc.plotAxes())
if lc.Mode == "dot" { if lc.Mode == "dot" {
ps = append(ps, lc.renderDot()...) buf.Merge(lc.renderDot())
} else { } else {
ps = append(ps, lc.renderBraille()...) buf.Merge(lc.renderBraille())
} }
return lc.Block.chopOverflow(ps) return buf
} }

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -41,64 +41,49 @@ type List struct {
func NewList() *List { func NewList() *List {
l := &List{Block: *NewBlock()} l := &List{Block: *NewBlock()}
l.Overflow = "hidden" l.Overflow = "hidden"
l.ItemFgColor = theme.ListItemFg l.ItemFgColor = ThemeAttr("list.item.fg")
l.ItemBgColor = theme.ListItemBg l.ItemBgColor = ThemeAttr("list.item.bg")
return l return l
} }
// Buffer implements Bufferer interface. // Buffer implements Bufferer interface.
func (l *List) Buffer() []Point { func (l *List) Buffer() Buffer {
ps := l.Block.Buffer() buf := l.Block.Buffer()
switch l.Overflow { switch l.Overflow {
case "wrap": 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 i, j, k := 0, 0, 0
for i < l.innerHeight && k < len(rs) { for i < l.innerArea.Dy() && k < len(cs) {
w := charWidth(rs[k]) w := cs[k].Width()
if rs[k] == '\n' || j+w > l.innerWidth { if cs[k].Ch == '\n' || j+w > l.innerArea.Dx() {
i++ i++
j = 0 j = 0
if rs[k] == '\n' { if cs[k].Ch == '\n' {
k++ k++
} }
continue continue
} }
pi := Point{} buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, cs[k])
pi.X = l.innerX + j
pi.Y = l.innerY + i
pi.Ch = rs[k]
pi.Bg = l.ItemBgColor
pi.Fg = l.ItemFgColor
ps = append(ps, pi)
k++ k++
j++ j++
} }
case "hidden": case "hidden":
trimItems := l.Items trimItems := l.Items
if len(trimItems) > l.innerHeight { if len(trimItems) > l.innerArea.Dy() {
trimItems = trimItems[:l.innerHeight] trimItems = trimItems[:l.innerArea.Dy()]
} }
for i, v := range trimItems { for i, v := range trimItems {
rs := trimStr2Runes(v, l.innerWidth) cs := DTrimTxCls(DefaultTxBuilder.Build(v, l.ItemFgColor, l.ItemBgColor), l.innerArea.Dx())
j := 0 j := 0
for _, vv := range rs { for _, vv := range cs {
w := charWidth(vv) w := vv.Width()
p := Point{} buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, vv)
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)
j += w j += w
} }
} }
} }
return l.Block.chopOverflow(ps) return buf
} }

View File

@ -1,4 +1,4 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved. // Copyright 2016 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
@ -48,16 +48,16 @@ type MBarChart struct {
// NewBarChart returns a new *BarChart with current theme. // NewBarChart returns a new *BarChart with current theme.
func NewMBarChart() *MBarChart { func NewMBarChart() *MBarChart {
bc := &MBarChart{Block: *NewBlock()} bc := &MBarChart{Block: *NewBlock()}
bc.BarColor[0] = theme.MBarChartBar bc.BarColor[0] = ThemeAttr("mbarchart.bar.bg")
bc.NumColor[0] = theme.MBarChartNum bc.NumColor[0] = ThemeAttr("mbarchart.num.fg")
bc.TextColor = theme.MBarChartText bc.TextColor = ThemeAttr("mbarchart.text.fg")
bc.BarGap = 1 bc.BarGap = 1
bc.BarWidth = 3 bc.BarWidth = 3
return bc return bc
} }
func (bc *MBarChart) layout() { 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) bc.labels = make([][]rune, bc.numBar)
DataLen := 0 DataLen := 0
LabelLen := len(bc.DataLabels) LabelLen := len(bc.DataLabels)
@ -129,9 +129,9 @@ func (bc *MBarChart) layout() {
if bc.ShowScale { if bc.ShowScale {
s := fmt.Sprintf("%d", bc.max) s := fmt.Sprintf("%d", bc.max)
bc.maxScale = trimStr2Runes(s, len(s)) 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 { } 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. // Buffer implements Bufferer interface.
func (bc *MBarChart) Buffer() []Point { func (bc *MBarChart) Buffer() Buffer {
ps := bc.Block.Buffer() buf := bc.Block.Buffer()
bc.layout() bc.layout()
var oftX int var oftX int
@ -157,15 +157,17 @@ func (bc *MBarChart) Buffer() []Point {
// plot bars // plot bars
for j := 0; j < bc.BarWidth; j++ { for j := 0; j < bc.BarWidth; j++ {
for k := 0; k < h; k++ { for k := 0; k < h; k++ {
p := Point{} c := Cell{
p.Ch = ' ' Ch: ' ',
p.Bg = bc.BarColor[i1] Bg: bc.BarColor[i1],
if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
p.Bg |= AttrReverse
} }
p.X = bc.innerX + i*(bc.BarWidth+bc.BarGap) + j if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
p.Y = bc.innerY + bc.innerHeight - 2 - k - ph c.Bg |= AttrReverse
ps = append(ps, p) }
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 ph += h
@ -173,13 +175,14 @@ func (bc *MBarChart) Buffer() []Point {
// plot text // plot text
for j, k := 0, 0; j < len(bc.labels[i]); j++ { for j, k := 0, 0; j < len(bc.labels[i]); j++ {
w := charWidth(bc.labels[i][j]) w := charWidth(bc.labels[i][j])
p := Point{} c := Cell{
p.Ch = bc.labels[i][j] Ch: bc.labels[i][j],
p.Bg = bc.BgColor Bg: bc.Bg,
p.Fg = bc.TextColor Fg: bc.TextColor,
p.Y = bc.innerY + bc.innerHeight - 1 }
p.X = bc.innerX + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
ps = append(ps, p) x := bc.innerArea.Max.X + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
buf.Set(x, y, c)
k += w k += w
} }
// plot num // plot num
@ -187,19 +190,20 @@ func (bc *MBarChart) Buffer() []Point {
for i1 := 0; i1 < bc.numStack; i1++ { for i1 := 0; i1 < bc.numStack; i1++ {
h := int(float64(bc.Data[i1][i]) / bc.scale) h := int(float64(bc.Data[i1][i]) / bc.scale)
for j := 0; j < len(bc.dataNum[i1][i]) && h > 0; j++ { for j := 0; j < len(bc.dataNum[i1][i]) && h > 0; j++ {
p := Point{} c := Cell{
p.Ch = bc.dataNum[i1][i][j] Ch: bc.dataNum[i1][i][j],
p.Fg = bc.NumColor[i1] Fg: bc.NumColor[i1],
p.Bg = bc.BarColor[i1] Bg: bc.BarColor[i1],
}
if bc.BarColor[i1] == ColorDefault { // the same as above if bc.BarColor[i1] == ColorDefault { // the same as above
p.Bg |= AttrReverse c.Bg |= AttrReverse
} }
if h == 0 { 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 x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
p.Y = bc.innerY + bc.innerHeight - 2 - ph y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - ph
ps = append(ps, p) buf.Set(x, y, c)
} }
ph += h ph += h
} }
@ -208,26 +212,31 @@ func (bc *MBarChart) Buffer() []Point {
if bc.ShowScale { if bc.ShowScale {
//Currently bar graph only supprts data range from 0 to MAX //Currently bar graph only supprts data range from 0 to MAX
//Plot 0 //Plot 0
p := Point{} c := Cell{
p.Ch = '0' Ch: '0',
p.Bg = bc.BgColor Bg: bc.Bg,
p.Fg = bc.TextColor Fg: bc.TextColor,
p.Y = bc.innerY + bc.innerHeight - 2 }
p.X = bc.X
ps = append(ps, p) y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
x := bc.X
buf.Set(x, y, c)
//Plot the maximum sacle value //Plot the maximum sacle value
for i := 0; i < len(bc.maxScale); i++ { for i := 0; i < len(bc.maxScale); i++ {
p := Point{} c := Cell{
p.Ch = bc.maxScale[i] Ch: bc.maxScale[i],
p.Bg = bc.BgColor Bg: bc.Bg,
p.Fg = bc.TextColor Fg: bc.TextColor,
p.Y = bc.innerY }
p.X = bc.X + i
ps = append(ps, p) y := bc.innerArea.Min.Y
x := bc.X + i
buf.Set(x, y, c)
} }
} }
return bc.Block.chopOverflow(ps) return buf
} }

View File

@ -1,71 +0,0 @@
// Copyright 2015 Zack Guo <gizak@icloud.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
// Par displays a paragraph.
/*
par := termui.NewPar("Simple Text")
par.Height = 3
par.Width = 17
par.Border.Label = "Label"
*/
type Par struct {
Block
Text string
TextFgColor Attribute
TextBgColor Attribute
}
// NewPar returns a new *Par with given text as its content.
func NewPar(s string) *Par {
return &Par{
Block: *NewBlock(),
Text: s,
TextFgColor: theme.ParTextFg,
TextBgColor: theme.ParTextBg}
}
// Buffer implements Bufferer interface.
func (p *Par) Buffer() []Point {
ps := p.Block.Buffer()
rs := str2runes(p.Text)
i, j, k := 0, 0, 0
for i < p.innerHeight && k < len(rs) {
// the width of char is about to print
w := charWidth(rs[k])
if rs[k] == '\n' || j+w > p.innerWidth {
i++
j = 0 // set x = 0
if rs[k] == '\n' {
k++
}
if i >= p.innerHeight {
ps = append(ps, newPointWithAttrs('…',
p.innerX+p.innerWidth-1,
p.innerY+p.innerHeight-1,
p.TextFgColor, p.TextBgColor))
break
}
continue
}
pi := Point{}
pi.X = p.innerX + j
pi.Y = p.innerY + i
pi.Ch = rs[k]
pi.Bg = p.TextBgColor
pi.Fg = p.TextFgColor
ps = append(ps, pi)
k++
j += w
}
return p.Block.chopOverflow(ps)
}

64
Godeps/_workspace/src/github.com/gizak/termui/par.go generated vendored Normal file
View File

@ -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
}

View File

@ -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
}

78
Godeps/_workspace/src/github.com/gizak/termui/pos.go generated vendored Normal file
View File

@ -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)
}

View File

@ -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 // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
package termui package termui
import tm "github.com/nsf/termbox-go" import (
"image"
"sync"
"time"
tm "github.com/nsf/termbox-go"
)
// Bufferer should be implemented by all renderable components. // Bufferer should be implemented by all renderable components.
type Bufferer interface { type Bufferer interface {
Buffer() []Point Buffer() Buffer
} }
// Init initializes termui library. This function should be called before any others. // Init initializes termui library. This function should be called before any others.
// After initialization, the library must be finalized by 'Close' function. // After initialization, the library must be finalized by 'Close' function.
func Init() error { 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 = NewGrid()
Body.X = 0 Body.X = 0
Body.Y = 0 Body.Y = 0
Body.BgColor = theme.BodyBg Body.BgColor = ThemeAttr("bg")
defer func() { Body.Width = TermWidth()
w, _ := tm.Size()
Body.Width = w DefaultEvtStream.Init()
evtListen() 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, // Close finalizes termui library,
@ -32,29 +65,71 @@ func Close() {
tm.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. // TermWidth returns the current terminal's width.
func TermWidth() int { func TermWidth() int {
tm.Sync() termSync()
w, _ := tm.Size() return termWidth
return w
} }
// TermHeight returns the current terminal's height. // TermHeight returns the current terminal's height.
func TermHeight() int { func TermHeight() int {
tm.Sync() termSync()
_, h := tm.Size() return termHeight
return h
} }
// Render renders all Bufferer in the given order from left to right, // Render renders all Bufferer in the given order from left to right,
// right could overlap on left ones. // right could overlap on left ones.
func Render(rs ...Bufferer) { func render(bs ...Bufferer) {
tm.Clear(tm.ColorDefault, toTmAttr(theme.BodyBg))
for _, r := range rs { for _, b := range bs {
buf := r.Buffer()
for _, v := range buf { buf := b.Buffer()
tm.SetCell(v.X, v.Y, v.Ch, toTmAttr(v.Fg), toTmAttr(v.Bg)) // 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() tm.Flush()
} }
var renderJobs chan []Bufferer
func Render(bs ...Bufferer) {
//go func() { renderJobs <- bs }()
renderJobs <- bs
}

View File

@ -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 // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
package termui package termui
import "math" // Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃
/* /*
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} 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() spl := termui.NewSparkline()
@ -49,8 +47,8 @@ func (s *Sparklines) Add(sl Sparkline) {
func NewSparkline() Sparkline { func NewSparkline() Sparkline {
return Sparkline{ return Sparkline{
Height: 1, Height: 1,
TitleColor: theme.SparklineTitle, TitleColor: ThemeAttr("sparkline.title.fg"),
LineColor: theme.SparklineLine} LineColor: ThemeAttr("sparkline.line.fg")}
} }
// NewSparklines return a new *Spaklines with given Sparkline(s), you can always add a new Sparkline later. // 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.Lines[i].displayHeight = v.Height + 1
} }
} }
sl.displayWidth = sl.innerWidth sl.displayWidth = sl.innerArea.Dx()
// get how many lines gotta display // get how many lines gotta display
h := 0 h := 0
sl.displayLines = 0 sl.displayLines = 0
for _, v := range sl.Lines { for _, v := range sl.Lines {
if h+v.displayHeight <= sl.innerHeight { if h+v.displayHeight <= sl.innerArea.Dy() {
sl.displayLines++ sl.displayLines++
} else { } else {
break break
@ -84,20 +82,24 @@ func (sl *Sparklines) update() {
for i := 0; i < sl.displayLines; i++ { for i := 0; i < sl.displayLines; i++ {
data := sl.Lines[i].Data data := sl.Lines[i].Data
max := math.MinInt32 max := 0
for _, v := range data { for _, v := range data {
if max < v { if max < v {
max = v max = v
} }
} }
sl.Lines[i].max = max sl.Lines[i].max = max
if max != 0 {
sl.Lines[i].scale = float32(8*sl.Lines[i].Height) / float32(max) 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. // Buffer implements Bufferer interface.
func (sl *Sparklines) Buffer() []Point { func (sl *Sparklines) Buffer() Buffer {
ps := sl.Block.Buffer() buf := sl.Block.Buffer()
sl.update() sl.update()
oftY := 0 oftY := 0
@ -105,52 +107,61 @@ func (sl *Sparklines) Buffer() []Point {
l := sl.Lines[i] l := sl.Lines[i]
data := l.Data data := l.Data
if len(data) > sl.innerWidth { if len(data) > sl.innerArea.Dx() {
data = data[len(data)-sl.innerWidth:] data = data[len(data)-sl.innerArea.Dx():]
} }
if l.Title != "" { if l.Title != "" {
rs := trimStr2Runes(l.Title, sl.innerWidth) rs := trimStr2Runes(l.Title, sl.innerArea.Dx())
oftX := 0 oftX := 0
for _, v := range rs { for _, v := range rs {
w := charWidth(v) w := charWidth(v)
p := Point{} c := Cell{
p.Ch = v Ch: v,
p.Fg = l.TitleColor Fg: l.TitleColor,
p.Bg = sl.BgColor Bg: sl.Bg,
p.X = sl.innerX + oftX }
p.Y = sl.innerY + oftY x := sl.innerArea.Min.X + oftX
ps = append(ps, p) y := sl.innerArea.Min.Y + oftY
buf.Set(x, y, c)
oftX += w oftX += w
} }
} }
for j, v := range data { for j, v := range data {
// display height of the data point, zero when data is negative
h := int(float32(v)*l.scale + 0.5) h := int(float32(v)*l.scale + 0.5)
if v < 0 {
h = 0
}
barCnt := h / 8 barCnt := h / 8
barMod := h % 8 barMod := h % 8
for jj := 0; jj < barCnt; jj++ { for jj := 0; jj < barCnt; jj++ {
p := Point{} c := Cell{
p.X = sl.innerX + j Ch: ' ', // => sparks[7]
p.Y = sl.innerY + oftY + l.Height - jj Bg: l.LineColor,
p.Ch = ' ' // => sparks[7] }
p.Bg = l.LineColor x := sl.innerArea.Min.X + j
y := sl.innerArea.Min.Y + oftY + l.Height - jj
//p.Bg = sl.BgColor //p.Bg = sl.BgColor
ps = append(ps, p) buf.Set(x, y, c)
} }
if barMod != 0 { if barMod != 0 {
p := Point{} c := Cell{
p.X = sl.innerX + j Ch: sparks[barMod-1],
p.Y = sl.innerY + oftY + l.Height - barCnt Fg: l.LineColor,
p.Ch = sparks[barMod-1] Bg: sl.Bg,
p.Fg = l.LineColor }
p.Bg = sl.BgColor x := sl.innerArea.Min.X + j
ps = append(ps, p) y := sl.innerArea.Min.Y + oftY + l.Height - barCnt
buf.Set(x, y, c)
} }
} }
oftY += l.displayHeight oftY += l.displayHeight
} }
return sl.Block.chopOverflow(ps) return buf
} }

View File

@ -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()
}

View File

@ -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{}
}

View File

@ -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 // Use of this source code is governed by a MIT license that can
// be found in the LICENSE file. // be found in the LICENSE file.
package termui package termui
import "strings"
/*
// A ColorScheme represents the current look-and-feel of the dashboard. // A ColorScheme represents the current look-and-feel of the dashboard.
type ColorScheme struct { type ColorScheme struct {
BodyBg Attribute BodyBg Attribute
@ -29,6 +32,7 @@ type ColorScheme struct {
MBarChartBar Attribute MBarChartBar Attribute
MBarChartText Attribute MBarChartText Attribute
MBarChartNum Attribute MBarChartNum Attribute
TabActiveBg Attribute
} }
// default color scheme depends on the user's terminal setting. // default color scheme depends on the user's terminal setting.
@ -58,6 +62,7 @@ var themeHelloWorld = ColorScheme{
MBarChartBar: ColorRed, MBarChartBar: ColorRed,
MBarChartNum: ColorWhite, MBarChartNum: ColorWhite,
MBarChartText: ColorCyan, MBarChartText: ColorCyan,
TabActiveBg: ColorMagenta,
} }
var theme = themeDefault // global dep var theme = themeDefault // global dep
@ -82,3 +87,54 @@ func UseTheme(th string) {
theme = themeDefault 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)
}

View File

@ -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)
}

15
Godeps/_workspace/src/github.com/golang/snappy/AUTHORS generated vendored Normal file
View File

@ -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>

View File

@ -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>

27
Godeps/_workspace/src/github.com/golang/snappy/LICENSE generated vendored Normal file
View File

@ -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.

View File

@ -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.

View File

@ -13,6 +13,8 @@ import (
var ( var (
// ErrCorrupt reports that the input is invalid. // ErrCorrupt reports that the input is invalid.
ErrCorrupt = errors.New("snappy: corrupt input") 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 reports that the input isn't supported.
ErrUnsupported = errors.New("snappy: unsupported input") ErrUnsupported = errors.New("snappy: unsupported input")
) )
@ -27,11 +29,13 @@ func DecodedLen(src []byte) (int, error) {
// that the length header occupied. // that the length header occupied.
func decodedLen(src []byte) (blockLen, headerLen int, err error) { func decodedLen(src []byte) (blockLen, headerLen int, err error) {
v, n := binary.Uvarint(src) v, n := binary.Uvarint(src)
if n == 0 { if n <= 0 || v > 0xffffffff {
return 0, 0, ErrCorrupt 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 return int(v), n, nil
} }
@ -56,7 +60,7 @@ func Decode(dst, src []byte) ([]byte, error) {
x := uint(src[s] >> 2) x := uint(src[s] >> 2)
switch { switch {
case x < 60: case x < 60:
s += 1 s++
case x == 60: case x == 60:
s += 2 s += 2
if s > len(src) { 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 // NewReader returns a new Reader that decompresses from r, using the framing
// format described at // 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 { func NewReader(r io.Reader) *Reader {
return &Reader{ return &Reader{
r: r, 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 { type Reader struct {
r io.Reader r io.Reader
err error err error
@ -200,7 +204,7 @@ func (r *Reader) Read(p []byte) (int, error) {
} }
// The chunk types are specified at // 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 { switch chunkType {
case chunkTypeCompressedData: case chunkTypeCompressedData:
// Section 4.2. Compressed data (chunk type 0x00). // Section 4.2. Compressed data (chunk type 0x00).
@ -280,8 +284,7 @@ func (r *Reader) Read(p []byte) (int, error) {
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
r.err = ErrUnsupported r.err = ErrUnsupported
return 0, r.err return 0, r.err
}
} else {
// Section 4.4 Padding (chunk type 0xfe). // Section 4.4 Padding (chunk type 0xfe).
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
if !r.readFull(r.buf[:chunkLen]) { if !r.readFull(r.buf[:chunkLen]) {
@ -289,4 +292,3 @@ func (r *Reader) Read(p []byte) (int, error) {
} }
} }
} }
}

View File

@ -6,6 +6,7 @@ package snappy
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"io" "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. // slice of dst if dst was large enough to hold the entire encoded block.
// Otherwise, a newly allocated slice will be returned. // Otherwise, a newly allocated slice will be returned.
// It is valid to pass a nil dst. // 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 { if n := MaxEncodedLen(len(src)); len(dst) < n {
dst = make([]byte, n) dst = make([]byte, n)
} }
@ -92,7 +93,7 @@ func Encode(dst, src []byte) ([]byte, error) {
if len(src) != 0 { if len(src) != 0 {
d += emitLiteral(dst[d:], src) 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. // 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 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 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] { 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 continue
} }
// Otherwise, we have a match. First, emit any pending literal bytes. // 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) { if lit != len(src) {
d += emitLiteral(dst[d:], src[lit:]) d += emitLiteral(dst[d:], src[lit:])
} }
return dst[:d], nil return dst[:d]
} }
// MaxEncodedLen returns the maximum length of a snappy block, given its // MaxEncodedLen returns the maximum length of a snappy block, given its
@ -174,13 +176,36 @@ func MaxEncodedLen(srcLen int) int {
return 32 + srcLen + srcLen/6 return 32 + srcLen + srcLen/6
} }
// NewWriter returns a new Writer that compresses to w, using the framing var errClosed = errors.New("snappy: Writer is closed")
// format described at
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt // 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 { func NewWriter(w io.Writer) *Writer {
return &Writer{ return &Writer{
w: w, w: w,
enc: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)), 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),
} }
} }
@ -188,9 +213,19 @@ func NewWriter(w io.Writer) *Writer {
type Writer struct { type Writer struct {
w io.Writer w io.Writer
err error err error
enc []byte
buf [checksumSize + chunkHeaderSize]byte // ibuf is a buffer for the incoming (uncompressed) bytes.
wroteHeader bool //
// 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 // 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) { func (w *Writer) Reset(writer io.Writer) {
w.w = writer w.w = writer
w.err = nil w.err = nil
w.wroteHeader = false if w.ibuf != nil {
w.ibuf = w.ibuf[:0]
}
w.wroteStreamHeader = false
} }
// Write satisfies the io.Writer interface. // 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 { if w.err != nil {
return 0, w.err 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 { for len(p) > 0 {
obufStart := len(magicChunk)
if !w.wroteStreamHeader {
w.wroteStreamHeader = true
copy(w.obuf, magicChunk)
obufStart = 0
}
var uncompressed []byte var uncompressed []byte
if len(p) > maxUncompressedChunkLen { if len(p) > maxUncompressedChunkLen {
uncompressed, p = p[:maxUncompressedChunkLen], 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 // Compress the buffer, discarding the result if the improvement
// isn't at least 12.5%. // isn't at least 12.5%.
compressed := Encode(w.obuf[obufHeaderLen:], uncompressed)
chunkType := uint8(chunkTypeCompressedData) chunkType := uint8(chunkTypeCompressedData)
chunkBody, err := Encode(w.enc, uncompressed) chunkLen := 4 + len(compressed)
if err != nil { obufEnd := obufHeaderLen + len(compressed)
w.err = err if len(compressed) >= len(uncompressed)-len(uncompressed)/8 {
return n, err chunkType = chunkTypeUncompressedData
} chunkLen = 4 + len(uncompressed)
if len(chunkBody) >= len(uncompressed)-len(uncompressed)/8 { obufEnd = obufHeaderLen
chunkType, chunkBody = chunkTypeUncompressedData, uncompressed
} }
chunkLen := 4 + len(chunkBody) // Fill in the per-chunk header that comes before the body.
w.buf[0] = chunkType w.obuf[len(magicChunk)+0] = chunkType
w.buf[1] = uint8(chunkLen >> 0) w.obuf[len(magicChunk)+1] = uint8(chunkLen >> 0)
w.buf[2] = uint8(chunkLen >> 8) w.obuf[len(magicChunk)+2] = uint8(chunkLen >> 8)
w.buf[3] = uint8(chunkLen >> 16) w.obuf[len(magicChunk)+3] = uint8(chunkLen >> 16)
w.buf[4] = uint8(checksum >> 0) w.obuf[len(magicChunk)+4] = uint8(checksum >> 0)
w.buf[5] = uint8(checksum >> 8) w.obuf[len(magicChunk)+5] = uint8(checksum >> 8)
w.buf[6] = uint8(checksum >> 16) w.obuf[len(magicChunk)+6] = uint8(checksum >> 16)
w.buf[7] = uint8(checksum >> 24) w.obuf[len(magicChunk)+7] = uint8(checksum >> 24)
if _, err = w.w.Write(w.buf[:]); err != nil {
if _, err := w.w.Write(w.obuf[obufStart:obufEnd]); err != nil {
w.err = err w.err = err
return n, err return nRet, err
} }
if _, err = w.w.Write(chunkBody); err != nil { if chunkType == chunkTypeUncompressedData {
if _, err := w.w.Write(uncompressed); err != nil {
w.err = err w.err = err
return n, err return nRet, err
} }
n += len(uncompressed)
} }
return n, nil nRet += len(uncompressed)
}
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
} }

View File

@ -5,7 +5,7 @@
// Package snappy implements the snappy block-based compression format. // Package snappy implements the snappy block-based compression format.
// It aims for very high speeds and reasonable compression. // 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 package snappy
import ( import (
@ -46,9 +46,18 @@ const (
chunkHeaderSize = 4 chunkHeaderSize = 4
magicChunk = "\xff\x06\x00\x00" + magicBody magicChunk = "\xff\x06\x00\x00" + magicBody
magicBody = "sNaPpY" 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". // that "the uncompressed data in a chunk must be no longer than 65536 bytes".
maxUncompressedChunkLen = 65536 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 ( const (
@ -61,7 +70,7 @@ const (
var crcTable = crc32.MakeTable(crc32.Castagnoli) var crcTable = crc32.MakeTable(crc32.Castagnoli)
// crc implements the checksum specified in section 3 of // 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 { func crc(b []byte) uint32 {
c := crc32.Update(0, crcTable, b) c := crc32.Update(0, crcTable, b)
return uint32(c>>15|c<<17) + 0xa282ead8 return uint32(c>>15|c<<17) + 0xa282ead8

View File

@ -0,0 +1,212 @@
package lru
import (
"fmt"
"sync"
"github.com/hashicorp/golang-lru/simplelru"
)
const (
// Default2QRecentRatio is the ratio of the 2Q cache dedicated
// to recently added entries that have only been accessed once.
Default2QRecentRatio = 0.25
// Default2QGhostEntries is the default ratio of ghost
// entries kept to track entries recently evicted
Default2QGhostEntries = 0.50
)
// TwoQueueCache is a thread-safe fixed size 2Q cache.
// 2Q is an enhancement over the standard LRU cache
// in that it tracks both frequently and recently used
// entries separately. This avoids a burst in access to new
// entries from evicting frequently used entries. It adds some
// additional tracking overhead to the standard LRU cache, and is
// computationally about 2x the cost, and adds some metadata over
// head. The ARCCache is similar, but does not require setting any
// parameters.
type TwoQueueCache struct {
size int
recentSize int
recent *simplelru.LRU
frequent *simplelru.LRU
recentEvict *simplelru.LRU
lock sync.RWMutex
}
// New2Q creates a new TwoQueueCache using the default
// values for the parameters.
func New2Q(size int) (*TwoQueueCache, error) {
return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries)
}
// New2QParams creates a new TwoQueueCache using the provided
// parameter values.
func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) {
if size <= 0 {
return nil, fmt.Errorf("invalid size")
}
if recentRatio < 0.0 || recentRatio > 1.0 {
return nil, fmt.Errorf("invalid recent ratio")
}
if ghostRatio < 0.0 || ghostRatio > 1.0 {
return nil, fmt.Errorf("invalid ghost ratio")
}
// Determine the sub-sizes
recentSize := int(float64(size) * recentRatio)
evictSize := int(float64(size) * ghostRatio)
// Allocate the LRUs
recent, err := simplelru.NewLRU(size, nil)
if err != nil {
return nil, err
}
frequent, err := simplelru.NewLRU(size, nil)
if err != nil {
return nil, err
}
recentEvict, err := simplelru.NewLRU(evictSize, nil)
if err != nil {
return nil, err
}
// Initialize the cache
c := &TwoQueueCache{
size: size,
recentSize: recentSize,
recent: recent,
frequent: frequent,
recentEvict: recentEvict,
}
return c, nil
}
func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) {
c.lock.Lock()
defer c.lock.Unlock()
// Check if this is a frequent value
if val, ok := c.frequent.Get(key); ok {
return val, ok
}
// If the value is contained in recent, then we
// promote it to frequent
if val, ok := c.recent.Peek(key); ok {
c.recent.Remove(key)
c.frequent.Add(key, val)
return val, ok
}
// No hit
return nil, false
}
func (c *TwoQueueCache) Add(key, value interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
// Check if the value is frequently used already,
// and just update the value
if c.frequent.Contains(key) {
c.frequent.Add(key, value)
return
}
// Check if the value is recently used, and promote
// the value into the frequent list
if c.recent.Contains(key) {
c.recent.Remove(key)
c.frequent.Add(key, value)
return
}
// If the value was recently evicted, add it to the
// frequently used list
if c.recentEvict.Contains(key) {
c.ensureSpace(true)
c.recentEvict.Remove(key)
c.frequent.Add(key, value)
return
}
// Add to the recently seen list
c.ensureSpace(false)
c.recent.Add(key, value)
return
}
// ensureSpace is used to ensure we have space in the cache
func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
// If we have space, nothing to do
recentLen := c.recent.Len()
freqLen := c.frequent.Len()
if recentLen+freqLen < c.size {
return
}
// If the recent buffer is larger than
// the target, evict from there
if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) {
k, _, _ := c.recent.RemoveOldest()
c.recentEvict.Add(k, nil)
return
}
// Remove from the frequent list otherwise
c.frequent.RemoveOldest()
}
func (c *TwoQueueCache) Len() int {
c.lock.RLock()
defer c.lock.RUnlock()
return c.recent.Len() + c.frequent.Len()
}
func (c *TwoQueueCache) Keys() []interface{} {
c.lock.RLock()
defer c.lock.RUnlock()
k1 := c.frequent.Keys()
k2 := c.recent.Keys()
return append(k1, k2...)
}
func (c *TwoQueueCache) Remove(key interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
if c.frequent.Remove(key) {
return
}
if c.recent.Remove(key) {
return
}
if c.recentEvict.Remove(key) {
return
}
}
func (c *TwoQueueCache) Purge() {
c.lock.Lock()
defer c.lock.Unlock()
c.recent.Purge()
c.frequent.Purge()
c.recentEvict.Purge()
}
func (c *TwoQueueCache) Contains(key interface{}) bool {
c.lock.RLock()
defer c.lock.RUnlock()
return c.frequent.Contains(key) || c.recent.Contains(key)
}
func (c *TwoQueueCache) Peek(key interface{}) (interface{}, bool) {
c.lock.RLock()
defer c.lock.RUnlock()
if val, ok := c.frequent.Peek(key); ok {
return val, ok
}
return c.recent.Peek(key)
}

View File

@ -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)
}

View File

@ -4,24 +4,15 @@
package lru package lru
import ( import (
"container/list"
"errors"
"sync" "sync"
"github.com/hashicorp/golang-lru/simplelru"
) )
// Cache is a thread-safe fixed size LRU cache. // Cache is a thread-safe fixed size LRU cache.
type Cache struct { type Cache struct {
size int lru *simplelru.LRU
evictList *list.List
items map[interface{}]*list.Element
lock sync.RWMutex 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{}
} }
// New creates an LRU of the given size // New creates an LRU of the given size
@ -29,15 +20,15 @@ func New(size int) (*Cache, error) {
return NewWithEvict(size, nil) 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) { func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
if size <= 0 { lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted))
return nil, errors.New("Must provide a positive size") if err != nil {
return nil, err
} }
c := &Cache{ c := &Cache{
size: size, lru: lru,
evictList: list.New(),
items: make(map[interface{}]*list.Element, size),
onEvicted: onEvicted,
} }
return c, nil 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 // Purge is used to completely clear the cache
func (c *Cache) Purge() { func (c *Cache) Purge() {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() c.lru.Purge()
c.lock.Unlock()
if c.onEvicted != nil {
for k, v := range c.items {
c.onEvicted(k, v.Value.(*entry).value)
}
} }
c.evictList = list.New() // Add adds a value to the cache. Returns true if an eviction occurred.
c.items = make(map[interface{}]*list.Element, c.size)
}
// Add adds a value to the cache. Returns true if an eviction occured.
func (c *Cache) Add(key, value interface{}) bool { func (c *Cache) Add(key, value interface{}) bool {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
return c.lru.Add(key, value)
// 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. // 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() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
if ent, ok := c.items[key]; ok { if c.lru.Contains(key) {
c.evictList.MoveToFront(ent) return true, false
return ent.Value.(*entry).value, true } 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. // Remove removes the provided key from the cache.
func (c *Cache) Remove(key interface{}) { func (c *Cache) Remove(key interface{}) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() c.lru.Remove(key)
c.lock.Unlock()
if ent, ok := c.items[key]; ok {
c.removeElement(ent)
}
} }
// RemoveOldest removes the oldest item from the cache. // RemoveOldest removes the oldest item from the cache.
func (c *Cache) RemoveOldest() { func (c *Cache) RemoveOldest() {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() c.lru.RemoveOldest()
c.removeOldest() c.lock.Unlock()
} }
// Keys returns a slice of the keys in the cache, from oldest to newest. // Keys returns a slice of the keys in the cache, from oldest to newest.
func (c *Cache) Keys() []interface{} { func (c *Cache) Keys() []interface{} {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
return c.lru.Keys()
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
} }
// Len returns the number of items in the cache. // Len returns the number of items in the cache.
func (c *Cache) Len() int { func (c *Cache) Len() int {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
return c.evictList.Len() return c.lru.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)
}
} }

View File

@ -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")
}
}

View 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)
}
}

View File

@ -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.

View File

@ -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!

Some files were not shown because too many files have changed in this diff Show More