├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── average.go ├── average_test.go ├── data.go ├── demo ├── demo_1var.png ├── demo_multi.png ├── demo_single.png └── demo_small.png ├── expvars.go ├── expvars.json ├── expvars_advanced.json ├── expvars_test.go ├── go.mod ├── go.sum ├── main.go ├── self.go ├── service.go ├── stack.go ├── stack_test.go ├── ui.go ├── ui_dummy.go ├── ui_multi.go ├── ui_single.go ├── utils.go ├── utils_test.go ├── var.go ├── var_test.go └── vendor ├── github.com ├── antonholmquist │ └── jason │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ └── jason.go ├── bsiegert │ └── ranges │ │ ├── Makefile │ │ ├── README │ │ └── ranges.go ├── gizak │ └── termui │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── barchart.go │ │ ├── block.go │ │ ├── block_common.go │ │ ├── block_windows.go │ │ ├── buffer.go │ │ ├── canvas.go │ │ ├── doc.go │ │ ├── events.go │ │ ├── gauge.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── grid.go │ │ ├── linechart.go │ │ ├── linechart_others.go │ │ ├── linechart_windows.go │ │ ├── list.go │ │ ├── paragraph.go │ │ ├── piechart.go │ │ ├── position.go │ │ ├── render.go │ │ ├── sparkline.go │ │ ├── stacked_barchart.go │ │ ├── table.go │ │ ├── tabpane.go │ │ ├── textbuilder.go │ │ ├── theme.go │ │ ├── utils.go │ │ └── widget.go ├── mattn │ └── go-runewidth │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.mkd │ │ ├── runewidth.go │ │ ├── runewidth_appengine.go │ │ ├── runewidth_js.go │ │ ├── runewidth_posix.go │ │ └── runewidth_windows.go ├── mitchellh │ └── go-wordwrap │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── go.mod │ │ └── wordwrap.go ├── nsf │ └── termbox-go │ │ ├── AUTHORS │ │ ├── LICENSE │ │ ├── README.md │ │ ├── api.go │ │ ├── api_common.go │ │ ├── api_windows.go │ │ ├── collect_terminfo.py │ │ ├── escwait.go │ │ ├── escwait_darwin.go │ │ ├── syscalls.go │ │ ├── syscalls_darwin.go │ │ ├── syscalls_darwin_amd64.go │ │ ├── syscalls_dragonfly.go │ │ ├── syscalls_freebsd.go │ │ ├── syscalls_linux.go │ │ ├── syscalls_netbsd.go │ │ ├── syscalls_openbsd.go │ │ ├── syscalls_windows.go │ │ ├── termbox.go │ │ ├── termbox_common.go │ │ ├── termbox_windows.go │ │ ├── terminfo.go │ │ └── terminfo_builtin.go └── pyk │ └── byten │ ├── LICENSE │ ├── README.md │ └── size.go └── modules.txt /.gitignore: -------------------------------------------------------------------------------- 1 | expvarmon 2 | *.swp 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.8.3 4 | - 1.9 5 | 6 | script: 7 | - go test -v . 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ivan Danyliuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExpvarMon 2 | 3 | TermUI based Go apps monitor using [expvars](http://golang.org/pkg/expvar/) variables (/debug/vars). Quickest way to monitor your Go app. 4 | 5 | ## Introduction 6 | 7 | Go apps console monitoring tool. Minimal configuration efforts. Quick and easy monitoring solution for one or multiple services. 8 | 9 | ## Features 10 | 11 | * Single- and multi-apps mode 12 | * Local and remote apps support 13 | * HTTP and HTTPS endpoints, including Basic Auth support 14 | * Arbitrary number of apps and vars to monitor (from 1 to 30+, depends on size of your terminal) 15 | * Track restarted/failed apps 16 | * Show maximum value 17 | * Supports: Integer, float, duration, memory, string, bool, array variables 18 | * Sparkline charts for integer, duration and memory data 19 | * Auto-resize on font-size change or window resize 20 | * Uses amazing [TermUI](https://github.com/gizak/termui) library by [gizak](https://github.com/gizak) 21 | 22 | ## Demo 23 | 24 | ### Multiple apps mode 25 | Multi mode 26 | 27 | ### Single app mode 28 | Single mode 29 | 30 | You can monitor arbitrary number of services and variables: 31 | 32 | 1 var 25 apps 33 | 34 | ## Purpose 35 | 36 | This app targets debug/develop sessions when you need an instant way to monitor you app(s). It's not intended to monitor apps in production. 37 | Also it doesn't use any storage engines and doesn't send notifications. 38 | 39 | ## Install 40 | 41 | Just run go get: 42 | 43 | go install github.com/divan/expvarmon@latest 44 | 45 | ## Usage 46 | 47 | ### Prepare your app 48 | 49 | First, you have to add [expvars](http://golang.org/pkg/expvar/) support into your Go program. It's as simple as: 50 | 51 | import _ "expvar" 52 | 53 | and note the port your app is listening on. If it's not, just add two lines: 54 | 55 | import "net/http" 56 | ... 57 | http.ListenAndServe(":1234", nil) 58 | 59 | and expvar will add handler for "localhost:1234/debug/vars" to your app. 60 | 61 | By default, expvars adds two variables: *memstats* and *cmdline*. It's enough to monitor memory and garbage collector status in your app. 62 | 63 | ### Run expvarmon 64 | 65 | Just run expvarmon with -ports="1234" flag: 66 | 67 | expvarmon -ports="1234" 68 | 69 | That's it. 70 | 71 | More examples: 72 | 73 | ./expvarmon -ports="80" 74 | ./expvarmon -ports="23000-23010,http://example.com:80-81" -i=1m 75 | ./expvarmon -ports="80,remoteapp:80" -vars="mem:memstats.Alloc,duration:Response.Mean,Counter" 76 | ./expvarmon -ports="1234-1236" -vars="Goroutines" -self 77 | ./expvarmon -ports="https://user:pass@my.remote.app.com:443" -vars="Goroutines" -self 78 | 79 | ## Advanced usage 80 | 81 | If you need to monitor more (or less) vars, you can specify them with -vars command line flag. 82 | 83 | $ no ports specified. Use -ports arg to specify ports of Go apps to monitor 84 | Usage of ./expvarmon: 85 | -dummy 86 | Use dummy (console) output 87 | -endpoint string 88 | URL endpoint for expvars (default "/debug/vars") 89 | -i duration 90 | Polling interval (default 5s) 91 | -ports string 92 | Ports/URLs for accessing services expvars (start-end,port2,port3,https://host:port) 93 | -self 94 | Monitor itself 95 | -vars string 96 | Vars to monitor (comma-separated) (default "mem:memstats.Alloc,mem:memstats.Sys,mem:memstats.HeapAlloc,mem:memstats.HeapInuse,duration:memstats.PauseNs,duration:memstats.PauseTotalNs") 97 | 98 | Examples: 99 | ./expvarmon -ports="80" 100 | ./expvarmon -ports="23000-23010,http://example.com:80-81" -i=1m 101 | ./expvarmon -ports="80,remoteapp:80" -vars="mem:memstats.Alloc,duration:Response.Mean,Counter" 102 | ./expvarmon -ports="1234-1236" -vars="Goroutines" -self 103 | 104 | For more details and docs, see README: http://github.com/divan/expvarmon 105 | 106 | 107 | So, yes, you can specify multiple ports, using '-' for ranges, and specify fully-qualified URLs for remote apps. To override default URL endpoint ("/debug/vars"), use -endpoint flag. 108 | 109 | You can also monitor expvarmon itself, using -self flag. 110 | 111 | ### Basic Auth 112 | 113 | If your expvar endpoint is protected by Basic Auth, you have two options: 114 | 115 | - Set environmental variables *HTTP_USER* and *HTTP_PASSWORD* accordingly. These values will be applied to each endpoint. 116 | - Embed your credentials to URL via command line flag: `-ports="http://user:pass@myapp:1234"` 117 | 118 | ### Vars 119 | 120 | Expvarmon doesn't restrict you to monitor only memstats. You can publish your own counters and variables using [expvar.Publish()](http://golang.org/pkg/expvar/#Publish) method or using expvar wrappers libraries. Just pass your variables names as they appear in JSON to -var command line flag. 121 | 122 | Notation is dot-separated, for example: **memstats.Alloc** for .MemStats.Alloc field. Quick link to runtime.MemStats documentation: http://golang.org/pkg/runtime/#MemStats 123 | 124 | Expvar allows to export only basic types - structs, ints, floats, arrays (int or float), bools and strings. For arrays, average will be calculated. Ints are used for sparklines, and displayed as is. But you can specify modifier to make sure it will be rendered properly. 125 | 126 | Vars are specified as a comma-separated list of var identifiers with (optional) modifiers. 127 | 128 | | Modifier | Description | 129 | | --------- | ----------- | 130 | | mem: | renders int64 as memory string (KB, MB, etc) | 131 | | duration: | renders int64 as time.Duration (1s, 2ms, 12h23h) | 132 | | str: | doesn't display sparklines chart for this value, just display as string | 133 | -------------------------------------------------------------------------------- /average.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/antonholmquist/jason" 5 | ) 6 | 7 | func averageJason(array []*jason.Value) float64 { 8 | var arr []float64 9 | for _, v := range array { 10 | val, _ := v.Float64() 11 | arr = append(arr, val) 12 | } 13 | return average(arr) 14 | } 15 | 16 | // average calculates average (mean) value for int/float array 17 | // trimming zero values from the right. 18 | // 19 | // The whole array/average thing was added to support memstats.PauseNs 20 | // array, which may be filled with zeroes on very beginning. 21 | // Probably it would be better to use Weighted Moving Average and 22 | // add some advanced arrays avarages support, but it's probably wouldn't 23 | // be used much, but PauseNs will be for sure. 24 | func average(arr []float64) float64 { 25 | // find rightmost non-zero and trim 26 | right := len(arr) 27 | for i := right; i > 0; i-- { 28 | if arr[i-1] != 0.0 { 29 | right = i 30 | break 31 | } 32 | } 33 | trimmed := arr[:right] 34 | 35 | // calculate mean 36 | var sum float64 37 | for _, v := range trimmed { 38 | sum += v 39 | } 40 | return sum / float64(len(trimmed)) 41 | } 42 | -------------------------------------------------------------------------------- /average_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAverage(t *testing.T) { 8 | avg := average(samplesPartial) 9 | want := 621090.75 10 | if avg != want { 11 | t.Fatalf("Average must be %v, but got %v", want, avg) 12 | } 13 | } 14 | 15 | var samplesPartial = []float64{507472, 433979, 610916, 931996, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 16 | -------------------------------------------------------------------------------- /data.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "time" 4 | 5 | // UIData represents data to be passed to UI. 6 | type UIData struct { 7 | Services []*Service 8 | Vars []VarName 9 | LastTimestamp time.Time 10 | } 11 | 12 | // NewUIData inits and return new data object. 13 | func NewUIData(vars []VarName) *UIData { 14 | return &UIData{ 15 | Vars: vars, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demo/demo_1var.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/divan/expvarmon/8e0b3d2778b3bb505fbfd14707ad52e68443e330/demo/demo_1var.png -------------------------------------------------------------------------------- /demo/demo_multi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/divan/expvarmon/8e0b3d2778b3bb505fbfd14707ad52e68443e330/demo/demo_multi.png -------------------------------------------------------------------------------- /demo/demo_single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/divan/expvarmon/8e0b3d2778b3bb505fbfd14707ad52e68443e330/demo/demo_single.png -------------------------------------------------------------------------------- /demo/demo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/divan/expvarmon/8e0b3d2778b3bb505fbfd14707ad52e68443e330/demo/demo_small.png -------------------------------------------------------------------------------- /expvars.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | "time" 10 | 11 | "github.com/antonholmquist/jason" 12 | ) 13 | 14 | // DefaultEndpoint is the default url for fetching expvar info. 15 | var DefaultEndpoint = "/debug/vars" 16 | 17 | // Expvar represents fetched expvar variable. 18 | type Expvar struct { 19 | *jason.Object 20 | } 21 | 22 | func getBasicAuthEnv() (user, password string) { 23 | return os.Getenv("HTTP_USER"), os.Getenv("HTTP_PASSWORD") 24 | } 25 | 26 | // FetchExpvar fetches expvar by http for the given addr (host:port) 27 | func FetchExpvar(u url.URL) (*Expvar, error) { 28 | e := &Expvar{&jason.Object{}} 29 | client := &http.Client{ 30 | Timeout: 1 * time.Second, // TODO: make it configurable or left default? 31 | } 32 | 33 | req, _ := http.NewRequest("GET", "localhost", nil) 34 | req.URL = &u 35 | req.Host = u.Host 36 | 37 | if user, pass := getBasicAuthEnv(); user != "" && pass != "" { 38 | req.SetBasicAuth(user, pass) 39 | } 40 | resp, err := client.Do(req) 41 | if err != nil { 42 | return e, err 43 | } 44 | defer resp.Body.Close() 45 | if resp.StatusCode == http.StatusNotFound { 46 | return e, errors.New("Vars not found. Did you import expvars?") 47 | } 48 | 49 | expvar, err := ParseExpvar(resp.Body) 50 | if err != nil { 51 | return e, err 52 | } 53 | e = expvar 54 | return e, nil 55 | } 56 | 57 | // ParseExpvar parses expvar data from reader. 58 | func ParseExpvar(r io.Reader) (*Expvar, error) { 59 | object, err := jason.NewObjectFromReader(r) 60 | return &Expvar{object}, err 61 | } 62 | -------------------------------------------------------------------------------- /expvars.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmdline": ["./geo.service/geo.service","-p=:40004","-dsn=root:@tcp(localhost:3306)/geo"], 3 | "memstats": {"Alloc":306192,"TotalAlloc":18134040,"Sys":3999992,"Lookups":961,"Mallocs":219206,"Frees":218107,"HeapAlloc":306192,"HeapSys":1851392,"HeapIdle":1163264,"HeapInuse":688128,"HeapReleased":1114112,"HeapObjects":1099,"StackInuse":245760,"StackSys":245760,"MSpanInuse":6968,"MSpanSys":16384,"MCacheInuse":1200,"MCacheSys":16384,"BuckHashSys":1445672,"GCSys":137579,"OtherSys":286821,"NextGC":499776,"LastGC":1429554826339587426,"PauseTotalNs":58953963,"PauseNs":[149673,116970,116282,152960,241678,269277,2345682,2813202,395563,896996,413271,524246,411490,438143,465269,410017,407874,429678,407380,400152,462808,416069,396655,411999,2235261,483709,532587,423111,402061,410275,527945,377815,454049,398089,439974,428984,454590,448865,438237,408540,432028,476609,461090,459348,407046,447736,442569,501791,390718,451293,411698,554251,612890,454626,539950,475728,481920,436132,537898,407788,366017,528018,445895,475310,426996,510830,563059,562691,632919,605119,580397,469276,593978,815423,426771,575456,872826,929380,699880,1099353,727790,825090,815515,1143818,801543,2286288,826334,791492,817455,440831,376237,2237337,519402,694540,782088,948074,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1429551836329548506,1429551836329992823,1429551836330364410,1429551836330972293,1429551836332086156,1429551836332787858,1429551896183637429,1429552016336417703,1429552038892146378,1429552038905749325,1429552038920018001,1429552038931843671,1429552038945437201,1429552038960316731,1429552038974053317,1429552038989797695,1429552039002185699,1429552039014183958,1429552039026587369,1429552039038164871,1429552039051065585,1429552039062402317,1429552039074574600,1429552039087577539,1429552039099006520,1429552039113868847,1429552039126842855,1429552039138210121,1429552039150669078,1429552045073734820,1429552045084533164,1429552045097820035,1429552045111401775,1429552045124221035,1429552045136039350,1429552045146856774,1429552045158451390,1429552045172432052,1429552045183862574,1429552045195450754,1429552045206890854,1429552045217811105,1429552045229223666,1429552045239924164,1429552045252202806,1429552045263999145,1429552045275883429,1429552045286550637,1429552045299642824,1429552045310732720,1429552045322523365,1429552049787455566,1429552049801233968,1429552049813748109,1429552049826657828,1429552049838428456,1429552049849704476,1429552049860731436,1429552049872770863,1429552049887348193,1429552049899354726,1429552049910091446,1429552049922791888,1429552049935592566,1429552049949048309,1429552049961817616,1429552049975874584,1429552049992176815,1429552050006202883,1429552050021524454,1429552050035892415,1429552050051383684,1429552050063614314,1429552170337189191,1429552291337252450,1429552412338543040,1429552533335058879,1429552653337824128,1429552774336205167,1429552895338586147,1429553016337759124,1429553136339470606,1429553257339468848,1429553378336184002,1429553499338033468,1429553619340990287,1429553740339511139,1429553861334820854,1429553981339514309,1429554102337221147,1429554223338016039,1429554344338826682,1429554465336311605,1429554585339334313,1429554706335732334,1429554826339586952,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":96,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":39541,"Frees":39497},{"Size":16,"Mallocs":47557,"Frees":47188},{"Size":32,"Mallocs":25990,"Frees":25864},{"Size":48,"Mallocs":36196,"Frees":36005},{"Size":64,"Mallocs":9930,"Frees":9822},{"Size":80,"Mallocs":585,"Frees":542},{"Size":96,"Mallocs":5530,"Frees":5518},{"Size":112,"Mallocs":8345,"Frees":8336},{"Size":128,"Mallocs":921,"Frees":916},{"Size":144,"Mallocs":23,"Frees":21},{"Size":160,"Mallocs":1451,"Frees":1432},{"Size":176,"Mallocs":13320,"Frees":13295},{"Size":192,"Mallocs":10,"Frees":2},{"Size":208,"Mallocs":3866,"Frees":3841},{"Size":224,"Mallocs":1,"Frees":0},{"Size":240,"Mallocs":1,"Frees":0},{"Size":256,"Mallocs":3,"Frees":0},{"Size":288,"Mallocs":62,"Frees":31},{"Size":320,"Mallocs":4,"Frees":3},{"Size":352,"Mallocs":69,"Frees":62},{"Size":384,"Mallocs":2,"Frees":0},{"Size":416,"Mallocs":932,"Frees":925},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":2,"Frees":0},{"Size":512,"Mallocs":0,"Frees":0},{"Size":576,"Mallocs":30,"Frees":19},{"Size":640,"Mallocs":5,"Frees":2},{"Size":704,"Mallocs":4,"Frees":3},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":13,"Frees":9},{"Size":1024,"Mallocs":1,"Frees":0},{"Size":1152,"Mallocs":21,"Frees":18},{"Size":1280,"Mallocs":2,"Frees":2},{"Size":1408,"Mallocs":3,"Frees":1},{"Size":1536,"Mallocs":0,"Frees":0},{"Size":1664,"Mallocs":8,"Frees":4},{"Size":2048,"Mallocs":70,"Frees":68},{"Size":2304,"Mallocs":20,"Frees":14},{"Size":2560,"Mallocs":2,"Frees":1},{"Size":2816,"Mallocs":1,"Frees":1},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3328,"Mallocs":4,"Frees":1},{"Size":4096,"Mallocs":1856,"Frees":1852},{"Size":4608,"Mallocs":19,"Frees":18},{"Size":5376,"Mallocs":7,"Frees":4},{"Size":6144,"Mallocs":22,"Frees":17},{"Size":6400,"Mallocs":0,"Frees":0},{"Size":6656,"Mallocs":1,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":0,"Frees":0},{"Size":8448,"Mallocs":0,"Frees":0},{"Size":8704,"Mallocs":2,"Frees":2},{"Size":9472,"Mallocs":4,"Frees":3},{"Size":10496,"Mallocs":1,"Frees":0},{"Size":12288,"Mallocs":1,"Frees":1},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14080,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":16640,"Mallocs":0,"Frees":0},{"Size":17664,"Mallocs":0,"Frees":0}]} 4 | } 5 | -------------------------------------------------------------------------------- /expvars_advanced.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmdline": ["./geo.service/geo.service","-p=:40004","-dsn=root:@tcp(localhost:3306)/geo"], 3 | "memstats": {"Alloc":306192,"TotalAlloc":18134040,"Sys":3999992,"Lookups":961,"Mallocs":219206,"Frees":218107,"HeapAlloc":306192,"HeapSys":1851392,"HeapIdle":1163264,"HeapInuse":688128,"HeapReleased":1114112,"HeapObjects":1099,"StackInuse":245760,"StackSys":245760,"MSpanInuse":6968,"MSpanSys":16384,"MCacheInuse":1200,"MCacheSys":16384,"BuckHashSys":1445672,"GCSys":137579,"OtherSys":286821,"NextGC":499776,"LastGC":1429554826339587426,"PauseTotalNs":58953963,"PauseNs":[149673,116970,116282,152960,241678,269277,2345682,2813202,395563,896996,413271,524246,411490,438143,465269,410017,407874,429678,407380,400152,462808,416069,396655,411999,2235261,483709,532587,423111,402061,410275,527945,377815,454049,398089,439974,428984,454590,448865,438237,408540,432028,476609,461090,459348,407046,447736,442569,501791,390718,451293,411698,554251,612890,454626,539950,475728,481920,436132,537898,407788,366017,528018,445895,475310,426996,510830,563059,562691,632919,605119,580397,469276,593978,815423,426771,575456,872826,929380,699880,1099353,727790,825090,815515,1143818,801543,2286288,826334,791492,817455,440831,376237,2237337,519402,694540,782088,948074,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1429551836329548506,1429551836329992823,1429551836330364410,1429551836330972293,1429551836332086156,1429551836332787858,1429551896183637429,1429552016336417703,1429552038892146378,1429552038905749325,1429552038920018001,1429552038931843671,1429552038945437201,1429552038960316731,1429552038974053317,1429552038989797695,1429552039002185699,1429552039014183958,1429552039026587369,1429552039038164871,1429552039051065585,1429552039062402317,1429552039074574600,1429552039087577539,1429552039099006520,1429552039113868847,1429552039126842855,1429552039138210121,1429552039150669078,1429552045073734820,1429552045084533164,1429552045097820035,1429552045111401775,1429552045124221035,1429552045136039350,1429552045146856774,1429552045158451390,1429552045172432052,1429552045183862574,1429552045195450754,1429552045206890854,1429552045217811105,1429552045229223666,1429552045239924164,1429552045252202806,1429552045263999145,1429552045275883429,1429552045286550637,1429552045299642824,1429552045310732720,1429552045322523365,1429552049787455566,1429552049801233968,1429552049813748109,1429552049826657828,1429552049838428456,1429552049849704476,1429552049860731436,1429552049872770863,1429552049887348193,1429552049899354726,1429552049910091446,1429552049922791888,1429552049935592566,1429552049949048309,1429552049961817616,1429552049975874584,1429552049992176815,1429552050006202883,1429552050021524454,1429552050035892415,1429552050051383684,1429552050063614314,1429552170337189191,1429552291337252450,1429552412338543040,1429552533335058879,1429552653337824128,1429552774336205167,1429552895338586147,1429553016337759124,1429553136339470606,1429553257339468848,1429553378336184002,1429553499338033468,1429553619340990287,1429553740339511139,1429553861334820854,1429553981339514309,1429554102337221147,1429554223338016039,1429554344338826682,1429554465336311605,1429554585339334313,1429554706335732334,1429554826339586952,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":96,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":39541,"Frees":39497},{"Size":16,"Mallocs":47557,"Frees":47188},{"Size":32,"Mallocs":25990,"Frees":25864},{"Size":48,"Mallocs":36196,"Frees":36005},{"Size":64,"Mallocs":9930,"Frees":9822},{"Size":80,"Mallocs":585,"Frees":542},{"Size":96,"Mallocs":5530,"Frees":5518},{"Size":112,"Mallocs":8345,"Frees":8336},{"Size":128,"Mallocs":921,"Frees":916},{"Size":144,"Mallocs":23,"Frees":21},{"Size":160,"Mallocs":1451,"Frees":1432},{"Size":176,"Mallocs":13320,"Frees":13295},{"Size":192,"Mallocs":10,"Frees":2},{"Size":208,"Mallocs":3866,"Frees":3841},{"Size":224,"Mallocs":1,"Frees":0},{"Size":240,"Mallocs":1,"Frees":0},{"Size":256,"Mallocs":3,"Frees":0},{"Size":288,"Mallocs":62,"Frees":31},{"Size":320,"Mallocs":4,"Frees":3},{"Size":352,"Mallocs":69,"Frees":62},{"Size":384,"Mallocs":2,"Frees":0},{"Size":416,"Mallocs":932,"Frees":925},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":2,"Frees":0},{"Size":512,"Mallocs":0,"Frees":0},{"Size":576,"Mallocs":30,"Frees":19},{"Size":640,"Mallocs":5,"Frees":2},{"Size":704,"Mallocs":4,"Frees":3},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":13,"Frees":9},{"Size":1024,"Mallocs":1,"Frees":0},{"Size":1152,"Mallocs":21,"Frees":18},{"Size":1280,"Mallocs":2,"Frees":2},{"Size":1408,"Mallocs":3,"Frees":1},{"Size":1536,"Mallocs":0,"Frees":0},{"Size":1664,"Mallocs":8,"Frees":4},{"Size":2048,"Mallocs":70,"Frees":68},{"Size":2304,"Mallocs":20,"Frees":14},{"Size":2560,"Mallocs":2,"Frees":1},{"Size":2816,"Mallocs":1,"Frees":1},{"Size":3072,"Mallocs":0,"Frees":0},{"Size":3328,"Mallocs":4,"Frees":1},{"Size":4096,"Mallocs":1856,"Frees":1852},{"Size":4608,"Mallocs":19,"Frees":18},{"Size":5376,"Mallocs":7,"Frees":4},{"Size":6144,"Mallocs":22,"Frees":17},{"Size":6400,"Mallocs":0,"Frees":0},{"Size":6656,"Mallocs":1,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":0,"Frees":0},{"Size":8448,"Mallocs":0,"Frees":0},{"Size":8704,"Mallocs":2,"Frees":2},{"Size":9472,"Mallocs":4,"Frees":3},{"Size":10496,"Mallocs":1,"Frees":0},{"Size":12288,"Mallocs":1,"Frees":1},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14080,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":16640,"Mallocs":0,"Frees":0},{"Size":17664,"Mallocs":0,"Frees":0}]}, 4 | "goroutines": 10, 5 | "counters": { "A": 123.12, "B": 245342 } 6 | } 7 | -------------------------------------------------------------------------------- /expvars_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | expvarsTestFile = "./expvars.json" 10 | expvarsAdvTestFile = "./expvars_advanced.json" 11 | ) 12 | 13 | func TestExpvars(t *testing.T) { 14 | file, err := os.Open(expvarsTestFile) 15 | if err != nil { 16 | t.Fatalf("cannot open test file %v", err) 17 | } 18 | defer file.Close() 19 | 20 | expvar, err := ParseExpvar(file) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | cmdline, err := expvar.GetStringArray("cmdline") 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | if len(cmdline) != 3 { 30 | t.Fatalf("Cmdline should have 3 items, but has %d", len(cmdline)) 31 | } 32 | 33 | alloc, err := expvar.GetInt64(VarName("memstats.Alloc").ToSlice()...) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | if alloc == 0 { 38 | t.Fatalf("Alloc should be greater than 0") 39 | } 40 | 41 | pauses, err := expvar.GetInt64Array(VarName("memstats.PauseNs").ToSlice()...) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if len(pauses) == 0 { 46 | t.Fatalf("Pauses length should be greater than 0") 47 | } 48 | } 49 | 50 | func TestExpvarsAdvanced(t *testing.T) { 51 | file, err := os.Open(expvarsAdvTestFile) 52 | if err != nil { 53 | t.Fatalf("cannot open test file %v", err) 54 | } 55 | defer file.Close() 56 | 57 | expvar, err := ParseExpvar(file) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | 62 | goroutines, err := expvar.GetInt64("goroutines") 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | if goroutines != 10 { 67 | t.Fatalf("Expecting 'goroutines' to be %d, but got %d", 10, goroutines) 68 | } 69 | 70 | counterA, err := expvar.GetFloat64(VarName("counters.A").ToSlice()...) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | if counterA != 123.12 { 75 | t.Fatalf("Expecting 'counters.A' to be %f, but got %f", 123.12, counterA) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/divan/expvarmon 2 | 3 | require ( 4 | github.com/antonholmquist/jason v1.0.0 5 | github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63 6 | github.com/gizak/termui v0.0.0-20181228210747-b136f68f55f1 7 | github.com/mattn/go-runewidth v0.0.4 // indirect 8 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 9 | github.com/pyk/byten v0.0.0-20140925233358-f847a130bf6d 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/antonholmquist/jason v1.0.0 h1:Ytg94Bcf1Bfi965K2q0s22mig/n4eGqEij/atENBhA0= 2 | github.com/antonholmquist/jason v1.0.0/go.mod h1:+GxMEKI0Va2U8h3os6oiUAetHAlGMvxjdpAH/9uvUMA= 3 | github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63 h1:FxdkNGQyRwwk94rJ+IMNrTjv864XzT93/cvk7UpMM38= 4 | github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63/go.mod h1:8z71/aZjDHLs4ihK/5nD5wZVQxm/W4eRDnxQZcJmVD4= 5 | github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd h1:XtfPmj9tQRilnrEmI1HjQhxXWRhEM+m8CACtaMJE/kM= 6 | github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/gizak/termui v0.0.0-20181228210747-b136f68f55f1 h1:TCe+sQ3zOPa8KNePfjrb5Pix5FPa44WsiiCzsYv0Yts= 9 | github.com/gizak/termui v0.0.0-20181228210747-b136f68f55f1/go.mod h1:W48Llfv3G9PIK3TQjqynxzC9mP4HUcAn/6vlDWekq9k= 10 | github.com/gizak/termui v0.0.0-20190202061602-5ece2b7178ff h1:md8xjZ5dOJsdTnDXsnbu2W9WLBpZ3yh8ebRad+i2bQY= 11 | github.com/gizak/termui v0.0.0-20190202061602-5ece2b7178ff/go.mod h1:TISPFiAGKVhyNxElRxDknJ+HzcUyO7sAJCsc8dfAe+M= 12 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 13 | github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= 14 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 15 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 16 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= 17 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 18 | github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb h1:YahEjAGkJtCrkqgVHhX6n8ZX+CZ3hDRL9fjLYugLfSs= 19 | github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= 20 | github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840= 21 | github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= 22 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 23 | github.com/pyk/byten v0.0.0-20140925233358-f847a130bf6d h1:/0nqYrqVyPTVDT2jPx9z9BZ+ItJWlWA9VITUGvNaZkI= 24 | github.com/pyk/byten v0.0.0-20140925233358-f847a130bf6d/go.mod h1:El8LdwAxb76Ih0mRmpu9uMVq+ajiqGSxmYiUe+nSr+w= 25 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 26 | golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= 27 | golang.org/x/net v0.0.0-20180801234040-f4c29de78a2a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 28 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "sync" 9 | "time" 10 | 11 | "github.com/gizak/termui" 12 | ) 13 | 14 | var ( 15 | interval = flag.Duration("i", 5*time.Second, "Polling interval") 16 | urls = flag.String("ports", "", "Ports/URLs for accessing services expvars (start-end,port2,port3,https://host:port)") 17 | varsArg = flag.String("vars", "mem:memstats.Alloc,mem:memstats.Sys,mem:memstats.HeapAlloc,mem:memstats.HeapInuse,duration:memstats.PauseNs,duration:memstats.PauseTotalNs", "Vars to monitor (comma-separated)") 18 | dummy = flag.Bool("dummy", false, "Use dummy (console) output") 19 | self = flag.Bool("self", false, "Monitor itself") 20 | endpoint = flag.String("endpoint", DefaultEndpoint, "URL endpoint for expvars") 21 | ) 22 | 23 | func main() { 24 | flag.Usage = Usage 25 | flag.Parse() 26 | 27 | DefaultEndpoint = *endpoint 28 | 29 | // Process ports/urls 30 | ports, _ := ParsePorts(*urls) 31 | if *self { 32 | port, err := StartSelfMonitor() 33 | if err == nil { 34 | ports = append(ports, port) 35 | } 36 | } 37 | if len(ports) == 0 { 38 | fmt.Fprintln(os.Stderr, "no ports specified. Use -ports arg to specify ports of Go apps to monitor") 39 | Usage() 40 | os.Exit(1) 41 | } 42 | if *interval <= 0 { 43 | fmt.Fprintln(os.Stderr, "update interval is not valid. Valid examples: 5s, 1m, 1h30m") 44 | Usage() 45 | os.Exit(1) 46 | } 47 | 48 | // Process vars 49 | vars, err := ParseVars(*varsArg) 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | // Init UIData 55 | data := NewUIData(vars) 56 | for _, port := range ports { 57 | service := NewService(port, vars) 58 | data.Services = append(data.Services, service) 59 | } 60 | 61 | // Start proper UI 62 | var ui UI 63 | if len(data.Services) > 1 { 64 | ui = &TermUI{} 65 | } else { 66 | ui = &TermUISingle{} 67 | } 68 | if *dummy { 69 | ui = &DummyUI{} 70 | } 71 | 72 | if err := ui.Init(*data); err != nil { 73 | log.Fatal(err) 74 | } 75 | defer ui.Close() 76 | 77 | tick := time.NewTicker(*interval) 78 | 79 | UpdateAll(ui, data) 80 | for { 81 | select { 82 | case <-tick.C: 83 | UpdateAll(ui, data) 84 | case e := <-termui.PollEvents(): 85 | if e.Type == termui.KeyboardEvent && e.ID == "q" { 86 | return 87 | } 88 | if e.Type == termui.ResizeEvent { 89 | ui.Update(*data) 90 | } 91 | } 92 | } 93 | } 94 | 95 | // UpdateAll collects data from expvars and refreshes UI. 96 | func UpdateAll(ui UI, data *UIData) { 97 | var wg sync.WaitGroup 98 | for _, service := range data.Services { 99 | wg.Add(1) 100 | go service.Update(&wg) 101 | } 102 | wg.Wait() 103 | 104 | data.LastTimestamp = time.Now() 105 | 106 | ui.Update(*data) 107 | } 108 | 109 | // Usage reimplements flag.Usage 110 | func Usage() { 111 | progname := os.Args[0] 112 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", progname) 113 | flag.PrintDefaults() 114 | fmt.Fprintf(os.Stderr, ` 115 | Examples: 116 | %s -ports="80" 117 | %s -ports="23000-23010,http://example.com:80-81" -i=1m 118 | %s -ports="80,remoteapp:80" -vars="mem:memstats.Alloc,duration:Response.Mean,Counter" 119 | %s -ports="1234-1236" -vars="Goroutines" -self 120 | 121 | For more details and docs, see README: http://github.com/divan/expvarmon 122 | `, progname, progname, progname, progname) 123 | } 124 | -------------------------------------------------------------------------------- /self.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "expvar" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "runtime" 10 | "time" 11 | ) 12 | 13 | var startTime = time.Now().UTC() 14 | 15 | func goroutines() interface{} { 16 | return runtime.NumGoroutine() 17 | } 18 | 19 | // uptime is an expvar.Func compliant wrapper for uptime info. 20 | func uptime() interface{} { 21 | uptime := time.Since(startTime) 22 | return int64(uptime) 23 | } 24 | 25 | // startPort defines lower port for bind 26 | const startPort = 32768 27 | 28 | // StartSelfMonitor starts http server on random port and exports expvars. 29 | // 30 | // It tries 1024 ports, starting from startPort and registers some expvars if ok. 31 | func StartSelfMonitor() (url.URL, error) { 32 | for port := startPort; port < startPort+1024; port++ { 33 | bind := fmt.Sprintf("localhost:%d", port) 34 | l, err := net.Listen("tcp", bind) 35 | if err != nil { 36 | continue 37 | } 38 | if err := l.Close(); err != nil { 39 | continue 40 | } 41 | 42 | expvar.Publish("Goroutines", expvar.Func(goroutines)) 43 | expvar.Publish("Uptime", expvar.Func(uptime)) 44 | go http.ListenAndServe(bind, nil) 45 | 46 | return NewURL(fmt.Sprintf("%d", port)), nil 47 | } 48 | 49 | return url.URL{}, fmt.Errorf("no free ports found") 50 | } 51 | -------------------------------------------------------------------------------- /service.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/antonholmquist/jason" 9 | ) 10 | 11 | var ( 12 | // uptimeCounter is a variable used for tracking uptime status. 13 | // It should be always incrementing and included into default expvar vars. 14 | // Could be replaced with something different or made configurable in 15 | // the future. 16 | uptimeCounter = VarName("memstats.PauseTotalNs").ToSlice() 17 | ) 18 | 19 | // Service represents constantly updating info about single service. 20 | type Service struct { 21 | URL url.URL 22 | Name string 23 | Cmdline string 24 | 25 | stacks map[VarName]*Stack 26 | 27 | Err error 28 | Restarted bool 29 | UptimeCounter int64 30 | } 31 | 32 | // NewService returns new Service object. 33 | func NewService(url url.URL, vars []VarName) *Service { 34 | values := make(map[VarName]*Stack) 35 | for _, name := range vars { 36 | values[VarName(name)] = NewStack() 37 | } 38 | 39 | return &Service{ 40 | Name: url.Host, // we have only port on start, so use it as name until resolved 41 | URL: url, 42 | 43 | stacks: values, 44 | } 45 | } 46 | 47 | // Update updates Service info from Expvar variable. 48 | func (s *Service) Update(wg *sync.WaitGroup) { 49 | defer wg.Done() 50 | expvar, err := FetchExpvar(s.URL) 51 | // check for restart 52 | if s.Err != nil && err == nil { 53 | s.Restarted = true 54 | } 55 | s.Err = err 56 | 57 | // if memstat.PauseTotalNs less than s.UptimeCounter 58 | // then service was restarted 59 | c, err := expvar.GetInt64(uptimeCounter...) 60 | if err != nil { 61 | s.Err = err 62 | } else { 63 | if s.UptimeCounter > c { 64 | s.Restarted = true 65 | } 66 | s.UptimeCounter = c 67 | } 68 | 69 | // Update Cmdline & Name only once 70 | if len(s.Cmdline) == 0 { 71 | cmdline, err := expvar.GetStringArray("cmdline") 72 | if err != nil { 73 | s.Err = err 74 | } else { 75 | s.Cmdline = strings.Join(cmdline, " ") 76 | s.Name = BaseCommand(cmdline) 77 | } 78 | } 79 | 80 | // For all vars, fetch desired value from Json and push to it's own stack. 81 | for name, stack := range s.stacks { 82 | value, err := expvar.GetValue(name.ToSlice()...) 83 | if err != nil { 84 | stack.Push(nil) 85 | continue 86 | } 87 | v := guessValue(value) 88 | if v != nil { 89 | stack.Push(v) 90 | } 91 | } 92 | } 93 | 94 | // guessValue attemtps to bruteforce all supported types. 95 | func guessValue(value *jason.Value) interface{} { 96 | if v, err := value.Int64(); err == nil { 97 | return v 98 | } else if v, err := value.Float64(); err == nil { 99 | return v 100 | } else if v, err := value.Boolean(); err == nil { 101 | return v 102 | } else if v, err := value.String(); err == nil { 103 | return v 104 | } else if v, err := value.Array(); err == nil { 105 | // if we get an array, calculate average 106 | 107 | // empty array, treat as zero 108 | if len(v) == 0 { 109 | return 0 110 | } 111 | 112 | avg := averageJason(v) 113 | 114 | // cast to int64 for Int64 values 115 | if _, err := v[0].Int64(); err == nil { 116 | return int64(avg) 117 | } 118 | 119 | return avg 120 | } 121 | 122 | return nil 123 | } 124 | 125 | // Value returns current value for the given var of this service. 126 | // 127 | // It also formats value, if kind is specified. 128 | func (s Service) Value(name VarName) string { 129 | if s.Err != nil { 130 | return "N/A" 131 | } 132 | val, ok := s.stacks[name] 133 | if !ok { 134 | return "N/A" 135 | } 136 | 137 | v := val.Front() 138 | if v == nil { 139 | return "N/A" 140 | } 141 | 142 | return Format(v, name.Kind()) 143 | } 144 | 145 | // Values returns slice of ints with recent 146 | // values of the given var, to be used with sparkline. 147 | func (s Service) Values(name VarName) []int { 148 | stack, ok := s.stacks[name] 149 | if !ok { 150 | return nil 151 | } 152 | 153 | return stack.IntValues() 154 | } 155 | 156 | // Max returns maximum recorded value for given service and var. 157 | func (s Service) Max(name VarName) interface{} { 158 | val, ok := s.stacks[name] 159 | if !ok { 160 | return nil 161 | } 162 | 163 | v := val.Max 164 | if v == nil { 165 | return nil 166 | } 167 | 168 | return Format(v, name.Kind()) 169 | } 170 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // DefaultSize specifies maximum number of items in stack. 4 | // 5 | // Values should be enough for sparklines on high-res terminals 6 | // with minimal font size. 7 | const DefaultSize = 1200 8 | 9 | // Stack is a limited FIFO for holding sparkline values. 10 | type Stack struct { 11 | Values []VarValue 12 | Len int 13 | Max VarValue 14 | } 15 | 16 | // NewStack inits new Stack with default size limit. 17 | func NewStack() *Stack { 18 | return NewStackWithSize(DefaultSize) 19 | } 20 | 21 | // NewStackWithSize inits new Stack with size limit. 22 | func NewStackWithSize(size int) *Stack { 23 | return &Stack{ 24 | Values: make([]VarValue, size), 25 | Len: size, 26 | } 27 | } 28 | 29 | // Push inserts data to stack, preserving constant length. 30 | func (s *Stack) Push(val VarValue) { 31 | s.Values = append(s.Values, val) 32 | if len(s.Values) > s.Len { 33 | s.Values = s.Values[1:] 34 | } 35 | 36 | if s.Max == nil { 37 | s.Max = val 38 | return 39 | } 40 | 41 | switch val.(type) { 42 | case int64: 43 | switch s.Max.(type) { 44 | case int64: 45 | if val.(int64) > s.Max.(int64) { 46 | s.Max = val 47 | } 48 | case float64: 49 | if float64(val.(int64)) > s.Max.(float64) { 50 | s.Max = val 51 | } 52 | } 53 | case float64: 54 | switch s.Max.(type) { 55 | case int64: 56 | if val.(float64) > float64(s.Max.(int64)) { 57 | s.Max = val 58 | } 59 | case float64: 60 | if val.(float64) > s.Max.(float64) { 61 | s.Max = val 62 | } 63 | } 64 | } 65 | } 66 | 67 | // Front returns front value. 68 | func (s *Stack) Front() VarValue { 69 | if len(s.Values) == 0 { 70 | return nil 71 | } 72 | return s.Values[len(s.Values)-1] 73 | } 74 | 75 | // IntValues returns stack values explicitly casted to int. 76 | // 77 | // Main case is to use with termui.Sparklines. 78 | func (s *Stack) IntValues() []int { 79 | ret := make([]int, s.Len) 80 | for i, v := range s.Values { 81 | n, ok := v.(int64) 82 | if ok { 83 | ret[i] = int(n) 84 | continue 85 | } 86 | 87 | f, ok := v.(float64) 88 | if ok { 89 | // 12.34 (float) -> 1234 (int) 90 | ret[i] = int(f * 100) 91 | continue 92 | } 93 | 94 | b, ok := v.(bool) 95 | if ok { 96 | // false => 0, true = 1 97 | if b { 98 | ret[i] = 1 99 | } else { 100 | ret[i] = 0 101 | } 102 | } 103 | } 104 | return ret 105 | } 106 | -------------------------------------------------------------------------------- /stack_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestPushWithFloatAndIntValue(t *testing.T) { 6 | s := NewStack() 7 | s.Push(VarValue(int64(0.0))) // from service.go:guessValue 8 | s.Push(VarValue(5.0)) 9 | s.Push(VarValue(float64(15.0))) 10 | if _, ok := s.Max.(float64); !ok { 11 | t.Fatalf("Expected Max to be float64, but it's not") 12 | } 13 | s.Push(VarValue(int64(25.0))) 14 | if _, ok := s.Max.(int64); !ok { 15 | t.Fatalf("Expected Max to be int64, but it's not") 16 | } 17 | } 18 | 19 | func TestStack(t *testing.T) { 20 | size := 10 21 | s := NewStackWithSize(size) 22 | 23 | for i := 0; i < size+5; i++ { 24 | s.Push(i) 25 | l := len(s.Values) 26 | 27 | if l < size { 28 | if l != i+1 { 29 | t.Fatalf("len is incorrect. expecting %d, got %d", i, l) 30 | } 31 | } else { 32 | if l != size { 33 | t.Fatalf("len is incorrect. expecting %d, got %d", size, l) 34 | } 35 | } 36 | } 37 | 38 | if s.Front().(int) != 14 { 39 | t.Fatalf("Front returns wrong value: expecting %d, got %d", 14, s.Front()) 40 | } 41 | 42 | s1 := NewStackWithSize(3) 43 | s1.Push(true) 44 | s1.Push(false) 45 | s1.Push(true) 46 | 47 | ints1 := s1.IntValues() 48 | if len(ints1) != 3 { 49 | t.Fatalf("expecting len of to be %d, but got %d", 3, len(ints1)) 50 | } 51 | if ints1[0] != 1 || ints1[1] != 0 || ints1[2] != 1 { 52 | t.Fatalf("bool values converted to int incorrectly: %v", ints1) 53 | } 54 | 55 | s2 := NewStackWithSize(3) 56 | s2.Push(0.1) 57 | s2.Push(0.5) 58 | s2.Push(0.03) 59 | 60 | ints2 := s2.IntValues() 61 | if len(ints2) != 3 { 62 | t.Fatalf("expecting len to be %d, but got %d", 3, len(ints2)) 63 | } 64 | if ints2[0] != 10 || ints2[1] != 50 || ints2[2] != 3 { 65 | t.Fatalf("float values converted to int incorrectly: %v", ints2) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ui.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // UI represents UI renderer. 4 | type UI interface { 5 | Init(UIData) error 6 | Close() 7 | Update(UIData) 8 | } 9 | -------------------------------------------------------------------------------- /ui_dummy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // DummyUI is an simple console UI mockup, for testing purposes. 9 | type DummyUI struct{} 10 | 11 | // Init implements UI. 12 | func (*DummyUI) Init(UIData) error { return nil } 13 | 14 | // Close implements UI. 15 | func (*DummyUI) Close() {} 16 | 17 | // Update implements UI. 18 | func (*DummyUI) Update(data UIData) { 19 | if data.Services == nil { 20 | return 21 | } 22 | fmt.Println(time.Now().Format("15:04:05 02/01")) 23 | for _, service := range data.Services { 24 | fmt.Printf("%s: ", service.Name) 25 | if service.Err != nil { 26 | fmt.Printf("ERROR: %s\n", service.Err) 27 | continue 28 | } 29 | 30 | for _, name := range data.Vars { 31 | fmt.Printf("%s: %v, ", name.Short(), service.Value(name)) 32 | } 33 | 34 | fmt.Printf("\n") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ui_multi.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/gizak/termui" 8 | ) 9 | 10 | // TermUI is a termUI implementation of UI interface. 11 | type TermUI struct { 12 | Title *termui.Paragraph 13 | Status *termui.Paragraph 14 | Services *termui.List 15 | Lists []*termui.List 16 | Sparkline1 *termui.Sparklines 17 | Sparkline2 *termui.Sparklines 18 | } 19 | 20 | // Init creates widgets, sets sizes and labels. 21 | func (t *TermUI) Init(data UIData) error { 22 | err := termui.Init() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | t.Title = func() *termui.Paragraph { 28 | p := termui.NewParagraph("") 29 | p.Height = 3 30 | p.TextFgColor = termui.ColorWhite 31 | p.Border = true 32 | p.BorderLabel = "Services Monitor" 33 | p.BorderFg = termui.ColorCyan 34 | return p 35 | }() 36 | t.Status = func() *termui.Paragraph { 37 | p := termui.NewParagraph("") 38 | p.Height = 3 39 | p.TextFgColor = termui.ColorWhite 40 | p.Border = true 41 | p.BorderLabel = "Status" 42 | p.BorderFg = termui.ColorCyan 43 | return p 44 | }() 45 | t.Services = func() *termui.List { 46 | list := termui.NewList() 47 | list.ItemFgColor = termui.ColorGreen 48 | list.Border = true 49 | list.BorderLabelFg = termui.ColorGreen | termui.AttrBold 50 | list.BorderLabel = "Services" 51 | list.Height = len(data.Services) + 2 52 | return list 53 | }() 54 | 55 | t.Lists = make([]*termui.List, len(data.Vars)) 56 | for i, name := range data.Vars { 57 | list := termui.NewList() 58 | list.ItemFgColor = colorByKind(name.Kind()) 59 | list.Border = true 60 | list.BorderLabel = name.Short() 61 | list.BorderLabelFg = termui.ColorGreen 62 | if i < 2 { 63 | list.BorderLabelFg = termui.ColorGreen | termui.AttrBold 64 | } 65 | list.Height = len(data.Services) + 2 66 | t.Lists[i] = list 67 | } 68 | 69 | makeSparkline := func(name VarName) *termui.Sparklines { 70 | var sparklines []termui.Sparkline 71 | for _, service := range data.Services { 72 | spl := termui.NewSparkline() 73 | spl.Height = 1 74 | spl.LineColor = termui.ColorGreen 75 | spl.Title = service.Name 76 | sparklines = append(sparklines, spl) 77 | } 78 | 79 | s := termui.NewSparklines(sparklines...) 80 | s.Height = 2*len(data.Services) + 2 81 | s.Border = true 82 | s.BorderLabel = fmt.Sprintf("Monitoring %s", name.Long()) 83 | return s 84 | } 85 | t.Sparkline1 = makeSparkline(data.Vars[0]) 86 | if len(data.Vars) > 1 { 87 | t.Sparkline2 = makeSparkline(data.Vars[1]) 88 | } 89 | 90 | t.Relayout() 91 | 92 | return nil 93 | } 94 | 95 | // Update updates UI widgets from UIData. 96 | func (t *TermUI) Update(data UIData) { 97 | t.Title.Text = fmt.Sprintf("monitoring %d services every %v, press q to quit", len(data.Services), *interval) 98 | t.Status.Text = fmt.Sprintf("Last update: %v", data.LastTimestamp.Format(time.Stamp)) 99 | 100 | // List with service names 101 | var services []string 102 | for _, service := range data.Services { 103 | services = append(services, StatusLine(service)) 104 | } 105 | t.Services.Items = services 106 | 107 | // Lists with values 108 | for i, name := range data.Vars { 109 | var lines []string 110 | for _, service := range data.Services { 111 | lines = append(lines, service.Value(name)) 112 | } 113 | t.Lists[i].Items = lines 114 | } 115 | 116 | // Sparklines 117 | for i, service := range data.Services { 118 | max := formatMax(service.Max(data.Vars[0])) 119 | t.Sparkline1.Lines[i].Title = fmt.Sprintf("%s%s", service.Name, max) 120 | t.Sparkline1.Lines[i].Data = service.Values(data.Vars[0]) 121 | 122 | if len(data.Vars) > 1 { 123 | max = formatMax(service.Max(data.Vars[1])) 124 | t.Sparkline2.Lines[i].Title = fmt.Sprintf("%s%s", service.Name, max) 125 | t.Sparkline2.Lines[i].Data = service.Values(data.Vars[1]) 126 | } 127 | } 128 | 129 | t.Relayout() 130 | 131 | var widgets []termui.Bufferer 132 | widgets = append(widgets, t.Title, t.Status, t.Services, t.Sparkline1) 133 | for _, list := range t.Lists { 134 | widgets = append(widgets, list) 135 | } 136 | if t.Sparkline2 != nil { 137 | widgets = append(widgets, t.Sparkline2) 138 | } 139 | termui.Render(widgets...) 140 | } 141 | 142 | // Relayout recalculates widgets sizes and coords. 143 | func (t *TermUI) Relayout() { 144 | tw, th := termui.TermWidth(), termui.TermHeight() 145 | h := th 146 | 147 | // First row: Title and Status pars 148 | firstRowH := 3 149 | t.Title.Height = firstRowH 150 | t.Title.Width = tw / 2 151 | if tw%2 == 1 { 152 | t.Title.Width++ 153 | } 154 | t.Status.Height = firstRowH 155 | t.Status.Width = tw / 2 156 | t.Status.X = t.Title.X + t.Title.Width 157 | h -= firstRowH 158 | 159 | // Second Row: lists 160 | num := len(t.Lists) + 1 161 | listW := tw / num 162 | 163 | // Services list must have visible names 164 | minNameWidth := 20 165 | t.Services.Width = minNameWidth 166 | if listW > minNameWidth { 167 | t.Services.Width = listW 168 | } 169 | 170 | // Recalculate widths for each list 171 | listW = (tw - t.Services.Width) / (num - 1) 172 | 173 | // Finally, enlarge services list, if there is a space left 174 | if listW*(num-1)+t.Services.Width < tw { 175 | t.Services.Width = tw - (listW * (num - 1)) 176 | } 177 | 178 | t.Services.Y = th - h 179 | 180 | for i, list := range t.Lists { 181 | list.Y = th - h 182 | list.Width = listW 183 | list.Height = len(t.Lists[0].Items) + 2 184 | list.X = t.Services.X + t.Services.Width + i*listW 185 | } 186 | h -= t.Lists[0].Height 187 | 188 | // Third row: sparklines for two vars 189 | t.Sparkline1.Width = tw 190 | t.Sparkline1.Height = h 191 | t.Sparkline1.Y = th - h 192 | if t.Sparkline2 != nil { 193 | t.Sparkline1.Width = tw / 2 194 | 195 | t.Sparkline2.Width = tw / 2 196 | if tw%2 == 1 { 197 | t.Sparkline2.Width++ 198 | } 199 | t.Sparkline2.X = t.Sparkline1.X + t.Sparkline1.Width 200 | t.Sparkline2.Height = h 201 | t.Sparkline2.Y = th - h 202 | } 203 | 204 | } 205 | 206 | // Close shuts down UI module. 207 | func (t *TermUI) Close() { 208 | termui.Close() 209 | } 210 | 211 | // StatusLine returns status line for service with it's name and status. 212 | func StatusLine(s *Service) string { 213 | if s.Err != nil { 214 | return fmt.Sprintf("[E] ⛔ %s failed", s.Name) 215 | } 216 | 217 | if s.Restarted { 218 | return fmt.Sprintf("[R] 🔥 %s", s.Name) 219 | } 220 | 221 | return fmt.Sprintf("[R] %s", s.Name) 222 | } 223 | 224 | func colorByKind(kind VarKind) termui.Attribute { 225 | switch kind { 226 | case KindMemory: 227 | return termui.ColorRed | termui.AttrBold 228 | case KindDuration: 229 | return termui.ColorYellow | termui.AttrBold 230 | case KindString: 231 | return termui.ColorGreen | termui.AttrBold 232 | default: 233 | return termui.ColorBlue | termui.AttrBold 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /ui_single.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/gizak/termui" 8 | ) 9 | 10 | // TermUISingle is a termUI implementation of UI interface. 11 | type TermUISingle struct { 12 | Title *termui.Paragraph 13 | Status *termui.Paragraph 14 | Sparklines map[VarName]*termui.Sparkline 15 | Sparkline *termui.Sparklines 16 | Pars []*termui.Paragraph 17 | } 18 | 19 | // Init creates widgets, sets sizes and labels. 20 | func (t *TermUISingle) Init(data UIData) error { 21 | err := termui.Init() 22 | if err != nil { 23 | return err 24 | } 25 | 26 | t.Sparklines = make(map[VarName]*termui.Sparkline) 27 | 28 | t.Title = func() *termui.Paragraph { 29 | p := termui.NewParagraph("") 30 | p.Height = 3 31 | p.TextFgColor = termui.ColorWhite 32 | p.Border = true 33 | p.BorderLabel = "Services Monitor" 34 | p.BorderFg = termui.ColorCyan 35 | return p 36 | }() 37 | t.Status = func() *termui.Paragraph { 38 | p := termui.NewParagraph("") 39 | p.Height = 3 40 | p.TextFgColor = termui.ColorWhite 41 | p.Border = true 42 | p.BorderLabel = "Status" 43 | p.BorderFg = termui.ColorCyan 44 | return p 45 | }() 46 | 47 | t.Pars = make([]*termui.Paragraph, len(data.Vars)) 48 | for i, name := range data.Vars { 49 | par := termui.NewParagraph("") 50 | par.TextFgColor = colorByKind(name.Kind()) 51 | par.Border = true 52 | par.BorderLabel = name.Short() 53 | par.BorderLabelFg = termui.ColorGreen 54 | par.Height = 3 55 | t.Pars[i] = par 56 | } 57 | 58 | var sparklines []termui.Sparkline 59 | for _, name := range data.Vars { 60 | spl := termui.NewSparkline() 61 | spl.Height = 1 62 | spl.TitleColor = colorByKind(name.Kind()) 63 | spl.LineColor = colorByKind(name.Kind()) 64 | spl.Title = name.Long() 65 | sparklines = append(sparklines, spl) 66 | } 67 | 68 | t.Sparkline = func() *termui.Sparklines { 69 | s := termui.NewSparklines(sparklines...) 70 | s.Height = 2*len(sparklines) + 2 71 | s.Border = true 72 | s.BorderLabel = fmt.Sprintf("Monitoring") 73 | return s 74 | }() 75 | 76 | t.Relayout() 77 | 78 | return nil 79 | } 80 | 81 | // Update updates UI widgets from UIData. 82 | func (t *TermUISingle) Update(data UIData) { 83 | // single mode assumes we have one service only to monitor 84 | service := data.Services[0] 85 | 86 | t.Title.Text = fmt.Sprintf("monitoring %s every %v, press q to quit", service.Name, *interval) 87 | t.Status.Text = fmt.Sprintf("Last update: %v", data.LastTimestamp.Format(time.Stamp)) 88 | 89 | // Pars 90 | for i, name := range data.Vars { 91 | t.Pars[i].Text = service.Value(name) 92 | } 93 | 94 | // Sparklines 95 | for i, name := range data.Vars { 96 | spl := &t.Sparkline.Lines[i] 97 | 98 | max := formatMax(service.Max(name)) 99 | spl.Title = fmt.Sprintf("%s: %v%s", name.Long(), service.Value(name), max) 100 | spl.TitleColor = colorByKind(name.Kind()) 101 | spl.LineColor = colorByKind(name.Kind()) 102 | 103 | if name.Kind() == KindString { 104 | continue 105 | } 106 | spl.Data = service.Values(name) 107 | } 108 | 109 | t.Relayout() 110 | 111 | var widgets []termui.Bufferer 112 | widgets = append(widgets, t.Title, t.Status, t.Sparkline) 113 | for _, par := range t.Pars { 114 | widgets = append(widgets, par) 115 | } 116 | termui.Render(widgets...) 117 | } 118 | 119 | // Close shuts down UI module. 120 | func (t *TermUISingle) Close() { 121 | termui.Close() 122 | } 123 | 124 | // Relayout recalculates widgets sizes and coords. 125 | func (t *TermUISingle) Relayout() { 126 | tw, th := termui.TermWidth(), termui.TermHeight() 127 | h := th 128 | 129 | // First row: Title and Status pars 130 | firstRowH := 3 131 | t.Title.Height = firstRowH 132 | t.Title.Width = tw / 2 133 | if tw%2 == 1 { 134 | t.Title.Width++ 135 | } 136 | t.Status.Height = firstRowH 137 | t.Status.Width = tw / 2 138 | t.Status.X = t.Title.X + t.Title.Width 139 | h -= firstRowH 140 | 141 | // Second row: lists 142 | secondRowH := 3 143 | num := len(t.Pars) 144 | parW := tw / num 145 | for i, par := range t.Pars { 146 | par.Y = th - h 147 | par.Width = parW 148 | par.Height = secondRowH 149 | par.X = i * parW 150 | } 151 | if num*parW < tw { 152 | t.Pars[num-1].Width = tw - ((num - 1) * parW) 153 | } 154 | h -= secondRowH 155 | 156 | // Third row: Sparklines 157 | t.Sparkline.Width = tw 158 | t.Sparkline.Height = h 159 | t.Sparkline.Y = th - h 160 | } 161 | 162 | func formatMax(max interface{}) string { 163 | var str string 164 | if max != nil { 165 | str = fmt.Sprintf(" (max: %v)", max) 166 | } 167 | return str 168 | } 169 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/bsiegert/ranges" 11 | ) 12 | 13 | var ErrParsePorts = fmt.Errorf("cannot parse ports argument") 14 | 15 | // ParseVars returns parsed and validated slice of strings with 16 | // variables names that will be used for monitoring. 17 | func ParseVars(vars string) ([]VarName, error) { 18 | if vars == "" { 19 | return nil, errors.New("no vars specified") 20 | } 21 | 22 | ss := strings.FieldsFunc(vars, func(r rune) bool { return r == ',' }) 23 | var ret []VarName 24 | for _, s := range ss { 25 | ret = append(ret, VarName(s)) 26 | } 27 | return ret, nil 28 | } 29 | 30 | // BaseCommand returns cleaned command name from Cmdline array. 31 | // 32 | // I.e. "./some.service/binary.name -arg 1 -arg" will be "binary.name". 33 | func BaseCommand(cmdline []string) string { 34 | if len(cmdline) == 0 { 35 | return "" 36 | } 37 | return filepath.Base(cmdline[0]) 38 | } 39 | 40 | // flattenURLs returns URLs for the given addr and set of ports. 41 | // 42 | // Note, rawurl shouldn't contain port, as port will be appended. 43 | func flattenURLs(rawurl string, ports []string) ([]url.URL, error) { 44 | var urls []url.URL 45 | 46 | // Add http by default 47 | if !strings.HasPrefix(rawurl, "http") { 48 | rawurl = fmt.Sprintf("http://%s", rawurl) 49 | } 50 | 51 | // Make URL from rawurl 52 | baseURL, err := url.Parse(rawurl) 53 | if err != nil { 54 | return nil, err 55 | } 56 | if baseURL.Path == "" { 57 | baseURL.Path = DefaultEndpoint 58 | } 59 | 60 | // Create new URL for each port 61 | for _, port := range ports { 62 | u := *baseURL 63 | u.Host = fmt.Sprintf("%s:%s", u.Host, port) 64 | urls = append(urls, u) 65 | } 66 | return urls, nil 67 | } 68 | 69 | // ParsePorts parses and flattens comma-separated ports/urls into URLs slice 70 | func ParsePorts(s string) ([]url.URL, error) { 71 | var urls []url.URL 72 | fields := strings.FieldsFunc(s, func(r rune) bool { return r == ',' }) 73 | for _, field := range fields { 74 | rawurl, portsRange := extractURLAndPorts(field) 75 | 76 | ports, err := parseRange(portsRange) 77 | if err != nil { 78 | return nil, ErrParsePorts 79 | } 80 | 81 | purls, err := flattenURLs(rawurl, ports) 82 | if err != nil { 83 | return nil, ErrParsePorts 84 | } 85 | 86 | urls = append(urls, purls...) 87 | } 88 | 89 | return urls, nil 90 | } 91 | 92 | // extractUrlAndPorts attempts to split url and extract raw url 93 | // for the single port and range of ports to parse. 94 | // 95 | // i.e. "http://name:1234-1236/_endpoint" would return "http://name/_endpoint" and 96 | // "1234-1236" 97 | func extractURLAndPorts(s string) (string, string) { 98 | var rawurl, ports string 99 | parts := strings.Split(s, ":") 100 | switch len(parts) { 101 | case 1: 102 | // "1234-234" 103 | rawurl = "http://localhost" 104 | ports = parts[0] 105 | case 2: 106 | // "localhost:1234" 107 | rawurl, ports = parts[0], parts[1] 108 | default: 109 | // "https://user:pass@remote.name:1234" or "http://name:1234-1236/_endpoint" 110 | 111 | // construct endpoint from the first part of URI, before ports appera 112 | rawurl = strings.Join(parts[:len(parts)-1], ":") 113 | 114 | // get either "1234-1235" or "1234-1235/_endpoint" 115 | lastPart := parts[len(parts)-1] 116 | 117 | // try to find endpoint and attach it to rawurl 118 | fields := strings.SplitN(lastPart, "/", 2) 119 | ports = fields[0] 120 | if len(fields) > 1 { 121 | rawurl = fmt.Sprintf("%s/%s", rawurl, fields[1]) 122 | } 123 | } 124 | 125 | return rawurl, ports 126 | } 127 | 128 | // parseRange flattens port ranges, such as "1234-1240,1333" 129 | func parseRange(s string) ([]string, error) { 130 | portsInt, err := ranges.Parse(s) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | var ports []string 136 | for _, port := range portsInt { 137 | ports = append(ports, fmt.Sprintf("%d", port)) 138 | } 139 | return ports, nil 140 | } 141 | 142 | // NewURL returns net.URL for the given port, with expvarmon defaults set. 143 | func NewURL(port string) url.URL { 144 | return url.URL{ 145 | Scheme: "http", 146 | Host: fmt.Sprintf("localhost:%s", port), 147 | Path: "/debug/vars", 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestUtils(t *testing.T) { 6 | str := "memstats.Alloc,memstats.Sys" 7 | 8 | vars, err := ParseVars(str) 9 | if err != nil { 10 | t.Fatalf("Err not nil: %v", err) 11 | } 12 | 13 | if len(vars) != 2 { 14 | t.Fatalf("vars should contain 2 elements, but has %d", len(vars)) 15 | } 16 | 17 | str = "memstats.Alloc,memstats.Sys,goroutines,Counter.A" 18 | 19 | vars, err = ParseVars(str) 20 | if err != nil { 21 | t.Fatalf("Err not nil: %v", err) 22 | } 23 | 24 | if len(vars) != 4 { 25 | t.Fatalf("vars should contain 4 elements, but has %d", len(vars)) 26 | } 27 | } 28 | 29 | func TestExtractUrlAndPorts(t *testing.T) { 30 | var rawurl, ports string 31 | rawurl, ports = extractURLAndPorts("40000-40002") 32 | if rawurl != "http://localhost" || ports != "40000-40002" { 33 | t.Fatalf("extract url and ports failed: %v, %v", rawurl, ports) 34 | } 35 | 36 | rawurl, ports = extractURLAndPorts("https://example.com:1234") 37 | if rawurl != "https://example.com" || ports != "1234" { 38 | t.Fatalf("extract url and ports failed: %v, %v", rawurl, ports) 39 | } 40 | 41 | rawurl, ports = extractURLAndPorts("http://user:passwd@example.com:1234-1256") 42 | if rawurl != "http://user:passwd@example.com" || ports != "1234-1256" { 43 | t.Fatalf("extract url and ports failed: %v, %v", rawurl, ports) 44 | } 45 | 46 | rawurl, ports = extractURLAndPorts("https://example.com:1234-1256/_endpoint") 47 | if rawurl != "https://example.com/_endpoint" || ports != "1234-1256" { 48 | t.Fatalf("extract url and ports failed: %v, %v", rawurl, ports) 49 | } 50 | } 51 | 52 | func TestPorts(t *testing.T) { 53 | arg := "1234,1235" 54 | ports, err := ParsePorts(arg) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | if len(ports) != 2 || ports[0].Host != "localhost:1234" { 59 | t.Fatalf("ParsePorts returns wrong data: %v", ports) 60 | } 61 | 62 | arg = "1234-1237,2000" 63 | ports, err = ParsePorts(arg) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | if len(ports) != 5 || ports[0].Host != "localhost:1234" || ports[4].Host != "localhost:2000" { 68 | t.Fatalf("ParsePorts returns wrong data: %v", ports) 69 | } 70 | 71 | arg = "40000-40002,localhost:2000-2002,remote:1234-1235,https://example.com:1234-1236" 72 | ports, err = ParsePorts(arg) 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | if len(ports) != 11 || 77 | ports[0].Host != "localhost:40000" || 78 | ports[3].Host != "localhost:2000" || 79 | ports[7].Host != "remote:1235" || 80 | ports[7].Path != "/debug/vars" || 81 | ports[10].Host != "example.com:1236" || 82 | ports[10].Scheme != "https" { 83 | t.Fatalf("ParsePorts returns wrong data: %v", ports) 84 | } 85 | 86 | // Test Auth 87 | arg = "http://user:pass@localhost:2000-2002" 88 | ports, err = ParsePorts(arg) 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | pass, isSet := ports[0].User.Password() 93 | if len(ports) != 3 || 94 | ports[0].User.Username() != "user" || 95 | pass != "pass" || !isSet || 96 | ports[0].Scheme != "http" { 97 | t.Fatalf("ParsePorts returns wrong data: %v", ports) 98 | } 99 | 100 | arg = "localhost:2000-2002,remote:1234-1235,some:weird:1234-123input" 101 | _, err = ParsePorts(arg) 102 | if err == nil { 103 | t.Fatalf("err shouldn't be nil") 104 | } 105 | 106 | arg = "string,sdasd" 107 | _, err = ParsePorts(arg) 108 | if err == nil { 109 | t.Fatalf("err shouldn't be nil") 110 | } 111 | 112 | // Test endpoints 113 | arg = "localhost:2000,https://example.com:1234/_custom_expvars" 114 | ports, err = ParsePorts(arg) 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | if ports[0].Path != "/debug/vars" || ports[1].Path != "/_custom_expvars" { 119 | t.Fatalf("ParsePorts returns wrong data: %v", ports) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /var.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/pyk/byten" 9 | ) 10 | 11 | // VarName represents variable name. 12 | // 13 | // It has dot-separated format, like "memstats.Alloc", 14 | // but can be used in different forms, hence it's own type. 15 | // 16 | // It also can have optional "kind:" modifier, like "mem:" or "duration:" 17 | type VarName string 18 | 19 | // VarKind specifies special kinds of values, affects formatting. 20 | type VarKind int 21 | 22 | // VarValue represents arbitrary value for variable. 23 | type VarValue interface{} 24 | 25 | const ( 26 | KindDefault VarKind = iota 27 | KindMemory 28 | KindDuration 29 | KindString 30 | ) 31 | 32 | // ToSlice converts "dot-separated" notation into the "slice of strings". 33 | // 34 | // "dot-separated" notation is a human-readable format, passed via args. 35 | // "slice of strings" is used by Jason library. 36 | // 37 | // Example: "memstats.Alloc" => []string{"memstats", "Alloc"} 38 | // Example: "mem:memstats.Alloc" => []string{"memstats", "Alloc"} 39 | func (v VarName) ToSlice() []string { 40 | start := strings.IndexRune(string(v), ':') + 1 41 | slice := DottedFieldsToSliceEscaped(string(v)[start:]) 42 | return slice 43 | } 44 | 45 | // Short returns short name, which is typically is the last word in the long names. 46 | func (v VarName) Short() string { 47 | if v == "" { 48 | return "" 49 | } 50 | 51 | slice := v.ToSlice() 52 | return slice[len(slice)-1] 53 | } 54 | 55 | // Long returns long name, without kind: modifier. 56 | func (v VarName) Long() string { 57 | if v == "" { 58 | return "" 59 | } 60 | 61 | start := strings.IndexRune(string(v), ':') + 1 62 | return string(v)[start:] 63 | } 64 | 65 | // Kind returns kind of variable, based on it's name modifiers ("mem:") 66 | func (v VarName) Kind() VarKind { 67 | start := strings.IndexRune(string(v), ':') 68 | if start == -1 { 69 | return KindDefault 70 | } 71 | 72 | switch string(v)[:start] { 73 | case "mem": 74 | return KindMemory 75 | case "duration": 76 | return KindDuration 77 | case "str": 78 | return KindString 79 | } 80 | return KindDefault 81 | } 82 | 83 | // Format returns human-readable var value representation. 84 | func Format(v VarValue, kind VarKind) string { 85 | switch kind { 86 | case KindMemory: 87 | if _, ok := v.(int64); !ok { 88 | break 89 | } 90 | return fmt.Sprintf("%s", byten.Size(v.(int64))) 91 | case KindDuration: 92 | if _, ok := v.(int64); !ok { 93 | break 94 | } 95 | return fmt.Sprintf("%s", roundDuration(time.Duration(v.(int64)))) 96 | } 97 | 98 | if f, ok := v.(float64); ok { 99 | return fmt.Sprintf("%.2f", f) 100 | } 101 | 102 | return fmt.Sprintf("%v", v) 103 | } 104 | 105 | // roundDuration removes unneeded precision from the String() output for time.Duration. 106 | func roundDuration(d time.Duration) time.Duration { 107 | r := time.Second 108 | if d < time.Second { 109 | r = time.Millisecond 110 | } 111 | if d < time.Millisecond { 112 | r = time.Microsecond 113 | } 114 | if r <= 0 { 115 | return d 116 | } 117 | neg := d < 0 118 | if neg { 119 | d = -d 120 | } 121 | if m := d % r; m+m < r { 122 | d = d - m 123 | } else { 124 | d = d + r - m 125 | } 126 | if neg { 127 | return -d 128 | } 129 | return d 130 | } 131 | 132 | func DottedFieldsToSliceEscaped(s string) []string { 133 | rv := make([]string, 0) 134 | lastSlash := false 135 | curr := "" 136 | for _, r := range s { 137 | // base case, dot not after slash 138 | if !lastSlash && r == '.' { 139 | if len(curr) > 0 { 140 | rv = append(rv, curr) 141 | curr = "" 142 | } 143 | continue 144 | } else if !lastSlash { 145 | // any character not after slash 146 | curr += string(r) 147 | if r == '\\' { 148 | lastSlash = true 149 | } else { 150 | lastSlash = false 151 | } 152 | continue 153 | } else if r == '\\' { 154 | // last was slash, and so is this 155 | lastSlash = false // 2 slashes = 0 156 | // we already appended a single slash on first 157 | continue 158 | } else if r == '.' { 159 | // we see \. but already appended \ last time 160 | // replace it with . 161 | curr = curr[:len(curr)-1] + "." 162 | lastSlash = false 163 | } else { 164 | // \ and any other character, ignore 165 | curr += string(r) 166 | lastSlash = false 167 | continue 168 | } 169 | } 170 | if len(curr) > 0 { 171 | rv = append(rv, curr) 172 | } 173 | return rv 174 | } 175 | -------------------------------------------------------------------------------- /var_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestVarName(t *testing.T) { 8 | v := VarName("memstats.Alloc") 9 | 10 | slice := v.ToSlice() 11 | if len(slice) != 2 || slice[0] != "memstats" || slice[1] != "Alloc" { 12 | t.Fatalf("ToSlice failed: %v", slice) 13 | } 14 | 15 | short := v.Short() 16 | if short != "Alloc" { 17 | t.Fatalf("Expecting Short() to be 'Alloc', but got: %s", short) 18 | } 19 | 20 | kind := v.Kind() 21 | if kind != KindDefault { 22 | t.Fatalf("Expecting kind to be %v, but got: %v", KindDefault, kind) 23 | } 24 | 25 | v = VarName("mem:memstats.Alloc") 26 | 27 | slice = v.ToSlice() 28 | if len(slice) != 2 || slice[0] != "memstats" || slice[1] != "Alloc" { 29 | t.Fatalf("ToSlice failed: %v", slice) 30 | } 31 | 32 | short = v.Short() 33 | if short != "Alloc" { 34 | t.Fatalf("Expecting Short() to be 'Alloc', but got: %s", short) 35 | } 36 | 37 | kind = v.Kind() 38 | if kind != KindMemory { 39 | t.Fatalf("Expecting kind to be %v, but got: %v", KindMemory, kind) 40 | } 41 | 42 | v = VarName("duration:ResponseTimes.API.Users") 43 | kind = v.Kind() 44 | if kind != KindDuration { 45 | t.Fatalf("Expecting kind to be %v, but got: %v", KindDuration, kind) 46 | } 47 | 48 | // single \. escapes the dot 49 | v = VarName(`bleve.indexes.bench\.bleve.index.lookup_queue_len`) 50 | 51 | slice = v.ToSlice() 52 | if len(slice) != 5 || slice[0] != "bleve" || slice[1] != "indexes" || slice[2] != "bench.bleve" || 53 | slice[3] != "index" || slice[4] != "lookup_queue_len" { 54 | t.Fatalf("ToSlice failed: %v", slice) 55 | } 56 | 57 | // double \\. escapes backslash, not dot 58 | v = VarName(`bleve.indexes.bench\\.bleve.index.lookup_queue_len`) 59 | 60 | slice = v.ToSlice() 61 | if len(slice) != 6 || slice[0] != "bleve" || slice[1] != "indexes" || slice[2] != "bench\\" || 62 | slice[3] != "bleve" || slice[4] != "index" || slice[5] != "lookup_queue_len" { 63 | t.Fatalf("ToSlice failed: %v", slice) 64 | } 65 | 66 | // triple \\\. escapes backslash then dot 67 | v = VarName(`bleve.indexes.bench\\\.bleve.index.lookup_queue_len`) 68 | 69 | slice = v.ToSlice() 70 | if len(slice) != 5 || slice[0] != "bleve" || slice[1] != "indexes" || slice[2] != "bench\\.bleve" || 71 | slice[3] != "index" || slice[4] != "lookup_queue_len" { 72 | t.Fatalf("ToSlice failed: %v", slice) 73 | } 74 | 75 | // quadruple \\\\. escapes two backslashes, not dot 76 | v = VarName(`bleve.indexes.bench\\\\.bleve.index.lookup_queue_len`) 77 | 78 | slice = v.ToSlice() 79 | if len(slice) != 6 || slice[0] != "bleve" || slice[1] != "indexes" || slice[2] != "bench\\\\" || 80 | slice[3] != "bleve" || slice[4] != "index" || slice[5] != "lookup_queue_len" { 81 | t.Fatalf("ToSlice failed: %v", slice) 82 | } 83 | 84 | // unsupported \x passes through unaltered 85 | v = VarName(`bleve.indexes.bench\xbleve.index.lookup_queue_len`) 86 | 87 | slice = v.ToSlice() 88 | if len(slice) != 5 || slice[0] != "bleve" || slice[1] != "indexes" || slice[2] != "bench\\xbleve" || 89 | slice[3] != "index" || slice[4] != "lookup_queue_len" { 90 | t.Fatalf("ToSlice failed: %v", slice) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /vendor/github.com/antonholmquist/jason/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /vendor/github.com/antonholmquist/jason/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.1 5 | - 1.2 6 | - 1.3 7 | - tip 8 | -------------------------------------------------------------------------------- /vendor/github.com/antonholmquist/jason/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Anton Holmquist, anton.holmquist@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/antonholmquist/jason/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![logo](https://us-bucket-host.s3.amazonaws.com/jason/jason_cropped_4.svg) 3 | 4 | Jason is an easy-to-use JSON library for Go. 5 | 6 | [![Build Status](https://img.shields.io/travis/antonholmquist/jason.svg?style=flat)](https://travis-ci.org/antonholmquist/jason) [![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/antonholmquist/jason) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/antonholmquist/jason/master/LICENSE) 7 | 8 | # About 9 | 10 | Jason is designed to be convenient for reading arbitrary JSON while still honoring the strictness of the language. Inspired by other libraries and improved to work well for common use cases. It currently focuses on reading JSON data rather than creating it. [API Documentation](http://godoc.org/github.com/antonholmquist/jason) can be found on godoc.org. 11 | 12 | ## Install 13 | 14 | ```shell 15 | go get github.com/antonholmquist/jason 16 | ``` 17 | 18 | ## Import 19 | 20 | ```go 21 | import ( 22 | "github.com/antonholmquist/jason" 23 | ) 24 | ``` 25 | 26 | ## Data types 27 | 28 | The following golang values are used to represent JSON data types. It is consistent with how `encoding/json` uses primitive types. 29 | 30 | - `bool`, for JSON booleans 31 | - `json.Number/float64/int64`, for JSON numbers 32 | - `string`, for JSON strings 33 | - `[]*Value`, for JSON arrays 34 | - `map[string]*Value`, for JSON objects 35 | - `nil` for JSON null 36 | 37 | ## Examples 38 | 39 | ### Create from bytes 40 | 41 | Create object from bytes. Returns an error if the bytes are not valid JSON. 42 | 43 | ```go 44 | v, err := jason.NewObjectFromBytes(b) 45 | 46 | ``` 47 | 48 | If the root object is not an array, use this method instead. It can then be cased to the expected type with one of the As-Methods. 49 | 50 | ```go 51 | v, err := jason.NewValueFromBytes(b) 52 | 53 | ``` 54 | 55 | ### Create from a reader (like a http response) 56 | 57 | Create value from a io.reader. Returns an error if the string couldn't be parsed. 58 | 59 | ```go 60 | v, err := jason.NewObjectFromReader(res.Body) 61 | 62 | ``` 63 | 64 | ### Read values 65 | 66 | Reading values is easy. If the key path is invalid or type doesn't match, it will return an error and the default value. 67 | 68 | ```go 69 | name, err := v.GetString("name") 70 | age, err := v.GetInt64("age") 71 | verified, err := v.GetBoolean("verified") 72 | education, err := v.GetObject("education") 73 | friends, err := v.GetObjectArray("friends") 74 | interests, err := v.GetStringArray("interests") 75 | 76 | ``` 77 | 78 | ### Read nested values 79 | 80 | Reading nested values is easy. If the path is invalid or type doesn't match, it will return the default value and an error. 81 | 82 | ```go 83 | name, err := v.GetString("person", "name") 84 | age, err := v.GetInt64("person", "age") 85 | verified, err := v.GetBoolean("person", "verified") 86 | education, err := v.GetObject("person", "education") 87 | friends, err := v.GetObjectArray("person", "friends") 88 | 89 | ``` 90 | 91 | ### Loop through array 92 | 93 | Looping through an array is done with `GetValueArray()` or `GetObjectArray()`. It returns an error if the value at that keypath is null (or something else than an array). 94 | 95 | ```go 96 | friends, err := person.GetObjectArray("friends") 97 | for _, friend := range friends { 98 | name, err := friend.GetString("name") 99 | age, err := friend.GetNumber("age") 100 | } 101 | ``` 102 | 103 | ### Loop through object 104 | 105 | Looping through an object is easy. `GetObject()` returns an error if the value at that keypath is null (or something else than an object). 106 | 107 | ```go 108 | person, err := person.GetObject("person") 109 | for key, value := range person.Map() { 110 | ... 111 | } 112 | ``` 113 | 114 | ## Sample App 115 | 116 | Example project: 117 | 118 | ```go 119 | package main 120 | 121 | import ( 122 | "github.com/antonholmquist/jason" 123 | "log" 124 | ) 125 | 126 | func main() { 127 | 128 | exampleJSON := `{ 129 | "name": "Walter White", 130 | "age": 51, 131 | "children": [ 132 | "junior", 133 | "holly" 134 | ], 135 | "other": { 136 | "occupation": "chemist", 137 | "years": 23 138 | } 139 | }` 140 | 141 | v, _ := jason.NewObjectFromBytes([]byte(exampleJSON)) 142 | 143 | name, _ := v.GetString("name") 144 | age, _ := v.GetNumber("age") 145 | occupation, _ := v.GetString("other", "occupation") 146 | years, _ := v.GetNumber("other", "years") 147 | 148 | log.Println("age:", age) 149 | log.Println("name:", name) 150 | log.Println("occupation:", occupation) 151 | log.Println("years:", years) 152 | 153 | children, _ := v.GetStringArray("children") 154 | for i, child := range children { 155 | log.Printf("child %d: %s", i, child) 156 | } 157 | 158 | others, _ := v.GetObject("other") 159 | 160 | for _, value := range others.Map() { 161 | 162 | s, sErr := value.String() 163 | n, nErr := value.Number() 164 | 165 | if sErr == nil { 166 | log.Println("string value: ", s) 167 | } else if nErr == nil { 168 | log.Println("number value: ", n) 169 | } 170 | } 171 | } 172 | 173 | ``` 174 | 175 | ## Documentation 176 | 177 | Documentation can be found a godoc: 178 | 179 | https://godoc.org/github.com/antonholmquist/jason 180 | 181 | ## Test 182 | To run the project tests: 183 | 184 | ```shell 185 | go test 186 | ``` 187 | 188 | ## Compatibility 189 | 190 | Go 1.1 and up. 191 | 192 | ## Where does the name come from? 193 | 194 | I remebered it from an email one of our projects managers sent a couple of years ago. 195 | 196 | > "Don't worry. We can handle both XML and Jason" 197 | 198 | ## Author 199 | 200 | Anton Holmquist, http://twitter.com/antonholmquist 201 | -------------------------------------------------------------------------------- /vendor/github.com/bsiegert/ranges/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2011 The Go Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | include $(GOROOT)/src/Make.inc 6 | 7 | TARG=ranges 8 | GOFILES=\ 9 | ranges.go\ 10 | 11 | include $(GOROOT)/src/Make.pkg 12 | 13 | README: $(GOFILES) 14 | godoc . > README 15 | -------------------------------------------------------------------------------- /vendor/github.com/bsiegert/ranges/README: -------------------------------------------------------------------------------- 1 | PACKAGE 2 | 3 | package ranges 4 | import "." 5 | 6 | Package ranges contains tools for working with integer ranges. 7 | 8 | 9 | An "integer range" allows to give a set of numbers as a string, which 10 | can be parsed by a call to Parse. The result can be obtained as a slice 11 | of integers by calling Expand or be tested against with Contains. 12 | 13 | FUNCTIONS 14 | 15 | func Parse(r string) ([]int, error) 16 | 17 | 18 | TYPES 19 | 20 | type IntRange struct { 21 | Lo, Hi int 22 | } 23 | An IntRange is a single component of an integer range expression. 24 | 25 | func (ir *IntRange) Clean() 26 | Clean exchanges the upper and lower bound if the upper bound is smaller 27 | than the lower one. 28 | 29 | func (ir *IntRange) Contains(value int) bool 30 | Contains returns true if ir contains value. 31 | 32 | func (ir *IntRange) Expand() []int 33 | Expand returns a sorted slice of integers that contains all the numbers 34 | in ir. 35 | 36 | type IntRanges []IntRange 37 | IntRanges is a slice of multiple integer ranges, allowing the expression 38 | of non-contiguous ranges (for example "1,3-4"). 39 | 40 | func (ir *IntRanges) Contains(value int) bool 41 | Contains returns true if ir contains value. 42 | 43 | func (ir *IntRanges) Expand() []int 44 | Expand returns a slice of integers that contains all the numbers in ir. 45 | If ir has been cleaned by calling Clean, the slice will be sorted. 46 | 47 | func (ir *IntRanges) Len() int 48 | Len returns the number of distinct ranges in ir. 49 | 50 | func (ir *IntRanges) Less(i, j int) bool 51 | Less returns true if the lower bound of the i-th element is smaller than 52 | the one of the j-th element. 53 | 54 | func (ir *IntRanges) Swap(i, j int) 55 | Swap swaps the i-th and the j-th element. 56 | 57 | 58 | -------------------------------------------------------------------------------- /vendor/github.com/bsiegert/ranges/ranges.go: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2011 3 | * Benny Siegert 4 | * 5 | * Provided that these terms and disclaimer and all copyright notices 6 | * are retained or reproduced in an accompanying document, permission 7 | * is granted to deal in this work without restriction, including un- 8 | * limited rights to use, publicly perform, distribute, sell, modify, 9 | * merge, give away, or sublicence. 10 | * 11 | * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to 12 | * the utmost extent permitted by applicable law, neither express nor 13 | * implied; without malicious intent or gross negligence. In no event 14 | * may a licensor, author or contributor be held liable for indirect, 15 | * direct, other damage, loss, or other issues arising in any way out 16 | * of dealing in the work, even if advised of the possibility of such 17 | * damage or existence of a defect, except proven that it results out 18 | * of said person's immediate fault when using the work as intended. 19 | */ 20 | 21 | // Package ranges contains tools for working with integer ranges. 22 | // 23 | // An "integer range" allows to give a set of numbers as a string, 24 | // which can be parsed by a call to Parse. The result can be obtained 25 | // as a slice of integers by calling Expand or be tested against with 26 | // Contains. 27 | package ranges 28 | 29 | import ( 30 | "fmt" 31 | "strconv" 32 | "strings" 33 | ) 34 | 35 | // An IntRange is a single component of an integer range expression. 36 | type IntRange struct { 37 | Lo, Hi int 38 | } 39 | 40 | // Contains returns true if ir contains value. 41 | func (ir *IntRange) Contains(value int) bool { 42 | return value >= ir.Lo && value <= ir.Hi 43 | } 44 | 45 | // Expand returns a sorted slice of integers that contains all the numbers 46 | // in ir. 47 | func (ir *IntRange) Expand() []int { 48 | e := make([]int, 0, ir.Hi-ir.Lo+1) 49 | for i := ir.Lo; i <= ir.Hi; i++ { 50 | e = append(e, i) 51 | } 52 | return e 53 | } 54 | 55 | // Clean exchanges the upper and lower bound if the upper bound is 56 | // smaller than the lower one. 57 | func (ir *IntRange) Clean() { 58 | if ir.Hi < ir.Lo { 59 | ir.Hi, ir.Lo = ir.Lo, ir.Hi 60 | } 61 | } 62 | 63 | // IntRanges is a slice of multiple integer ranges, allowing the 64 | // expression of non-contiguous ranges (for example "1,3-4"). 65 | type IntRanges []IntRange 66 | 67 | // Contains returns true if ir contains value. 68 | func (ir *IntRanges) Contains(value int) bool { 69 | for _, r := range *ir { 70 | if r.Contains(value) { 71 | return true 72 | } 73 | } 74 | return false 75 | } 76 | 77 | // Expand returns a slice of integers that contains all the numbers in ir. 78 | // If ir has been cleaned by calling Clean, the slice will be sorted. 79 | func (ir *IntRanges) Expand() []int { 80 | // This guess for the length is as good as any other. 81 | e := make([]int, 0, 2*len(*ir)) 82 | 83 | for _, r := range *ir { 84 | e = append(e, r.Expand()...) 85 | } 86 | return e 87 | } 88 | 89 | // Len returns the number of distinct ranges in ir. 90 | func (ir *IntRanges) Len() int { 91 | return len(*ir) 92 | } 93 | 94 | // Less returns true if the lower bound of the i-th element is smaller 95 | // than the one of the j-th element. 96 | func (ir *IntRanges) Less(i, j int) bool { 97 | return (*ir)[i].Lo < (*ir)[j].Lo 98 | } 99 | 100 | // Swap swaps the i-th and the j-th element. 101 | func (ir *IntRanges) Swap(i, j int) { 102 | (*ir)[i], (*ir)[j] = (*ir)[j], (*ir)[i] 103 | } 104 | 105 | func Parse(r string) ([]int, error) { 106 | var expanded []int 107 | 108 | for _, item := range strings.Split(r, ",") { 109 | lohi := strings.Split(item, "-") 110 | switch len(lohi) { 111 | case 1: 112 | v, err := strconv.Atoi(item) 113 | if err != nil { 114 | return nil, err 115 | } 116 | expanded = append(expanded, v) 117 | case 2: 118 | lo, err := strconv.Atoi(lohi[0]) 119 | if err != nil { 120 | return nil, err 121 | } 122 | hi, err := strconv.Atoi(lohi[1]) 123 | if err != nil { 124 | return nil, err 125 | } 126 | for i := lo; i <= hi; i++ { 127 | expanded = append(expanded, i) 128 | } 129 | default: 130 | return nil, fmt.Errorf("invalid range: %s", item) 131 | } 132 | } 133 | return expanded, nil 134 | } 135 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .DS_Store 26 | /vendor 27 | .vscode/ 28 | .mypy_cache/ 29 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - tip 5 | 6 | script: go test -v ./ -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 18/11/29 2 | 3 | - Move Tabpane from termui/extra to termui and rename it to TabPane 4 | - Rename PollEvent to PollEvents 5 | 6 | ## 18/11/28 7 | 8 | - Migrated from Dep to vgo 9 | - Overhauled the event system 10 | - check the wiki/examples for details 11 | - Renamed Par widget to Paragraph 12 | - Renamed MBarChart widget to StackedBarChart 13 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Zack Guo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/README.md: -------------------------------------------------------------------------------- 1 | # termui 2 | 3 | [![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) 4 | 5 | demo cast under osx 10.10; Terminal.app; Menlo Regular 12pt.) 6 | 7 | `termui` is a cross-platform, easy-to-compile, and fully-customizable terminal dashboard built on top of [termbox-go](https://github.com/nsf/termbox-go). It is inspired by [blessed-contrib](https://github.com/yaronn/blessed-contrib) and written purely in Go. 8 | 9 | **termui is currently undergoing some API changes so make sure to check the changelog when upgrading** 10 | 11 | ## Installation 12 | 13 | Installing from the master branch is recommended: 14 | 15 | ```bash 16 | go get -u github.com/gizak/termui@master 17 | ``` 18 | 19 | ## Usage 20 | 21 | ### Hello World 22 | 23 | ```go 24 | package main 25 | 26 | import ui "github.com/gizak/termui" 27 | 28 | func main() { 29 | err := ui.Init() 30 | if err != nil { 31 | panic(err) 32 | } 33 | defer ui.Close() 34 | 35 | p := ui.NewParagraph("Hello World!") 36 | p.Width = 25 37 | p.Height = 5 38 | ui.Render(p) 39 | 40 | for e := range ui.PollEvents() { 41 | if e.Type == ui.KeyboardEvent { 42 | break 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | ### Widgets 49 | 50 | Click image to see the corresponding demo codes. 51 | 52 | [barchart](https://github.com/gizak/termui/blob/master/_examples/barchart.go) 53 | [gauge](https://github.com/gizak/termui/blob/master/_examples/gauge.go) 54 | [linechart](https://github.com/gizak/termui/blob/master/_examples/linechart.go) 55 | [list](https://github.com/gizak/termui/blob/master/_examples/list.go) 56 | [paragraph](https://github.com/gizak/termui/blob/master/_examples/par.go) 57 | [sparklines](https://github.com/gizak/termui/blob/master/_examples/sparklines.go) 58 | [stackedbarchart](https://github.com/gizak/termui/blob/master/_examples/mbarchart.go) 59 | [table](https://github.com/gizak/termui/blob/master/_examples/table.go) 60 | 61 | ### Examples 62 | 63 | Examples can be found in [\_examples](./_examples). Run with `go run _examples/...` or run all of them consecutively with `./scripts/run_examples.py`. 64 | 65 | ## Documentation 66 | 67 | - [godoc](https://godoc.org/github.com/gizak/termui) for code documentation 68 | - [wiki](https://github.com/gizak/termui/wiki) for general information 69 | 70 | ## Uses 71 | 72 | - [go-ethereum/monitorcmd](https://github.com/ethereum/go-ethereum/blob/96116758d22ddbff4dbef2050d6b63a7b74502d8/cmd/geth/monitorcmd.go) 73 | 74 | ## Related Works 75 | 76 | - [blessed-contrib](https://github.com/yaronn/blessed-contrib) 77 | - [tui-rs](https://github.com/fdehau/tui-rs) 78 | - [gocui](https://github.com/jroimartin/gocui) 79 | 80 | ## License 81 | 82 | This library is under the [MIT License](http://opensource.org/licenses/MIT) 83 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/barchart.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import "fmt" 8 | 9 | // BarChart creates multiple bars in a widget: 10 | /* 11 | bc := termui.NewBarChart() 12 | data := []int{3, 2, 5, 3, 9, 5} 13 | bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"} 14 | bc.BorderLabel = "Bar Chart" 15 | bc.Data = data 16 | bc.Width = 26 17 | bc.Height = 10 18 | bc.DataLabels = bclabels 19 | bc.TextColor = termui.ColorGreen 20 | bc.BarColor = termui.ColorRed 21 | bc.NumColor = termui.ColorYellow 22 | */ 23 | type BarChart struct { 24 | Block 25 | BarColor Attribute 26 | TextColor Attribute 27 | NumColor Attribute 28 | NumFmt func(int) string 29 | Data []int 30 | DataLabels []string 31 | BarWidth int 32 | BarGap int 33 | CellChar rune 34 | labels [][]rune 35 | dataNum [][]rune 36 | numBar int 37 | scale float64 38 | max int 39 | } 40 | 41 | // NewBarChart returns a new *BarChart with current theme. 42 | func NewBarChart() *BarChart { 43 | return &BarChart{ 44 | Block: *NewBlock(), 45 | BarColor: ThemeAttr("barchart.bar.bg"), 46 | NumColor: ThemeAttr("barchart.num.fg"), 47 | TextColor: ThemeAttr("barchart.text.fg"), 48 | NumFmt: func(n int) string { return fmt.Sprint(n) }, 49 | BarGap: 1, 50 | BarWidth: 3, 51 | CellChar: ' ', 52 | } 53 | } 54 | 55 | func (bc *BarChart) layout() { 56 | bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth) 57 | bc.labels = make([][]rune, bc.numBar) 58 | bc.dataNum = make([][]rune, len(bc.Data)) 59 | 60 | for i := 0; i < bc.numBar && i < len(bc.DataLabels) && i < len(bc.Data); i++ { 61 | bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth) 62 | n := bc.Data[i] 63 | s := bc.NumFmt(n) 64 | bc.dataNum[i] = trimStr2Runes(s, bc.BarWidth) 65 | } 66 | 67 | //bc.max = bc.Data[0] // what if Data is nil? Sometimes when bar graph is nill it produces panic with panic: runtime error: index out of range 68 | // Assign a negative value to get maxvalue auto-populates 69 | if bc.max == 0 { 70 | bc.max = -1 71 | } 72 | for i := 0; i < len(bc.Data); i++ { 73 | if bc.max < bc.Data[i] { 74 | bc.max = bc.Data[i] 75 | } 76 | } 77 | bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1) 78 | } 79 | 80 | func (bc *BarChart) SetMax(max int) { 81 | if max > 0 { 82 | bc.max = max 83 | } 84 | } 85 | 86 | // Buffer implements Bufferer interface. 87 | func (bc *BarChart) Buffer() Buffer { 88 | buf := bc.Block.Buffer() 89 | bc.layout() 90 | 91 | for i := 0; i < bc.numBar && i < len(bc.Data) && i < len(bc.DataLabels); i++ { 92 | h := int(float64(bc.Data[i]) / bc.scale) 93 | oftX := i * (bc.BarWidth + bc.BarGap) 94 | 95 | barBg := bc.Bg 96 | barFg := bc.BarColor 97 | 98 | if bc.CellChar == ' ' { 99 | barBg = bc.BarColor 100 | barFg = ColorDefault 101 | if bc.BarColor == ColorDefault { // the same as above 102 | barBg |= AttrReverse 103 | } 104 | } 105 | 106 | // plot bar 107 | for j := 0; j < bc.BarWidth; j++ { 108 | for k := 0; k < h; k++ { 109 | c := Cell{ 110 | Ch: bc.CellChar, 111 | Bg: barBg, 112 | Fg: barFg, 113 | } 114 | 115 | x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j 116 | y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k 117 | buf.Set(x, y, c) 118 | } 119 | } 120 | // plot text 121 | for j, k := 0, 0; j < len(bc.labels[i]); j++ { 122 | w := charWidth(bc.labels[i][j]) 123 | c := Cell{ 124 | Ch: bc.labels[i][j], 125 | Bg: bc.Bg, 126 | Fg: bc.TextColor, 127 | } 128 | y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1 129 | x := bc.innerArea.Min.X + oftX + k 130 | buf.Set(x, y, c) 131 | k += w 132 | } 133 | // plot num 134 | for j := 0; j < len(bc.dataNum[i]); j++ { 135 | c := Cell{ 136 | Ch: bc.dataNum[i][j], 137 | Fg: bc.NumColor, 138 | Bg: barBg, 139 | } 140 | 141 | if h == 0 { 142 | c.Bg = bc.Bg 143 | } 144 | x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i]))/2 + j 145 | y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 146 | buf.Set(x, y, c) 147 | } 148 | } 149 | 150 | return buf 151 | } 152 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/block.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import "image" 8 | 9 | // Hline is a horizontal line. 10 | type Hline struct { 11 | X int 12 | Y int 13 | Len int 14 | Fg Attribute 15 | Bg Attribute 16 | } 17 | 18 | // Vline is a vertical line. 19 | type Vline struct { 20 | X int 21 | Y int 22 | Len int 23 | Fg Attribute 24 | Bg Attribute 25 | } 26 | 27 | // Buffer draws a horizontal line. 28 | func (l Hline) Buffer() Buffer { 29 | if l.Len <= 0 { 30 | return NewBuffer() 31 | } 32 | return NewFilledBuffer(l.X, l.Y, l.X+l.Len, l.Y+1, HORIZONTAL_LINE, l.Fg, l.Bg) 33 | } 34 | 35 | // Buffer draws a vertical line. 36 | func (l Vline) Buffer() Buffer { 37 | if l.Len <= 0 { 38 | return NewBuffer() 39 | } 40 | return NewFilledBuffer(l.X, l.Y, l.X+1, l.Y+l.Len, VERTICAL_LINE, l.Fg, l.Bg) 41 | } 42 | 43 | // Buffer draws a box border. 44 | func (b Block) drawBorder(buf Buffer) { 45 | if !b.Border { 46 | return 47 | } 48 | 49 | min := b.area.Min 50 | max := b.area.Max 51 | 52 | x0 := min.X 53 | y0 := min.Y 54 | x1 := max.X - 1 55 | y1 := max.Y - 1 56 | 57 | // draw lines 58 | if b.BorderTop { 59 | buf.Merge(Hline{x0, y0, x1 - x0, b.BorderFg, b.BorderBg}.Buffer()) 60 | } 61 | if b.BorderBottom { 62 | buf.Merge(Hline{x0, y1, x1 - x0, b.BorderFg, b.BorderBg}.Buffer()) 63 | } 64 | if b.BorderLeft { 65 | buf.Merge(Vline{x0, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer()) 66 | } 67 | if b.BorderRight { 68 | buf.Merge(Vline{x1, y0, y1 - y0, b.BorderFg, b.BorderBg}.Buffer()) 69 | } 70 | 71 | // draw corners 72 | if b.BorderTop && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 0 { 73 | buf.Set(x0, y0, Cell{TOP_LEFT, b.BorderFg, b.BorderBg}) 74 | } 75 | if b.BorderTop && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 0 { 76 | buf.Set(x1, y0, Cell{TOP_RIGHT, b.BorderFg, b.BorderBg}) 77 | } 78 | if b.BorderBottom && b.BorderLeft && b.area.Dx() > 0 && b.area.Dy() > 1 { 79 | buf.Set(x0, y1, Cell{BOTTOM_LEFT, b.BorderFg, b.BorderBg}) 80 | } 81 | if b.BorderBottom && b.BorderRight && b.area.Dx() > 1 && b.area.Dy() > 1 { 82 | buf.Set(x1, y1, Cell{BOTTOM_RIGHT, b.BorderFg, b.BorderBg}) 83 | } 84 | } 85 | 86 | func (b Block) drawBorderLabel(buf Buffer) { 87 | maxTxtW := b.area.Dx() - 2 88 | tx := DTrimTxCls(DefaultTxBuilder.Build(b.BorderLabel, b.BorderLabelFg, b.BorderLabelBg), maxTxtW) 89 | 90 | for i, w := 0, 0; i < len(tx); i++ { 91 | buf.Set(b.area.Min.X+1+w, b.area.Min.Y, tx[i]) 92 | w += tx[i].Width() 93 | } 94 | } 95 | 96 | // Block is a base struct for all other upper level widgets, 97 | // consider it as css: display:block. 98 | // Normally you do not need to create it manually. 99 | type Block struct { 100 | area image.Rectangle 101 | innerArea image.Rectangle 102 | X int 103 | Y int 104 | Border bool 105 | BorderFg Attribute 106 | BorderBg Attribute 107 | BorderLeft bool 108 | BorderRight bool 109 | BorderTop bool 110 | BorderBottom bool 111 | BorderLabel string 112 | BorderLabelFg Attribute 113 | BorderLabelBg Attribute 114 | Display bool 115 | Bg Attribute 116 | Width int 117 | Height int 118 | PaddingTop int 119 | PaddingBottom int 120 | PaddingLeft int 121 | PaddingRight int 122 | id string 123 | Float Align 124 | } 125 | 126 | // NewBlock returns a *Block which inherits styles from current theme. 127 | func NewBlock() *Block { 128 | return &Block{ 129 | Display: true, 130 | Border: true, 131 | BorderLeft: true, 132 | BorderRight: true, 133 | BorderTop: true, 134 | BorderBottom: true, 135 | BorderBg: ThemeAttr("border.bg"), 136 | BorderFg: ThemeAttr("border.fg"), 137 | BorderLabelBg: ThemeAttr("label.bg"), 138 | BorderLabelFg: ThemeAttr("label.fg"), 139 | Bg: ThemeAttr("block.bg"), 140 | Width: 2, 141 | Height: 2, 142 | id: GenId(), 143 | Float: AlignNone, 144 | } 145 | } 146 | 147 | func (b Block) Id() string { 148 | return b.id 149 | } 150 | 151 | // Align computes box model 152 | func (b *Block) Align() { 153 | // outer 154 | b.area.Min.X = 0 155 | b.area.Min.Y = 0 156 | b.area.Max.X = b.Width 157 | b.area.Max.Y = b.Height 158 | 159 | // float 160 | b.area = AlignArea(TermRect(), b.area, b.Float) 161 | b.area = MoveArea(b.area, b.X, b.Y) 162 | 163 | // inner 164 | b.innerArea.Min.X = b.area.Min.X + b.PaddingLeft 165 | b.innerArea.Min.Y = b.area.Min.Y + b.PaddingTop 166 | b.innerArea.Max.X = b.area.Max.X - b.PaddingRight 167 | b.innerArea.Max.Y = b.area.Max.Y - b.PaddingBottom 168 | 169 | if b.Border { 170 | if b.BorderLeft { 171 | b.innerArea.Min.X++ 172 | } 173 | if b.BorderRight { 174 | b.innerArea.Max.X-- 175 | } 176 | if b.BorderTop { 177 | b.innerArea.Min.Y++ 178 | } 179 | if b.BorderBottom { 180 | b.innerArea.Max.Y-- 181 | } 182 | } 183 | } 184 | 185 | // InnerBounds returns the internal bounds of the block after aligning and 186 | // calculating the padding and border, if any. 187 | func (b *Block) InnerBounds() image.Rectangle { 188 | b.Align() 189 | return b.innerArea 190 | } 191 | 192 | // Buffer implements Bufferer interface. 193 | // Draw background and border (if any). 194 | func (b *Block) Buffer() Buffer { 195 | b.Align() 196 | 197 | buf := NewBuffer() 198 | buf.SetArea(b.area) 199 | buf.Fill(' ', ColorDefault, b.Bg) 200 | 201 | b.drawBorder(buf) 202 | b.drawBorderLabel(buf) 203 | 204 | return buf 205 | } 206 | 207 | // GetHeight implements GridBufferer. 208 | // It returns current height of the block. 209 | func (b Block) GetHeight() int { 210 | return b.Height 211 | } 212 | 213 | // SetX implements GridBufferer interface, which sets block's x position. 214 | func (b *Block) SetX(x int) { 215 | b.X = x 216 | } 217 | 218 | // SetY implements GridBufferer interface, it sets y position for block. 219 | func (b *Block) SetY(y int) { 220 | b.Y = y 221 | } 222 | 223 | // SetWidth implements GridBuffer interface, it sets block's width. 224 | func (b *Block) SetWidth(w int) { 225 | b.Width = w 226 | } 227 | 228 | func (b Block) InnerWidth() int { 229 | return b.innerArea.Dx() 230 | } 231 | 232 | func (b Block) InnerHeight() int { 233 | return b.innerArea.Dy() 234 | } 235 | 236 | func (b Block) InnerX() int { 237 | return b.innerArea.Min.X 238 | } 239 | 240 | func (b Block) InnerY() int { return b.innerArea.Min.Y } 241 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/block_common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | // +build !windows 6 | 7 | package termui 8 | 9 | const TOP_RIGHT = '┐' 10 | const VERTICAL_LINE = '│' 11 | const HORIZONTAL_LINE = '─' 12 | const TOP_LEFT = '┌' 13 | const BOTTOM_RIGHT = '┘' 14 | const BOTTOM_LEFT = '└' 15 | const VERTICAL_LEFT = '┤' 16 | const VERTICAL_RIGHT = '├' 17 | const HORIZONTAL_DOWN = '┬' 18 | const HORIZONTAL_UP = '┴' 19 | const QUOTA_LEFT = '«' 20 | const QUOTA_RIGHT = '»' 21 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/block_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | // +build windows 6 | 7 | package termui 8 | 9 | const TOP_RIGHT = '+' 10 | const VERTICAL_LINE = '|' 11 | const HORIZONTAL_LINE = '-' 12 | const TOP_LEFT = '+' 13 | const BOTTOM_RIGHT = '+' 14 | const BOTTOM_LEFT = '+' 15 | const VERTICAL_LEFT = '+' 16 | const VERTICAL_RIGHT = '+' 17 | const HORIZONTAL_DOWN = '+' 18 | const HORIZONTAL_UP = '+' 19 | const QUOTA_LEFT = '<' 20 | const QUOTA_RIGHT = '>' 21 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/buffer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import "image" 8 | 9 | // Cell is a rune with assigned Fg and Bg 10 | type Cell struct { 11 | Ch rune 12 | Fg Attribute 13 | Bg Attribute 14 | } 15 | 16 | // Buffer is a renderable rectangle cell data container. 17 | type Buffer struct { 18 | Area image.Rectangle // selected drawing area 19 | CellMap map[image.Point]Cell 20 | } 21 | 22 | // At returns the cell at (x,y). 23 | func (b Buffer) At(x, y int) Cell { 24 | return b.CellMap[image.Pt(x, y)] 25 | } 26 | 27 | // Set assigns a char to (x,y) 28 | func (b Buffer) Set(x, y int, c Cell) { 29 | b.CellMap[image.Pt(x, y)] = c 30 | } 31 | 32 | // Bounds returns the domain for which At can return non-zero color. 33 | func (b Buffer) Bounds() image.Rectangle { 34 | x0, y0, x1, y1 := 0, 0, 0, 0 35 | for p := range b.CellMap { 36 | if p.X > x1 { 37 | x1 = p.X 38 | } 39 | if p.X < x0 { 40 | x0 = p.X 41 | } 42 | if p.Y > y1 { 43 | y1 = p.Y 44 | } 45 | if p.Y < y0 { 46 | y0 = p.Y 47 | } 48 | } 49 | return image.Rect(x0, y0, x1+1, y1+1) 50 | } 51 | 52 | // SetArea assigns a new rect area to Buffer b. 53 | func (b *Buffer) SetArea(r image.Rectangle) { 54 | b.Area.Max = r.Max 55 | b.Area.Min = r.Min 56 | } 57 | 58 | // Sync sets drawing area to the buffer's bound 59 | func (b *Buffer) Sync() { 60 | b.SetArea(b.Bounds()) 61 | } 62 | 63 | // NewCell returns a new cell 64 | func NewCell(ch rune, fg, bg Attribute) Cell { 65 | return Cell{ch, fg, bg} 66 | } 67 | 68 | // Merge merges bs Buffers onto b 69 | func (b *Buffer) Merge(bs ...Buffer) { 70 | for _, buf := range bs { 71 | for p, v := range buf.CellMap { 72 | b.Set(p.X, p.Y, v) 73 | } 74 | b.SetArea(b.Area.Union(buf.Area)) 75 | } 76 | } 77 | 78 | // NewBuffer returns a new Buffer 79 | func NewBuffer() Buffer { 80 | return Buffer{ 81 | CellMap: make(map[image.Point]Cell), 82 | Area: image.Rectangle{}} 83 | } 84 | 85 | // Fill fills the Buffer b with ch,fg and bg. 86 | func (b Buffer) Fill(ch rune, fg, bg Attribute) { 87 | for x := b.Area.Min.X; x < b.Area.Max.X; x++ { 88 | for y := b.Area.Min.Y; y < b.Area.Max.Y; y++ { 89 | b.Set(x, y, Cell{ch, fg, bg}) 90 | } 91 | } 92 | } 93 | 94 | // NewFilledBuffer returns a new Buffer filled with ch, fb and bg. 95 | func NewFilledBuffer(x0, y0, x1, y1 int, ch rune, fg, bg Attribute) Buffer { 96 | buf := NewBuffer() 97 | buf.Area.Min = image.Pt(x0, y0) 98 | buf.Area.Max = image.Pt(x1, y1) 99 | 100 | for x := buf.Area.Min.X; x < buf.Area.Max.X; x++ { 101 | for y := buf.Area.Min.Y; y < buf.Area.Max.Y; y++ { 102 | buf.Set(x, y, Cell{ch, fg, bg}) 103 | } 104 | } 105 | return buf 106 | } 107 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/canvas.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | /* 8 | dots: 9 | ,___, 10 | |1 4| 11 | |2 5| 12 | |3 6| 13 | |7 8| 14 | ````` 15 | */ 16 | 17 | var brailleBase = '\u2800' 18 | 19 | var brailleOftMap = [4][2]rune{ 20 | {'\u0001', '\u0008'}, 21 | {'\u0002', '\u0010'}, 22 | {'\u0004', '\u0020'}, 23 | {'\u0040', '\u0080'}} 24 | 25 | // Canvas contains drawing map: i,j -> rune 26 | type Canvas map[[2]int]rune 27 | 28 | // NewCanvas returns an empty Canvas 29 | func NewCanvas() Canvas { 30 | return make(map[[2]int]rune) 31 | } 32 | 33 | func chOft(x, y int) rune { 34 | return brailleOftMap[y%4][x%2] 35 | } 36 | 37 | func (c Canvas) rawCh(x, y int) rune { 38 | if ch, ok := c[[2]int{x, y}]; ok { 39 | return ch 40 | } 41 | return '\u0000' //brailleOffset 42 | } 43 | 44 | // return coordinate in terminal 45 | func chPos(x, y int) (int, int) { 46 | return y / 4, x / 2 47 | } 48 | 49 | // Set sets a point (x,y) in the virtual coordinate 50 | func (c Canvas) Set(x, y int) { 51 | i, j := chPos(x, y) 52 | ch := c.rawCh(i, j) 53 | ch |= chOft(x, y) 54 | c[[2]int{i, j}] = ch 55 | } 56 | 57 | // Unset removes point (x,y) 58 | func (c Canvas) Unset(x, y int) { 59 | i, j := chPos(x, y) 60 | ch := c.rawCh(i, j) 61 | ch &= ^chOft(x, y) 62 | c[[2]int{i, j}] = ch 63 | } 64 | 65 | // Buffer returns un-styled points 66 | func (c Canvas) Buffer() Buffer { 67 | buf := NewBuffer() 68 | for k, v := range c { 69 | buf.Set(k[0], k[1], Cell{Ch: v + brailleBase}) 70 | } 71 | return buf 72 | } 73 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | /* 6 | Package termui is a library for creating terminal user interfaces (TUIs) using widgets. 7 | */ 8 | package termui 9 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/events.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import ( 8 | "strconv" 9 | 10 | tb "github.com/nsf/termbox-go" 11 | ) 12 | 13 | /* 14 | List of events: 15 | mouse events: 16 | 17 | 18 | keyboard events: 19 | any uppercase or lowercase letter or a set of two letters like j or jj or J or JJ 20 | etc 21 | etc 22 | 23 | 24 | 25 | > etc 26 | terminal events: 27 | 28 | */ 29 | 30 | type EventType int 31 | 32 | const ( 33 | KeyboardEvent EventType = iota 34 | MouseEvent 35 | ResizeEvent 36 | ) 37 | 38 | type Event struct { 39 | Type EventType 40 | ID string 41 | Payload interface{} 42 | } 43 | 44 | // Mouse payload. 45 | type Mouse struct { 46 | Drag bool 47 | X int 48 | Y int 49 | } 50 | 51 | // Resize payload. 52 | type Resize struct { 53 | Width int 54 | Height int 55 | } 56 | 57 | // PollEvents gets events from termbox, converts them, then sends them to each of its channels. 58 | func PollEvents() <-chan Event { 59 | ch := make(chan Event) 60 | go func() { 61 | for { 62 | ch <- convertTermboxEvent(tb.PollEvent()) 63 | } 64 | }() 65 | return ch 66 | } 67 | 68 | // convertTermboxKeyboardEvent converts a termbox keyboard event to a more friendly string format. 69 | // Combines modifiers into the string instead of having them as additional fields in an event. 70 | func convertTermboxKeyboardEvent(e tb.Event) Event { 71 | k := string(e.Ch) 72 | pre := "" 73 | mod := "" 74 | 75 | if e.Mod == tb.ModAlt { 76 | mod = " 0xFFFF-12 { 80 | k = "" 81 | } else if e.Key > 0xFFFF-25 { 82 | ks := []string{"", "", "", "", "", "", "", "", "", ""} 83 | k = ks[0xFFFF-int(e.Key)-12] 84 | } 85 | 86 | if e.Key <= 0x7F { 87 | pre = ""}, 91 | tb.KeyBackspace: {"", ""}, 92 | tb.KeyTab: {"", ""}, 93 | tb.KeyEnter: {"", ""}, 94 | tb.KeyEsc: {"", ""}, 95 | tb.KeyCtrlBackslash: {"C-", "\\"}, 96 | tb.KeyCtrlSlash: {"C-", "/"}, 97 | tb.KeySpace: {"", ""}, 98 | tb.KeyCtrl8: {"C-", "8"}, 99 | } 100 | if sk, ok := kmap[e.Key]; ok { 101 | pre = sk[0] 102 | k = sk[1] 103 | } 104 | } 105 | } 106 | 107 | if pre != "" { 108 | k += ">" 109 | } 110 | 111 | id := pre + mod + k 112 | 113 | return Event{ 114 | Type: KeyboardEvent, 115 | ID: id, 116 | } 117 | } 118 | 119 | func convertTermboxMouseEvent(e tb.Event) Event { 120 | mouseButtonMap := map[tb.Key]string{ 121 | tb.MouseLeft: "", 122 | tb.MouseMiddle: "", 123 | tb.MouseRight: "", 124 | tb.MouseRelease: "", 125 | tb.MouseWheelUp: "", 126 | tb.MouseWheelDown: "", 127 | } 128 | 129 | converted, ok := mouseButtonMap[e.Key] 130 | if !ok { 131 | converted = "Unknown_Mouse_Button" 132 | } 133 | 134 | Drag := false 135 | if e.Mod == tb.ModMotion { 136 | Drag = true 137 | } 138 | 139 | return Event{ 140 | Type: MouseEvent, 141 | ID: converted, 142 | Payload: Mouse{ 143 | X: e.MouseX, 144 | Y: e.MouseY, 145 | Drag: Drag, 146 | }, 147 | } 148 | } 149 | 150 | // convertTermboxEvent turns a termbox event into a termui event. 151 | func convertTermboxEvent(e tb.Event) Event { 152 | if e.Type == tb.EventError { 153 | panic(e.Err) 154 | } 155 | 156 | var event Event 157 | 158 | switch e.Type { 159 | case tb.EventKey: 160 | event = convertTermboxKeyboardEvent(e) 161 | case tb.EventMouse: 162 | event = convertTermboxMouseEvent(e) 163 | case tb.EventResize: 164 | event = Event{ 165 | Type: ResizeEvent, 166 | ID: "", 167 | Payload: Resize{ 168 | Width: e.Width, 169 | Height: e.Height, 170 | }, 171 | } 172 | } 173 | 174 | return event 175 | } 176 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/gauge.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import ( 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // Gauge is a progress bar like widget. 13 | // A simple example: 14 | /* 15 | g := termui.NewGauge() 16 | g.Percent = 40 17 | g.Width = 50 18 | g.Height = 3 19 | g.BorderLabel = "Slim Gauge" 20 | g.BarColor = termui.ColorRed 21 | g.PercentColor = termui.ColorBlue 22 | */ 23 | 24 | const ColorUndef Attribute = Attribute(^uint16(0)) 25 | 26 | type Gauge struct { 27 | Block 28 | Percent int 29 | BarColor Attribute 30 | PercentColor Attribute 31 | PercentColorHighlighted Attribute 32 | Label string 33 | LabelAlign Align 34 | } 35 | 36 | // NewGauge return a new gauge with current theme. 37 | func NewGauge() *Gauge { 38 | g := &Gauge{ 39 | Block: *NewBlock(), 40 | PercentColor: ThemeAttr("gauge.percent.fg"), 41 | BarColor: ThemeAttr("gauge.bar.bg"), 42 | Label: "{{percent}}%", 43 | LabelAlign: AlignCenter, 44 | PercentColorHighlighted: ColorUndef, 45 | } 46 | g.Width = 12 47 | g.Height = 5 48 | return g 49 | } 50 | 51 | // Buffer implements Bufferer interface. 52 | func (g *Gauge) Buffer() Buffer { 53 | buf := g.Block.Buffer() 54 | 55 | // plot bar 56 | w := g.Percent * g.innerArea.Dx() / 100 57 | for i := 0; i < g.innerArea.Dy(); i++ { 58 | for j := 0; j < w; j++ { 59 | c := Cell{} 60 | c.Ch = ' ' 61 | c.Bg = g.BarColor 62 | if c.Bg == ColorDefault { 63 | c.Bg |= AttrReverse 64 | } 65 | buf.Set(g.innerArea.Min.X+j, g.innerArea.Min.Y+i, c) 66 | } 67 | } 68 | 69 | // plot percentage 70 | s := strings.Replace(g.Label, "{{percent}}", strconv.Itoa(g.Percent), -1) 71 | pry := g.innerArea.Min.Y + g.innerArea.Dy()/2 72 | rs := str2runes(s) 73 | var pos int 74 | switch g.LabelAlign { 75 | case AlignLeft: 76 | pos = 0 77 | 78 | case AlignCenter: 79 | pos = (g.innerArea.Dx() - strWidth(s)) / 2 80 | 81 | case AlignRight: 82 | pos = g.innerArea.Dx() - strWidth(s) - 1 83 | } 84 | pos += g.innerArea.Min.X 85 | 86 | for i, v := range rs { 87 | c := Cell{ 88 | Ch: v, 89 | Fg: g.PercentColor, 90 | } 91 | 92 | if w+g.innerArea.Min.X > pos+i { 93 | c.Bg = g.BarColor 94 | if c.Bg == ColorDefault { 95 | c.Bg |= AttrReverse 96 | } 97 | 98 | if g.PercentColorHighlighted != ColorUndef { 99 | c.Fg = g.PercentColorHighlighted 100 | } 101 | } else { 102 | c.Bg = g.Block.Bg 103 | } 104 | 105 | buf.Set(1+pos+i, pry, c) 106 | } 107 | return buf 108 | } 109 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gizak/termui 2 | 3 | require ( 4 | github.com/davecgh/go-spew v1.1.0 5 | github.com/mattn/go-runewidth v0.0.2 6 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 7 | github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb 8 | github.com/pmezard/go-difflib v1.0.0 // indirect 9 | github.com/stretchr/testify v1.2.2 10 | golang.org/x/net v0.0.0-20180801234040-f4c29de78a2a 11 | ) 12 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o= 4 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 5 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= 6 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 7 | github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb h1:YahEjAGkJtCrkqgVHhX6n8ZX+CZ3hDRL9fjLYugLfSs= 8 | github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 12 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 13 | golang.org/x/net v0.0.0-20180801234040-f4c29de78a2a h1:8fCF9zjAir2SP3N+axz9xs+0r4V8dqPzqsWO10t8zoo= 14 | golang.org/x/net v0.0.0-20180801234040-f4c29de78a2a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 15 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/grid.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | // GridBufferer introduces a Bufferer that can be manipulated by Grid. 8 | type GridBufferer interface { 9 | Bufferer 10 | GetHeight() int 11 | SetWidth(int) 12 | SetX(int) 13 | SetY(int) 14 | } 15 | 16 | // Row builds a layout tree 17 | type Row struct { 18 | Cols []*Row //children 19 | Widget GridBufferer // root 20 | X int 21 | Y int 22 | Width int 23 | Height int 24 | Span int 25 | Offset int 26 | } 27 | 28 | // calculate and set the underlying layout tree's x, y, height and width. 29 | func (r *Row) calcLayout() { 30 | r.assignWidth(r.Width) 31 | r.Height = r.solveHeight() 32 | r.assignX(r.X) 33 | r.assignY(r.Y) 34 | } 35 | 36 | // tell if the node is leaf in the tree. 37 | func (r *Row) isLeaf() bool { 38 | return r.Cols == nil || len(r.Cols) == 0 39 | } 40 | 41 | func (r *Row) isRenderableLeaf() bool { 42 | return r.isLeaf() && r.Widget != nil 43 | } 44 | 45 | // assign widgets' (and their parent rows') width recursively. 46 | func (r *Row) assignWidth(w int) { 47 | r.SetWidth(w) 48 | 49 | accW := 0 // acc span and offset 50 | calcW := make([]int, len(r.Cols)) // calculated width 51 | calcOftX := make([]int, len(r.Cols)) // computed start position of x 52 | 53 | for i, c := range r.Cols { 54 | accW += c.Span + c.Offset 55 | cw := int(float64(c.Span*r.Width) / 12.0) 56 | 57 | if i >= 1 { 58 | calcOftX[i] = calcOftX[i-1] + 59 | calcW[i-1] + 60 | int(float64(r.Cols[i-1].Offset*r.Width)/12.0) 61 | } 62 | 63 | // use up the space if it is the last col 64 | if i == len(r.Cols)-1 && accW == 12 { 65 | cw = r.Width - calcOftX[i] 66 | } 67 | calcW[i] = cw 68 | r.Cols[i].assignWidth(cw) 69 | } 70 | } 71 | 72 | // bottom up calc and set rows' (and their widgets') height, 73 | // return r's total height. 74 | func (r *Row) solveHeight() int { 75 | if r.isRenderableLeaf() { 76 | r.Height = r.Widget.GetHeight() 77 | return r.Widget.GetHeight() 78 | } 79 | 80 | maxh := 0 81 | if !r.isLeaf() { 82 | for _, c := range r.Cols { 83 | nh := c.solveHeight() 84 | // when embed rows in Cols, row widgets stack up 85 | if r.Widget != nil { 86 | nh += r.Widget.GetHeight() 87 | } 88 | if nh > maxh { 89 | maxh = nh 90 | } 91 | } 92 | } 93 | 94 | r.Height = maxh 95 | return maxh 96 | } 97 | 98 | // recursively assign x position for r tree. 99 | func (r *Row) assignX(x int) { 100 | r.SetX(x) 101 | 102 | if !r.isLeaf() { 103 | acc := 0 104 | for i, c := range r.Cols { 105 | if c.Offset != 0 { 106 | acc += int(float64(c.Offset*r.Width) / 12.0) 107 | } 108 | r.Cols[i].assignX(x + acc) 109 | acc += c.Width 110 | } 111 | } 112 | } 113 | 114 | // recursively assign y position to r. 115 | func (r *Row) assignY(y int) { 116 | r.SetY(y) 117 | 118 | if r.isLeaf() { 119 | return 120 | } 121 | 122 | for i := range r.Cols { 123 | acc := 0 124 | if r.Widget != nil { 125 | acc = r.Widget.GetHeight() 126 | } 127 | r.Cols[i].assignY(y + acc) 128 | } 129 | 130 | } 131 | 132 | // GetHeight implements GridBufferer interface. 133 | func (r Row) GetHeight() int { 134 | return r.Height 135 | } 136 | 137 | // SetX implements GridBufferer interface. 138 | func (r *Row) SetX(x int) { 139 | r.X = x 140 | if r.Widget != nil { 141 | r.Widget.SetX(x) 142 | } 143 | } 144 | 145 | // SetY implements GridBufferer interface. 146 | func (r *Row) SetY(y int) { 147 | r.Y = y 148 | if r.Widget != nil { 149 | r.Widget.SetY(y) 150 | } 151 | } 152 | 153 | // SetWidth implements GridBufferer interface. 154 | func (r *Row) SetWidth(w int) { 155 | r.Width = w 156 | if r.Widget != nil { 157 | r.Widget.SetWidth(w) 158 | } 159 | } 160 | 161 | // Buffer implements Bufferer interface, 162 | // recursively merge all widgets buffer 163 | func (r *Row) Buffer() Buffer { 164 | merged := NewBuffer() 165 | 166 | if r.isRenderableLeaf() { 167 | return r.Widget.Buffer() 168 | } 169 | 170 | // for those are not leaves but have a renderable widget 171 | if r.Widget != nil { 172 | merged.Merge(r.Widget.Buffer()) 173 | } 174 | 175 | // collect buffer from children 176 | if !r.isLeaf() { 177 | for _, c := range r.Cols { 178 | merged.Merge(c.Buffer()) 179 | } 180 | } 181 | 182 | return merged 183 | } 184 | 185 | // Grid implements 12 columns system. 186 | // A simple example: 187 | /* 188 | import ui "github.com/gizak/termui" 189 | // init and create widgets... 190 | 191 | // build 192 | ui.Body.AddRows( 193 | ui.NewRow( 194 | ui.NewCol(6, 0, widget0), 195 | ui.NewCol(6, 0, widget1)), 196 | ui.NewRow( 197 | ui.NewCol(3, 0, widget2), 198 | ui.NewCol(3, 0, widget30, widget31, widget32), 199 | ui.NewCol(6, 0, widget4))) 200 | 201 | // calculate layout 202 | ui.Body.Align() 203 | 204 | ui.Render(ui.Body) 205 | */ 206 | type Grid struct { 207 | Rows []*Row 208 | Width int 209 | X int 210 | Y int 211 | BgColor Attribute 212 | } 213 | 214 | // NewGrid returns *Grid with given rows. 215 | func NewGrid(rows ...*Row) *Grid { 216 | return &Grid{Rows: rows} 217 | } 218 | 219 | // AddRows appends given rows to Grid. 220 | func (g *Grid) AddRows(rs ...*Row) { 221 | g.Rows = append(g.Rows, rs...) 222 | } 223 | 224 | // NewRow creates a new row out of given columns. 225 | func NewRow(cols ...*Row) *Row { 226 | rs := &Row{Span: 12, Cols: cols} 227 | return rs 228 | } 229 | 230 | // NewCol accepts: widgets are LayoutBufferer or widgets is A NewRow. 231 | // Note that if multiple widgets are provided, they will stack up in the col. 232 | func NewCol(span, offset int, widgets ...GridBufferer) *Row { 233 | r := &Row{Span: span, Offset: offset} 234 | 235 | if widgets != nil && len(widgets) == 1 { 236 | wgt := widgets[0] 237 | nw, isRow := wgt.(*Row) 238 | if isRow { 239 | r.Cols = nw.Cols 240 | } else { 241 | r.Widget = wgt 242 | } 243 | return r 244 | } 245 | 246 | r.Cols = []*Row{} 247 | ir := r 248 | for _, w := range widgets { 249 | nr := &Row{Span: 12, Widget: w} 250 | ir.Cols = []*Row{nr} 251 | ir = nr 252 | } 253 | 254 | return r 255 | } 256 | 257 | // Align calculate each rows' layout. 258 | func (g *Grid) Align() { 259 | h := 0 260 | for _, r := range g.Rows { 261 | r.SetWidth(g.Width) 262 | r.SetX(g.X) 263 | r.SetY(g.Y + h) 264 | r.calcLayout() 265 | h += r.GetHeight() 266 | } 267 | } 268 | 269 | // Buffer implements Bufferer interface. 270 | func (g Grid) Buffer() Buffer { 271 | buf := NewBuffer() 272 | 273 | for _, r := range g.Rows { 274 | buf.Merge(r.Buffer()) 275 | } 276 | return buf 277 | } 278 | 279 | var Body *Grid 280 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/linechart_others.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | // +build !windows 6 | 7 | package termui 8 | 9 | const VDASH = '┊' 10 | const HDASH = '┈' 11 | const ORIGIN = '└' 12 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/linechart_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | // +build windows 6 | 7 | package termui 8 | 9 | const VDASH = '|' 10 | const HDASH = '-' 11 | const ORIGIN = '+' 12 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import "strings" 8 | 9 | // List displays []string as its items, 10 | // it has a Overflow option (default is "hidden"), when set to "hidden", 11 | // the item exceeding List's width is truncated, but when set to "wrap", 12 | // the overflowed text breaks into next line. 13 | /* 14 | strs := []string{ 15 | "[0] github.com/gizak/termui", 16 | "[1] editbox.go", 17 | "[2] interrupt.go", 18 | "[3] keyboard.go", 19 | "[4] output.go", 20 | "[5] random_out.go", 21 | "[6] dashboard.go", 22 | "[7] nsf/termbox-go"} 23 | 24 | ls := termui.NewList() 25 | ls.Items = strs 26 | ls.ItemFgColor = termui.ColorYellow 27 | ls.BorderLabel = "List" 28 | ls.Height = 7 29 | ls.Width = 25 30 | ls.Y = 0 31 | */ 32 | type List struct { 33 | Block 34 | Items []string 35 | Overflow string 36 | ItemFgColor Attribute 37 | ItemBgColor Attribute 38 | } 39 | 40 | // NewList returns a new *List with current theme. 41 | func NewList() *List { 42 | return &List{ 43 | Block: *NewBlock(), 44 | Overflow: "hidden", 45 | ItemFgColor: ThemeAttr("list.item.fg"), 46 | ItemBgColor: ThemeAttr("list.item.bg"), 47 | } 48 | } 49 | 50 | // Buffer implements Bufferer interface. 51 | func (l *List) Buffer() Buffer { 52 | buf := l.Block.Buffer() 53 | 54 | switch l.Overflow { 55 | case "wrap": 56 | cs := DefaultTxBuilder.Build(strings.Join(l.Items, "\n"), l.ItemFgColor, l.ItemBgColor) 57 | i, j, k := 0, 0, 0 58 | for i < l.innerArea.Dy() && k < len(cs) { 59 | w := cs[k].Width() 60 | if cs[k].Ch == '\n' || j+w > l.innerArea.Dx() { 61 | i++ 62 | j = 0 63 | if cs[k].Ch == '\n' { 64 | k++ 65 | } 66 | continue 67 | } 68 | buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, cs[k]) 69 | 70 | k++ 71 | j++ 72 | } 73 | 74 | case "hidden": 75 | trimItems := l.Items 76 | if len(trimItems) > l.innerArea.Dy() { 77 | trimItems = trimItems[:l.innerArea.Dy()] 78 | } 79 | for i, v := range trimItems { 80 | cs := DTrimTxCls(DefaultTxBuilder.Build(v, l.ItemFgColor, l.ItemBgColor), l.innerArea.Dx()) 81 | j := 0 82 | for _, vv := range cs { 83 | w := vv.Width() 84 | buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, vv) 85 | j += w 86 | } 87 | } 88 | } 89 | 90 | return buf 91 | } 92 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/paragraph.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | // Paragraph displays a paragraph. 8 | /* 9 | par := termui.NewParagraph("Simple Text") 10 | par.Height = 3 11 | par.Width = 17 12 | par.BorderLabel = "Label" 13 | */ 14 | type Paragraph struct { 15 | Block 16 | Text string 17 | TextFgColor Attribute 18 | TextBgColor Attribute 19 | WrapLength int // words wrap limit. Note it may not work properly with multi-width char 20 | } 21 | 22 | // NewParagraph returns a new *Paragraph with given text as its content. 23 | func NewParagraph(s string) *Paragraph { 24 | return &Paragraph{ 25 | Block: *NewBlock(), 26 | Text: s, 27 | TextFgColor: ThemeAttr("par.text.fg"), 28 | TextBgColor: ThemeAttr("par.text.bg"), 29 | WrapLength: 0, 30 | } 31 | } 32 | 33 | // Buffer implements Bufferer interface. 34 | func (p *Paragraph) Buffer() Buffer { 35 | buf := p.Block.Buffer() 36 | 37 | fg, bg := p.TextFgColor, p.TextBgColor 38 | cs := DefaultTxBuilder.Build(p.Text, fg, bg) 39 | 40 | // wrap if WrapLength set 41 | if p.WrapLength < 0 { 42 | cs = wrapTx(cs, p.Width-2) 43 | } else if p.WrapLength > 0 { 44 | cs = wrapTx(cs, p.WrapLength) 45 | } 46 | 47 | y, x, n := 0, 0, 0 48 | for y < p.innerArea.Dy() && n < len(cs) { 49 | w := cs[n].Width() 50 | if cs[n].Ch == '\n' || x+w > p.innerArea.Dx() { 51 | y++ 52 | x = 0 // set x = 0 53 | if cs[n].Ch == '\n' { 54 | n++ 55 | } 56 | 57 | if y >= p.innerArea.Dy() { 58 | buf.Set(p.innerArea.Min.X+p.innerArea.Dx()-1, 59 | p.innerArea.Min.Y+p.innerArea.Dy()-1, 60 | Cell{Ch: '…', Fg: p.TextFgColor, Bg: p.TextBgColor}) 61 | break 62 | } 63 | continue 64 | } 65 | 66 | buf.Set(p.innerArea.Min.X+x, p.innerArea.Min.Y+y, cs[n]) 67 | 68 | n++ 69 | x += w 70 | } 71 | 72 | return buf 73 | } 74 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/position.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import "image" 8 | 9 | // Align is the position of the gauge's label. 10 | type Align uint 11 | 12 | // All supported positions. 13 | const ( 14 | AlignNone Align = 0 15 | AlignLeft Align = 1 << iota 16 | AlignRight 17 | AlignBottom 18 | AlignTop 19 | AlignCenterVertical 20 | AlignCenterHorizontal 21 | AlignCenter = AlignCenterVertical | AlignCenterHorizontal 22 | ) 23 | 24 | func AlignArea(parent, child image.Rectangle, a Align) image.Rectangle { 25 | w, h := child.Dx(), child.Dy() 26 | 27 | // parent center 28 | pcx, pcy := parent.Min.X+parent.Dx()/2, parent.Min.Y+parent.Dy()/2 29 | // child center 30 | ccx, ccy := child.Min.X+child.Dx()/2, child.Min.Y+child.Dy()/2 31 | 32 | if a&AlignLeft == AlignLeft { 33 | child.Min.X = parent.Min.X 34 | child.Max.X = child.Min.X + w 35 | } 36 | 37 | if a&AlignRight == AlignRight { 38 | child.Max.X = parent.Max.X 39 | child.Min.X = child.Max.X - w 40 | } 41 | 42 | if a&AlignBottom == AlignBottom { 43 | child.Max.Y = parent.Max.Y 44 | child.Min.Y = child.Max.Y - h 45 | } 46 | 47 | if a&AlignTop == AlignRight { 48 | child.Min.Y = parent.Min.Y 49 | child.Max.Y = child.Min.Y + h 50 | } 51 | 52 | if a&AlignCenterHorizontal == AlignCenterHorizontal { 53 | child.Min.X += pcx - ccx 54 | child.Max.X = child.Min.X + w 55 | } 56 | 57 | if a&AlignCenterVertical == AlignCenterVertical { 58 | child.Min.Y += pcy - ccy 59 | child.Max.Y = child.Min.Y + h 60 | } 61 | 62 | return child 63 | } 64 | 65 | func MoveArea(a image.Rectangle, dx, dy int) image.Rectangle { 66 | a.Min.X += dx 67 | a.Max.X += dx 68 | a.Min.Y += dy 69 | a.Max.Y += dy 70 | return a 71 | } 72 | 73 | var termWidth int 74 | var termHeight int 75 | 76 | func TermRect() image.Rectangle { 77 | return image.Rect(0, 0, termWidth, termHeight) 78 | } 79 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/render.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import ( 8 | "image" 9 | "runtime/debug" 10 | "sync" 11 | 12 | tb "github.com/nsf/termbox-go" 13 | ) 14 | 15 | // Bufferer should be implemented by all renderable components. 16 | type Bufferer interface { 17 | Buffer() Buffer 18 | } 19 | 20 | // Init initializes termui library. This function should be called before any others. 21 | // After initialization, the library must be finalized by 'Close' function. 22 | func Init() error { 23 | if err := tb.Init(); err != nil { 24 | return err 25 | } 26 | tb.SetInputMode(tb.InputEsc | tb.InputMouse) 27 | // DefaultEvtStream = NewEvtStream() 28 | 29 | // sysEvtChs = make([]chan Event, 0) 30 | // go hookTermboxEvt() 31 | 32 | renderJobs = make(chan []Bufferer) 33 | //renderLock = new(sync.RWMutex) 34 | 35 | Body = NewGrid() 36 | Body.X = 0 37 | Body.Y = 0 38 | Body.BgColor = ThemeAttr("bg") 39 | Body.Width = TermWidth() 40 | 41 | // resizeCh := Handle("") 42 | // go func() { 43 | // for e := range resizeCh { 44 | // payload := e.Payload.(Resize) 45 | // Body.Width = payload.Width 46 | // } 47 | // }() 48 | 49 | // DefaultWgtMgr = NewWgtMgr() 50 | // EventHook(DefaultWgtMgr.WgtHandlersHook()) 51 | 52 | go func() { 53 | for bs := range renderJobs { 54 | render(bs...) 55 | } 56 | }() 57 | 58 | return nil 59 | } 60 | 61 | // Close finalizes termui library, 62 | // should be called after successful initialization when termui's functionality isn't required anymore. 63 | func Close() { 64 | tb.Close() 65 | } 66 | 67 | var renderLock sync.Mutex 68 | 69 | func termSync() { 70 | renderLock.Lock() 71 | tb.Sync() 72 | termWidth, termHeight = tb.Size() 73 | renderLock.Unlock() 74 | } 75 | 76 | // TermWidth returns the current terminal's width. 77 | func TermWidth() int { 78 | termSync() 79 | return termWidth 80 | } 81 | 82 | // TermHeight returns the current terminal's height. 83 | func TermHeight() int { 84 | termSync() 85 | return termHeight 86 | } 87 | 88 | // Render renders all Bufferer in the given order from left to right, 89 | // right could overlap on left ones. 90 | func render(bs ...Bufferer) { 91 | defer func() { 92 | if e := recover(); e != nil { 93 | Close() 94 | panic(debug.Stack()) 95 | } 96 | }() 97 | for _, b := range bs { 98 | 99 | buf := b.Buffer() 100 | // set cels in buf 101 | for p, c := range buf.CellMap { 102 | if p.In(buf.Area) { 103 | 104 | tb.SetCell(p.X, p.Y, c.Ch, toTmAttr(c.Fg), toTmAttr(c.Bg)) 105 | 106 | } 107 | } 108 | 109 | } 110 | 111 | renderLock.Lock() 112 | // render 113 | tb.Flush() 114 | renderLock.Unlock() 115 | } 116 | 117 | func Clear() { 118 | tb.Clear(tb.ColorDefault, toTmAttr(ThemeAttr("bg"))) 119 | } 120 | 121 | func clearArea(r image.Rectangle, bg Attribute) { 122 | for i := r.Min.X; i < r.Max.X; i++ { 123 | for j := r.Min.Y; j < r.Max.Y; j++ { 124 | tb.SetCell(i, j, ' ', tb.ColorDefault, toTmAttr(bg)) 125 | } 126 | } 127 | } 128 | 129 | func ClearArea(r image.Rectangle, bg Attribute) { 130 | clearArea(r, bg) 131 | tb.Flush() 132 | } 133 | 134 | var renderJobs chan []Bufferer 135 | 136 | func Render(bs ...Bufferer) { 137 | //go func() { renderJobs <- bs }() 138 | renderJobs <- bs 139 | } 140 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/sparkline.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | // Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers. 8 | /* 9 | 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} 10 | spl := termui.NewSparkline() 11 | spl.Data = data 12 | spl.Title = "Sparkline 0" 13 | spl.LineColor = termui.ColorGreen 14 | */ 15 | type Sparkline struct { 16 | Data []int 17 | Height int 18 | Title string 19 | TitleColor Attribute 20 | LineColor Attribute 21 | displayHeight int 22 | scale float32 23 | max int 24 | } 25 | 26 | // Sparklines is a renderable widget which groups together the given sparklines. 27 | /* 28 | spls := termui.NewSparklines(spl0,spl1,spl2) //... 29 | spls.Height = 2 30 | spls.Width = 20 31 | */ 32 | type Sparklines struct { 33 | Block 34 | Lines []Sparkline 35 | displayLines int 36 | displayWidth int 37 | } 38 | 39 | var sparks = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'} 40 | 41 | // Add appends a given Sparkline to s *Sparklines. 42 | func (s *Sparklines) Add(sl Sparkline) { 43 | s.Lines = append(s.Lines, sl) 44 | } 45 | 46 | // NewSparkline returns a unrenderable single sparkline that intended to be added into Sparklines. 47 | func NewSparkline() Sparkline { 48 | return Sparkline{ 49 | Height: 1, 50 | TitleColor: ThemeAttr("sparkline.title.fg"), 51 | LineColor: ThemeAttr("sparkline.line.fg")} 52 | } 53 | 54 | // NewSparklines return a new *Sparklines with given Sparkline(s), you can always add a new Sparkline later. 55 | func NewSparklines(ss ...Sparkline) *Sparklines { 56 | return &Sparklines{Block: *NewBlock(), Lines: ss} 57 | } 58 | 59 | func (sl *Sparklines) update() { 60 | for i, v := range sl.Lines { 61 | if v.Title == "" { 62 | sl.Lines[i].displayHeight = v.Height 63 | } else { 64 | sl.Lines[i].displayHeight = v.Height + 1 65 | } 66 | } 67 | sl.displayWidth = sl.innerArea.Dx() 68 | 69 | // get how many lines gotta display 70 | h := 0 71 | sl.displayLines = 0 72 | for _, v := range sl.Lines { 73 | if h+v.displayHeight <= sl.innerArea.Dy() { 74 | sl.displayLines++ 75 | } else { 76 | break 77 | } 78 | h += v.displayHeight 79 | } 80 | 81 | for i := 0; i < sl.displayLines; i++ { 82 | data := sl.Lines[i].Data 83 | 84 | max := 0 85 | for _, v := range data { 86 | if max < v { 87 | max = v 88 | } 89 | } 90 | sl.Lines[i].max = max 91 | if max != 0 { 92 | sl.Lines[i].scale = float32(8*sl.Lines[i].Height) / float32(max) 93 | } else { // when all negative 94 | sl.Lines[i].scale = 0 95 | } 96 | } 97 | } 98 | 99 | // Buffer implements Bufferer interface. 100 | func (sl *Sparklines) Buffer() Buffer { 101 | buf := sl.Block.Buffer() 102 | sl.update() 103 | 104 | oftY := 0 105 | for i := 0; i < sl.displayLines; i++ { 106 | l := sl.Lines[i] 107 | data := l.Data 108 | 109 | if len(data) > sl.innerArea.Dx() { 110 | data = data[len(data)-sl.innerArea.Dx():] 111 | } 112 | 113 | if l.Title != "" { 114 | rs := trimStr2Runes(l.Title, sl.innerArea.Dx()) 115 | oftX := 0 116 | for _, v := range rs { 117 | w := charWidth(v) 118 | c := Cell{ 119 | Ch: v, 120 | Fg: l.TitleColor, 121 | Bg: sl.Bg, 122 | } 123 | x := sl.innerArea.Min.X + oftX 124 | y := sl.innerArea.Min.Y + oftY 125 | buf.Set(x, y, c) 126 | oftX += w 127 | } 128 | } 129 | 130 | for j, v := range data { 131 | // display height of the data point, zero when data is negative 132 | h := int(float32(v)*l.scale + 0.5) 133 | if v < 0 { 134 | h = 0 135 | } 136 | 137 | barCnt := h / 8 138 | barMod := h % 8 139 | for jj := 0; jj < barCnt; jj++ { 140 | c := Cell{ 141 | Ch: ' ', // => sparks[7] 142 | Bg: l.LineColor, 143 | } 144 | x := sl.innerArea.Min.X + j 145 | y := sl.innerArea.Min.Y + oftY + l.Height - jj 146 | 147 | //p.Bg = sl.BgColor 148 | buf.Set(x, y, c) 149 | } 150 | if barMod != 0 { 151 | c := Cell{ 152 | Ch: sparks[barMod-1], 153 | Fg: l.LineColor, 154 | Bg: sl.Bg, 155 | } 156 | x := sl.innerArea.Min.X + j 157 | y := sl.innerArea.Min.Y + oftY + l.Height - barCnt 158 | buf.Set(x, y, c) 159 | } 160 | } 161 | 162 | oftY += l.displayHeight 163 | } 164 | 165 | return buf 166 | } 167 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import "strings" 8 | 9 | /* Table is like: 10 | 11 | ┌Awesome Table ────────────────────────────────────────────────┐ 12 | │ Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 | 13 | │──────────────────────────────────────────────────────────────│ 14 | │ Some Item #1 | AAA | 123 | CCCCC | EEEEE | GGGGG | IIIII | 15 | │──────────────────────────────────────────────────────────────│ 16 | │ Some Item #2 | BBB | 456 | DDDDD | FFFFF | HHHHH | JJJJJ | 17 | └──────────────────────────────────────────────────────────────┘ 18 | 19 | Datapoints are a two dimensional array of strings: [][]string 20 | 21 | Example: 22 | data := [][]string{ 23 | {"Col0", "Col1", "Col3", "Col4", "Col5", "Col6"}, 24 | {"Some Item #1", "AAA", "123", "CCCCC", "EEEEE", "GGGGG", "IIIII"}, 25 | {"Some Item #2", "BBB", "456", "DDDDD", "FFFFF", "HHHHH", "JJJJJ"}, 26 | } 27 | 28 | table := termui.NewTable() 29 | table.Rows = data // type [][]string 30 | table.FgColor = termui.ColorWhite 31 | table.BgColor = termui.ColorDefault 32 | table.Height = 7 33 | table.Width = 62 34 | table.Y = 0 35 | table.X = 0 36 | table.Border = true 37 | */ 38 | 39 | // Table tracks all the attributes of a Table instance 40 | type Table struct { 41 | Block 42 | Rows [][]string 43 | CellWidth []int 44 | FgColor Attribute 45 | BgColor Attribute 46 | FgColors []Attribute 47 | BgColors []Attribute 48 | Separator bool 49 | TextAlign Align 50 | } 51 | 52 | // NewTable returns a new Table instance 53 | func NewTable() *Table { 54 | return &Table{ 55 | Block: *NewBlock(), 56 | FgColor: ColorWhite, 57 | BgColor: ColorDefault, 58 | Separator: true, 59 | } 60 | } 61 | 62 | // CellsWidth calculates the width of a cell array and returns an int 63 | func cellsWidth(cells []Cell) int { 64 | width := 0 65 | for _, c := range cells { 66 | width += c.Width() 67 | } 68 | return width 69 | } 70 | 71 | // Analysis generates and returns an array of []Cell that represent all columns in the Table 72 | func (table *Table) Analysis() [][]Cell { 73 | var rowCells [][]Cell 74 | length := len(table.Rows) 75 | if length < 1 { 76 | return rowCells 77 | } 78 | 79 | if len(table.FgColors) == 0 { 80 | table.FgColors = make([]Attribute, len(table.Rows)) 81 | } 82 | if len(table.BgColors) == 0 { 83 | table.BgColors = make([]Attribute, len(table.Rows)) 84 | } 85 | 86 | cellWidths := make([]int, len(table.Rows[0])) 87 | 88 | for y, row := range table.Rows { 89 | if table.FgColors[y] == 0 { 90 | table.FgColors[y] = table.FgColor 91 | } 92 | if table.BgColors[y] == 0 { 93 | table.BgColors[y] = table.BgColor 94 | } 95 | for x, str := range row { 96 | cells := DefaultTxBuilder.Build(str, table.FgColors[y], table.BgColors[y]) 97 | cw := cellsWidth(cells) 98 | if cellWidths[x] < cw { 99 | cellWidths[x] = cw 100 | } 101 | rowCells = append(rowCells, cells) 102 | } 103 | } 104 | table.CellWidth = cellWidths 105 | return rowCells 106 | } 107 | 108 | // SetSize calculates the table size and sets the internal value 109 | func (table *Table) SetSize() { 110 | length := len(table.Rows) 111 | if table.Separator { 112 | table.Height = length*2 + 1 113 | } else { 114 | table.Height = length + 2 115 | } 116 | table.Width = 2 117 | if length != 0 { 118 | for _, cellWidth := range table.CellWidth { 119 | table.Width += cellWidth + 3 120 | } 121 | } 122 | } 123 | 124 | // CalculatePosition ... 125 | func (table *Table) CalculatePosition(x int, y int, coordinateX *int, coordinateY *int, cellStart *int) { 126 | if table.Separator { 127 | *coordinateY = table.innerArea.Min.Y + y*2 128 | } else { 129 | *coordinateY = table.innerArea.Min.Y + y 130 | } 131 | if x == 0 { 132 | *cellStart = table.innerArea.Min.X 133 | } else { 134 | *cellStart += table.CellWidth[x-1] + 3 135 | } 136 | 137 | switch table.TextAlign { 138 | case AlignRight: 139 | *coordinateX = *cellStart + (table.CellWidth[x] - len(table.Rows[y][x])) + 2 140 | case AlignCenter: 141 | *coordinateX = *cellStart + (table.CellWidth[x]-len(table.Rows[y][x]))/2 + 2 142 | default: 143 | *coordinateX = *cellStart + 2 144 | } 145 | } 146 | 147 | // Buffer ... 148 | func (table *Table) Buffer() Buffer { 149 | buffer := table.Block.Buffer() 150 | rowCells := table.Analysis() 151 | pointerX := table.innerArea.Min.X + 2 152 | pointerY := table.innerArea.Min.Y 153 | borderPointerX := table.innerArea.Min.X 154 | for y, row := range table.Rows { 155 | for x := range row { 156 | table.CalculatePosition(x, y, &pointerX, &pointerY, &borderPointerX) 157 | background := DefaultTxBuilder.Build(strings.Repeat(" ", table.CellWidth[x]+3), table.BgColors[y], table.BgColors[y]) 158 | cells := rowCells[y*len(row)+x] 159 | for i, back := range background { 160 | buffer.Set(borderPointerX+i, pointerY, back) 161 | } 162 | 163 | coordinateX := pointerX 164 | for _, printer := range cells { 165 | buffer.Set(coordinateX, pointerY, printer) 166 | coordinateX += printer.Width() 167 | } 168 | 169 | if x != 0 { 170 | dividors := DefaultTxBuilder.Build("|", table.FgColors[y], table.BgColors[y]) 171 | for _, dividor := range dividors { 172 | buffer.Set(borderPointerX, pointerY, dividor) 173 | } 174 | } 175 | } 176 | 177 | if table.Separator { 178 | border := DefaultTxBuilder.Build(strings.Repeat("─", table.Width-2), table.FgColor, table.BgColor) 179 | for i, cell := range border { 180 | buffer.Set(table.innerArea.Min.X+i, pointerY+1, cell) 181 | } 182 | } 183 | } 184 | 185 | return buffer 186 | } 187 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/tabpane.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import "unicode/utf8" 8 | 9 | type Tab struct { 10 | Label string 11 | RuneLen int 12 | Blocks []Bufferer 13 | } 14 | 15 | func NewTab(label string) *Tab { 16 | return &Tab{ 17 | Label: label, 18 | RuneLen: utf8.RuneCount([]byte(label))} 19 | } 20 | 21 | func (tab *Tab) AddBlocks(rs ...Bufferer) { 22 | for _, r := range rs { 23 | tab.Blocks = append(tab.Blocks, r) 24 | } 25 | } 26 | 27 | func (tab *Tab) Buffer() Buffer { 28 | buf := NewBuffer() 29 | for blockNum := 0; blockNum < len(tab.Blocks); blockNum++ { 30 | b := tab.Blocks[blockNum] 31 | buf.Merge(b.Buffer()) 32 | } 33 | return buf 34 | } 35 | 36 | type TabPane struct { 37 | Block 38 | Tabs []Tab 39 | activeTabIndex int 40 | ActiveTabBg Attribute 41 | posTabText []int 42 | offTabText int 43 | } 44 | 45 | func NewTabPane() *TabPane { 46 | tp := TabPane{ 47 | Block: *NewBlock(), 48 | activeTabIndex: 0, 49 | offTabText: 0, 50 | ActiveTabBg: ThemeAttr("bg.tab.active")} 51 | return &tp 52 | } 53 | 54 | func (tp *TabPane) SetTabs(tabs ...Tab) { 55 | tp.Tabs = make([]Tab, len(tabs)) 56 | tp.posTabText = make([]int, len(tabs)+1) 57 | off := 0 58 | for i := 0; i < len(tp.Tabs); i++ { 59 | tp.Tabs[i] = tabs[i] 60 | tp.posTabText[i] = off 61 | off += tp.Tabs[i].RuneLen + 1 //+1 for space between tabs 62 | } 63 | tp.posTabText[len(tabs)] = off - 1 //total length of Tab's text 64 | } 65 | 66 | func (tp *TabPane) SetActiveLeft() { 67 | if tp.activeTabIndex == 0 { 68 | return 69 | } 70 | tp.activeTabIndex -= 1 71 | if tp.posTabText[tp.activeTabIndex] < tp.offTabText { 72 | tp.offTabText = tp.posTabText[tp.activeTabIndex] 73 | } 74 | } 75 | 76 | func (tp *TabPane) SetActiveRight() { 77 | if tp.activeTabIndex == len(tp.Tabs)-1 { 78 | return 79 | } 80 | tp.activeTabIndex += 1 81 | endOffset := tp.posTabText[tp.activeTabIndex] + tp.Tabs[tp.activeTabIndex].RuneLen 82 | if endOffset+tp.offTabText > tp.InnerWidth() { 83 | tp.offTabText = endOffset - tp.InnerWidth() 84 | } 85 | } 86 | 87 | // Checks if left and right tabs are fully visible 88 | // if only left tabs are not visible return -1 89 | // if only right tabs are not visible return 1 90 | // if both return 0 91 | // use only if fitsWidth() returns false 92 | func (tp *TabPane) checkAlignment() int { 93 | ret := 0 94 | if tp.offTabText > 0 { 95 | ret = -1 96 | } 97 | if tp.offTabText+tp.InnerWidth() < tp.posTabText[len(tp.Tabs)] { 98 | ret += 1 99 | } 100 | return ret 101 | } 102 | 103 | // Checks if all tabs fits innerWidth of TabPane 104 | func (tp *TabPane) fitsWidth() bool { 105 | return tp.InnerWidth() >= tp.posTabText[len(tp.Tabs)] 106 | } 107 | 108 | func (tp *TabPane) align() { 109 | if !tp.fitsWidth() && !tp.Border { 110 | tp.PaddingLeft += 1 111 | tp.PaddingRight += 1 112 | tp.Block.Align() 113 | } 114 | } 115 | 116 | // bridge the old Point stuct 117 | type point struct { 118 | X int 119 | Y int 120 | Ch rune 121 | Fg Attribute 122 | Bg Attribute 123 | } 124 | 125 | func buf2pt(b Buffer) []point { 126 | ps := make([]point, 0, len(b.CellMap)) 127 | for k, c := range b.CellMap { 128 | ps = append(ps, point{X: k.X, Y: k.Y, Ch: c.Ch, Fg: c.Fg, Bg: c.Bg}) 129 | } 130 | 131 | return ps 132 | } 133 | 134 | // Adds the point only if it is visible in TabPane. 135 | // Point can be invisible if concatenation of Tab's texts is widther then 136 | // innerWidth of TabPane 137 | func (tp *TabPane) addPoint(ptab []point, charOffset *int, oftX *int, points ...point) []point { 138 | if *charOffset < tp.offTabText || tp.offTabText+tp.InnerWidth() < *charOffset { 139 | *charOffset++ 140 | return ptab 141 | } 142 | for _, p := range points { 143 | p.X = *oftX 144 | ptab = append(ptab, p) 145 | } 146 | *oftX++ 147 | *charOffset++ 148 | return ptab 149 | } 150 | 151 | // Draws the point and redraws upper and lower border points (if it has one) 152 | func (tp *TabPane) drawPointWithBorder(p point, ch rune, chbord rune, chdown rune, chup rune) []point { 153 | var addp []point 154 | p.Ch = ch 155 | if tp.Border { 156 | p.Ch = chdown 157 | p.Y = tp.InnerY() - 1 158 | addp = append(addp, p) 159 | p.Ch = chup 160 | p.Y = tp.InnerY() + 1 161 | addp = append(addp, p) 162 | p.Ch = chbord 163 | } 164 | p.Y = tp.InnerY() 165 | return append(addp, p) 166 | } 167 | 168 | func (tp *TabPane) Buffer() Buffer { 169 | if tp.Border { 170 | tp.Height = 3 171 | } else { 172 | tp.Height = 1 173 | } 174 | if tp.Width > tp.posTabText[len(tp.Tabs)]+2 { 175 | tp.Width = tp.posTabText[len(tp.Tabs)] + 2 176 | } 177 | buf := tp.Block.Buffer() 178 | ps := []point{} 179 | 180 | tp.align() 181 | if tp.InnerHeight() <= 0 || tp.InnerWidth() <= 0 { 182 | return NewBuffer() 183 | } 184 | oftX := tp.InnerX() 185 | charOffset := 0 186 | pt := point{Bg: tp.BorderBg, Fg: tp.BorderFg} 187 | for i, tab := range tp.Tabs { 188 | 189 | if i != 0 { 190 | pt.X = oftX 191 | pt.Y = tp.InnerY() 192 | addp := tp.drawPointWithBorder(pt, ' ', VERTICAL_LINE, HORIZONTAL_DOWN, HORIZONTAL_UP) 193 | ps = tp.addPoint(ps, &charOffset, &oftX, addp...) 194 | } 195 | 196 | if i == tp.activeTabIndex { 197 | pt.Bg = tp.ActiveTabBg 198 | } 199 | rs := []rune(tab.Label) 200 | for k := 0; k < len(rs); k++ { 201 | 202 | addp := make([]point, 0, 2) 203 | if i == tp.activeTabIndex && tp.Border { 204 | pt.Ch = ' ' 205 | pt.Y = tp.InnerY() + 1 206 | pt.Bg = tp.BorderBg 207 | addp = append(addp, pt) 208 | pt.Bg = tp.ActiveTabBg 209 | } 210 | 211 | pt.Y = tp.InnerY() 212 | pt.Ch = rs[k] 213 | 214 | addp = append(addp, pt) 215 | ps = tp.addPoint(ps, &charOffset, &oftX, addp...) 216 | } 217 | pt.Bg = tp.BorderBg 218 | 219 | if !tp.fitsWidth() { 220 | all := tp.checkAlignment() 221 | pt.X = tp.InnerX() - 1 222 | 223 | pt.Ch = '*' 224 | if tp.Border { 225 | pt.Ch = VERTICAL_LINE 226 | } 227 | ps = append(ps, pt) 228 | 229 | if all <= 0 { 230 | addp := tp.drawPointWithBorder(pt, '<', QUOTA_LEFT, HORIZONTAL_LINE, HORIZONTAL_LINE) 231 | ps = append(ps, addp...) 232 | } 233 | 234 | pt.X = tp.InnerX() + tp.InnerWidth() 235 | pt.Ch = '*' 236 | if tp.Border { 237 | pt.Ch = VERTICAL_LINE 238 | } 239 | ps = append(ps, pt) 240 | if all >= 0 { 241 | addp := tp.drawPointWithBorder(pt, '>', QUOTA_RIGHT, HORIZONTAL_LINE, HORIZONTAL_LINE) 242 | ps = append(ps, addp...) 243 | } 244 | } 245 | 246 | //draw tab content below the TabPane 247 | if i == tp.activeTabIndex { 248 | blockPoints := buf2pt(tab.Buffer()) 249 | for i := 0; i < len(blockPoints); i++ { 250 | blockPoints[i].Y += tp.Height + tp.Y 251 | } 252 | ps = append(ps, blockPoints...) 253 | } 254 | } 255 | 256 | for _, v := range ps { 257 | buf.Set(v.X, v.Y, NewCell(v.Ch, v.Fg, v.Bg)) 258 | } 259 | buf.Sync() 260 | return buf 261 | } 262 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/theme.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import "strings" 8 | 9 | /* 10 | // A ColorScheme represents the current look-and-feel of the dashboard. 11 | type ColorScheme struct { 12 | BodyBg Attribute 13 | BlockBg Attribute 14 | HasBorder bool 15 | BorderFg Attribute 16 | BorderBg Attribute 17 | BorderLabelTextFg Attribute 18 | BorderLabelTextBg Attribute 19 | ParTextFg Attribute 20 | ParTextBg Attribute 21 | SparklineLine Attribute 22 | SparklineTitle Attribute 23 | GaugeBar Attribute 24 | GaugePercent Attribute 25 | LineChartLine Attribute 26 | LineChartAxes Attribute 27 | ListItemFg Attribute 28 | ListItemBg Attribute 29 | BarChartBar Attribute 30 | BarChartText Attribute 31 | BarChartNum Attribute 32 | MBarChartBar Attribute 33 | MBarChartText Attribute 34 | MBarChartNum Attribute 35 | TabActiveBg Attribute 36 | } 37 | 38 | // default color scheme depends on the user's terminal setting. 39 | var themeDefault = ColorScheme{HasBorder: true} 40 | 41 | var themeHelloWorld = ColorScheme{ 42 | BodyBg: ColorBlack, 43 | BlockBg: ColorBlack, 44 | HasBorder: true, 45 | BorderFg: ColorWhite, 46 | BorderBg: ColorBlack, 47 | BorderLabelTextBg: ColorBlack, 48 | BorderLabelTextFg: ColorGreen, 49 | ParTextBg: ColorBlack, 50 | ParTextFg: ColorWhite, 51 | SparklineLine: ColorMagenta, 52 | SparklineTitle: ColorWhite, 53 | GaugeBar: ColorRed, 54 | GaugePercent: ColorWhite, 55 | LineChartLine: ColorYellow | AttrBold, 56 | LineChartAxes: ColorWhite, 57 | ListItemBg: ColorBlack, 58 | ListItemFg: ColorYellow, 59 | BarChartBar: ColorRed, 60 | BarChartNum: ColorWhite, 61 | BarChartText: ColorCyan, 62 | MBarChartBar: ColorRed, 63 | MBarChartNum: ColorWhite, 64 | MBarChartText: ColorCyan, 65 | TabActiveBg: ColorMagenta, 66 | } 67 | 68 | var theme = themeDefault // global dep 69 | 70 | // Theme returns the currently used theme. 71 | func Theme() ColorScheme { 72 | return theme 73 | } 74 | 75 | // SetTheme sets a new, custom theme. 76 | func SetTheme(newTheme ColorScheme) { 77 | theme = newTheme 78 | } 79 | 80 | // UseTheme sets a predefined scheme. Currently available: "hello-world" and 81 | // "black-and-white". 82 | func UseTheme(th string) { 83 | switch th { 84 | case "helloworld": 85 | theme = themeHelloWorld 86 | default: 87 | theme = themeDefault 88 | } 89 | } 90 | */ 91 | 92 | var ColorMap = map[string]Attribute{ 93 | "fg": ColorWhite, 94 | "bg": ColorDefault, 95 | "border.fg": ColorWhite, 96 | "label.fg": ColorGreen, 97 | "par.fg": ColorYellow, 98 | "par.label.bg": ColorWhite, 99 | } 100 | 101 | func ThemeAttr(name string) Attribute { 102 | return lookUpAttr(ColorMap, name) 103 | } 104 | 105 | func lookUpAttr(clrmap map[string]Attribute, name string) Attribute { 106 | a, ok := clrmap[name] 107 | if ok { 108 | return a 109 | } 110 | 111 | ns := strings.Split(name, ".") 112 | for i := range ns { 113 | nn := strings.Join(ns[i:len(ns)], ".") 114 | a, ok = ColorMap[nn] 115 | if ok { 116 | break 117 | } 118 | } 119 | 120 | return a 121 | } 122 | 123 | // 0<=r,g,b <= 5 124 | func ColorRGB(r, g, b int) Attribute { 125 | within := func(n int) int { 126 | if n < 0 { 127 | return 0 128 | } 129 | 130 | if n > 5 { 131 | return 5 132 | } 133 | 134 | return n 135 | } 136 | 137 | r, b, g = within(r), within(b), within(g) 138 | return Attribute(0x0f + 36*r + 6*g + b) 139 | } 140 | 141 | // Convert from familiar 24 bit colors into 6 bit terminal colors 142 | func ColorRGB24(r, g, b int) Attribute { 143 | return ColorRGB(r/51, g/51, b/51) 144 | } 145 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import ( 8 | "regexp" 9 | "strings" 10 | 11 | rw "github.com/mattn/go-runewidth" 12 | tb "github.com/nsf/termbox-go" 13 | ) 14 | 15 | /* ---------------Port from termbox-go --------------------- */ 16 | 17 | // Attribute is printable cell's color and style. 18 | type Attribute uint16 19 | 20 | // 8 basic clolrs 21 | const ( 22 | ColorDefault Attribute = iota 23 | ColorBlack 24 | ColorRed 25 | ColorGreen 26 | ColorYellow 27 | ColorBlue 28 | ColorMagenta 29 | ColorCyan 30 | ColorWhite 31 | ) 32 | 33 | // Have a constant that defines number of colors 34 | const NumberofColors = 8 35 | 36 | // Text style 37 | const ( 38 | AttrBold Attribute = 1 << (iota + 9) 39 | AttrUnderline 40 | AttrReverse 41 | ) 42 | 43 | var ( 44 | dot = "…" 45 | dotw = rw.StringWidth(dot) 46 | ) 47 | 48 | // termbox passthrough 49 | type OutputMode int 50 | 51 | // termbox passthrough 52 | const ( 53 | OutputCurrent OutputMode = iota 54 | OutputNormal 55 | Output256 56 | Output216 57 | OutputGrayscale 58 | ) 59 | 60 | /* ----------------------- End ----------------------------- */ 61 | 62 | func toTmAttr(x Attribute) tb.Attribute { 63 | return tb.Attribute(x) 64 | } 65 | 66 | func str2runes(s string) []rune { 67 | return []rune(s) 68 | } 69 | 70 | // Here for backwards-compatibility. 71 | func trimStr2Runes(s string, w int) []rune { 72 | return TrimStr2Runes(s, w) 73 | } 74 | 75 | // TrimStr2Runes trims string to w[-1 rune], appends …, and returns the runes 76 | // of that string if string is grather then n. If string is small then w, 77 | // return the runes. 78 | func TrimStr2Runes(s string, w int) []rune { 79 | if w <= 0 { 80 | return []rune{} 81 | } 82 | 83 | sw := rw.StringWidth(s) 84 | if sw > w { 85 | return []rune(rw.Truncate(s, w, dot)) 86 | } 87 | return str2runes(s) 88 | } 89 | 90 | // TrimStrIfAppropriate trim string to "s[:-1] + …" 91 | // if string > width otherwise return string 92 | func TrimStrIfAppropriate(s string, w int) string { 93 | if w <= 0 { 94 | return "" 95 | } 96 | 97 | sw := rw.StringWidth(s) 98 | if sw > w { 99 | return rw.Truncate(s, w, dot) 100 | } 101 | 102 | return s 103 | } 104 | 105 | func strWidth(s string) int { 106 | return rw.StringWidth(s) 107 | } 108 | 109 | func charWidth(ch rune) int { 110 | return rw.RuneWidth(ch) 111 | } 112 | 113 | var whiteSpaceRegex = regexp.MustCompile(`\s`) 114 | 115 | // StringToAttribute converts text to a termui attribute. You may specify more 116 | // then one attribute like that: "BLACK, BOLD, ...". All whitespaces 117 | // are ignored. 118 | func StringToAttribute(text string) Attribute { 119 | text = whiteSpaceRegex.ReplaceAllString(strings.ToLower(text), "") 120 | attributes := strings.Split(text, ",") 121 | result := Attribute(0) 122 | 123 | for _, theAttribute := range attributes { 124 | var match Attribute 125 | switch theAttribute { 126 | case "reset", "default": 127 | match = ColorDefault 128 | 129 | case "black": 130 | match = ColorBlack 131 | 132 | case "red": 133 | match = ColorRed 134 | 135 | case "green": 136 | match = ColorGreen 137 | 138 | case "yellow": 139 | match = ColorYellow 140 | 141 | case "blue": 142 | match = ColorBlue 143 | 144 | case "magenta": 145 | match = ColorMagenta 146 | 147 | case "cyan": 148 | match = ColorCyan 149 | 150 | case "white": 151 | match = ColorWhite 152 | 153 | case "bold": 154 | match = AttrBold 155 | 156 | case "underline": 157 | match = AttrUnderline 158 | 159 | case "reverse": 160 | match = AttrReverse 161 | } 162 | 163 | result |= match 164 | } 165 | 166 | return result 167 | } 168 | 169 | // TextCells returns a coloured text cells []Cell 170 | func TextCells(s string, fg, bg Attribute) []Cell { 171 | cs := make([]Cell, 0, len(s)) 172 | 173 | // sequence := MarkdownTextRendererFactory{}.TextRenderer(s).Render(fg, bg) 174 | // runes := []rune(sequence.NormalizedText) 175 | runes := str2runes(s) 176 | 177 | for n := range runes { 178 | // point, _ := sequence.PointAt(n, 0, 0) 179 | // cs = append(cs, Cell{point.Ch, point.Fg, point.Bg}) 180 | cs = append(cs, Cell{runes[n], fg, bg}) 181 | } 182 | return cs 183 | } 184 | 185 | // Width returns the actual screen space the cell takes (usually 1 or 2). 186 | func (c Cell) Width() int { 187 | return charWidth(c.Ch) 188 | } 189 | 190 | // Copy return a copy of c 191 | func (c Cell) Copy() Cell { 192 | return c 193 | } 194 | 195 | // TrimTxCells trims the overflowed text cells sequence. 196 | func TrimTxCells(cs []Cell, w int) []Cell { 197 | if len(cs) <= w { 198 | return cs 199 | } 200 | return cs[:w] 201 | } 202 | 203 | // DTrimTxCls trims the overflowed text cells sequence and append dots at the end. 204 | func DTrimTxCls(cs []Cell, w int) []Cell { 205 | l := len(cs) 206 | if l <= 0 { 207 | return []Cell{} 208 | } 209 | 210 | rt := make([]Cell, 0, w) 211 | csw := 0 212 | for i := 0; i < l && csw <= w; i++ { 213 | c := cs[i] 214 | cw := c.Width() 215 | 216 | if cw+csw < w { 217 | rt = append(rt, c) 218 | csw += cw 219 | } else { 220 | rt = append(rt, Cell{'…', c.Fg, c.Bg}) 221 | break 222 | } 223 | } 224 | 225 | return rt 226 | } 227 | 228 | func CellsToStr(cs []Cell) string { 229 | str := "" 230 | for _, c := range cs { 231 | str += string(c.Ch) 232 | } 233 | return str 234 | } 235 | 236 | // Passthrough to termbox using termbox constants above 237 | func SetOutputMode(mode OutputMode) { 238 | switch mode { 239 | case OutputCurrent: 240 | tb.SetOutputMode(tb.OutputCurrent) 241 | case OutputNormal: 242 | tb.SetOutputMode(tb.OutputNormal) 243 | case Output256: 244 | tb.SetOutputMode(tb.Output256) 245 | case Output216: 246 | tb.SetOutputMode(tb.Output216) 247 | case OutputGrayscale: 248 | tb.SetOutputMode(tb.OutputGrayscale) 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /vendor/github.com/gizak/termui/widget.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Zack Guo . All rights reserved. 2 | // Use of this source code is governed by a MIT license that can 3 | // be found in the LICENSE file. 4 | 5 | package termui 6 | 7 | import ( 8 | "fmt" 9 | "sync" 10 | ) 11 | 12 | // event mixins 13 | type WgtMgr map[string]WgtInfo 14 | 15 | type WgtInfo struct { 16 | Handlers map[string]func(Event) 17 | WgtRef Widget 18 | Id string 19 | } 20 | 21 | type Widget interface { 22 | Id() string 23 | } 24 | 25 | func NewWgtInfo(wgt Widget) WgtInfo { 26 | return WgtInfo{ 27 | Handlers: make(map[string]func(Event)), 28 | WgtRef: wgt, 29 | Id: wgt.Id(), 30 | } 31 | } 32 | 33 | func NewWgtMgr() WgtMgr { 34 | wm := WgtMgr(make(map[string]WgtInfo)) 35 | return wm 36 | } 37 | 38 | func (wm WgtMgr) AddWgt(wgt Widget) { 39 | wm[wgt.Id()] = NewWgtInfo(wgt) 40 | } 41 | 42 | func (wm WgtMgr) RmWgt(wgt Widget) { 43 | wm.RmWgtById(wgt.Id()) 44 | } 45 | 46 | func (wm WgtMgr) RmWgtById(id string) { 47 | delete(wm, id) 48 | } 49 | 50 | func (wm WgtMgr) AddWgtHandler(id, path string, h func(Event)) { 51 | if w, ok := wm[id]; ok { 52 | w.Handlers[path] = h 53 | } 54 | } 55 | 56 | func (wm WgtMgr) RmWgtHandler(id, path string) { 57 | if w, ok := wm[id]; ok { 58 | delete(w.Handlers, path) 59 | } 60 | } 61 | 62 | var counter struct { 63 | sync.RWMutex 64 | count int 65 | } 66 | 67 | func GenId() string { 68 | counter.Lock() 69 | defer counter.Unlock() 70 | 71 | counter.count += 1 72 | return fmt.Sprintf("%d", counter.count) 73 | } 74 | 75 | func (wm WgtMgr) WgtHandlersHook() func(Event) { 76 | return func(e Event) { 77 | for _, v := range wm { 78 | if val, ok := v.Handlers[e.ID]; ok { 79 | val(e) 80 | } 81 | } 82 | } 83 | } 84 | 85 | var DefaultWgtMgr WgtMgr 86 | 87 | func (b *Block) Handle(path string, handler func(Event)) { 88 | if _, ok := DefaultWgtMgr[b.Id()]; !ok { 89 | DefaultWgtMgr.AddWgt(b) 90 | } 91 | 92 | DefaultWgtMgr.AddWgtHandler(b.Id(), path, handler) 93 | } 94 | -------------------------------------------------------------------------------- /vendor/github.com/mattn/go-runewidth/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - tip 4 | before_install: 5 | - go get github.com/mattn/goveralls 6 | - go get golang.org/x/tools/cmd/cover 7 | script: 8 | - $HOME/gopath/bin/goveralls -repotoken lAKAWPzcGsD3A8yBX3BGGtRUdJ6CaGERL 9 | -------------------------------------------------------------------------------- /vendor/github.com/mattn/go-runewidth/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yasuhiro Matsumoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/mattn/go-runewidth/README.mkd: -------------------------------------------------------------------------------- 1 | go-runewidth 2 | ============ 3 | 4 | [![Build Status](https://travis-ci.org/mattn/go-runewidth.png?branch=master)](https://travis-ci.org/mattn/go-runewidth) 5 | [![Coverage Status](https://coveralls.io/repos/mattn/go-runewidth/badge.png?branch=HEAD)](https://coveralls.io/r/mattn/go-runewidth?branch=HEAD) 6 | [![GoDoc](https://godoc.org/github.com/mattn/go-runewidth?status.svg)](http://godoc.org/github.com/mattn/go-runewidth) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-runewidth)](https://goreportcard.com/report/github.com/mattn/go-runewidth) 8 | 9 | Provides functions to get fixed width of the character or string. 10 | 11 | Usage 12 | ----- 13 | 14 | ```go 15 | runewidth.StringWidth("つのだ☆HIRO") == 12 16 | ``` 17 | 18 | 19 | Author 20 | ------ 21 | 22 | Yasuhiro Matsumoto 23 | 24 | License 25 | ------- 26 | 27 | under the MIT License: http://mattn.mit-license.org/2013 28 | -------------------------------------------------------------------------------- /vendor/github.com/mattn/go-runewidth/runewidth_appengine.go: -------------------------------------------------------------------------------- 1 | // +build appengine 2 | 3 | package runewidth 4 | 5 | // IsEastAsian return true if the current locale is CJK 6 | func IsEastAsian() bool { 7 | return false 8 | } 9 | -------------------------------------------------------------------------------- /vendor/github.com/mattn/go-runewidth/runewidth_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | // +build !appengine 3 | 4 | package runewidth 5 | 6 | func IsEastAsian() bool { 7 | // TODO: Implement this for the web. Detect east asian in a compatible way, and return true. 8 | return false 9 | } 10 | -------------------------------------------------------------------------------- /vendor/github.com/mattn/go-runewidth/runewidth_posix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | // +build !js 3 | // +build !appengine 4 | 5 | package runewidth 6 | 7 | import ( 8 | "os" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`) 14 | 15 | var mblenTable = map[string]int{ 16 | "utf-8": 6, 17 | "utf8": 6, 18 | "jis": 8, 19 | "eucjp": 3, 20 | "euckr": 2, 21 | "euccn": 2, 22 | "sjis": 2, 23 | "cp932": 2, 24 | "cp51932": 2, 25 | "cp936": 2, 26 | "cp949": 2, 27 | "cp950": 2, 28 | "big5": 2, 29 | "gbk": 2, 30 | "gb2312": 2, 31 | } 32 | 33 | func isEastAsian(locale string) bool { 34 | charset := strings.ToLower(locale) 35 | r := reLoc.FindStringSubmatch(locale) 36 | if len(r) == 2 { 37 | charset = strings.ToLower(r[1]) 38 | } 39 | 40 | if strings.HasSuffix(charset, "@cjk_narrow") { 41 | return false 42 | } 43 | 44 | for pos, b := range []byte(charset) { 45 | if b == '@' { 46 | charset = charset[:pos] 47 | break 48 | } 49 | } 50 | max := 1 51 | if m, ok := mblenTable[charset]; ok { 52 | max = m 53 | } 54 | if max > 1 && (charset[0] != 'u' || 55 | strings.HasPrefix(locale, "ja") || 56 | strings.HasPrefix(locale, "ko") || 57 | strings.HasPrefix(locale, "zh")) { 58 | return true 59 | } 60 | return false 61 | } 62 | 63 | // IsEastAsian return true if the current locale is CJK 64 | func IsEastAsian() bool { 65 | locale := os.Getenv("LC_CTYPE") 66 | if locale == "" { 67 | locale = os.Getenv("LANG") 68 | } 69 | 70 | // ignore C locale 71 | if locale == "POSIX" || locale == "C" { 72 | return false 73 | } 74 | if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') { 75 | return false 76 | } 77 | 78 | return isEastAsian(locale) 79 | } 80 | -------------------------------------------------------------------------------- /vendor/github.com/mattn/go-runewidth/runewidth_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | // +build !appengine 3 | 4 | package runewidth 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | var ( 11 | kernel32 = syscall.NewLazyDLL("kernel32") 12 | procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP") 13 | ) 14 | 15 | // IsEastAsian return true if the current locale is CJK 16 | func IsEastAsian() bool { 17 | r1, _, _ := procGetConsoleOutputCP.Call() 18 | if r1 == 0 { 19 | return false 20 | } 21 | 22 | switch int(r1) { 23 | case 932, 51932, 936, 949, 950: 24 | return true 25 | } 26 | 27 | return false 28 | } 29 | -------------------------------------------------------------------------------- /vendor/github.com/mitchellh/go-wordwrap/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mitchell Hashimoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/mitchellh/go-wordwrap/README.md: -------------------------------------------------------------------------------- 1 | # go-wordwrap 2 | 3 | `go-wordwrap` (Golang package: `wordwrap`) is a package for Go that 4 | automatically wraps words into multiple lines. The primary use case for this 5 | is in formatting CLI output, but of course word wrapping is a generally useful 6 | thing to do. 7 | 8 | ## Installation and Usage 9 | 10 | Install using `go get github.com/mitchellh/go-wordwrap`. 11 | 12 | Full documentation is available at 13 | http://godoc.org/github.com/mitchellh/go-wordwrap 14 | 15 | Below is an example of its usage ignoring errors: 16 | 17 | ```go 18 | wrapped := wordwrap.WrapString("foo bar baz", 3) 19 | fmt.Println(wrapped) 20 | ``` 21 | 22 | Would output: 23 | 24 | ``` 25 | foo 26 | bar 27 | baz 28 | ``` 29 | 30 | ## Word Wrap Algorithm 31 | 32 | This library doesn't use any clever algorithm for word wrapping. The wrapping 33 | is actually very naive: whenever there is whitespace or an explicit linebreak. 34 | The goal of this library is for word wrapping CLI output, so the input is 35 | typically pretty well controlled human language. Because of this, the naive 36 | approach typically works just fine. 37 | 38 | In the future, we'd like to make the algorithm more advanced. We would do 39 | so without breaking the API. 40 | -------------------------------------------------------------------------------- /vendor/github.com/mitchellh/go-wordwrap/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mitchellh/go-wordwrap 2 | -------------------------------------------------------------------------------- /vendor/github.com/mitchellh/go-wordwrap/wordwrap.go: -------------------------------------------------------------------------------- 1 | package wordwrap 2 | 3 | import ( 4 | "bytes" 5 | "unicode" 6 | ) 7 | 8 | // WrapString wraps the given string within lim width in characters. 9 | // 10 | // Wrapping is currently naive and only happens at white-space. A future 11 | // version of the library will implement smarter wrapping. This means that 12 | // pathological cases can dramatically reach past the limit, such as a very 13 | // long word. 14 | func WrapString(s string, lim uint) string { 15 | // Initialize a buffer with a slightly larger size to account for breaks 16 | init := make([]byte, 0, len(s)) 17 | buf := bytes.NewBuffer(init) 18 | 19 | var current uint 20 | var wordBuf, spaceBuf bytes.Buffer 21 | 22 | for _, char := range s { 23 | if char == '\n' { 24 | if wordBuf.Len() == 0 { 25 | if current+uint(spaceBuf.Len()) > lim { 26 | current = 0 27 | } else { 28 | current += uint(spaceBuf.Len()) 29 | spaceBuf.WriteTo(buf) 30 | } 31 | spaceBuf.Reset() 32 | } else { 33 | current += uint(spaceBuf.Len() + wordBuf.Len()) 34 | spaceBuf.WriteTo(buf) 35 | spaceBuf.Reset() 36 | wordBuf.WriteTo(buf) 37 | wordBuf.Reset() 38 | } 39 | buf.WriteRune(char) 40 | current = 0 41 | } else if unicode.IsSpace(char) { 42 | if spaceBuf.Len() == 0 || wordBuf.Len() > 0 { 43 | current += uint(spaceBuf.Len() + wordBuf.Len()) 44 | spaceBuf.WriteTo(buf) 45 | spaceBuf.Reset() 46 | wordBuf.WriteTo(buf) 47 | wordBuf.Reset() 48 | } 49 | 50 | spaceBuf.WriteRune(char) 51 | } else { 52 | 53 | wordBuf.WriteRune(char) 54 | 55 | if current+uint(spaceBuf.Len()+wordBuf.Len()) > lim && uint(wordBuf.Len()) < lim { 56 | buf.WriteRune('\n') 57 | current = 0 58 | spaceBuf.Reset() 59 | } 60 | } 61 | } 62 | 63 | if wordBuf.Len() == 0 { 64 | if current+uint(spaceBuf.Len()) <= lim { 65 | spaceBuf.WriteTo(buf) 66 | } 67 | } else { 68 | spaceBuf.WriteTo(buf) 69 | wordBuf.WriteTo(buf) 70 | } 71 | 72 | return buf.String() 73 | } 74 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/AUTHORS: -------------------------------------------------------------------------------- 1 | # Please keep this file sorted. 2 | 3 | Georg Reinke 4 | nsf 5 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 termbox-go authors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/nsf/termbox-go?status.svg)](http://godoc.org/github.com/nsf/termbox-go) 2 | 3 | ## Termbox 4 | Termbox is a library that provides a minimalistic API which allows the programmer to write text-based user interfaces. The library is crossplatform and has both terminal-based implementations on *nix operating systems and a winapi console based implementation for windows operating systems. The basic idea is an abstraction of the greatest common subset of features available on all major terminals and other terminal-like APIs in a minimalistic fashion. Small API means it is easy to implement, test, maintain and learn it, that's what makes the termbox a distinct library in its area. 5 | 6 | ### Installation 7 | Install and update this go package with `go get -u github.com/nsf/termbox-go` 8 | 9 | ### Examples 10 | For examples of what can be done take a look at demos in the _demos directory. You can try them with go run: `go run _demos/keyboard.go` 11 | 12 | There are also some interesting projects using termbox-go: 13 | - [godit](https://github.com/nsf/godit) is an emacsish lightweight text editor written using termbox. 14 | - [gotetris](https://github.com/jjinux/gotetris) is an implementation of Tetris. 15 | - [sokoban-go](https://github.com/rn2dy/sokoban-go) is an implementation of sokoban game. 16 | - [hecate](https://github.com/evanmiller/hecate) is a hex editor designed by Satan. 17 | - [httopd](https://github.com/verdverm/httopd) is top for httpd logs. 18 | - [mop](https://github.com/mop-tracker/mop) is stock market tracker for hackers. 19 | - [termui](https://github.com/gizak/termui) is a terminal dashboard. 20 | - [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine. 21 | - [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart. 22 | - [gocui](https://github.com/jroimartin/gocui) is a minimalist Go library aimed at creating console user interfaces. 23 | - [dry](https://github.com/moncho/dry) is an interactive cli to manage Docker containers. 24 | - [pxl](https://github.com/ichinaski/pxl) displays images in the terminal. 25 | - [snake-game](https://github.com/DyegoCosta/snake-game) is an implementation of the Snake game. 26 | - [gone](https://github.com/guillaumebreton/gone) is a CLI pomodoro® timer. 27 | - [Spoof.go](https://github.com/sabey/spoofgo) controllable movement spoofing from the cli 28 | - [lf](https://github.com/gokcehan/lf) is a terminal file manager 29 | - [rat](https://github.com/ericfreese/rat) lets you compose shell commands to build terminal applications. 30 | - [httplab](https://github.com/gchaincl/httplab) An interactive web server. 31 | - [tetris](https://github.com/MichaelS11/tetris) Go Tetris with AI option 32 | - [wot](https://github.com/kyu-suke/wot) Wait time during command is completed. 33 | - [2048-go](https://github.com/1984weed/2048-go) is 2048 in Go 34 | - [jv](https://github.com/maxzender/jv) helps you view JSON on the command-line. 35 | - [pinger](https://github.com/hirose31/pinger) helps you to monitor numerous hosts using ICMP ECHO_REQUEST. 36 | - [vixl44](https://github.com/sebashwa/vixl44) lets you create pixel art inside your terminal using vim movements 37 | - [zterm](https://github.com/varunrau/zterm) is a typing game inspired by http://zty.pe/ 38 | - [gotypist](https://github.com/pb-/gotypist) is a fun touch-typing tutor following Steve Yegge's method. 39 | - [cointop](https://github.com/miguelmota/cointop) is an interactive terminal based UI application for tracking cryptocurrencies. 40 | - [pexpo](https://github.com/nnao45/pexpo) is a terminal sending ping tool written in Go. 41 | 42 | ### API reference 43 | [godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go) 44 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/api_common.go: -------------------------------------------------------------------------------- 1 | // termbox is a library for creating cross-platform text-based interfaces 2 | package termbox 3 | 4 | // public API, common OS agnostic part 5 | 6 | type ( 7 | InputMode int 8 | OutputMode int 9 | EventType uint8 10 | Modifier uint8 11 | Key uint16 12 | Attribute uint16 13 | ) 14 | 15 | // This type represents a termbox event. The 'Mod', 'Key' and 'Ch' fields are 16 | // valid if 'Type' is EventKey. The 'Width' and 'Height' fields are valid if 17 | // 'Type' is EventResize. The 'Err' field is valid if 'Type' is EventError. 18 | type Event struct { 19 | Type EventType // one of Event* constants 20 | Mod Modifier // one of Mod* constants or 0 21 | Key Key // one of Key* constants, invalid if 'Ch' is not 0 22 | Ch rune // a unicode character 23 | Width int // width of the screen 24 | Height int // height of the screen 25 | Err error // error in case if input failed 26 | MouseX int // x coord of mouse 27 | MouseY int // y coord of mouse 28 | N int // number of bytes written when getting a raw event 29 | } 30 | 31 | // A cell, single conceptual entity on the screen. The screen is basically a 2d 32 | // array of cells. 'Ch' is a unicode character, 'Fg' and 'Bg' are foreground 33 | // and background attributes respectively. 34 | type Cell struct { 35 | Ch rune 36 | Fg Attribute 37 | Bg Attribute 38 | } 39 | 40 | // To know if termbox has been initialized or not 41 | var ( 42 | IsInit bool = false 43 | ) 44 | 45 | // Key constants, see Event.Key field. 46 | const ( 47 | KeyF1 Key = 0xFFFF - iota 48 | KeyF2 49 | KeyF3 50 | KeyF4 51 | KeyF5 52 | KeyF6 53 | KeyF7 54 | KeyF8 55 | KeyF9 56 | KeyF10 57 | KeyF11 58 | KeyF12 59 | KeyInsert 60 | KeyDelete 61 | KeyHome 62 | KeyEnd 63 | KeyPgup 64 | KeyPgdn 65 | KeyArrowUp 66 | KeyArrowDown 67 | KeyArrowLeft 68 | KeyArrowRight 69 | key_min // see terminfo 70 | MouseLeft 71 | MouseMiddle 72 | MouseRight 73 | MouseRelease 74 | MouseWheelUp 75 | MouseWheelDown 76 | ) 77 | 78 | const ( 79 | KeyCtrlTilde Key = 0x00 80 | KeyCtrl2 Key = 0x00 81 | KeyCtrlSpace Key = 0x00 82 | KeyCtrlA Key = 0x01 83 | KeyCtrlB Key = 0x02 84 | KeyCtrlC Key = 0x03 85 | KeyCtrlD Key = 0x04 86 | KeyCtrlE Key = 0x05 87 | KeyCtrlF Key = 0x06 88 | KeyCtrlG Key = 0x07 89 | KeyBackspace Key = 0x08 90 | KeyCtrlH Key = 0x08 91 | KeyTab Key = 0x09 92 | KeyCtrlI Key = 0x09 93 | KeyCtrlJ Key = 0x0A 94 | KeyCtrlK Key = 0x0B 95 | KeyCtrlL Key = 0x0C 96 | KeyEnter Key = 0x0D 97 | KeyCtrlM Key = 0x0D 98 | KeyCtrlN Key = 0x0E 99 | KeyCtrlO Key = 0x0F 100 | KeyCtrlP Key = 0x10 101 | KeyCtrlQ Key = 0x11 102 | KeyCtrlR Key = 0x12 103 | KeyCtrlS Key = 0x13 104 | KeyCtrlT Key = 0x14 105 | KeyCtrlU Key = 0x15 106 | KeyCtrlV Key = 0x16 107 | KeyCtrlW Key = 0x17 108 | KeyCtrlX Key = 0x18 109 | KeyCtrlY Key = 0x19 110 | KeyCtrlZ Key = 0x1A 111 | KeyEsc Key = 0x1B 112 | KeyCtrlLsqBracket Key = 0x1B 113 | KeyCtrl3 Key = 0x1B 114 | KeyCtrl4 Key = 0x1C 115 | KeyCtrlBackslash Key = 0x1C 116 | KeyCtrl5 Key = 0x1D 117 | KeyCtrlRsqBracket Key = 0x1D 118 | KeyCtrl6 Key = 0x1E 119 | KeyCtrl7 Key = 0x1F 120 | KeyCtrlSlash Key = 0x1F 121 | KeyCtrlUnderscore Key = 0x1F 122 | KeySpace Key = 0x20 123 | KeyBackspace2 Key = 0x7F 124 | KeyCtrl8 Key = 0x7F 125 | ) 126 | 127 | // Alt modifier constant, see Event.Mod field and SetInputMode function. 128 | const ( 129 | ModAlt Modifier = 1 << iota 130 | ModMotion 131 | ) 132 | 133 | // Cell colors, you can combine a color with multiple attributes using bitwise 134 | // OR ('|'). 135 | const ( 136 | ColorDefault Attribute = iota 137 | ColorBlack 138 | ColorRed 139 | ColorGreen 140 | ColorYellow 141 | ColorBlue 142 | ColorMagenta 143 | ColorCyan 144 | ColorWhite 145 | ) 146 | 147 | // Cell attributes, it is possible to use multiple attributes by combining them 148 | // using bitwise OR ('|'). Although, colors cannot be combined. But you can 149 | // combine attributes and a single color. 150 | // 151 | // It's worth mentioning that some platforms don't support certain attributes. 152 | // For example windows console doesn't support AttrUnderline. And on some 153 | // terminals applying AttrBold to background may result in blinking text. Use 154 | // them with caution and test your code on various terminals. 155 | const ( 156 | AttrBold Attribute = 1 << (iota + 9) 157 | AttrUnderline 158 | AttrReverse 159 | ) 160 | 161 | // Input mode. See SetInputMode function. 162 | const ( 163 | InputEsc InputMode = 1 << iota 164 | InputAlt 165 | InputMouse 166 | InputCurrent InputMode = 0 167 | ) 168 | 169 | // Output mode. See SetOutputMode function. 170 | const ( 171 | OutputCurrent OutputMode = iota 172 | OutputNormal 173 | Output256 174 | Output216 175 | OutputGrayscale 176 | ) 177 | 178 | // Event type. See Event.Type field. 179 | const ( 180 | EventKey EventType = iota 181 | EventResize 182 | EventMouse 183 | EventError 184 | EventInterrupt 185 | EventRaw 186 | EventNone 187 | ) 188 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/api_windows.go: -------------------------------------------------------------------------------- 1 | package termbox 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | // public API 8 | 9 | // Initializes termbox library. This function should be called before any other functions. 10 | // After successful initialization, the library must be finalized using 'Close' function. 11 | // 12 | // Example usage: 13 | // err := termbox.Init() 14 | // if err != nil { 15 | // panic(err) 16 | // } 17 | // defer termbox.Close() 18 | func Init() error { 19 | var err error 20 | 21 | interrupt, err = create_event() 22 | if err != nil { 23 | return err 24 | } 25 | 26 | in, err = syscall.Open("CONIN$", syscall.O_RDWR, 0) 27 | if err != nil { 28 | return err 29 | } 30 | out, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | err = get_console_mode(in, &orig_mode) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | err = set_console_mode(in, enable_window_input) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | orig_size = get_term_size(out) 46 | win_size := get_win_size(out) 47 | 48 | err = set_console_screen_buffer_size(out, win_size) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | err = get_console_cursor_info(out, &orig_cursor_info) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | show_cursor(false) 59 | term_size = get_term_size(out) 60 | back_buffer.init(int(term_size.x), int(term_size.y)) 61 | front_buffer.init(int(term_size.x), int(term_size.y)) 62 | back_buffer.clear() 63 | front_buffer.clear() 64 | clear() 65 | 66 | diffbuf = make([]diff_msg, 0, 32) 67 | 68 | go input_event_producer() 69 | IsInit = true 70 | return nil 71 | } 72 | 73 | // Finalizes termbox library, should be called after successful initialization 74 | // when termbox's functionality isn't required anymore. 75 | func Close() { 76 | // we ignore errors here, because we can't really do anything about them 77 | Clear(0, 0) 78 | Flush() 79 | 80 | // stop event producer 81 | cancel_comm <- true 82 | set_event(interrupt) 83 | select { 84 | case <-input_comm: 85 | default: 86 | } 87 | <-cancel_done_comm 88 | 89 | set_console_cursor_info(out, &orig_cursor_info) 90 | set_console_cursor_position(out, coord{}) 91 | set_console_screen_buffer_size(out, orig_size) 92 | set_console_mode(in, orig_mode) 93 | syscall.Close(in) 94 | syscall.Close(out) 95 | syscall.Close(interrupt) 96 | IsInit = false 97 | } 98 | 99 | // Interrupt an in-progress call to PollEvent by causing it to return 100 | // EventInterrupt. Note that this function will block until the PollEvent 101 | // function has successfully been interrupted. 102 | func Interrupt() { 103 | interrupt_comm <- struct{}{} 104 | } 105 | 106 | // Synchronizes the internal back buffer with the terminal. 107 | func Flush() error { 108 | update_size_maybe() 109 | prepare_diff_messages() 110 | for _, diff := range diffbuf { 111 | r := small_rect{ 112 | left: 0, 113 | top: diff.pos, 114 | right: term_size.x - 1, 115 | bottom: diff.pos + diff.lines - 1, 116 | } 117 | write_console_output(out, diff.chars, r) 118 | } 119 | if !is_cursor_hidden(cursor_x, cursor_y) { 120 | move_cursor(cursor_x, cursor_y) 121 | } 122 | return nil 123 | } 124 | 125 | // Sets the position of the cursor. See also HideCursor(). 126 | func SetCursor(x, y int) { 127 | if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) { 128 | show_cursor(true) 129 | } 130 | 131 | if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) { 132 | show_cursor(false) 133 | } 134 | 135 | cursor_x, cursor_y = x, y 136 | if !is_cursor_hidden(cursor_x, cursor_y) { 137 | move_cursor(cursor_x, cursor_y) 138 | } 139 | } 140 | 141 | // The shortcut for SetCursor(-1, -1). 142 | func HideCursor() { 143 | SetCursor(cursor_hidden, cursor_hidden) 144 | } 145 | 146 | // Changes cell's parameters in the internal back buffer at the specified 147 | // position. 148 | func SetCell(x, y int, ch rune, fg, bg Attribute) { 149 | if x < 0 || x >= back_buffer.width { 150 | return 151 | } 152 | if y < 0 || y >= back_buffer.height { 153 | return 154 | } 155 | 156 | back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg} 157 | } 158 | 159 | // Returns a slice into the termbox's back buffer. You can get its dimensions 160 | // using 'Size' function. The slice remains valid as long as no 'Clear' or 161 | // 'Flush' function calls were made after call to this function. 162 | func CellBuffer() []Cell { 163 | return back_buffer.cells 164 | } 165 | 166 | // Wait for an event and return it. This is a blocking function call. 167 | func PollEvent() Event { 168 | select { 169 | case ev := <-input_comm: 170 | return ev 171 | case <-interrupt_comm: 172 | return Event{Type: EventInterrupt} 173 | } 174 | } 175 | 176 | // Returns the size of the internal back buffer (which is mostly the same as 177 | // console's window size in characters). But it doesn't always match the size 178 | // of the console window, after the console size has changed, the internal back 179 | // buffer will get in sync only after Clear or Flush function calls. 180 | func Size() (int, int) { 181 | return int(term_size.x), int(term_size.y) 182 | } 183 | 184 | // Clears the internal back buffer. 185 | func Clear(fg, bg Attribute) error { 186 | foreground, background = fg, bg 187 | update_size_maybe() 188 | back_buffer.clear() 189 | return nil 190 | } 191 | 192 | // Sets termbox input mode. Termbox has two input modes: 193 | // 194 | // 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match 195 | // any known sequence. ESC means KeyEsc. This is the default input mode. 196 | // 197 | // 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match 198 | // any known sequence. ESC enables ModAlt modifier for the next keyboard event. 199 | // 200 | // Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will 201 | // enable mouse button press/release and drag events. 202 | // 203 | // If 'mode' is InputCurrent, returns the current input mode. See also Input* 204 | // constants. 205 | func SetInputMode(mode InputMode) InputMode { 206 | if mode == InputCurrent { 207 | return input_mode 208 | } 209 | if mode&InputMouse != 0 { 210 | err := set_console_mode(in, enable_window_input|enable_mouse_input|enable_extended_flags) 211 | if err != nil { 212 | panic(err) 213 | } 214 | } else { 215 | err := set_console_mode(in, enable_window_input) 216 | if err != nil { 217 | panic(err) 218 | } 219 | } 220 | 221 | input_mode = mode 222 | return input_mode 223 | } 224 | 225 | // Sets the termbox output mode. 226 | // 227 | // Windows console does not support extra colour modes, 228 | // so this will always set and return OutputNormal. 229 | func SetOutputMode(mode OutputMode) OutputMode { 230 | return OutputNormal 231 | } 232 | 233 | // Sync comes handy when something causes desync between termbox's understanding 234 | // of a terminal buffer and the reality. Such as a third party process. Sync 235 | // forces a complete resync between the termbox and a terminal, it may not be 236 | // visually pretty though. At the moment on Windows it does nothing. 237 | func Sync() error { 238 | return nil 239 | } 240 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/collect_terminfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys, os, subprocess 4 | 5 | def escaped(s): 6 | return repr(s)[1:-1] 7 | 8 | def tput(term, name): 9 | try: 10 | return subprocess.check_output(['tput', '-T%s' % term, name]).decode() 11 | except subprocess.CalledProcessError as e: 12 | return e.output.decode() 13 | 14 | 15 | def w(s): 16 | if s == None: 17 | return 18 | sys.stdout.write(s) 19 | 20 | terminals = { 21 | 'xterm' : 'xterm', 22 | 'rxvt-256color' : 'rxvt_256color', 23 | 'rxvt-unicode' : 'rxvt_unicode', 24 | 'linux' : 'linux', 25 | 'Eterm' : 'eterm', 26 | 'screen' : 'screen' 27 | } 28 | 29 | keys = [ 30 | "F1", "kf1", 31 | "F2", "kf2", 32 | "F3", "kf3", 33 | "F4", "kf4", 34 | "F5", "kf5", 35 | "F6", "kf6", 36 | "F7", "kf7", 37 | "F8", "kf8", 38 | "F9", "kf9", 39 | "F10", "kf10", 40 | "F11", "kf11", 41 | "F12", "kf12", 42 | "INSERT", "kich1", 43 | "DELETE", "kdch1", 44 | "HOME", "khome", 45 | "END", "kend", 46 | "PGUP", "kpp", 47 | "PGDN", "knp", 48 | "KEY_UP", "kcuu1", 49 | "KEY_DOWN", "kcud1", 50 | "KEY_LEFT", "kcub1", 51 | "KEY_RIGHT", "kcuf1" 52 | ] 53 | 54 | funcs = [ 55 | "T_ENTER_CA", "smcup", 56 | "T_EXIT_CA", "rmcup", 57 | "T_SHOW_CURSOR", "cnorm", 58 | "T_HIDE_CURSOR", "civis", 59 | "T_CLEAR_SCREEN", "clear", 60 | "T_SGR0", "sgr0", 61 | "T_UNDERLINE", "smul", 62 | "T_BOLD", "bold", 63 | "T_BLINK", "blink", 64 | "T_REVERSE", "rev", 65 | "T_ENTER_KEYPAD", "smkx", 66 | "T_EXIT_KEYPAD", "rmkx" 67 | ] 68 | 69 | def iter_pairs(iterable): 70 | iterable = iter(iterable) 71 | while True: 72 | yield (next(iterable), next(iterable)) 73 | 74 | def do_term(term, nick): 75 | w("// %s\n" % term) 76 | w("var %s_keys = []string{\n\t" % nick) 77 | for k, v in iter_pairs(keys): 78 | w('"') 79 | w(escaped(tput(term, v))) 80 | w('",') 81 | w("\n}\n") 82 | w("var %s_funcs = []string{\n\t" % nick) 83 | for k,v in iter_pairs(funcs): 84 | w('"') 85 | if v == "sgr": 86 | w("\\033[3%d;4%dm") 87 | elif v == "cup": 88 | w("\\033[%d;%dH") 89 | else: 90 | w(escaped(tput(term, v))) 91 | w('", ') 92 | w("\n}\n\n") 93 | 94 | def do_terms(d): 95 | w("var terms = []struct {\n") 96 | w("\tname string\n") 97 | w("\tkeys []string\n") 98 | w("\tfuncs []string\n") 99 | w("}{\n") 100 | for k, v in d.items(): 101 | w('\t{"%s", %s_keys, %s_funcs},\n' % (k, v, v)) 102 | w("}\n\n") 103 | 104 | w("// +build !windows\n\npackage termbox\n\n") 105 | 106 | for k,v in terminals.items(): 107 | do_term(k, v) 108 | 109 | do_terms(terminals) 110 | 111 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/escwait.go: -------------------------------------------------------------------------------- 1 | // +build !darwin 2 | 3 | package termbox 4 | 5 | // On all systems other than macOS, disable behavior which will wait before 6 | // deciding that the escape key was pressed, to account for partially send 7 | // escape sequences, especially with regard to lengthy mouse sequences. 8 | // See https://github.com/nsf/termbox-go/issues/132 9 | func enable_wait_for_escape_sequence() bool { 10 | return false 11 | } 12 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/escwait_darwin.go: -------------------------------------------------------------------------------- 1 | package termbox 2 | 3 | // On macOS, enable behavior which will wait before deciding that the escape 4 | // key was pressed, to account for partially send escape sequences, especially 5 | // with regard to lengthy mouse sequences. 6 | // See https://github.com/nsf/termbox-go/issues/132 7 | func enable_wait_for_escape_sequence() bool { 8 | return true 9 | } 10 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/syscalls.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package termbox 4 | 5 | /* 6 | #include 7 | #include 8 | */ 9 | import "C" 10 | 11 | type syscall_Termios C.struct_termios 12 | 13 | const ( 14 | syscall_IGNBRK = C.IGNBRK 15 | syscall_BRKINT = C.BRKINT 16 | syscall_PARMRK = C.PARMRK 17 | syscall_ISTRIP = C.ISTRIP 18 | syscall_INLCR = C.INLCR 19 | syscall_IGNCR = C.IGNCR 20 | syscall_ICRNL = C.ICRNL 21 | syscall_IXON = C.IXON 22 | syscall_OPOST = C.OPOST 23 | syscall_ECHO = C.ECHO 24 | syscall_ECHONL = C.ECHONL 25 | syscall_ICANON = C.ICANON 26 | syscall_ISIG = C.ISIG 27 | syscall_IEXTEN = C.IEXTEN 28 | syscall_CSIZE = C.CSIZE 29 | syscall_PARENB = C.PARENB 30 | syscall_CS8 = C.CS8 31 | syscall_VMIN = C.VMIN 32 | syscall_VTIME = C.VTIME 33 | 34 | // on darwin change these to (on *bsd too?): 35 | // C.TIOCGETA 36 | // C.TIOCSETA 37 | syscall_TCGETS = C.TCGETS 38 | syscall_TCSETS = C.TCSETS 39 | ) 40 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/syscalls_darwin.go: -------------------------------------------------------------------------------- 1 | // Created by cgo -godefs - DO NOT EDIT 2 | // cgo -godefs syscalls.go 3 | 4 | // +build !amd64 5 | 6 | package termbox 7 | 8 | type syscall_Termios struct { 9 | Iflag uint32 10 | Oflag uint32 11 | Cflag uint32 12 | Lflag uint32 13 | Cc [20]uint8 14 | Ispeed uint32 15 | Ospeed uint32 16 | } 17 | 18 | const ( 19 | syscall_IGNBRK = 0x1 20 | syscall_BRKINT = 0x2 21 | syscall_PARMRK = 0x8 22 | syscall_ISTRIP = 0x20 23 | syscall_INLCR = 0x40 24 | syscall_IGNCR = 0x80 25 | syscall_ICRNL = 0x100 26 | syscall_IXON = 0x200 27 | syscall_OPOST = 0x1 28 | syscall_ECHO = 0x8 29 | syscall_ECHONL = 0x10 30 | syscall_ICANON = 0x100 31 | syscall_ISIG = 0x80 32 | syscall_IEXTEN = 0x400 33 | syscall_CSIZE = 0x300 34 | syscall_PARENB = 0x1000 35 | syscall_CS8 = 0x300 36 | syscall_VMIN = 0x10 37 | syscall_VTIME = 0x11 38 | 39 | syscall_TCGETS = 0x402c7413 40 | syscall_TCSETS = 0x802c7414 41 | ) 42 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/syscalls_darwin_amd64.go: -------------------------------------------------------------------------------- 1 | // Created by cgo -godefs - DO NOT EDIT 2 | // cgo -godefs syscalls.go 3 | 4 | package termbox 5 | 6 | type syscall_Termios struct { 7 | Iflag uint64 8 | Oflag uint64 9 | Cflag uint64 10 | Lflag uint64 11 | Cc [20]uint8 12 | Pad_cgo_0 [4]byte 13 | Ispeed uint64 14 | Ospeed uint64 15 | } 16 | 17 | const ( 18 | syscall_IGNBRK = 0x1 19 | syscall_BRKINT = 0x2 20 | syscall_PARMRK = 0x8 21 | syscall_ISTRIP = 0x20 22 | syscall_INLCR = 0x40 23 | syscall_IGNCR = 0x80 24 | syscall_ICRNL = 0x100 25 | syscall_IXON = 0x200 26 | syscall_OPOST = 0x1 27 | syscall_ECHO = 0x8 28 | syscall_ECHONL = 0x10 29 | syscall_ICANON = 0x100 30 | syscall_ISIG = 0x80 31 | syscall_IEXTEN = 0x400 32 | syscall_CSIZE = 0x300 33 | syscall_PARENB = 0x1000 34 | syscall_CS8 = 0x300 35 | syscall_VMIN = 0x10 36 | syscall_VTIME = 0x11 37 | 38 | syscall_TCGETS = 0x40487413 39 | syscall_TCSETS = 0x80487414 40 | ) 41 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go: -------------------------------------------------------------------------------- 1 | // Created by cgo -godefs - DO NOT EDIT 2 | // cgo -godefs syscalls.go 3 | 4 | package termbox 5 | 6 | type syscall_Termios struct { 7 | Iflag uint32 8 | Oflag uint32 9 | Cflag uint32 10 | Lflag uint32 11 | Cc [20]uint8 12 | Ispeed uint32 13 | Ospeed uint32 14 | } 15 | 16 | const ( 17 | syscall_IGNBRK = 0x1 18 | syscall_BRKINT = 0x2 19 | syscall_PARMRK = 0x8 20 | syscall_ISTRIP = 0x20 21 | syscall_INLCR = 0x40 22 | syscall_IGNCR = 0x80 23 | syscall_ICRNL = 0x100 24 | syscall_IXON = 0x200 25 | syscall_OPOST = 0x1 26 | syscall_ECHO = 0x8 27 | syscall_ECHONL = 0x10 28 | syscall_ICANON = 0x100 29 | syscall_ISIG = 0x80 30 | syscall_IEXTEN = 0x400 31 | syscall_CSIZE = 0x300 32 | syscall_PARENB = 0x1000 33 | syscall_CS8 = 0x300 34 | syscall_VMIN = 0x10 35 | syscall_VTIME = 0x11 36 | 37 | syscall_TCGETS = 0x402c7413 38 | syscall_TCSETS = 0x802c7414 39 | ) 40 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/syscalls_freebsd.go: -------------------------------------------------------------------------------- 1 | // Created by cgo -godefs - DO NOT EDIT 2 | // cgo -godefs syscalls.go 3 | 4 | package termbox 5 | 6 | type syscall_Termios struct { 7 | Iflag uint32 8 | Oflag uint32 9 | Cflag uint32 10 | Lflag uint32 11 | Cc [20]uint8 12 | Ispeed uint32 13 | Ospeed uint32 14 | } 15 | 16 | const ( 17 | syscall_IGNBRK = 0x1 18 | syscall_BRKINT = 0x2 19 | syscall_PARMRK = 0x8 20 | syscall_ISTRIP = 0x20 21 | syscall_INLCR = 0x40 22 | syscall_IGNCR = 0x80 23 | syscall_ICRNL = 0x100 24 | syscall_IXON = 0x200 25 | syscall_OPOST = 0x1 26 | syscall_ECHO = 0x8 27 | syscall_ECHONL = 0x10 28 | syscall_ICANON = 0x100 29 | syscall_ISIG = 0x80 30 | syscall_IEXTEN = 0x400 31 | syscall_CSIZE = 0x300 32 | syscall_PARENB = 0x1000 33 | syscall_CS8 = 0x300 34 | syscall_VMIN = 0x10 35 | syscall_VTIME = 0x11 36 | 37 | syscall_TCGETS = 0x402c7413 38 | syscall_TCSETS = 0x802c7414 39 | ) 40 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/syscalls_linux.go: -------------------------------------------------------------------------------- 1 | // Created by cgo -godefs - DO NOT EDIT 2 | // cgo -godefs syscalls.go 3 | 4 | package termbox 5 | 6 | import "syscall" 7 | 8 | type syscall_Termios syscall.Termios 9 | 10 | const ( 11 | syscall_IGNBRK = syscall.IGNBRK 12 | syscall_BRKINT = syscall.BRKINT 13 | syscall_PARMRK = syscall.PARMRK 14 | syscall_ISTRIP = syscall.ISTRIP 15 | syscall_INLCR = syscall.INLCR 16 | syscall_IGNCR = syscall.IGNCR 17 | syscall_ICRNL = syscall.ICRNL 18 | syscall_IXON = syscall.IXON 19 | syscall_OPOST = syscall.OPOST 20 | syscall_ECHO = syscall.ECHO 21 | syscall_ECHONL = syscall.ECHONL 22 | syscall_ICANON = syscall.ICANON 23 | syscall_ISIG = syscall.ISIG 24 | syscall_IEXTEN = syscall.IEXTEN 25 | syscall_CSIZE = syscall.CSIZE 26 | syscall_PARENB = syscall.PARENB 27 | syscall_CS8 = syscall.CS8 28 | syscall_VMIN = syscall.VMIN 29 | syscall_VTIME = syscall.VTIME 30 | 31 | syscall_TCGETS = syscall.TCGETS 32 | syscall_TCSETS = syscall.TCSETS 33 | ) 34 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/syscalls_netbsd.go: -------------------------------------------------------------------------------- 1 | // Created by cgo -godefs - DO NOT EDIT 2 | // cgo -godefs syscalls.go 3 | 4 | package termbox 5 | 6 | type syscall_Termios struct { 7 | Iflag uint32 8 | Oflag uint32 9 | Cflag uint32 10 | Lflag uint32 11 | Cc [20]uint8 12 | Ispeed int32 13 | Ospeed int32 14 | } 15 | 16 | const ( 17 | syscall_IGNBRK = 0x1 18 | syscall_BRKINT = 0x2 19 | syscall_PARMRK = 0x8 20 | syscall_ISTRIP = 0x20 21 | syscall_INLCR = 0x40 22 | syscall_IGNCR = 0x80 23 | syscall_ICRNL = 0x100 24 | syscall_IXON = 0x200 25 | syscall_OPOST = 0x1 26 | syscall_ECHO = 0x8 27 | syscall_ECHONL = 0x10 28 | syscall_ICANON = 0x100 29 | syscall_ISIG = 0x80 30 | syscall_IEXTEN = 0x400 31 | syscall_CSIZE = 0x300 32 | syscall_PARENB = 0x1000 33 | syscall_CS8 = 0x300 34 | syscall_VMIN = 0x10 35 | syscall_VTIME = 0x11 36 | 37 | syscall_TCGETS = 0x402c7413 38 | syscall_TCSETS = 0x802c7414 39 | ) 40 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/syscalls_openbsd.go: -------------------------------------------------------------------------------- 1 | // Created by cgo -godefs - DO NOT EDIT 2 | // cgo -godefs syscalls.go 3 | 4 | package termbox 5 | 6 | type syscall_Termios struct { 7 | Iflag uint32 8 | Oflag uint32 9 | Cflag uint32 10 | Lflag uint32 11 | Cc [20]uint8 12 | Ispeed int32 13 | Ospeed int32 14 | } 15 | 16 | const ( 17 | syscall_IGNBRK = 0x1 18 | syscall_BRKINT = 0x2 19 | syscall_PARMRK = 0x8 20 | syscall_ISTRIP = 0x20 21 | syscall_INLCR = 0x40 22 | syscall_IGNCR = 0x80 23 | syscall_ICRNL = 0x100 24 | syscall_IXON = 0x200 25 | syscall_OPOST = 0x1 26 | syscall_ECHO = 0x8 27 | syscall_ECHONL = 0x10 28 | syscall_ICANON = 0x100 29 | syscall_ISIG = 0x80 30 | syscall_IEXTEN = 0x400 31 | syscall_CSIZE = 0x300 32 | syscall_PARENB = 0x1000 33 | syscall_CS8 = 0x300 34 | syscall_VMIN = 0x10 35 | syscall_VTIME = 0x11 36 | 37 | syscall_TCGETS = 0x402c7413 38 | syscall_TCSETS = 0x802c7414 39 | ) 40 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/syscalls_windows.go: -------------------------------------------------------------------------------- 1 | // Created by cgo -godefs - DO NOT EDIT 2 | // cgo -godefs -- -DUNICODE syscalls.go 3 | 4 | package termbox 5 | 6 | const ( 7 | foreground_blue = 0x1 8 | foreground_green = 0x2 9 | foreground_red = 0x4 10 | foreground_intensity = 0x8 11 | background_blue = 0x10 12 | background_green = 0x20 13 | background_red = 0x40 14 | background_intensity = 0x80 15 | std_input_handle = -0xa 16 | std_output_handle = -0xb 17 | key_event = 0x1 18 | mouse_event = 0x2 19 | window_buffer_size_event = 0x4 20 | enable_window_input = 0x8 21 | enable_mouse_input = 0x10 22 | enable_extended_flags = 0x80 23 | 24 | vk_f1 = 0x70 25 | vk_f2 = 0x71 26 | vk_f3 = 0x72 27 | vk_f4 = 0x73 28 | vk_f5 = 0x74 29 | vk_f6 = 0x75 30 | vk_f7 = 0x76 31 | vk_f8 = 0x77 32 | vk_f9 = 0x78 33 | vk_f10 = 0x79 34 | vk_f11 = 0x7a 35 | vk_f12 = 0x7b 36 | vk_insert = 0x2d 37 | vk_delete = 0x2e 38 | vk_home = 0x24 39 | vk_end = 0x23 40 | vk_pgup = 0x21 41 | vk_pgdn = 0x22 42 | vk_arrow_up = 0x26 43 | vk_arrow_down = 0x28 44 | vk_arrow_left = 0x25 45 | vk_arrow_right = 0x27 46 | vk_backspace = 0x8 47 | vk_tab = 0x9 48 | vk_enter = 0xd 49 | vk_esc = 0x1b 50 | vk_space = 0x20 51 | 52 | left_alt_pressed = 0x2 53 | left_ctrl_pressed = 0x8 54 | right_alt_pressed = 0x1 55 | right_ctrl_pressed = 0x4 56 | shift_pressed = 0x10 57 | 58 | generic_read = 0x80000000 59 | generic_write = 0x40000000 60 | console_textmode_buffer = 0x1 61 | ) 62 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/termbox_common.go: -------------------------------------------------------------------------------- 1 | package termbox 2 | 3 | // private API, common OS agnostic part 4 | 5 | type cellbuf struct { 6 | width int 7 | height int 8 | cells []Cell 9 | } 10 | 11 | func (this *cellbuf) init(width, height int) { 12 | this.width = width 13 | this.height = height 14 | this.cells = make([]Cell, width*height) 15 | } 16 | 17 | func (this *cellbuf) resize(width, height int) { 18 | if this.width == width && this.height == height { 19 | return 20 | } 21 | 22 | oldw := this.width 23 | oldh := this.height 24 | oldcells := this.cells 25 | 26 | this.init(width, height) 27 | this.clear() 28 | 29 | minw, minh := oldw, oldh 30 | 31 | if width < minw { 32 | minw = width 33 | } 34 | if height < minh { 35 | minh = height 36 | } 37 | 38 | for i := 0; i < minh; i++ { 39 | srco, dsto := i*oldw, i*width 40 | src := oldcells[srco : srco+minw] 41 | dst := this.cells[dsto : dsto+minw] 42 | copy(dst, src) 43 | } 44 | } 45 | 46 | func (this *cellbuf) clear() { 47 | for i := range this.cells { 48 | c := &this.cells[i] 49 | c.Ch = ' ' 50 | c.Fg = foreground 51 | c.Bg = background 52 | } 53 | } 54 | 55 | const cursor_hidden = -1 56 | 57 | func is_cursor_hidden(x, y int) bool { 58 | return x == cursor_hidden || y == cursor_hidden 59 | } 60 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/terminfo.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | // This file contains a simple and incomplete implementation of the terminfo 3 | // database. Information was taken from the ncurses manpages term(5) and 4 | // terminfo(5). Currently, only the string capabilities for special keys and for 5 | // functions without parameters are actually used. Colors are still done with 6 | // ANSI escape sequences. Other special features that are not (yet?) supported 7 | // are reading from ~/.terminfo, the TERMINFO_DIRS variable, Berkeley database 8 | // format and extended capabilities. 9 | 10 | package termbox 11 | 12 | import ( 13 | "bytes" 14 | "encoding/binary" 15 | "encoding/hex" 16 | "errors" 17 | "fmt" 18 | "io/ioutil" 19 | "os" 20 | "strings" 21 | ) 22 | 23 | const ( 24 | ti_magic = 0432 25 | ti_header_length = 12 26 | ti_mouse_enter = "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" 27 | ti_mouse_leave = "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" 28 | ) 29 | 30 | func load_terminfo() ([]byte, error) { 31 | var data []byte 32 | var err error 33 | 34 | term := os.Getenv("TERM") 35 | if term == "" { 36 | return nil, fmt.Errorf("termbox: TERM not set") 37 | } 38 | 39 | // The following behaviour follows the one described in terminfo(5) as 40 | // distributed by ncurses. 41 | 42 | terminfo := os.Getenv("TERMINFO") 43 | if terminfo != "" { 44 | // if TERMINFO is set, no other directory should be searched 45 | return ti_try_path(terminfo) 46 | } 47 | 48 | // next, consider ~/.terminfo 49 | home := os.Getenv("HOME") 50 | if home != "" { 51 | data, err = ti_try_path(home + "/.terminfo") 52 | if err == nil { 53 | return data, nil 54 | } 55 | } 56 | 57 | // next, TERMINFO_DIRS 58 | dirs := os.Getenv("TERMINFO_DIRS") 59 | if dirs != "" { 60 | for _, dir := range strings.Split(dirs, ":") { 61 | if dir == "" { 62 | // "" -> "/usr/share/terminfo" 63 | dir = "/usr/share/terminfo" 64 | } 65 | data, err = ti_try_path(dir) 66 | if err == nil { 67 | return data, nil 68 | } 69 | } 70 | } 71 | 72 | // next, /lib/terminfo 73 | data, err = ti_try_path("/lib/terminfo") 74 | if err == nil { 75 | return data, nil 76 | } 77 | 78 | // fall back to /usr/share/terminfo 79 | return ti_try_path("/usr/share/terminfo") 80 | } 81 | 82 | func ti_try_path(path string) (data []byte, err error) { 83 | // load_terminfo already made sure it is set 84 | term := os.Getenv("TERM") 85 | 86 | // first try, the typical *nix path 87 | terminfo := path + "/" + term[0:1] + "/" + term 88 | data, err = ioutil.ReadFile(terminfo) 89 | if err == nil { 90 | return 91 | } 92 | 93 | // fallback to darwin specific dirs structure 94 | terminfo = path + "/" + hex.EncodeToString([]byte(term[:1])) + "/" + term 95 | data, err = ioutil.ReadFile(terminfo) 96 | return 97 | } 98 | 99 | func setup_term_builtin() error { 100 | name := os.Getenv("TERM") 101 | if name == "" { 102 | return errors.New("termbox: TERM environment variable not set") 103 | } 104 | 105 | for _, t := range terms { 106 | if t.name == name { 107 | keys = t.keys 108 | funcs = t.funcs 109 | return nil 110 | } 111 | } 112 | 113 | compat_table := []struct { 114 | partial string 115 | keys []string 116 | funcs []string 117 | }{ 118 | {"xterm", xterm_keys, xterm_funcs}, 119 | {"rxvt", rxvt_unicode_keys, rxvt_unicode_funcs}, 120 | {"linux", linux_keys, linux_funcs}, 121 | {"Eterm", eterm_keys, eterm_funcs}, 122 | {"screen", screen_keys, screen_funcs}, 123 | // let's assume that 'cygwin' is xterm compatible 124 | {"cygwin", xterm_keys, xterm_funcs}, 125 | {"st", xterm_keys, xterm_funcs}, 126 | } 127 | 128 | // try compatibility variants 129 | for _, it := range compat_table { 130 | if strings.Contains(name, it.partial) { 131 | keys = it.keys 132 | funcs = it.funcs 133 | return nil 134 | } 135 | } 136 | 137 | return errors.New("termbox: unsupported terminal") 138 | } 139 | 140 | func setup_term() (err error) { 141 | var data []byte 142 | var header [6]int16 143 | var str_offset, table_offset int16 144 | 145 | data, err = load_terminfo() 146 | if err != nil { 147 | return setup_term_builtin() 148 | } 149 | 150 | rd := bytes.NewReader(data) 151 | // 0: magic number, 1: size of names section, 2: size of boolean section, 3: 152 | // size of numbers section (in integers), 4: size of the strings section (in 153 | // integers), 5: size of the string table 154 | 155 | err = binary.Read(rd, binary.LittleEndian, header[:]) 156 | if err != nil { 157 | return 158 | } 159 | 160 | number_sec_len := int16(2) 161 | if header[0] == 542 { // doc says it should be octal 0542, but what I see it terminfo files is 542, learn to program please... thank you.. 162 | number_sec_len = 4 163 | } 164 | 165 | if (header[1]+header[2])%2 != 0 { 166 | // old quirk to align everything on word boundaries 167 | header[2] += 1 168 | } 169 | str_offset = ti_header_length + header[1] + header[2] + number_sec_len*header[3] 170 | table_offset = str_offset + 2*header[4] 171 | 172 | keys = make([]string, 0xFFFF-key_min) 173 | for i, _ := range keys { 174 | keys[i], err = ti_read_string(rd, str_offset+2*ti_keys[i], table_offset) 175 | if err != nil { 176 | return 177 | } 178 | } 179 | funcs = make([]string, t_max_funcs) 180 | // the last two entries are reserved for mouse. because the table offset is 181 | // not there, the two entries have to fill in manually 182 | for i, _ := range funcs[:len(funcs)-2] { 183 | funcs[i], err = ti_read_string(rd, str_offset+2*ti_funcs[i], table_offset) 184 | if err != nil { 185 | return 186 | } 187 | } 188 | funcs[t_max_funcs-2] = ti_mouse_enter 189 | funcs[t_max_funcs-1] = ti_mouse_leave 190 | return nil 191 | } 192 | 193 | func ti_read_string(rd *bytes.Reader, str_off, table int16) (string, error) { 194 | var off int16 195 | 196 | _, err := rd.Seek(int64(str_off), 0) 197 | if err != nil { 198 | return "", err 199 | } 200 | err = binary.Read(rd, binary.LittleEndian, &off) 201 | if err != nil { 202 | return "", err 203 | } 204 | _, err = rd.Seek(int64(table+off), 0) 205 | if err != nil { 206 | return "", err 207 | } 208 | var bs []byte 209 | for { 210 | b, err := rd.ReadByte() 211 | if err != nil { 212 | return "", err 213 | } 214 | if b == byte(0x00) { 215 | break 216 | } 217 | bs = append(bs, b) 218 | } 219 | return string(bs), nil 220 | } 221 | 222 | // "Maps" the function constants from termbox.go to the number of the respective 223 | // string capability in the terminfo file. Taken from (ncurses) term.h. 224 | var ti_funcs = []int16{ 225 | 28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88, 226 | } 227 | 228 | // Same as above for the special keys. 229 | var ti_keys = []int16{ 230 | 66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69, 70, 231 | 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61, 79, 83, 232 | } 233 | -------------------------------------------------------------------------------- /vendor/github.com/nsf/termbox-go/terminfo_builtin.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package termbox 4 | 5 | // Eterm 6 | var eterm_keys = []string{ 7 | "\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C", 8 | } 9 | var eterm_funcs = []string{ 10 | "\x1b7\x1b[?47h", "\x1b[2J\x1b[?47l\x1b8", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "", "", "", "", 11 | } 12 | 13 | // screen 14 | var screen_keys = []string{ 15 | "\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1bOA", "\x1bOB", "\x1bOD", "\x1bOC", 16 | } 17 | var screen_funcs = []string{ 18 | "\x1b[?1049h", "\x1b[?1049l", "\x1b[34h\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b[?1h\x1b=", "\x1b[?1l\x1b>", ti_mouse_enter, ti_mouse_leave, 19 | } 20 | 21 | // xterm 22 | var xterm_keys = []string{ 23 | "\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1bOH", "\x1bOF", "\x1b[5~", "\x1b[6~", "\x1bOA", "\x1bOB", "\x1bOD", "\x1bOC", 24 | } 25 | var xterm_funcs = []string{ 26 | "\x1b[?1049h", "\x1b[?1049l", "\x1b[?12l\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b(B\x1b[m", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b[?1h\x1b=", "\x1b[?1l\x1b>", ti_mouse_enter, ti_mouse_leave, 27 | } 28 | 29 | // rxvt-unicode 30 | var rxvt_unicode_keys = []string{ 31 | "\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C", 32 | } 33 | var rxvt_unicode_funcs = []string{ 34 | "\x1b[?1049h", "\x1b[r\x1b[?1049l", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x1b(B", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b=", "\x1b>", ti_mouse_enter, ti_mouse_leave, 35 | } 36 | 37 | // linux 38 | var linux_keys = []string{ 39 | "\x1b[[A", "\x1b[[B", "\x1b[[C", "\x1b[[D", "\x1b[[E", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C", 40 | } 41 | var linux_funcs = []string{ 42 | "", "", "\x1b[?25h\x1b[?0c", "\x1b[?25l\x1b[?1c", "\x1b[H\x1b[J", "\x1b[0;10m", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "", "", "", "", 43 | } 44 | 45 | // rxvt-256color 46 | var rxvt_256color_keys = []string{ 47 | "\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C", 48 | } 49 | var rxvt_256color_funcs = []string{ 50 | "\x1b7\x1b[?47h", "\x1b[2J\x1b[?47l\x1b8", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b=", "\x1b>", ti_mouse_enter, ti_mouse_leave, 51 | } 52 | 53 | var terms = []struct { 54 | name string 55 | keys []string 56 | funcs []string 57 | }{ 58 | {"Eterm", eterm_keys, eterm_funcs}, 59 | {"screen", screen_keys, screen_funcs}, 60 | {"xterm", xterm_keys, xterm_funcs}, 61 | {"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs}, 62 | {"linux", linux_keys, linux_funcs}, 63 | {"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs}, 64 | } 65 | -------------------------------------------------------------------------------- /vendor/github.com/pyk/byten/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Bayu Aldi Yansyah 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /vendor/github.com/pyk/byten/README.md: -------------------------------------------------------------------------------- 1 | byten 2 | ===== 3 | Bit the size of file and turn it into human readable format. 4 | 5 | This is a Go Package that convert size of file into human readable format. 6 | 7 | Another weekend project by [pyk](http://google.com/+bayualdiyansyah). 8 | ### Usage 9 | First thing first, get the remote package 10 | ``` 11 | $ go get github.com/pyk/byten 12 | ``` 13 | and import package into your project 14 | ``` 15 | import "github.com/pyk/byten" 16 | ``` 17 | then bite the bytes! 18 | ``` 19 | byten.Size(1024) # => 1.0KB 20 | byten.Size(206848) # => 202KB 21 | byten.Size(10239999998976) # => 9.3TB 22 | byten.Size(6314666666666665984) # => 5.5EB 23 | ``` 24 | easy huh? =)) 25 | 26 | ### Docs 27 | 28 | Nothing fancy, but you can see [here](https://godoc.org/github.com/pyk/byten) 29 | 30 | ### License 31 | [MIT License](https://github.com/pyk/byten/blob/master/LICENSE) 32 | -------------------------------------------------------------------------------- /vendor/github.com/pyk/byten/size.go: -------------------------------------------------------------------------------- 1 | package byten 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func index(s int64) float64 { 9 | x := math.Log(float64(s)) / math.Log(1024) 10 | return math.Floor(x) 11 | } 12 | 13 | func countSize(s int64, i float64) float64 { 14 | return float64(s) / math.Pow(1024, math.Floor(i)) 15 | } 16 | 17 | // Size return a formated string from file size 18 | func Size(s int64) string { 19 | 20 | symbols := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} 21 | i := index(s) 22 | if s < 10 { 23 | return fmt.Sprintf("%dB", s) 24 | } 25 | size := countSize(s, i) 26 | format := "%.0f" 27 | if size < 10 { 28 | format = "%.1f" 29 | } 30 | 31 | return fmt.Sprintf(format+"%s", size, symbols[int(i)]) 32 | } 33 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/antonholmquist/jason v1.0.0 2 | github.com/antonholmquist/jason 3 | # github.com/bsiegert/ranges v0.0.0-20111221115336-19303dc7aa63 4 | github.com/bsiegert/ranges 5 | # github.com/gizak/termui v0.0.0-20181228210747-b136f68f55f1 6 | github.com/gizak/termui 7 | # github.com/mattn/go-runewidth v0.0.4 8 | github.com/mattn/go-runewidth 9 | # github.com/mitchellh/go-wordwrap v1.0.0 10 | github.com/mitchellh/go-wordwrap 11 | # github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb 12 | github.com/nsf/termbox-go 13 | # github.com/pyk/byten v0.0.0-20140925233358-f847a130bf6d 14 | github.com/pyk/byten 15 | --------------------------------------------------------------------------------