Adde vendor folder contents

This commit is contained in:
erroneousboat 2017-08-26 10:53:28 +02:00
parent 0d8ce935e7
commit 732e292e44
118 changed files with 15817 additions and 2 deletions

22
vendor/github.com/gizak/termui/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Zack Guo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

151
vendor/github.com/gizak/termui/README.md generated vendored Normal file
View File

@ -0,0 +1,151 @@
# termui [![Build Status](https://travis-ci.org/gizak/termui.svg?branch=master)](https://travis-ci.org/gizak/termui) [![Doc Status](https://godoc.org/github.com/gizak/termui?status.png)](https://godoc.org/github.com/gizak/termui)
<img src="./_example/dashboard.gif" alt="demo cast under osx 10.10; Terminal.app; Menlo Regular 12pt.)" width="80%">
`termui` is a cross-platform, easy-to-compile, and fully-customizable terminal dashboard. It is inspired by [blessed-contrib](https://github.com/yaronn/blessed-contrib), but purely in Go.
Now version v2 has arrived! It brings new event system, new theme system, new `Buffer` interface and specific colour text rendering. (some docs are missing, but it will be completed soon!)
## Installation
`master` mirrors v2 branch, to install:
go get -u github.com/gizak/termui
It is recommanded to use locked deps by using [glide](https://glide.sh): move to `termui` src directory then run `glide up`.
For the compatible reason, you can choose to install the legacy version of `termui`:
go get gopkg.in/gizak/termui.v1
## Usage
### Layout
To use `termui`, the very first thing you may want to know is how to manage layout. `termui` offers two ways of doing this, known as absolute layout and grid layout.
__Absolute layout__
Each widget has an underlying block structure which basically is a box model. It has border, label and padding properties. A border of a widget can be chosen to hide or display (with its border label), you can pick a different front/back colour for the border as well. To display such a widget at a specific location in terminal window, you need to assign `.X`, `.Y`, `.Height`, `.Width` values for each widget before sending it to `.Render`. Let's demonstrate these by a code snippet:
`````go
import ui "github.com/gizak/termui" // <- ui shortcut, optional
func main() {
err := ui.Init()
if err != nil {
panic(err)
}
defer ui.Close()
p := ui.NewPar(":PRESS q TO QUIT DEMO")
p.Height = 3
p.Width = 50
p.TextFgColor = ui.ColorWhite
p.BorderLabel = "Text Box"
p.BorderFg = ui.ColorCyan
g := ui.NewGauge()
g.Percent = 50
g.Width = 50
g.Height = 3
g.Y = 11
g.BorderLabel = "Gauge"
g.BarColor = ui.ColorRed
g.BorderFg = ui.ColorWhite
g.BorderLabelFg = ui.ColorCyan
ui.Render(p, g) // feel free to call Render, it's async and non-block
// event handler...
}
`````
Note that components can be overlapped (I'd rather call this a feature...), `Render(rs ...Renderer)` renders its args from left to right (i.e. each component's weight is arising from left to right).
__Grid layout:__
<img src="./_example/grid.gif" alt="grid" width="60%">
Grid layout uses [12 columns grid system](http://www.w3schools.com/bootstrap/bootstrap_grid_system.asp) with expressive syntax. To use `Grid`, all we need to do is build a widget tree consisting of `Row`s and `Col`s (Actually a `Col` is also a `Row` but with a widget endpoint attached).
```go
import ui "github.com/gizak/termui"
// init and create widgets...
// build
ui.Body.AddRows(
ui.NewRow(
ui.NewCol(6, 0, widget0),
ui.NewCol(6, 0, widget1)),
ui.NewRow(
ui.NewCol(3, 0, widget2),
ui.NewCol(3, 0, widget30, widget31, widget32),
ui.NewCol(6, 0, widget4)))
// calculate layout
ui.Body.Align()
ui.Render(ui.Body)
```
### Events
`termui` ships with a http-like event mux handling system. All events are channeled up from different sources (typing, click, windows resize, custom event) and then encoded as universal `Event` object. `Event.Path` indicates the event type and `Event.Data` stores the event data struct. Add a handler to a certain event is easy as below:
```go
// handle key q pressing
ui.Handle("/sys/kbd/q", func(ui.Event) {
// press q to quit
ui.StopLoop()
})
ui.Handle("/sys/kbd/C-x", func(ui.Event) {
// handle Ctrl + x combination
})
ui.Handle("/sys/kbd", func(ui.Event) {
// handle all other key pressing
})
// handle a 1s timer
ui.Handle("/timer/1s", func(e ui.Event) {
t := e.Data.(ui.EvtTimer)
// t is a EvtTimer
if t.Count%2 ==0 {
// do something
}
})
ui.Loop() // block until StopLoop is called
```
### Widgets
Click image to see the corresponding demo codes.
[<img src="./_example/par.png" alt="par" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/par.go)
[<img src="./_example/list.png" alt="list" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/list.go)
[<img src="./_example/gauge.png" alt="gauge" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/gauge.go)
[<img src="./_example/linechart.png" alt="linechart" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/linechart.go)
[<img src="./_example/barchart.png" alt="barchart" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/barchart.go)
[<img src="./_example/mbarchart.png" alt="barchart" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/mbarchart.go)
[<img src="./_example/sparklines.png" alt="sparklines" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/sparklines.go)
[<img src="./_example/table.png" alt="table" type="image/png" width="45%">](https://github.com/gizak/termui/blob/master/_example/table.go)
## GoDoc
[godoc](https://godoc.org/github.com/gizak/termui)
## TODO
- [x] Grid layout
- [x] Event system
- [x] Canvas widget
- [x] Refine APIs
- [ ] Focusable widgets
## Changelog
## License
This library is under the [MIT License](http://opensource.org/licenses/MIT)

149
vendor/github.com/gizak/termui/barchart.go generated vendored Normal file
View File

@ -0,0 +1,149 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import "fmt"
// BarChart creates multiple bars in a widget:
/*
bc := termui.NewBarChart()
data := []int{3, 2, 5, 3, 9, 5}
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
bc.BorderLabel = "Bar Chart"
bc.Data = data
bc.Width = 26
bc.Height = 10
bc.DataLabels = bclabels
bc.TextColor = termui.ColorGreen
bc.BarColor = termui.ColorRed
bc.NumColor = termui.ColorYellow
*/
type BarChart struct {
Block
BarColor Attribute
TextColor Attribute
NumColor Attribute
Data []int
DataLabels []string
BarWidth int
BarGap int
CellChar rune
labels [][]rune
dataNum [][]rune
numBar int
scale float64
max int
}
// NewBarChart returns a new *BarChart with current theme.
func NewBarChart() *BarChart {
bc := &BarChart{Block: *NewBlock()}
bc.BarColor = ThemeAttr("barchart.bar.bg")
bc.NumColor = ThemeAttr("barchart.num.fg")
bc.TextColor = ThemeAttr("barchart.text.fg")
bc.BarGap = 1
bc.BarWidth = 3
bc.CellChar = ' '
return bc
}
func (bc *BarChart) layout() {
bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
bc.labels = make([][]rune, bc.numBar)
bc.dataNum = make([][]rune, len(bc.Data))
for i := 0; i < bc.numBar && i < len(bc.DataLabels) && i < len(bc.Data); i++ {
bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
n := bc.Data[i]
s := fmt.Sprint(n)
bc.dataNum[i] = trimStr2Runes(s, bc.BarWidth)
}
//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
// Asign a negative value to get maxvalue auto-populates
if bc.max == 0 {
bc.max = -1
}
for i := 0; i < len(bc.Data); i++ {
if bc.max < bc.Data[i] {
bc.max = bc.Data[i]
}
}
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
}
func (bc *BarChart) SetMax(max int) {
if max > 0 {
bc.max = max
}
}
// Buffer implements Bufferer interface.
func (bc *BarChart) Buffer() Buffer {
buf := bc.Block.Buffer()
bc.layout()
for i := 0; i < bc.numBar && i < len(bc.Data) && i < len(bc.DataLabels); i++ {
h := int(float64(bc.Data[i]) / bc.scale)
oftX := i * (bc.BarWidth + bc.BarGap)
barBg := bc.Bg
barFg := bc.BarColor
if bc.CellChar == ' ' {
barBg = bc.BarColor
barFg = ColorDefault
if bc.BarColor == ColorDefault { // the same as above
barBg |= AttrReverse
}
}
// plot bar
for j := 0; j < bc.BarWidth; j++ {
for k := 0; k < h; k++ {
c := Cell{
Ch: bc.CellChar,
Bg: barBg,
Fg: barFg,
}
x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k
buf.Set(x, y, c)
}
}
// plot text
for j, k := 0, 0; j < len(bc.labels[i]); j++ {
w := charWidth(bc.labels[i][j])
c := Cell{
Ch: bc.labels[i][j],
Bg: bc.Bg,
Fg: bc.TextColor,
}
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
x := bc.innerArea.Min.X + oftX + k
buf.Set(x, y, c)
k += w
}
// plot num
for j := 0; j < len(bc.dataNum[i]); j++ {
c := Cell{
Ch: bc.dataNum[i][j],
Fg: bc.NumColor,
Bg: barBg,
}
if h == 0 {
c.Bg = bc.Bg
}
x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i]))/2 + j
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
buf.Set(x, y, c)
}
}
return buf
}

240
vendor/github.com/gizak/termui/block.go generated vendored Normal file
View File

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

20
vendor/github.com/gizak/termui/block_common.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
// +build !windows
package termui
const TOP_RIGHT = '┐'
const VERTICAL_LINE = '│'
const HORIZONTAL_LINE = '─'
const TOP_LEFT = '┌'
const BOTTOM_RIGHT = '┘'
const BOTTOM_LEFT = '└'
const VERTICAL_LEFT = '┤'
const VERTICAL_RIGHT = '├'
const HORIZONTAL_DOWN = '┬'
const HORIZONTAL_UP = '┴'
const QUOTA_LEFT = '«'
const QUOTA_RIGHT = '»'

14
vendor/github.com/gizak/termui/block_windows.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
// +build windows
package termui
const TOP_RIGHT = '+'
const VERTICAL_LINE = '|'
const HORIZONTAL_LINE = '-'
const TOP_LEFT = '+'
const BOTTOM_RIGHT = '+'
const BOTTOM_LEFT = '+'

106
vendor/github.com/gizak/termui/buffer.go generated vendored Normal file
View File

@ -0,0 +1,106 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import "image"
// Cell is a rune with assigned Fg and Bg
type Cell struct {
Ch rune
Fg Attribute
Bg Attribute
}
// Buffer is a renderable rectangle cell data container.
type Buffer struct {
Area image.Rectangle // selected drawing area
CellMap map[image.Point]Cell
}
// At returns the cell at (x,y).
func (b Buffer) At(x, y int) Cell {
return b.CellMap[image.Pt(x, y)]
}
// Set assigns a char to (x,y)
func (b Buffer) Set(x, y int, c Cell) {
b.CellMap[image.Pt(x, y)] = c
}
// Bounds returns the domain for which At can return non-zero color.
func (b Buffer) Bounds() image.Rectangle {
x0, y0, x1, y1 := 0, 0, 0, 0
for p := range b.CellMap {
if p.X > x1 {
x1 = p.X
}
if p.X < x0 {
x0 = p.X
}
if p.Y > y1 {
y1 = p.Y
}
if p.Y < y0 {
y0 = p.Y
}
}
return image.Rect(x0, y0, x1+1, y1+1)
}
// SetArea assigns a new rect area to Buffer b.
func (b *Buffer) SetArea(r image.Rectangle) {
b.Area.Max = r.Max
b.Area.Min = r.Min
}
// Sync sets drawing area to the buffer's bound
func (b *Buffer) Sync() {
b.SetArea(b.Bounds())
}
// NewCell returns a new cell
func NewCell(ch rune, fg, bg Attribute) Cell {
return Cell{ch, fg, bg}
}
// Merge merges bs Buffers onto b
func (b *Buffer) Merge(bs ...Buffer) {
for _, buf := range bs {
for p, v := range buf.CellMap {
b.Set(p.X, p.Y, v)
}
b.SetArea(b.Area.Union(buf.Area))
}
}
// NewBuffer returns a new Buffer
func NewBuffer() Buffer {
return Buffer{
CellMap: make(map[image.Point]Cell),
Area: image.Rectangle{}}
}
// Fill fills the Buffer b with ch,fg and bg.
func (b Buffer) Fill(ch rune, fg, bg Attribute) {
for x := b.Area.Min.X; x < b.Area.Max.X; x++ {
for y := b.Area.Min.Y; y < b.Area.Max.Y; y++ {
b.Set(x, y, Cell{ch, fg, bg})
}
}
}
// NewFilledBuffer returns a new Buffer filled with ch, fb and bg.
func NewFilledBuffer(x0, y0, x1, y1 int, ch rune, fg, bg Attribute) Buffer {
buf := NewBuffer()
buf.Area.Min = image.Pt(x0, y0)
buf.Area.Max = image.Pt(x1, y1)
for x := buf.Area.Min.X; x < buf.Area.Max.X; x++ {
for y := buf.Area.Min.Y; y < buf.Area.Max.Y; y++ {
buf.Set(x, y, Cell{ch, fg, bg})
}
}
return buf
}

72
vendor/github.com/gizak/termui/canvas.go generated vendored Normal file
View File

@ -0,0 +1,72 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
/*
dots:
,___,
|1 4|
|2 5|
|3 6|
|7 8|
`````
*/
var brailleBase = '\u2800'
var brailleOftMap = [4][2]rune{
{'\u0001', '\u0008'},
{'\u0002', '\u0010'},
{'\u0004', '\u0020'},
{'\u0040', '\u0080'}}
// Canvas contains drawing map: i,j -> rune
type Canvas map[[2]int]rune
// NewCanvas returns an empty Canvas
func NewCanvas() Canvas {
return make(map[[2]int]rune)
}
func chOft(x, y int) rune {
return brailleOftMap[y%4][x%2]
}
func (c Canvas) rawCh(x, y int) rune {
if ch, ok := c[[2]int{x, y}]; ok {
return ch
}
return '\u0000' //brailleOffset
}
// return coordinate in terminal
func chPos(x, y int) (int, int) {
return y / 4, x / 2
}
// Set sets a point (x,y) in the virtual coordinate
func (c Canvas) Set(x, y int) {
i, j := chPos(x, y)
ch := c.rawCh(i, j)
ch |= chOft(x, y)
c[[2]int{i, j}] = ch
}
// Unset removes point (x,y)
func (c Canvas) Unset(x, y int) {
i, j := chPos(x, y)
ch := c.rawCh(i, j)
ch &= ^chOft(x, y)
c[[2]int{i, j}] = ch
}
// Buffer returns un-styled points
func (c Canvas) Buffer() Buffer {
buf := NewBuffer()
for k, v := range c {
buf.Set(k[0], k[1], Cell{Ch: v + brailleBase})
}
return buf
}

54
vendor/github.com/gizak/termui/config.py generated vendored Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
import re
import os
import io
copyright = """// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
"""
exclude_dirs = [".git", "_docs"]
exclude_files = []
include_dirs = [".", "debug", "extra", "test", "_example"]
def is_target(fpath):
if os.path.splitext(fpath)[-1] == ".go":
return True
return False
def update_copyright(fpath):
print("processing " + fpath)
f = io.open(fpath, 'r', encoding='utf-8')
fstr = f.read()
f.close()
# remove old
m = re.search('^// Copyright .+?\r?\n\r?\n', fstr, re.MULTILINE|re.DOTALL)
if m:
fstr = fstr[m.end():]
# add new
fstr = copyright + fstr
f = io.open(fpath, 'w',encoding='utf-8')
f.write(fstr)
f.close()
def main():
for d in include_dirs:
files = [
os.path.join(d, f) for f in os.listdir(d)
if os.path.isfile(os.path.join(d, f))
]
for f in files:
if is_target(f):
update_copyright(f)
if __name__ == '__main__':
main()

29
vendor/github.com/gizak/termui/doc.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
/*
Package termui is a library designed for creating command line UI. For more info, goto http://github.com/gizak/termui
A simplest example:
package main
import ui "github.com/gizak/termui"
func main() {
if err:=ui.Init(); err != nil {
panic(err)
}
defer ui.Close()
g := ui.NewGauge()
g.Percent = 50
g.Width = 50
g.BorderLabel = "Gauge"
ui.Render(g)
ui.Loop()
}
*/
package termui

324
vendor/github.com/gizak/termui/events.go generated vendored Normal file
View File

@ -0,0 +1,324 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"path"
"strconv"
"sync"
"time"
"github.com/nsf/termbox-go"
)
type Event struct {
Type string
Path string
From string
To string
Data interface{}
Time int64
}
var sysEvtChs []chan Event
type EvtKbd struct {
KeyStr string
}
func evtKbd(e termbox.Event) EvtKbd {
ek := EvtKbd{}
k := string(e.Ch)
pre := ""
mod := ""
if e.Mod == termbox.ModAlt {
mod = "M-"
}
if e.Ch == 0 {
if e.Key > 0xFFFF-12 {
k = "<f" + strconv.Itoa(0xFFFF-int(e.Key)+1) + ">"
} else if e.Key > 0xFFFF-25 {
ks := []string{"<insert>", "<delete>", "<home>", "<end>", "<previous>", "<next>", "<up>", "<down>", "<left>", "<right>"}
k = ks[0xFFFF-int(e.Key)-12]
}
if e.Key <= 0x7F {
pre = "C-"
k = string('a' - 1 + int(e.Key))
kmap := map[termbox.Key][2]string{
termbox.KeyCtrlSpace: {"C-", "<space>"},
termbox.KeyBackspace: {"", "<backspace>"},
termbox.KeyTab: {"", "<tab>"},
termbox.KeyEnter: {"", "<enter>"},
termbox.KeyEsc: {"", "<escape>"},
termbox.KeyCtrlBackslash: {"C-", "\\"},
termbox.KeyCtrlSlash: {"C-", "/"},
termbox.KeySpace: {"", "<space>"},
termbox.KeyCtrl8: {"C-", "8"},
}
if sk, ok := kmap[e.Key]; ok {
pre = sk[0]
k = sk[1]
}
}
}
ek.KeyStr = pre + mod + k
return ek
}
func crtTermboxEvt(e termbox.Event) Event {
systypemap := map[termbox.EventType]string{
termbox.EventKey: "keyboard",
termbox.EventResize: "window",
termbox.EventMouse: "mouse",
termbox.EventError: "error",
termbox.EventInterrupt: "interrupt",
}
ne := Event{From: "/sys", Time: time.Now().Unix()}
typ := e.Type
ne.Type = systypemap[typ]
switch typ {
case termbox.EventKey:
kbd := evtKbd(e)
ne.Path = "/sys/kbd/" + kbd.KeyStr
ne.Data = kbd
case termbox.EventResize:
wnd := EvtWnd{}
wnd.Width = e.Width
wnd.Height = e.Height
ne.Path = "/sys/wnd/resize"
ne.Data = wnd
case termbox.EventError:
err := EvtErr(e.Err)
ne.Path = "/sys/err"
ne.Data = err
case termbox.EventMouse:
m := EvtMouse{}
m.X = e.MouseX
m.Y = e.MouseY
ne.Path = "/sys/mouse"
ne.Data = m
}
return ne
}
type EvtWnd struct {
Width int
Height int
}
type EvtMouse struct {
X int
Y int
Press string
}
type EvtErr error
func hookTermboxEvt() {
for {
e := termbox.PollEvent()
for _, c := range sysEvtChs {
go func(ch chan Event) {
ch <- crtTermboxEvt(e)
}(c)
}
}
}
func NewSysEvtCh() chan Event {
ec := make(chan Event)
sysEvtChs = append(sysEvtChs, ec)
return ec
}
var DefaultEvtStream = NewEvtStream()
type EvtStream struct {
sync.RWMutex
srcMap map[string]chan Event
stream chan Event
wg sync.WaitGroup
sigStopLoop chan Event
Handlers map[string]func(Event)
hook func(Event)
}
func NewEvtStream() *EvtStream {
return &EvtStream{
srcMap: make(map[string]chan Event),
stream: make(chan Event),
Handlers: make(map[string]func(Event)),
sigStopLoop: make(chan Event),
}
}
func (es *EvtStream) Init() {
es.Merge("internal", es.sigStopLoop)
go func() {
es.wg.Wait()
close(es.stream)
}()
}
func cleanPath(p string) string {
if p == "" {
return "/"
}
if p[0] != '/' {
p = "/" + p
}
return path.Clean(p)
}
func isPathMatch(pattern, path string) bool {
if len(pattern) == 0 {
return false
}
n := len(pattern)
return len(path) >= n && path[0:n] == pattern
}
func (es *EvtStream) Merge(name string, ec chan Event) {
es.Lock()
defer es.Unlock()
es.wg.Add(1)
es.srcMap[name] = ec
go func(a chan Event) {
for n := range a {
n.From = name
es.stream <- n
}
es.wg.Done()
}(ec)
}
func (es *EvtStream) Handle(path string, handler func(Event)) {
es.Handlers[cleanPath(path)] = handler
}
func findMatch(mux map[string]func(Event), path string) string {
n := -1
pattern := ""
for m := range mux {
if !isPathMatch(m, path) {
continue
}
if len(m) > n {
pattern = m
n = len(m)
}
}
return pattern
}
// Remove all existing defined Handlers from the map
func (es *EvtStream) ResetHandlers() {
for Path, _ := range es.Handlers {
delete(es.Handlers, Path)
}
return
}
func (es *EvtStream) match(path string) string {
return findMatch(es.Handlers, path)
}
func (es *EvtStream) Hook(f func(Event)) {
es.hook = f
}
func (es *EvtStream) Loop() {
for e := range es.stream {
switch e.Path {
case "/sig/stoploop":
return
}
go func(a Event) {
es.RLock()
defer es.RUnlock()
if pattern := es.match(a.Path); pattern != "" {
es.Handlers[pattern](a)
}
}(e)
if es.hook != nil {
es.hook(e)
}
}
}
func (es *EvtStream) StopLoop() {
go func() {
e := Event{
Path: "/sig/stoploop",
}
es.sigStopLoop <- e
}()
}
func Merge(name string, ec chan Event) {
DefaultEvtStream.Merge(name, ec)
}
func Handle(path string, handler func(Event)) {
DefaultEvtStream.Handle(path, handler)
}
func Loop() {
DefaultEvtStream.Loop()
}
func StopLoop() {
DefaultEvtStream.StopLoop()
}
type EvtTimer struct {
Duration time.Duration
Count uint64
}
func NewTimerCh(du time.Duration) chan Event {
t := make(chan Event)
go func(a chan Event) {
n := uint64(0)
for {
n++
time.Sleep(du)
e := Event{}
e.Type = "timer"
e.Path = "/timer/" + du.String()
e.Time = time.Now().Unix()
e.Data = EvtTimer{
Duration: du,
Count: n,
}
t <- e
}
}(t)
return t
}
var DefaultHandler = func(e Event) {
}
var usrEvtCh = make(chan Event)
func SendCustomEvt(path string, data interface{}) {
e := Event{}
e.Path = path
e.Data = data
e.Time = time.Now().Unix()
usrEvtCh <- e
}

109
vendor/github.com/gizak/termui/gauge.go generated vendored Normal file
View File

@ -0,0 +1,109 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"strconv"
"strings"
)
// Gauge is a progress bar like widget.
// A simple example:
/*
g := termui.NewGauge()
g.Percent = 40
g.Width = 50
g.Height = 3
g.BorderLabel = "Slim Gauge"
g.BarColor = termui.ColorRed
g.PercentColor = termui.ColorBlue
*/
const ColorUndef Attribute = Attribute(^uint16(0))
type Gauge struct {
Block
Percent int
BarColor Attribute
PercentColor Attribute
PercentColorHighlighted Attribute
Label string
LabelAlign Align
}
// NewGauge return a new gauge with current theme.
func NewGauge() *Gauge {
g := &Gauge{
Block: *NewBlock(),
PercentColor: ThemeAttr("gauge.percent.fg"),
BarColor: ThemeAttr("gauge.bar.bg"),
Label: "{{percent}}%",
LabelAlign: AlignCenter,
PercentColorHighlighted: ColorUndef,
}
g.Width = 12
g.Height = 5
return g
}
// Buffer implements Bufferer interface.
func (g *Gauge) Buffer() Buffer {
buf := g.Block.Buffer()
// plot bar
w := g.Percent * g.innerArea.Dx() / 100
for i := 0; i < g.innerArea.Dy(); i++ {
for j := 0; j < w; j++ {
c := Cell{}
c.Ch = ' '
c.Bg = g.BarColor
if c.Bg == ColorDefault {
c.Bg |= AttrReverse
}
buf.Set(g.innerArea.Min.X+j, g.innerArea.Min.Y+i, c)
}
}
// plot percentage
s := strings.Replace(g.Label, "{{percent}}", strconv.Itoa(g.Percent), -1)
pry := g.innerArea.Min.Y + g.innerArea.Dy()/2
rs := str2runes(s)
var pos int
switch g.LabelAlign {
case AlignLeft:
pos = 0
case AlignCenter:
pos = (g.innerArea.Dx() - strWidth(s)) / 2
case AlignRight:
pos = g.innerArea.Dx() - strWidth(s) - 1
}
pos += g.innerArea.Min.X
for i, v := range rs {
c := Cell{
Ch: v,
Fg: g.PercentColor,
}
if w+g.innerArea.Min.X > pos+i {
c.Bg = g.BarColor
if c.Bg == ColorDefault {
c.Bg |= AttrReverse
}
if g.PercentColorHighlighted != ColorUndef {
c.Fg = g.PercentColorHighlighted
}
} else {
c.Bg = g.Block.Bg
}
buf.Set(1+pos+i, pry, c)
}
return buf
}

30
vendor/github.com/gizak/termui/glide.lock generated vendored Normal file
View File

@ -0,0 +1,30 @@
hash: 7a754ba100256404a978b2fc8738aee337beb822458e4b6060399fb89ebd215c
updated: 2016-11-03T17:39:24.323773674-04:00
imports:
- name: github.com/maruel/panicparse
version: ad661195ed0e88491e0f14be6613304e3b1141d6
subpackages:
- stack
- name: github.com/mattn/go-runewidth
version: 737072b4e32b7a5018b4a7125da8d12de90e8045
- name: github.com/mitchellh/go-wordwrap
version: ad45545899c7b13c020ea92b2072220eefad42b8
- name: github.com/nsf/termbox-go
version: b6acae516ace002cb8105a89024544a1480655a5
- name: golang.org/x/net
version: 569280fa63be4e201b975e5411e30a92178f0118
subpackages:
- websocket
testImports:
- name: github.com/davecgh/go-spew
version: 346938d642f2ec3594ed81d874461961cd0faa76
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 976c720a22c8eb4eb6a0b4348ad85ad12491a506
subpackages:
- assert

9
vendor/github.com/gizak/termui/glide.yaml generated vendored Normal file
View File

@ -0,0 +1,9 @@
package: github.com/gizak/termui
import:
- package: github.com/mattn/go-runewidth
- package: github.com/mitchellh/go-wordwrap
- package: github.com/nsf/termbox-go
- package: golang.org/x/net
subpackages:
- websocket
- package: github.com/maruel/panicparse

279
vendor/github.com/gizak/termui/grid.go generated vendored Normal file
View File

@ -0,0 +1,279 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
// GridBufferer introduces a Bufferer that can be manipulated by Grid.
type GridBufferer interface {
Bufferer
GetHeight() int
SetWidth(int)
SetX(int)
SetY(int)
}
// Row builds a layout tree
type Row struct {
Cols []*Row //children
Widget GridBufferer // root
X int
Y int
Width int
Height int
Span int
Offset int
}
// calculate and set the underlying layout tree's x, y, height and width.
func (r *Row) calcLayout() {
r.assignWidth(r.Width)
r.Height = r.solveHeight()
r.assignX(r.X)
r.assignY(r.Y)
}
// tell if the node is leaf in the tree.
func (r *Row) isLeaf() bool {
return r.Cols == nil || len(r.Cols) == 0
}
func (r *Row) isRenderableLeaf() bool {
return r.isLeaf() && r.Widget != nil
}
// assign widgets' (and their parent rows') width recursively.
func (r *Row) assignWidth(w int) {
r.SetWidth(w)
accW := 0 // acc span and offset
calcW := make([]int, len(r.Cols)) // calculated width
calcOftX := make([]int, len(r.Cols)) // computated start position of x
for i, c := range r.Cols {
accW += c.Span + c.Offset
cw := int(float64(c.Span*r.Width) / 12.0)
if i >= 1 {
calcOftX[i] = calcOftX[i-1] +
calcW[i-1] +
int(float64(r.Cols[i-1].Offset*r.Width)/12.0)
}
// use up the space if it is the last col
if i == len(r.Cols)-1 && accW == 12 {
cw = r.Width - calcOftX[i]
}
calcW[i] = cw
r.Cols[i].assignWidth(cw)
}
}
// bottom up calc and set rows' (and their widgets') height,
// return r's total height.
func (r *Row) solveHeight() int {
if r.isRenderableLeaf() {
r.Height = r.Widget.GetHeight()
return r.Widget.GetHeight()
}
maxh := 0
if !r.isLeaf() {
for _, c := range r.Cols {
nh := c.solveHeight()
// when embed rows in Cols, row widgets stack up
if r.Widget != nil {
nh += r.Widget.GetHeight()
}
if nh > maxh {
maxh = nh
}
}
}
r.Height = maxh
return maxh
}
// recursively assign x position for r tree.
func (r *Row) assignX(x int) {
r.SetX(x)
if !r.isLeaf() {
acc := 0
for i, c := range r.Cols {
if c.Offset != 0 {
acc += int(float64(c.Offset*r.Width) / 12.0)
}
r.Cols[i].assignX(x + acc)
acc += c.Width
}
}
}
// recursively assign y position to r.
func (r *Row) assignY(y int) {
r.SetY(y)
if r.isLeaf() {
return
}
for i := range r.Cols {
acc := 0
if r.Widget != nil {
acc = r.Widget.GetHeight()
}
r.Cols[i].assignY(y + acc)
}
}
// GetHeight implements GridBufferer interface.
func (r Row) GetHeight() int {
return r.Height
}
// SetX implements GridBufferer interface.
func (r *Row) SetX(x int) {
r.X = x
if r.Widget != nil {
r.Widget.SetX(x)
}
}
// SetY implements GridBufferer interface.
func (r *Row) SetY(y int) {
r.Y = y
if r.Widget != nil {
r.Widget.SetY(y)
}
}
// SetWidth implements GridBufferer interface.
func (r *Row) SetWidth(w int) {
r.Width = w
if r.Widget != nil {
r.Widget.SetWidth(w)
}
}
// Buffer implements Bufferer interface,
// recursively merge all widgets buffer
func (r *Row) Buffer() Buffer {
merged := NewBuffer()
if r.isRenderableLeaf() {
return r.Widget.Buffer()
}
// for those are not leaves but have a renderable widget
if r.Widget != nil {
merged.Merge(r.Widget.Buffer())
}
// collect buffer from children
if !r.isLeaf() {
for _, c := range r.Cols {
merged.Merge(c.Buffer())
}
}
return merged
}
// Grid implements 12 columns system.
// A simple example:
/*
import ui "github.com/gizak/termui"
// init and create widgets...
// build
ui.Body.AddRows(
ui.NewRow(
ui.NewCol(6, 0, widget0),
ui.NewCol(6, 0, widget1)),
ui.NewRow(
ui.NewCol(3, 0, widget2),
ui.NewCol(3, 0, widget30, widget31, widget32),
ui.NewCol(6, 0, widget4)))
// calculate layout
ui.Body.Align()
ui.Render(ui.Body)
*/
type Grid struct {
Rows []*Row
Width int
X int
Y int
BgColor Attribute
}
// NewGrid returns *Grid with given rows.
func NewGrid(rows ...*Row) *Grid {
return &Grid{Rows: rows}
}
// AddRows appends given rows to Grid.
func (g *Grid) AddRows(rs ...*Row) {
g.Rows = append(g.Rows, rs...)
}
// NewRow creates a new row out of given columns.
func NewRow(cols ...*Row) *Row {
rs := &Row{Span: 12, Cols: cols}
return rs
}
// NewCol accepts: widgets are LayoutBufferer or widgets is A NewRow.
// Note that if multiple widgets are provided, they will stack up in the col.
func NewCol(span, offset int, widgets ...GridBufferer) *Row {
r := &Row{Span: span, Offset: offset}
if widgets != nil && len(widgets) == 1 {
wgt := widgets[0]
nw, isRow := wgt.(*Row)
if isRow {
r.Cols = nw.Cols
} else {
r.Widget = wgt
}
return r
}
r.Cols = []*Row{}
ir := r
for _, w := range widgets {
nr := &Row{Span: 12, Widget: w}
ir.Cols = []*Row{nr}
ir = nr
}
return r
}
// Align calculate each rows' layout.
func (g *Grid) Align() {
h := 0
for _, r := range g.Rows {
r.SetWidth(g.Width)
r.SetX(g.X)
r.SetY(g.Y + h)
r.calcLayout()
h += r.GetHeight()
}
}
// Buffer implments Bufferer interface.
func (g Grid) Buffer() Buffer {
buf := NewBuffer()
for _, r := range g.Rows {
buf.Merge(r.Buffer())
}
return buf
}
var Body *Grid

222
vendor/github.com/gizak/termui/helper.go generated vendored Normal file
View File

@ -0,0 +1,222 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"regexp"
"strings"
tm "github.com/nsf/termbox-go"
)
import rw "github.com/mattn/go-runewidth"
/* ---------------Port from termbox-go --------------------- */
// Attribute is printable cell's color and style.
type Attribute uint16
// 8 basic clolrs
const (
ColorDefault Attribute = iota
ColorBlack
ColorRed
ColorGreen
ColorYellow
ColorBlue
ColorMagenta
ColorCyan
ColorWhite
)
//Have a constant that defines number of colors
const NumberofColors = 8
// Text style
const (
AttrBold Attribute = 1 << (iota + 9)
AttrUnderline
AttrReverse
)
var (
dot = "…"
dotw = rw.StringWidth(dot)
)
/* ----------------------- End ----------------------------- */
func toTmAttr(x Attribute) tm.Attribute {
return tm.Attribute(x)
}
func str2runes(s string) []rune {
return []rune(s)
}
// Here for backwards-compatibility.
func trimStr2Runes(s string, w int) []rune {
return TrimStr2Runes(s, w)
}
// TrimStr2Runes trims string to w[-1 rune], appends …, and returns the runes
// of that string if string is grather then n. If string is small then w,
// return the runes.
func TrimStr2Runes(s string, w int) []rune {
if w <= 0 {
return []rune{}
}
sw := rw.StringWidth(s)
if sw > w {
return []rune(rw.Truncate(s, w, dot))
}
return str2runes(s)
}
// TrimStrIfAppropriate trim string to "s[:-1] + …"
// if string > width otherwise return string
func TrimStrIfAppropriate(s string, w int) string {
if w <= 0 {
return ""
}
sw := rw.StringWidth(s)
if sw > w {
return rw.Truncate(s, w, dot)
}
return s
}
func strWidth(s string) int {
return rw.StringWidth(s)
}
func charWidth(ch rune) int {
return rw.RuneWidth(ch)
}
var whiteSpaceRegex = regexp.MustCompile(`\s`)
// StringToAttribute converts text to a termui attribute. You may specifiy more
// then one attribute like that: "BLACK, BOLD, ...". All whitespaces
// are ignored.
func StringToAttribute(text string) Attribute {
text = whiteSpaceRegex.ReplaceAllString(strings.ToLower(text), "")
attributes := strings.Split(text, ",")
result := Attribute(0)
for _, theAttribute := range attributes {
var match Attribute
switch theAttribute {
case "reset", "default":
match = ColorDefault
case "black":
match = ColorBlack
case "red":
match = ColorRed
case "green":
match = ColorGreen
case "yellow":
match = ColorYellow
case "blue":
match = ColorBlue
case "magenta":
match = ColorMagenta
case "cyan":
match = ColorCyan
case "white":
match = ColorWhite
case "bold":
match = AttrBold
case "underline":
match = AttrUnderline
case "reverse":
match = AttrReverse
}
result |= match
}
return result
}
// TextCells returns a coloured text cells []Cell
func TextCells(s string, fg, bg Attribute) []Cell {
cs := make([]Cell, 0, len(s))
// sequence := MarkdownTextRendererFactory{}.TextRenderer(s).Render(fg, bg)
// runes := []rune(sequence.NormalizedText)
runes := str2runes(s)
for n := range runes {
// point, _ := sequence.PointAt(n, 0, 0)
// cs = append(cs, Cell{point.Ch, point.Fg, point.Bg})
cs = append(cs, Cell{runes[n], fg, bg})
}
return cs
}
// Width returns the actual screen space the cell takes (usually 1 or 2).
func (c Cell) Width() int {
return charWidth(c.Ch)
}
// Copy return a copy of c
func (c Cell) Copy() Cell {
return c
}
// TrimTxCells trims the overflowed text cells sequence.
func TrimTxCells(cs []Cell, w int) []Cell {
if len(cs) <= w {
return cs
}
return cs[:w]
}
// DTrimTxCls trims the overflowed text cells sequence and append dots at the end.
func DTrimTxCls(cs []Cell, w int) []Cell {
l := len(cs)
if l <= 0 {
return []Cell{}
}
rt := make([]Cell, 0, w)
csw := 0
for i := 0; i < l && csw <= w; i++ {
c := cs[i]
cw := c.Width()
if cw+csw < w {
rt = append(rt, c)
csw += cw
} else {
rt = append(rt, Cell{'…', c.Fg, c.Bg})
break
}
}
return rt
}
func CellsToStr(cs []Cell) string {
str := ""
for _, c := range cs {
str += string(c.Ch)
}
return str
}

331
vendor/github.com/gizak/termui/linechart.go generated vendored Normal file
View File

@ -0,0 +1,331 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"fmt"
"math"
)
// only 16 possible combinations, why bother
var braillePatterns = map[[2]int]rune{
[2]int{0, 0}: '⣀',
[2]int{0, 1}: '⡠',
[2]int{0, 2}: '⡐',
[2]int{0, 3}: '⡈',
[2]int{1, 0}: '⢄',
[2]int{1, 1}: '⠤',
[2]int{1, 2}: '⠔',
[2]int{1, 3}: '⠌',
[2]int{2, 0}: '⢂',
[2]int{2, 1}: '⠢',
[2]int{2, 2}: '⠒',
[2]int{2, 3}: '⠊',
[2]int{3, 0}: '⢁',
[2]int{3, 1}: '⠡',
[2]int{3, 2}: '⠑',
[2]int{3, 3}: '⠉',
}
var lSingleBraille = [4]rune{'\u2840', '⠄', '⠂', '⠁'}
var rSingleBraille = [4]rune{'\u2880', '⠠', '⠐', '⠈'}
// LineChart has two modes: braille(default) and dot. Using braille gives 2x capicity as dot mode,
// because one braille char can represent two data points.
/*
lc := termui.NewLineChart()
lc.BorderLabel = "braille-mode Line Chart"
lc.Data = [1.2, 1.3, 1.5, 1.7, 1.5, 1.6, 1.8, 2.0]
lc.Width = 50
lc.Height = 12
lc.AxesColor = termui.ColorWhite
lc.LineColor = termui.ColorGreen | termui.AttrBold
// termui.Render(lc)...
*/
type LineChart struct {
Block
Data []float64
DataLabels []string // if unset, the data indices will be used
Mode string // braille | dot
DotStyle rune
LineColor Attribute
scale float64 // data span per cell on y-axis
AxesColor Attribute
drawingX int
drawingY int
axisYHeight int
axisXWidth int
axisYLabelGap int
axisXLabelGap int
topValue float64
bottomValue float64
labelX [][]rune
labelY [][]rune
labelYSpace int
maxY float64
minY float64
autoLabels bool
}
// NewLineChart returns a new LineChart with current theme.
func NewLineChart() *LineChart {
lc := &LineChart{Block: *NewBlock()}
lc.AxesColor = ThemeAttr("linechart.axes.fg")
lc.LineColor = ThemeAttr("linechart.line.fg")
lc.Mode = "braille"
lc.DotStyle = '•'
lc.axisXLabelGap = 2
lc.axisYLabelGap = 1
lc.bottomValue = math.Inf(1)
lc.topValue = math.Inf(-1)
return lc
}
// one cell contains two data points
// so the capicity is 2x as dot-mode
func (lc *LineChart) renderBraille() Buffer {
buf := NewBuffer()
// return: b -> which cell should the point be in
// m -> in the cell, divided into 4 equal height levels, which subcell?
getPos := func(d float64) (b, m int) {
cnt4 := int((d-lc.bottomValue)/(lc.scale/4) + 0.5)
b = cnt4 / 4
m = cnt4 % 4
return
}
// plot points
for i := 0; 2*i+1 < len(lc.Data) && i < lc.axisXWidth; i++ {
b0, m0 := getPos(lc.Data[2*i])
b1, m1 := getPos(lc.Data[2*i+1])
if b0 == b1 {
c := Cell{
Ch: braillePatterns[[2]int{m0, m1}],
Bg: lc.Bg,
Fg: lc.LineColor,
}
y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - b0
x := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
buf.Set(x, y, c)
} else {
c0 := Cell{Ch: lSingleBraille[m0],
Fg: lc.LineColor,
Bg: lc.Bg}
x0 := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
y0 := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - b0
buf.Set(x0, y0, c0)
c1 := Cell{Ch: rSingleBraille[m1],
Fg: lc.LineColor,
Bg: lc.Bg}
x1 := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
y1 := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - b1
buf.Set(x1, y1, c1)
}
}
return buf
}
func (lc *LineChart) renderDot() Buffer {
buf := NewBuffer()
for i := 0; i < len(lc.Data) && i < lc.axisXWidth; i++ {
c := Cell{
Ch: lc.DotStyle,
Fg: lc.LineColor,
Bg: lc.Bg,
}
x := lc.innerArea.Min.X + lc.labelYSpace + 1 + i
y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 3 - int((lc.Data[i]-lc.bottomValue)/lc.scale+0.5)
buf.Set(x, y, c)
}
return buf
}
func (lc *LineChart) calcLabelX() {
lc.labelX = [][]rune{}
for i, l := 0, 0; i < len(lc.DataLabels) && l < lc.axisXWidth; i++ {
if lc.Mode == "dot" {
if l >= len(lc.DataLabels) {
break
}
s := str2runes(lc.DataLabels[l])
w := strWidth(lc.DataLabels[l])
if l+w <= lc.axisXWidth {
lc.labelX = append(lc.labelX, s)
}
l += w + lc.axisXLabelGap
} else { // braille
if 2*l >= len(lc.DataLabels) {
break
}
s := str2runes(lc.DataLabels[2*l])
w := strWidth(lc.DataLabels[2*l])
if l+w <= lc.axisXWidth {
lc.labelX = append(lc.labelX, s)
}
l += w + lc.axisXLabelGap
}
}
}
func shortenFloatVal(x float64) string {
s := fmt.Sprintf("%.2f", x)
if len(s)-3 > 3 {
s = fmt.Sprintf("%.2e", x)
}
if x < 0 {
s = fmt.Sprintf("%.2f", x)
}
return s
}
func (lc *LineChart) calcLabelY() {
span := lc.topValue - lc.bottomValue
lc.scale = span / float64(lc.axisYHeight)
n := (1 + lc.axisYHeight) / (lc.axisYLabelGap + 1)
lc.labelY = make([][]rune, n)
maxLen := 0
for i := 0; i < n; i++ {
s := str2runes(shortenFloatVal(lc.bottomValue + float64(i)*span/float64(n)))
if len(s) > maxLen {
maxLen = len(s)
}
lc.labelY[i] = s
}
lc.labelYSpace = maxLen
}
func (lc *LineChart) calcLayout() {
// set datalabels if it is not provided
if (lc.DataLabels == nil || len(lc.DataLabels) == 0) || lc.autoLabels {
lc.autoLabels = true
lc.DataLabels = make([]string, len(lc.Data))
for i := range lc.Data {
lc.DataLabels[i] = fmt.Sprint(i)
}
}
// lazy increase, to avoid y shaking frequently
// update bound Y when drawing is gonna overflow
lc.minY = lc.Data[0]
lc.maxY = lc.Data[0]
// valid visible range
vrange := lc.innerArea.Dx()
if lc.Mode == "braille" {
vrange = 2 * lc.innerArea.Dx()
}
if vrange > len(lc.Data) {
vrange = len(lc.Data)
}
for _, v := range lc.Data[:vrange] {
if v > lc.maxY {
lc.maxY = v
}
if v < lc.minY {
lc.minY = v
}
}
span := lc.maxY - lc.minY
if lc.minY < lc.bottomValue {
lc.bottomValue = lc.minY - 0.2*span
}
if lc.maxY > lc.topValue {
lc.topValue = lc.maxY + 0.2*span
}
lc.axisYHeight = lc.innerArea.Dy() - 2
lc.calcLabelY()
lc.axisXWidth = lc.innerArea.Dx() - 1 - lc.labelYSpace
lc.calcLabelX()
lc.drawingX = lc.innerArea.Min.X + 1 + lc.labelYSpace
lc.drawingY = lc.innerArea.Min.Y
}
func (lc *LineChart) plotAxes() Buffer {
buf := NewBuffer()
origY := lc.innerArea.Min.Y + lc.innerArea.Dy() - 2
origX := lc.innerArea.Min.X + lc.labelYSpace
buf.Set(origX, origY, Cell{Ch: ORIGIN, Fg: lc.AxesColor, Bg: lc.Bg})
for x := origX + 1; x < origX+lc.axisXWidth; x++ {
buf.Set(x, origY, Cell{Ch: HDASH, Fg: lc.AxesColor, Bg: lc.Bg})
}
for dy := 1; dy <= lc.axisYHeight; dy++ {
buf.Set(origX, origY-dy, Cell{Ch: VDASH, Fg: lc.AxesColor, Bg: lc.Bg})
}
// x label
oft := 0
for _, rs := range lc.labelX {
if oft+len(rs) > lc.axisXWidth {
break
}
for j, r := range rs {
c := Cell{
Ch: r,
Fg: lc.AxesColor,
Bg: lc.Bg,
}
x := origX + oft + j
y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 1
buf.Set(x, y, c)
}
oft += len(rs) + lc.axisXLabelGap
}
// y labels
for i, rs := range lc.labelY {
for j, r := range rs {
buf.Set(
lc.innerArea.Min.X+j,
origY-i*(lc.axisYLabelGap+1),
Cell{Ch: r, Fg: lc.AxesColor, Bg: lc.Bg})
}
}
return buf
}
// Buffer implements Bufferer interface.
func (lc *LineChart) Buffer() Buffer {
buf := lc.Block.Buffer()
if lc.Data == nil || len(lc.Data) == 0 {
return buf
}
lc.calcLayout()
buf.Merge(lc.plotAxes())
if lc.Mode == "dot" {
buf.Merge(lc.renderDot())
} else {
buf.Merge(lc.renderBraille())
}
return buf
}

11
vendor/github.com/gizak/termui/linechart_others.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
// +build !windows
package termui
const VDASH = '┊'
const HDASH = '┈'
const ORIGIN = '└'

11
vendor/github.com/gizak/termui/linechart_windows.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
// +build windows
package termui
const VDASH = '|'
const HDASH = '-'
const ORIGIN = '+'

89
vendor/github.com/gizak/termui/list.go generated vendored Normal file
View File

@ -0,0 +1,89 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import "strings"
// List displays []string as its items,
// it has a Overflow option (default is "hidden"), when set to "hidden",
// the item exceeding List's width is truncated, but when set to "wrap",
// the overflowed text breaks into next line.
/*
strs := []string{
"[0] github.com/gizak/termui",
"[1] editbox.go",
"[2] iterrupt.go",
"[3] keyboard.go",
"[4] output.go",
"[5] random_out.go",
"[6] dashboard.go",
"[7] nsf/termbox-go"}
ls := termui.NewList()
ls.Items = strs
ls.ItemFgColor = termui.ColorYellow
ls.BorderLabel = "List"
ls.Height = 7
ls.Width = 25
ls.Y = 0
*/
type List struct {
Block
Items []string
Overflow string
ItemFgColor Attribute
ItemBgColor Attribute
}
// NewList returns a new *List with current theme.
func NewList() *List {
l := &List{Block: *NewBlock()}
l.Overflow = "hidden"
l.ItemFgColor = ThemeAttr("list.item.fg")
l.ItemBgColor = ThemeAttr("list.item.bg")
return l
}
// Buffer implements Bufferer interface.
func (l *List) Buffer() Buffer {
buf := l.Block.Buffer()
switch l.Overflow {
case "wrap":
cs := DefaultTxBuilder.Build(strings.Join(l.Items, "\n"), l.ItemFgColor, l.ItemBgColor)
i, j, k := 0, 0, 0
for i < l.innerArea.Dy() && k < len(cs) {
w := cs[k].Width()
if cs[k].Ch == '\n' || j+w > l.innerArea.Dx() {
i++
j = 0
if cs[k].Ch == '\n' {
k++
}
continue
}
buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, cs[k])
k++
j++
}
case "hidden":
trimItems := l.Items
if len(trimItems) > l.innerArea.Dy() {
trimItems = trimItems[:l.innerArea.Dy()]
}
for i, v := range trimItems {
cs := DTrimTxCls(DefaultTxBuilder.Build(v, l.ItemFgColor, l.ItemBgColor), l.innerArea.Dx())
j := 0
for _, vv := range cs {
w := vv.Width()
buf.Set(l.innerArea.Min.X+j, l.innerArea.Min.Y+i, vv)
j += w
}
}
}
return buf
}

242
vendor/github.com/gizak/termui/mbarchart.go generated vendored Normal file
View File

@ -0,0 +1,242 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"fmt"
)
// This is the implemetation of multi-colored or stacked bar graph. This is different from default barGraph which is implemented in bar.go
// Multi-Colored-BarChart creates multiple bars in a widget:
/*
bc := termui.NewMBarChart()
data := make([][]int, 2)
data[0] := []int{3, 2, 5, 7, 9, 4}
data[1] := []int{7, 8, 5, 3, 1, 6}
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
bc.BorderLabel = "Bar Chart"
bc.Data = data
bc.Width = 26
bc.Height = 10
bc.DataLabels = bclabels
bc.TextColor = termui.ColorGreen
bc.BarColor = termui.ColorRed
bc.NumColor = termui.ColorYellow
*/
type MBarChart struct {
Block
BarColor [NumberofColors]Attribute
TextColor Attribute
NumColor [NumberofColors]Attribute
Data [NumberofColors][]int
DataLabels []string
BarWidth int
BarGap int
labels [][]rune
dataNum [NumberofColors][][]rune
numBar int
scale float64
max int
minDataLen int
numStack int
ShowScale bool
maxScale []rune
}
// NewBarChart returns a new *BarChart with current theme.
func NewMBarChart() *MBarChart {
bc := &MBarChart{Block: *NewBlock()}
bc.BarColor[0] = ThemeAttr("mbarchart.bar.bg")
bc.NumColor[0] = ThemeAttr("mbarchart.num.fg")
bc.TextColor = ThemeAttr("mbarchart.text.fg")
bc.BarGap = 1
bc.BarWidth = 3
return bc
}
func (bc *MBarChart) layout() {
bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
bc.labels = make([][]rune, bc.numBar)
DataLen := 0
LabelLen := len(bc.DataLabels)
bc.minDataLen = 9999 //Set this to some very hight value so that we find the minimum one We want to know which array among data[][] has got the least length
// We need to know how many stack/data array data[0] , data[1] are there
for i := 0; i < len(bc.Data); i++ {
if bc.Data[i] == nil {
break
}
DataLen++
}
bc.numStack = DataLen
//We need to know what is the mimimum size of data array data[0] could have 10 elements data[1] could have only 5, so we plot only 5 bar graphs
for i := 0; i < DataLen; i++ {
if bc.minDataLen > len(bc.Data[i]) {
bc.minDataLen = len(bc.Data[i])
}
}
if LabelLen > bc.minDataLen {
LabelLen = bc.minDataLen
}
for i := 0; i < LabelLen && i < bc.numBar; i++ {
bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
}
for i := 0; i < bc.numStack; i++ {
bc.dataNum[i] = make([][]rune, len(bc.Data[i]))
//For each stack of bar calcualte the rune
for j := 0; j < LabelLen && i < bc.numBar; j++ {
n := bc.Data[i][j]
s := fmt.Sprint(n)
bc.dataNum[i][j] = trimStr2Runes(s, bc.BarWidth)
}
//If color is not defined by default then populate a color that is different from the prevous bar
if bc.BarColor[i] == ColorDefault && bc.NumColor[i] == ColorDefault {
if i == 0 {
bc.BarColor[i] = ColorBlack
} else {
bc.BarColor[i] = bc.BarColor[i-1] + 1
if bc.BarColor[i] > NumberofColors {
bc.BarColor[i] = ColorBlack
}
}
bc.NumColor[i] = (NumberofColors + 1) - bc.BarColor[i] //Make NumColor opposite of barColor for visibility
}
}
//If Max value is not set then we have to populate, this time the max value will be max(sum(d1[0],d2[0],d3[0]) .... sum(d1[n], d2[n], d3[n]))
if bc.max == 0 {
bc.max = -1
}
for i := 0; i < bc.minDataLen && i < LabelLen; i++ {
var dsum int
for j := 0; j < bc.numStack; j++ {
dsum += bc.Data[j][i]
}
if dsum > bc.max {
bc.max = dsum
}
}
//Finally Calculate max sale
if bc.ShowScale {
s := fmt.Sprintf("%d", bc.max)
bc.maxScale = trimStr2Runes(s, len(s))
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-2)
} else {
bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
}
}
func (bc *MBarChart) SetMax(max int) {
if max > 0 {
bc.max = max
}
}
// Buffer implements Bufferer interface.
func (bc *MBarChart) Buffer() Buffer {
buf := bc.Block.Buffer()
bc.layout()
var oftX int
for i := 0; i < bc.numBar && i < bc.minDataLen && i < len(bc.DataLabels); i++ {
ph := 0 //Previous Height to stack up
oftX = i * (bc.BarWidth + bc.BarGap)
for i1 := 0; i1 < bc.numStack; i1++ {
h := int(float64(bc.Data[i1][i]) / bc.scale)
// plot bars
for j := 0; j < bc.BarWidth; j++ {
for k := 0; k < h; k++ {
c := Cell{
Ch: ' ',
Bg: bc.BarColor[i1],
}
if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
c.Bg |= AttrReverse
}
x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k - ph
buf.Set(x, y, c)
}
}
ph += h
}
// plot text
for j, k := 0, 0; j < len(bc.labels[i]); j++ {
w := charWidth(bc.labels[i][j])
c := Cell{
Ch: bc.labels[i][j],
Bg: bc.Bg,
Fg: bc.TextColor,
}
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
x := bc.innerArea.Max.X + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
buf.Set(x, y, c)
k += w
}
// plot num
ph = 0 //re-initialize previous height
for i1 := 0; i1 < bc.numStack; i1++ {
h := int(float64(bc.Data[i1][i]) / bc.scale)
for j := 0; j < len(bc.dataNum[i1][i]) && h > 0; j++ {
c := Cell{
Ch: bc.dataNum[i1][i][j],
Fg: bc.NumColor[i1],
Bg: bc.BarColor[i1],
}
if bc.BarColor[i1] == ColorDefault { // the same as above
c.Bg |= AttrReverse
}
if h == 0 {
c.Bg = bc.Bg
}
x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - ph
buf.Set(x, y, c)
}
ph += h
}
}
if bc.ShowScale {
//Currently bar graph only supprts data range from 0 to MAX
//Plot 0
c := Cell{
Ch: '0',
Bg: bc.Bg,
Fg: bc.TextColor,
}
y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
x := bc.X
buf.Set(x, y, c)
//Plot the maximum sacle value
for i := 0; i < len(bc.maxScale); i++ {
c := Cell{
Ch: bc.maxScale[i],
Bg: bc.Bg,
Fg: bc.TextColor,
}
y := bc.innerArea.Min.Y
x := bc.X + i
buf.Set(x, y, c)
}
}
return buf
}

28
vendor/github.com/gizak/termui/mkdocs.yml generated vendored Normal file
View File

@ -0,0 +1,28 @@
pages:
- Home: 'index.md'
- Quickstart: 'quickstart.md'
- Recipes: 'recipes.md'
- References:
- Layouts: 'layouts.md'
- Components: 'components.md'
- Events: 'events.md'
- Themes: 'themes.md'
- Versions: 'versions.md'
- About: 'about.md'
site_name: termui
repo_url: https://github.com/gizak/termui/
site_description: 'termui user guide'
site_author: gizak
docs_dir: '_docs'
theme: readthedocs
markdown_extensions:
- smarty
- admonition
- toc
extra:
version: 1.0

73
vendor/github.com/gizak/termui/par.go generated vendored Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
// Par displays a paragraph.
/*
par := termui.NewPar("Simple Text")
par.Height = 3
par.Width = 17
par.BorderLabel = "Label"
*/
type Par struct {
Block
Text string
TextFgColor Attribute
TextBgColor Attribute
WrapLength int // words wrap limit. Note it may not work properly with multi-width char
}
// NewPar returns a new *Par with given text as its content.
func NewPar(s string) *Par {
return &Par{
Block: *NewBlock(),
Text: s,
TextFgColor: ThemeAttr("par.text.fg"),
TextBgColor: ThemeAttr("par.text.bg"),
WrapLength: 0,
}
}
// Buffer implements Bufferer interface.
func (p *Par) Buffer() Buffer {
buf := p.Block.Buffer()
fg, bg := p.TextFgColor, p.TextBgColor
cs := DefaultTxBuilder.Build(p.Text, fg, bg)
// wrap if WrapLength set
if p.WrapLength < 0 {
cs = wrapTx(cs, p.Width-2)
} else if p.WrapLength > 0 {
cs = wrapTx(cs, p.WrapLength)
}
y, x, n := 0, 0, 0
for y < p.innerArea.Dy() && n < len(cs) {
w := cs[n].Width()
if cs[n].Ch == '\n' || x+w > p.innerArea.Dx() {
y++
x = 0 // set x = 0
if cs[n].Ch == '\n' {
n++
}
if y >= p.innerArea.Dy() {
buf.Set(p.innerArea.Min.X+p.innerArea.Dx()-1,
p.innerArea.Min.Y+p.innerArea.Dy()-1,
Cell{Ch: '…', Fg: p.TextFgColor, Bg: p.TextBgColor})
break
}
continue
}
buf.Set(p.innerArea.Min.X+x, p.innerArea.Min.Y+y, cs[n])
n++
x += w
}
return buf
}

78
vendor/github.com/gizak/termui/pos.go generated vendored Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import "image"
// Align is the position of the gauge's label.
type Align uint
// All supported positions.
const (
AlignNone Align = 0
AlignLeft Align = 1 << iota
AlignRight
AlignBottom
AlignTop
AlignCenterVertical
AlignCenterHorizontal
AlignCenter = AlignCenterVertical | AlignCenterHorizontal
)
func AlignArea(parent, child image.Rectangle, a Align) image.Rectangle {
w, h := child.Dx(), child.Dy()
// parent center
pcx, pcy := parent.Min.X+parent.Dx()/2, parent.Min.Y+parent.Dy()/2
// child center
ccx, ccy := child.Min.X+child.Dx()/2, child.Min.Y+child.Dy()/2
if a&AlignLeft == AlignLeft {
child.Min.X = parent.Min.X
child.Max.X = child.Min.X + w
}
if a&AlignRight == AlignRight {
child.Max.X = parent.Max.X
child.Min.X = child.Max.X - w
}
if a&AlignBottom == AlignBottom {
child.Max.Y = parent.Max.Y
child.Min.Y = child.Max.Y - h
}
if a&AlignTop == AlignRight {
child.Min.Y = parent.Min.Y
child.Max.Y = child.Min.Y + h
}
if a&AlignCenterHorizontal == AlignCenterHorizontal {
child.Min.X += pcx - ccx
child.Max.X = child.Min.X + w
}
if a&AlignCenterVertical == AlignCenterVertical {
child.Min.Y += pcy - ccy
child.Max.Y = child.Min.Y + h
}
return child
}
func MoveArea(a image.Rectangle, dx, dy int) image.Rectangle {
a.Min.X += dx
a.Max.X += dx
a.Min.Y += dy
a.Max.Y += dy
return a
}
var termWidth int
var termHeight int
func TermRect() image.Rectangle {
return image.Rect(0, 0, termWidth, termHeight)
}

164
vendor/github.com/gizak/termui/render.go generated vendored Normal file
View File

@ -0,0 +1,164 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"image"
"io"
"sync"
"time"
"fmt"
"os"
"runtime/debug"
"bytes"
"github.com/maruel/panicparse/stack"
tm "github.com/nsf/termbox-go"
)
// Bufferer should be implemented by all renderable components.
type Bufferer interface {
Buffer() Buffer
}
// Init initializes termui library. This function should be called before any others.
// After initialization, the library must be finalized by 'Close' function.
func Init() error {
if err := tm.Init(); err != nil {
return err
}
sysEvtChs = make([]chan Event, 0)
go hookTermboxEvt()
renderJobs = make(chan []Bufferer)
//renderLock = new(sync.RWMutex)
Body = NewGrid()
Body.X = 0
Body.Y = 0
Body.BgColor = ThemeAttr("bg")
Body.Width = TermWidth()
DefaultEvtStream.Init()
DefaultEvtStream.Merge("termbox", NewSysEvtCh())
DefaultEvtStream.Merge("timer", NewTimerCh(time.Second))
DefaultEvtStream.Merge("custom", usrEvtCh)
DefaultEvtStream.Handle("/", DefaultHandler)
DefaultEvtStream.Handle("/sys/wnd/resize", func(e Event) {
w := e.Data.(EvtWnd)
Body.Width = w.Width
})
DefaultWgtMgr = NewWgtMgr()
DefaultEvtStream.Hook(DefaultWgtMgr.WgtHandlersHook())
go func() {
for bs := range renderJobs {
render(bs...)
}
}()
return nil
}
// Close finalizes termui library,
// should be called after successful initialization when termui's functionality isn't required anymore.
func Close() {
tm.Close()
}
var renderLock sync.Mutex
func termSync() {
renderLock.Lock()
tm.Sync()
termWidth, termHeight = tm.Size()
renderLock.Unlock()
}
// TermWidth returns the current terminal's width.
func TermWidth() int {
termSync()
return termWidth
}
// TermHeight returns the current terminal's height.
func TermHeight() int {
termSync()
return termHeight
}
// Render renders all Bufferer in the given order from left to right,
// right could overlap on left ones.
func render(bs ...Bufferer) {
defer func() {
if e := recover(); e != nil {
Close()
fmt.Fprintf(os.Stderr, "Captured a panic(value=%v) when rendering Bufferer. Exit termui and clean terminal...\nPrint stack trace:\n\n", e)
//debug.PrintStack()
gs, err := stack.ParseDump(bytes.NewReader(debug.Stack()), os.Stderr)
if err != nil {
debug.PrintStack()
os.Exit(1)
}
p := &stack.Palette{}
buckets := stack.SortBuckets(stack.Bucketize(gs, stack.AnyValue))
srcLen, pkgLen := stack.CalcLengths(buckets, false)
for _, bucket := range buckets {
io.WriteString(os.Stdout, p.BucketHeader(&bucket, false, len(buckets) > 1))
io.WriteString(os.Stdout, p.StackLines(&bucket.Signature, srcLen, pkgLen, false))
}
os.Exit(1)
}
}()
for _, b := range bs {
buf := b.Buffer()
// set cels in buf
for p, c := range buf.CellMap {
if p.In(buf.Area) {
tm.SetCell(p.X, p.Y, c.Ch, toTmAttr(c.Fg), toTmAttr(c.Bg))
}
}
}
renderLock.Lock()
// render
tm.Flush()
renderLock.Unlock()
}
func Clear() {
tm.Clear(tm.ColorDefault, toTmAttr(ThemeAttr("bg")))
}
func clearArea(r image.Rectangle, bg Attribute) {
for i := r.Min.X; i < r.Max.X; i++ {
for j := r.Min.Y; j < r.Max.Y; j++ {
tm.SetCell(i, j, ' ', tm.ColorDefault, toTmAttr(bg))
}
}
}
func ClearArea(r image.Rectangle, bg Attribute) {
clearArea(r, bg)
tm.Flush()
}
var renderJobs chan []Bufferer
func Render(bs ...Bufferer) {
//go func() { renderJobs <- bs }()
renderJobs <- bs
}

167
vendor/github.com/gizak/termui/sparkline.go generated vendored Normal file
View File

@ -0,0 +1,167 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
/*
data := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1}
spl := termui.NewSparkline()
spl.Data = data
spl.Title = "Sparkline 0"
spl.LineColor = termui.ColorGreen
*/
type Sparkline struct {
Data []int
Height int
Title string
TitleColor Attribute
LineColor Attribute
displayHeight int
scale float32
max int
}
// Sparklines is a renderable widget which groups together the given sparklines.
/*
spls := termui.NewSparklines(spl0,spl1,spl2) //...
spls.Height = 2
spls.Width = 20
*/
type Sparklines struct {
Block
Lines []Sparkline
displayLines int
displayWidth int
}
var sparks = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
// Add appends a given Sparkline to s *Sparklines.
func (s *Sparklines) Add(sl Sparkline) {
s.Lines = append(s.Lines, sl)
}
// NewSparkline returns a unrenderable single sparkline that intended to be added into Sparklines.
func NewSparkline() Sparkline {
return Sparkline{
Height: 1,
TitleColor: ThemeAttr("sparkline.title.fg"),
LineColor: ThemeAttr("sparkline.line.fg")}
}
// NewSparklines return a new *Spaklines with given Sparkline(s), you can always add a new Sparkline later.
func NewSparklines(ss ...Sparkline) *Sparklines {
s := &Sparklines{Block: *NewBlock(), Lines: ss}
return s
}
func (sl *Sparklines) update() {
for i, v := range sl.Lines {
if v.Title == "" {
sl.Lines[i].displayHeight = v.Height
} else {
sl.Lines[i].displayHeight = v.Height + 1
}
}
sl.displayWidth = sl.innerArea.Dx()
// get how many lines gotta display
h := 0
sl.displayLines = 0
for _, v := range sl.Lines {
if h+v.displayHeight <= sl.innerArea.Dy() {
sl.displayLines++
} else {
break
}
h += v.displayHeight
}
for i := 0; i < sl.displayLines; i++ {
data := sl.Lines[i].Data
max := 0
for _, v := range data {
if max < v {
max = v
}
}
sl.Lines[i].max = max
if max != 0 {
sl.Lines[i].scale = float32(8*sl.Lines[i].Height) / float32(max)
} else { // when all negative
sl.Lines[i].scale = 0
}
}
}
// Buffer implements Bufferer interface.
func (sl *Sparklines) Buffer() Buffer {
buf := sl.Block.Buffer()
sl.update()
oftY := 0
for i := 0; i < sl.displayLines; i++ {
l := sl.Lines[i]
data := l.Data
if len(data) > sl.innerArea.Dx() {
data = data[len(data)-sl.innerArea.Dx():]
}
if l.Title != "" {
rs := trimStr2Runes(l.Title, sl.innerArea.Dx())
oftX := 0
for _, v := range rs {
w := charWidth(v)
c := Cell{
Ch: v,
Fg: l.TitleColor,
Bg: sl.Bg,
}
x := sl.innerArea.Min.X + oftX
y := sl.innerArea.Min.Y + oftY
buf.Set(x, y, c)
oftX += w
}
}
for j, v := range data {
// display height of the data point, zero when data is negative
h := int(float32(v)*l.scale + 0.5)
if v < 0 {
h = 0
}
barCnt := h / 8
barMod := h % 8
for jj := 0; jj < barCnt; jj++ {
c := Cell{
Ch: ' ', // => sparks[7]
Bg: l.LineColor,
}
x := sl.innerArea.Min.X + j
y := sl.innerArea.Min.Y + oftY + l.Height - jj
//p.Bg = sl.BgColor
buf.Set(x, y, c)
}
if barMod != 0 {
c := Cell{
Ch: sparks[barMod-1],
Fg: l.LineColor,
Bg: sl.Bg,
}
x := sl.innerArea.Min.X + j
y := sl.innerArea.Min.Y + oftY + l.Height - barCnt
buf.Set(x, y, c)
}
}
oftY += l.displayHeight
}
return buf
}

185
vendor/github.com/gizak/termui/table.go generated vendored Normal file
View File

@ -0,0 +1,185 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import "strings"
/* Table is like:
Awesome Table
Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
Some Item #1 | AAA | 123 | CCCCC | EEEEE | GGGGG | IIIII |
Some Item #2 | BBB | 456 | DDDDD | FFFFF | HHHHH | JJJJJ |
Datapoints are a two dimensional array of strings: [][]string
Example:
data := [][]string{
{"Col0", "Col1", "Col3", "Col4", "Col5", "Col6"},
{"Some Item #1", "AAA", "123", "CCCCC", "EEEEE", "GGGGG", "IIIII"},
{"Some Item #2", "BBB", "456", "DDDDD", "FFFFF", "HHHHH", "JJJJJ"},
}
table := termui.NewTable()
table.Rows = data // type [][]string
table.FgColor = termui.ColorWhite
table.BgColor = termui.ColorDefault
table.Height = 7
table.Width = 62
table.Y = 0
table.X = 0
table.Border = true
*/
// Table tracks all the attributes of a Table instance
type Table struct {
Block
Rows [][]string
CellWidth []int
FgColor Attribute
BgColor Attribute
FgColors []Attribute
BgColors []Attribute
Separator bool
TextAlign Align
}
// NewTable returns a new Table instance
func NewTable() *Table {
table := &Table{Block: *NewBlock()}
table.FgColor = ColorWhite
table.BgColor = ColorDefault
table.Separator = true
return table
}
// CellsWidth calculates the width of a cell array and returns an int
func cellsWidth(cells []Cell) int {
width := 0
for _, c := range cells {
width += c.Width()
}
return width
}
// Analysis generates and returns an array of []Cell that represent all columns in the Table
func (table *Table) Analysis() [][]Cell {
var rowCells [][]Cell
length := len(table.Rows)
if length < 1 {
return rowCells
}
if len(table.FgColors) == 0 {
table.FgColors = make([]Attribute, len(table.Rows))
}
if len(table.BgColors) == 0 {
table.BgColors = make([]Attribute, len(table.Rows))
}
cellWidths := make([]int, len(table.Rows[0]))
for y, row := range table.Rows {
if table.FgColors[y] == 0 {
table.FgColors[y] = table.FgColor
}
if table.BgColors[y] == 0 {
table.BgColors[y] = table.BgColor
}
for x, str := range row {
cells := DefaultTxBuilder.Build(str, table.FgColors[y], table.BgColors[y])
cw := cellsWidth(cells)
if cellWidths[x] < cw {
cellWidths[x] = cw
}
rowCells = append(rowCells, cells)
}
}
table.CellWidth = cellWidths
return rowCells
}
// SetSize calculates the table size and sets the internal value
func (table *Table) SetSize() {
length := len(table.Rows)
if table.Separator {
table.Height = length*2 + 1
} else {
table.Height = length + 2
}
table.Width = 2
if length != 0 {
for _, cellWidth := range table.CellWidth {
table.Width += cellWidth + 3
}
}
}
// CalculatePosition ...
func (table *Table) CalculatePosition(x int, y int, coordinateX *int, coordinateY *int, cellStart *int) {
if table.Separator {
*coordinateY = table.innerArea.Min.Y + y*2
} else {
*coordinateY = table.innerArea.Min.Y + y
}
if x == 0 {
*cellStart = table.innerArea.Min.X
} else {
*cellStart += table.CellWidth[x-1] + 3
}
switch table.TextAlign {
case AlignRight:
*coordinateX = *cellStart + (table.CellWidth[x] - len(table.Rows[y][x])) + 2
case AlignCenter:
*coordinateX = *cellStart + (table.CellWidth[x]-len(table.Rows[y][x]))/2 + 2
default:
*coordinateX = *cellStart + 2
}
}
// Buffer ...
func (table *Table) Buffer() Buffer {
buffer := table.Block.Buffer()
rowCells := table.Analysis()
pointerX := table.innerArea.Min.X + 2
pointerY := table.innerArea.Min.Y
borderPointerX := table.innerArea.Min.X
for y, row := range table.Rows {
for x := range row {
table.CalculatePosition(x, y, &pointerX, &pointerY, &borderPointerX)
background := DefaultTxBuilder.Build(strings.Repeat(" ", table.CellWidth[x]+3), table.BgColors[y], table.BgColors[y])
cells := rowCells[y*len(row)+x]
for i, back := range background {
buffer.Set(borderPointerX+i, pointerY, back)
}
coordinateX := pointerX
for _, printer := range cells {
buffer.Set(coordinateX, pointerY, printer)
coordinateX += printer.Width()
}
if x != 0 {
dividors := DefaultTxBuilder.Build("|", table.FgColors[y], table.BgColors[y])
for _, dividor := range dividors {
buffer.Set(borderPointerX, pointerY, dividor)
}
}
}
if table.Separator {
border := DefaultTxBuilder.Build(strings.Repeat("─", table.Width-2), table.FgColor, table.BgColor)
for i, cell := range border {
buffer.Set(i+1, pointerY+1, cell)
}
}
}
return buffer
}

278
vendor/github.com/gizak/termui/textbuilder.go generated vendored Normal file
View File

@ -0,0 +1,278 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"regexp"
"strings"
"github.com/mitchellh/go-wordwrap"
)
// TextBuilder is a minimal interface to produce text []Cell using specific syntax (markdown).
type TextBuilder interface {
Build(s string, fg, bg Attribute) []Cell
}
// DefaultTxBuilder is set to be MarkdownTxBuilder.
var DefaultTxBuilder = NewMarkdownTxBuilder()
// MarkdownTxBuilder implements TextBuilder interface, using markdown syntax.
type MarkdownTxBuilder struct {
baseFg Attribute
baseBg Attribute
plainTx []rune
markers []marker
}
type marker struct {
st int
ed int
fg Attribute
bg Attribute
}
var colorMap = map[string]Attribute{
"red": ColorRed,
"blue": ColorBlue,
"black": ColorBlack,
"cyan": ColorCyan,
"yellow": ColorYellow,
"white": ColorWhite,
"default": ColorDefault,
"green": ColorGreen,
"magenta": ColorMagenta,
}
var attrMap = map[string]Attribute{
"bold": AttrBold,
"underline": AttrUnderline,
"reverse": AttrReverse,
}
func rmSpc(s string) string {
reg := regexp.MustCompile(`\s+`)
return reg.ReplaceAllString(s, "")
}
// readAttr translates strings like `fg-red,fg-bold,bg-white` to fg and bg Attribute
func (mtb MarkdownTxBuilder) readAttr(s string) (Attribute, Attribute) {
fg := mtb.baseFg
bg := mtb.baseBg
updateAttr := func(a Attribute, attrs []string) Attribute {
for _, s := range attrs {
// replace the color
if c, ok := colorMap[s]; ok {
a &= 0xFF00 // erase clr 0 ~ 8 bits
a |= c // set clr
}
// add attrs
if c, ok := attrMap[s]; ok {
a |= c
}
}
return a
}
ss := strings.Split(s, ",")
fgs := []string{}
bgs := []string{}
for _, v := range ss {
subs := strings.Split(v, "-")
if len(subs) > 1 {
if subs[0] == "fg" {
fgs = append(fgs, subs[1])
}
if subs[0] == "bg" {
bgs = append(bgs, subs[1])
}
}
}
fg = updateAttr(fg, fgs)
bg = updateAttr(bg, bgs)
return fg, bg
}
func (mtb *MarkdownTxBuilder) reset() {
mtb.plainTx = []rune{}
mtb.markers = []marker{}
}
// parse streams and parses text into normalized text and render sequence.
func (mtb *MarkdownTxBuilder) parse(str string) {
rs := str2runes(str)
normTx := []rune{}
square := []rune{}
brackt := []rune{}
accSquare := false
accBrackt := false
cntSquare := 0
reset := func() {
square = []rune{}
brackt = []rune{}
accSquare = false
accBrackt = false
cntSquare = 0
}
// pipe stacks into normTx and clear
rollback := func() {
normTx = append(normTx, square...)
normTx = append(normTx, brackt...)
reset()
}
// chop first and last
chop := func(s []rune) []rune {
return s[1 : len(s)-1]
}
for i, r := range rs {
switch {
// stacking brackt
case accBrackt:
brackt = append(brackt, r)
if ')' == r {
fg, bg := mtb.readAttr(string(chop(brackt)))
st := len(normTx)
ed := len(normTx) + len(square) - 2
mtb.markers = append(mtb.markers, marker{st, ed, fg, bg})
normTx = append(normTx, chop(square)...)
reset()
} else if i+1 == len(rs) {
rollback()
}
// stacking square
case accSquare:
switch {
// squares closed and followed by a '('
case cntSquare == 0 && '(' == r:
accBrackt = true
brackt = append(brackt, '(')
// squares closed but not followed by a '('
case cntSquare == 0:
rollback()
if '[' == r {
accSquare = true
cntSquare = 1
brackt = append(brackt, '[')
} else {
normTx = append(normTx, r)
}
// hit the end
case i+1 == len(rs):
square = append(square, r)
rollback()
case '[' == r:
cntSquare++
square = append(square, '[')
case ']' == r:
cntSquare--
square = append(square, ']')
// normal char
default:
square = append(square, r)
}
// stacking normTx
default:
if '[' == r {
accSquare = true
cntSquare = 1
square = append(square, '[')
} else {
normTx = append(normTx, r)
}
}
}
mtb.plainTx = normTx
}
func wrapTx(cs []Cell, wl int) []Cell {
tmpCell := make([]Cell, len(cs))
copy(tmpCell, cs)
// get the plaintext
plain := CellsToStr(cs)
// wrap
plainWrapped := wordwrap.WrapString(plain, uint(wl))
// find differences and insert
finalCell := tmpCell // finalcell will get the inserts and is what is returned
plainRune := []rune(plain)
plainWrappedRune := []rune(plainWrapped)
trigger := "go"
plainRuneNew := plainRune
for trigger != "stop" {
plainRune = plainRuneNew
for i := range plainRune {
if plainRune[i] == plainWrappedRune[i] {
trigger = "stop"
} else if plainRune[i] != plainWrappedRune[i] && plainWrappedRune[i] == 10 {
trigger = "go"
cell := Cell{10, 0, 0}
j := i - 0
// insert a cell into the []Cell in correct position
tmpCell[i] = cell
// insert the newline into plain so we avoid indexing errors
plainRuneNew = append(plainRune, 10)
copy(plainRuneNew[j+1:], plainRuneNew[j:])
plainRuneNew[j] = plainWrappedRune[j]
// restart the inner for loop until plain and plain wrapped are
// the same; yeah, it's inefficient, but the text amounts
// should be small
break
} else if plainRune[i] != plainWrappedRune[i] &&
plainWrappedRune[i-1] == 10 && // if the prior rune is a newline
plainRune[i] == 32 { // and this rune is a space
trigger = "go"
// need to delete plainRune[i] because it gets rid of an extra
// space
plainRuneNew = append(plainRune[:i], plainRune[i+1:]...)
break
} else {
trigger = "stop" // stops the outer for loop
}
}
}
finalCell = tmpCell
return finalCell
}
// Build implements TextBuilder interface.
func (mtb MarkdownTxBuilder) Build(s string, fg, bg Attribute) []Cell {
mtb.baseFg = fg
mtb.baseBg = bg
mtb.reset()
mtb.parse(s)
cs := make([]Cell, len(mtb.plainTx))
for i := range cs {
cs[i] = Cell{Ch: mtb.plainTx[i], Fg: fg, Bg: bg}
}
for _, mrk := range mtb.markers {
for i := mrk.st; i < mrk.ed; i++ {
cs[i].Fg = mrk.fg
cs[i].Bg = mrk.bg
}
}
return cs
}
// NewMarkdownTxBuilder returns a TextBuilder employing markdown syntax.
func NewMarkdownTxBuilder() TextBuilder {
return MarkdownTxBuilder{}
}

140
vendor/github.com/gizak/termui/theme.go generated vendored Normal file
View File

@ -0,0 +1,140 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import "strings"
/*
// A ColorScheme represents the current look-and-feel of the dashboard.
type ColorScheme struct {
BodyBg Attribute
BlockBg Attribute
HasBorder bool
BorderFg Attribute
BorderBg Attribute
BorderLabelTextFg Attribute
BorderLabelTextBg Attribute
ParTextFg Attribute
ParTextBg Attribute
SparklineLine Attribute
SparklineTitle Attribute
GaugeBar Attribute
GaugePercent Attribute
LineChartLine Attribute
LineChartAxes Attribute
ListItemFg Attribute
ListItemBg Attribute
BarChartBar Attribute
BarChartText Attribute
BarChartNum Attribute
MBarChartBar Attribute
MBarChartText Attribute
MBarChartNum Attribute
TabActiveBg Attribute
}
// default color scheme depends on the user's terminal setting.
var themeDefault = ColorScheme{HasBorder: true}
var themeHelloWorld = ColorScheme{
BodyBg: ColorBlack,
BlockBg: ColorBlack,
HasBorder: true,
BorderFg: ColorWhite,
BorderBg: ColorBlack,
BorderLabelTextBg: ColorBlack,
BorderLabelTextFg: ColorGreen,
ParTextBg: ColorBlack,
ParTextFg: ColorWhite,
SparklineLine: ColorMagenta,
SparklineTitle: ColorWhite,
GaugeBar: ColorRed,
GaugePercent: ColorWhite,
LineChartLine: ColorYellow | AttrBold,
LineChartAxes: ColorWhite,
ListItemBg: ColorBlack,
ListItemFg: ColorYellow,
BarChartBar: ColorRed,
BarChartNum: ColorWhite,
BarChartText: ColorCyan,
MBarChartBar: ColorRed,
MBarChartNum: ColorWhite,
MBarChartText: ColorCyan,
TabActiveBg: ColorMagenta,
}
var theme = themeDefault // global dep
// Theme returns the currently used theme.
func Theme() ColorScheme {
return theme
}
// SetTheme sets a new, custom theme.
func SetTheme(newTheme ColorScheme) {
theme = newTheme
}
// UseTheme sets a predefined scheme. Currently available: "hello-world" and
// "black-and-white".
func UseTheme(th string) {
switch th {
case "helloworld":
theme = themeHelloWorld
default:
theme = themeDefault
}
}
*/
var ColorMap = map[string]Attribute{
"fg": ColorWhite,
"bg": ColorDefault,
"border.fg": ColorWhite,
"label.fg": ColorGreen,
"par.fg": ColorYellow,
"par.label.bg": ColorWhite,
}
func ThemeAttr(name string) Attribute {
return lookUpAttr(ColorMap, name)
}
func lookUpAttr(clrmap map[string]Attribute, name string) Attribute {
a, ok := clrmap[name]
if ok {
return a
}
ns := strings.Split(name, ".")
for i := range ns {
nn := strings.Join(ns[i:len(ns)], ".")
a, ok = ColorMap[nn]
if ok {
break
}
}
return a
}
// 0<=r,g,b <= 5
func ColorRGB(r, g, b int) Attribute {
within := func(n int) int {
if n < 0 {
return 0
}
if n > 5 {
return 5
}
return n
}
r, b, g = within(r), within(b), within(g)
return Attribute(0x0f + 36*r + 6*g + b)
}

94
vendor/github.com/gizak/termui/widget.go generated vendored Normal file
View File

@ -0,0 +1,94 @@
// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.
package termui
import (
"fmt"
"sync"
)
// event mixins
type WgtMgr map[string]WgtInfo
type WgtInfo struct {
Handlers map[string]func(Event)
WgtRef Widget
Id string
}
type Widget interface {
Id() string
}
func NewWgtInfo(wgt Widget) WgtInfo {
return WgtInfo{
Handlers: make(map[string]func(Event)),
WgtRef: wgt,
Id: wgt.Id(),
}
}
func NewWgtMgr() WgtMgr {
wm := WgtMgr(make(map[string]WgtInfo))
return wm
}
func (wm WgtMgr) AddWgt(wgt Widget) {
wm[wgt.Id()] = NewWgtInfo(wgt)
}
func (wm WgtMgr) RmWgt(wgt Widget) {
wm.RmWgtById(wgt.Id())
}
func (wm WgtMgr) RmWgtById(id string) {
delete(wm, id)
}
func (wm WgtMgr) AddWgtHandler(id, path string, h func(Event)) {
if w, ok := wm[id]; ok {
w.Handlers[path] = h
}
}
func (wm WgtMgr) RmWgtHandler(id, path string) {
if w, ok := wm[id]; ok {
delete(w.Handlers, path)
}
}
var counter struct {
sync.RWMutex
count int
}
func GenId() string {
counter.Lock()
defer counter.Unlock()
counter.count += 1
return fmt.Sprintf("%d", counter.count)
}
func (wm WgtMgr) WgtHandlersHook() func(Event) {
return func(e Event) {
for _, v := range wm {
if k := findMatch(v.Handlers, e.Path); k != "" {
v.Handlers[k](e)
}
}
}
}
var DefaultWgtMgr WgtMgr
func (b *Block) Handle(path string, handler func(Event)) {
if _, ok := DefaultWgtMgr[b.Id()]; !ok {
DefaultWgtMgr.AddWgt(b)
}
DefaultWgtMgr.AddWgtHandler(b.Id(), path, handler)
}

201
vendor/github.com/maruel/panicparse/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Marc-Antoine Ruel
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

291
vendor/github.com/maruel/panicparse/stack/source.go generated vendored Normal file
View File

@ -0,0 +1,291 @@
// Copyright 2015 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
// This file contains the code to process sources, to be able to deduct the
// original types.
package stack
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"log"
"math"
"strings"
)
// cache is a cache of sources on the file system.
type cache struct {
files map[string][]byte
parsed map[string]*parsedFile
}
// Augment processes source files to improve calls to be more descriptive.
//
// It modifies goroutines in place.
func Augment(goroutines []Goroutine) {
c := &cache{}
for i := range goroutines {
c.augmentGoroutine(&goroutines[i])
}
}
// augmentGoroutine processes source files to improve call to be more
// descriptive.
//
// It modifies the routine.
func (c *cache) augmentGoroutine(goroutine *Goroutine) {
if c.files == nil {
c.files = map[string][]byte{}
}
if c.parsed == nil {
c.parsed = map[string]*parsedFile{}
}
// For each call site, look at the next call and populate it. Then we can
// walk back and reformat things.
for i := range goroutine.Stack.Calls {
c.load(goroutine.Stack.Calls[i].SourcePath)
}
// Once all loaded, we can look at the next call when available.
for i := 1; i < len(goroutine.Stack.Calls); i++ {
// Get the AST from the previous call and process the call line with it.
if f := c.getFuncAST(&goroutine.Stack.Calls[i]); f != nil {
processCall(&goroutine.Stack.Calls[i], f)
}
}
}
// Private stuff.
// load loads a source file and parses the AST tree. Failures are ignored.
func (c *cache) load(fileName string) {
if _, ok := c.parsed[fileName]; ok {
return
}
c.parsed[fileName] = nil
if !strings.HasSuffix(fileName, ".go") {
// Ignore C and assembly.
c.files[fileName] = nil
return
}
log.Printf("load(%s)", fileName)
if _, ok := c.files[fileName]; !ok {
var err error
if c.files[fileName], err = ioutil.ReadFile(fileName); err != nil {
log.Printf("Failed to read %s: %s", fileName, err)
c.files[fileName] = nil
return
}
}
fset := token.NewFileSet()
src := c.files[fileName]
parsed, err := parser.ParseFile(fset, fileName, src, 0)
if err != nil {
log.Printf("Failed to parse %s: %s", fileName, err)
return
}
// Convert the line number into raw file offset.
offsets := []int{0, 0}
start := 0
for l := 1; start < len(src); l++ {
start += bytes.IndexByte(src[start:], '\n') + 1
offsets = append(offsets, start)
}
c.parsed[fileName] = &parsedFile{offsets, parsed}
}
func (c *cache) getFuncAST(call *Call) *ast.FuncDecl {
if p := c.parsed[call.SourcePath]; p != nil {
return p.getFuncAST(call.Func.Name(), call.Line)
}
return nil
}
type parsedFile struct {
lineToByteOffset []int
parsed *ast.File
}
// getFuncAST gets the callee site function AST representation for the code
// inside the function f at line l.
func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
// Walk the AST to find the lineToByteOffset that fits the line number.
var lastFunc *ast.FuncDecl
var found ast.Node
// Inspect() goes depth first. This means for example that a function like:
// func a() {
// b := func() {}
// c()
// }
//
// Were we are looking at the c() call can return confused values. It is
// important to look at the actual ast.Node hierarchy.
ast.Inspect(p.parsed, func(n ast.Node) bool {
if d != nil {
return false
}
if n == nil {
return true
}
if found != nil {
// We are walking up.
}
if int(n.Pos()) >= p.lineToByteOffset[l] {
// We are expecting a ast.CallExpr node. It can be harder to figure out
// when there are multiple calls on a single line, as the stack trace
// doesn't have file byte offset information, only line based.
// gofmt will always format to one function call per line but there can
// be edge cases, like:
// a = A{Foo(), Bar()}
d = lastFunc
//p.processNode(call, n)
return false
} else if f, ok := n.(*ast.FuncDecl); ok {
lastFunc = f
}
return true
})
return
}
func name(n ast.Node) string {
if _, ok := n.(*ast.InterfaceType); ok {
return "interface{}"
}
if i, ok := n.(*ast.Ident); ok {
return i.Name
}
if _, ok := n.(*ast.FuncType); ok {
return "func"
}
if s, ok := n.(*ast.SelectorExpr); ok {
return s.Sel.Name
}
// TODO(maruel): Implement anything missing.
return "<unknown>"
}
// fieldToType returns the type name and whether if it's an ellipsis.
func fieldToType(f *ast.Field) (string, bool) {
switch arg := f.Type.(type) {
case *ast.ArrayType:
return "[]" + name(arg.Elt), false
case *ast.Ellipsis:
return name(arg.Elt), true
case *ast.FuncType:
// Do not print the function signature to not overload the trace.
return "func", false
case *ast.Ident:
return arg.Name, false
case *ast.InterfaceType:
return "interface{}", false
case *ast.SelectorExpr:
return arg.Sel.Name, false
case *ast.StarExpr:
return "*" + name(arg.X), false
default:
// TODO(maruel): Implement anything missing.
return "<unknown>", false
}
}
// extractArgumentsType returns the name of the type of each input argument.
func extractArgumentsType(f *ast.FuncDecl) ([]string, bool) {
var fields []*ast.Field
if f.Recv != nil {
if len(f.Recv.List) != 1 {
panic("Expect only one receiver; please fix panicparse's code")
}
// If it is an object receiver (vs a pointer receiver), its address is not
// printed in the stack trace so it needs to be ignored.
if _, ok := f.Recv.List[0].Type.(*ast.StarExpr); ok {
fields = append(fields, f.Recv.List[0])
}
}
var types []string
extra := false
for _, arg := range append(fields, f.Type.Params.List...) {
// Assert that extra is only set on the last item of fields?
var t string
t, extra = fieldToType(arg)
mult := len(arg.Names)
if mult == 0 {
mult = 1
}
for i := 0; i < mult; i++ {
types = append(types, t)
}
}
return types, extra
}
// processCall walks the function and populate call accordingly.
func processCall(call *Call, f *ast.FuncDecl) {
values := make([]uint64, len(call.Args.Values))
for i := range call.Args.Values {
values[i] = call.Args.Values[i].Value
}
index := 0
pop := func() uint64 {
if len(values) != 0 {
x := values[0]
values = values[1:]
index++
return x
}
return 0
}
popName := func() string {
n := call.Args.Values[index].Name
v := pop()
if len(n) == 0 {
return fmt.Sprintf("0x%x", v)
}
return n
}
types, extra := extractArgumentsType(f)
for i := 0; len(values) != 0; i++ {
var t string
if i >= len(types) {
if !extra {
// These are unexpected value! Print them as hex.
call.Args.Processed = append(call.Args.Processed, popName())
continue
}
t = types[len(types)-1]
} else {
t = types[i]
}
switch t {
case "float32":
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%g", math.Float32frombits(uint32(pop()))))
case "float64":
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%g", math.Float64frombits(pop())))
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%d", pop()))
case "string":
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s, len=%d)", t, popName(), pop()))
default:
if strings.HasPrefix(t, "*") {
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s)", t, popName()))
} else if strings.HasPrefix(t, "[]") {
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s len=%d cap=%d)", t, popName(), pop(), pop()))
} else {
// Assumes it's an interface. For now, discard the object value, which
// is probably not a good idea.
call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s)", t, popName()))
pop()
}
}
if len(values) == 0 && call.Args.Elided {
return
}
}
}

832
vendor/github.com/maruel/panicparse/stack/stack.go generated vendored Normal file
View File

@ -0,0 +1,832 @@
// Copyright 2015 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
// Package stack analyzes stack dump of Go processes and simplifies it.
//
// It is mostly useful on servers will large number of identical goroutines,
// making the crash dump harder to read than strictly necesary.
package stack
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"math"
"net/url"
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
const lockedToThread = "locked to thread"
var (
// TODO(maruel): Handle corrupted stack cases:
// - missed stack barrier
// - found next stack barrier at 0x123; expected
// - runtime: unexpected return pc for FUNC_NAME called from 0x123
reRoutineHeader = regexp.MustCompile("^goroutine (\\d+) \\[([^\\]]+)\\]\\:\n$")
reMinutes = regexp.MustCompile("^(\\d+) minutes$")
reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
// See gentraceback() in src/runtime/traceback.go for more information.
// - Sometimes the source file comes up as "<autogenerated>". It is the
// compiler than generated these, not the runtime.
// - The tab may be replaced with spaces when a user copy-paste it, handle
// this transparently.
// - "runtime.gopanic" is explicitly replaced with "panic" by gentraceback().
// - The +0x123 byte offset is printed when frame.pc > _func.entry. _func is
// generated by the linker.
// - The +0x123 byte offset is not included with generated code, e.g. unnamed
// functions "func·006()" which is generally go func() { ... }()
// statements. Since the _func is generated at runtime, it's probably why
// _func.entry is not set.
// - C calls may have fp=0x123 sp=0x123 appended. I think it normally happens
// when a signal is not correctly handled. It is printed with m.throwing>0.
// These are discarded.
// - For cgo, the source file may be "??".
reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+)\n$")
// Sadly, it doesn't note the goroutine number so we could cascade them per
// parenthood.
reCreated = regexp.MustCompile("^created by (.+)\n$")
reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\n$")
reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\n$")
// Include frequent GOROOT value on Windows, distro provided and user
// installed path. This simplifies the user's life when processing a trace
// generated on another VM.
// TODO(maruel): Guess the path automatically via traces containing the
// 'runtime' package, which is very frequent. This would be "less bad" than
// throwing up random values at the parser.
goroots = []string{runtime.GOROOT(), "c:/go", "/usr/lib/go", "/usr/local/go"}
)
// Similarity is the level at which two call lines arguments must match to be
// considered similar enough to coalesce them.
type Similarity int
const (
// ExactFlags requires same bits (e.g. Locked).
ExactFlags Similarity = iota
// ExactLines requests the exact same arguments on the call line.
ExactLines
// AnyPointer considers different pointers a similar call line.
AnyPointer
// AnyValue accepts any value as similar call line.
AnyValue
)
// Function is a function call.
//
// Go stack traces print a mangled function call, this wrapper unmangle the
// string before printing and adds other filtering methods.
type Function struct {
Raw string
}
// String is the fully qualified function name.
//
// Sadly Go is a bit confused when the package name doesn't match the directory
// containing the source file and will use the directory name instead of the
// real package name.
func (f Function) String() string {
s, _ := url.QueryUnescape(f.Raw)
return s
}
// Name is the naked function name.
func (f Function) Name() string {
parts := strings.SplitN(filepath.Base(f.Raw), ".", 2)
if len(parts) == 1 {
return parts[0]
}
return parts[1]
}
// PkgName is the package name for this function reference.
func (f Function) PkgName() string {
parts := strings.SplitN(filepath.Base(f.Raw), ".", 2)
if len(parts) == 1 {
return ""
}
s, _ := url.QueryUnescape(parts[0])
return s
}
// PkgDotName returns "<package>.<func>" format.
func (f Function) PkgDotName() string {
parts := strings.SplitN(filepath.Base(f.Raw), ".", 2)
s, _ := url.QueryUnescape(parts[0])
if len(parts) == 1 {
return parts[0]
}
if s != "" || parts[1] != "" {
return s + "." + parts[1]
}
return ""
}
// IsExported returns true if the function is exported.
func (f Function) IsExported() bool {
name := f.Name()
parts := strings.Split(name, ".")
r, _ := utf8.DecodeRuneInString(parts[len(parts)-1])
if unicode.ToUpper(r) == r {
return true
}
return f.PkgName() == "main" && name == "main"
}
// Arg is an argument on a Call.
type Arg struct {
Value uint64 // Value is the raw value as found in the stack trace
Name string // Name is a pseudo name given to the argument
}
// IsPtr returns true if we guess it's a pointer. It's only a guess, it can be
// easily be confused by a bitmask.
func (a *Arg) IsPtr() bool {
// Assumes all pointers are above 16Mb and positive.
return a.Value > 16*1024*1024 && a.Value < math.MaxInt64
}
func (a Arg) String() string {
if a.Name != "" {
return a.Name
}
if a.Value == 0 {
return "0"
}
return fmt.Sprintf("0x%x", a.Value)
}
// Args is a series of function call arguments.
type Args struct {
Values []Arg // Values is the arguments as shown on the stack trace. They are mangled via simplification.
Processed []string // Processed is the arguments generated from processing the source files. It can have a length lower than Values.
Elided bool // If set, it means there was a trailing ", ..."
}
func (a Args) String() string {
var v []string
if len(a.Processed) != 0 {
v = make([]string, 0, len(a.Processed))
for _, item := range a.Processed {
v = append(v, item)
}
} else {
v = make([]string, 0, len(a.Values))
for _, item := range a.Values {
v = append(v, item.String())
}
}
if a.Elided {
v = append(v, "...")
}
return strings.Join(v, ", ")
}
// Equal returns true only if both arguments are exactly equal.
func (a *Args) Equal(r *Args) bool {
if a.Elided != r.Elided || len(a.Values) != len(r.Values) {
return false
}
for i, l := range a.Values {
if l != r.Values[i] {
return false
}
}
return true
}
// Similar returns true if the two Args are equal or almost but not quite
// equal.
func (a *Args) Similar(r *Args, similar Similarity) bool {
if a.Elided != r.Elided || len(a.Values) != len(r.Values) {
return false
}
if similar == AnyValue {
return true
}
for i, l := range a.Values {
switch similar {
case ExactFlags, ExactLines:
if l != r.Values[i] {
return false
}
default:
if l.IsPtr() != r.Values[i].IsPtr() || (!l.IsPtr() && l != r.Values[i]) {
return false
}
}
}
return true
}
// Merge merges two similar Args, zapping out differences.
func (a *Args) Merge(r *Args) Args {
out := Args{
Values: make([]Arg, len(a.Values)),
Elided: a.Elided,
}
for i, l := range a.Values {
if l != r.Values[i] {
out.Values[i].Name = "*"
out.Values[i].Value = l.Value
} else {
out.Values[i] = l
}
}
return out
}
// Call is an item in the stack trace.
type Call struct {
SourcePath string // Full path name of the source file
Line int // Line number
Func Function // Fully qualified function name (encoded).
Args Args // Call arguments
}
// Equal returns true only if both calls are exactly equal.
func (c *Call) Equal(r *Call) bool {
return c.SourcePath == r.SourcePath && c.Line == r.Line && c.Func == r.Func && c.Args.Equal(&r.Args)
}
// Similar returns true if the two Call are equal or almost but not quite
// equal.
func (c *Call) Similar(r *Call, similar Similarity) bool {
return c.SourcePath == r.SourcePath && c.Line == r.Line && c.Func == r.Func && c.Args.Similar(&r.Args, similar)
}
// Merge merges two similar Call, zapping out differences.
func (c *Call) Merge(r *Call) Call {
return Call{
SourcePath: c.SourcePath,
Line: c.Line,
Func: c.Func,
Args: c.Args.Merge(&r.Args),
}
}
// SourceName returns the base file name of the source file.
func (c *Call) SourceName() string {
return filepath.Base(c.SourcePath)
}
// SourceLine returns "source.go:line", including only the base file name.
func (c *Call) SourceLine() string {
return fmt.Sprintf("%s:%d", c.SourceName(), c.Line)
}
// FullSourceLine returns "/path/to/source.go:line".
func (c *Call) FullSourceLine() string {
return fmt.Sprintf("%s:%d", c.SourcePath, c.Line)
}
// PkgSource is one directory plus the file name of the source file.
func (c *Call) PkgSource() string {
return filepath.Join(filepath.Base(filepath.Dir(c.SourcePath)), c.SourceName())
}
const testMainSource = "_test" + string(os.PathSeparator) + "_testmain.go"
// IsStdlib returns true if it is a Go standard library function. This includes
// the 'go test' generated main executable.
func (c *Call) IsStdlib() bool {
for _, goroot := range goroots {
if strings.HasPrefix(c.SourcePath, goroot) {
return true
}
}
// Consider _test/_testmain.go as stdlib since it's injected by "go test".
return c.PkgSource() == testMainSource
}
// IsPkgMain returns true if it is in the main package.
func (c *Call) IsPkgMain() bool {
return c.Func.PkgName() == "main"
}
// Stack is a call stack.
type Stack struct {
Calls []Call // Call stack. First is original function, last is leaf function.
Elided bool // Happens when there's >100 items in Stack, currently hardcoded in package runtime.
}
// Equal returns true on if both call stacks are exactly equal.
func (s *Stack) Equal(r *Stack) bool {
if len(s.Calls) != len(r.Calls) || s.Elided != r.Elided {
return false
}
for i := range s.Calls {
if !s.Calls[i].Equal(&r.Calls[i]) {
return false
}
}
return true
}
// Similar returns true if the two Stack are equal or almost but not quite
// equal.
func (s *Stack) Similar(r *Stack, similar Similarity) bool {
if len(s.Calls) != len(r.Calls) || s.Elided != r.Elided {
return false
}
for i := range s.Calls {
if !s.Calls[i].Similar(&r.Calls[i], similar) {
return false
}
}
return true
}
// Merge merges two similar Stack, zapping out differences.
func (s *Stack) Merge(r *Stack) *Stack {
// Assumes similar stacks have the same length.
out := &Stack{
Calls: make([]Call, len(s.Calls)),
Elided: s.Elided,
}
for i := range s.Calls {
out.Calls[i] = s.Calls[i].Merge(&r.Calls[i])
}
return out
}
// Less compares two Stack, where the ones that are less are more
// important, so they come up front. A Stack with more private functions is
// 'less' so it is at the top. Inversely, a Stack with only public
// functions is 'more' so it is at the bottom.
func (s *Stack) Less(r *Stack) bool {
lStdlib := 0
lPrivate := 0
for _, c := range s.Calls {
if c.IsStdlib() {
lStdlib++
} else {
lPrivate++
}
}
rStdlib := 0
rPrivate := 0
for _, s := range r.Calls {
if s.IsStdlib() {
rStdlib++
} else {
rPrivate++
}
}
if lPrivate > rPrivate {
return true
}
if lPrivate < rPrivate {
return false
}
if lStdlib > rStdlib {
return false
}
if lStdlib < rStdlib {
return true
}
// Stack lengths are the same.
for x := range s.Calls {
if s.Calls[x].Func.Raw < r.Calls[x].Func.Raw {
return true
}
if s.Calls[x].Func.Raw > r.Calls[x].Func.Raw {
return true
}
if s.Calls[x].PkgSource() < r.Calls[x].PkgSource() {
return true
}
if s.Calls[x].PkgSource() > r.Calls[x].PkgSource() {
return true
}
if s.Calls[x].Line < r.Calls[x].Line {
return true
}
if s.Calls[x].Line > r.Calls[x].Line {
return true
}
}
return false
}
// Signature represents the signature of one or multiple goroutines.
//
// It is effectively the stack trace plus the goroutine internal bits, like
// it's state, if it is thread locked, which call site created this goroutine,
// etc.
type Signature struct {
// Use git grep 'gopark(|unlock)\(' to find them all plus everything listed
// in runtime/traceback.go. Valid values includes:
// - chan send, chan receive, select
// - finalizer wait, mark wait (idle),
// - Concurrent GC wait, GC sweep wait, force gc (idle)
// - IO wait, panicwait
// - semacquire, semarelease
// - sleep, timer goroutine (idle)
// - trace reader (blocked)
// Stuck cases:
// - chan send (nil chan), chan receive (nil chan), select (no cases)
// Runnable states:
// - idle, runnable, running, syscall, waiting, dead, enqueue, copystack,
// Scan states:
// - scan, scanrunnable, scanrunning, scansyscall, scanwaiting, scandead,
// scanenqueue
State string
CreatedBy Call // Which other goroutine which created this one.
SleepMin int // Wait time in minutes, if applicable.
SleepMax int // Wait time in minutes, if applicable.
Stack Stack
Locked bool // Locked to an OS thread.
}
// Equal returns true only if both signatures are exactly equal.
func (s *Signature) Equal(r *Signature) bool {
if s.State != r.State || !s.CreatedBy.Equal(&r.CreatedBy) || s.Locked != r.Locked || s.SleepMin != r.SleepMin || s.SleepMax != r.SleepMax {
return false
}
return s.Stack.Equal(&r.Stack)
}
// Similar returns true if the two Signature are equal or almost but not quite
// equal.
func (s *Signature) Similar(r *Signature, similar Similarity) bool {
if s.State != r.State || !s.CreatedBy.Similar(&r.CreatedBy, similar) {
return false
}
if similar == ExactFlags && s.Locked != r.Locked {
return false
}
return s.Stack.Similar(&r.Stack, similar)
}
// Merge merges two similar Signature, zapping out differences.
func (s *Signature) Merge(r *Signature) *Signature {
min := s.SleepMin
if r.SleepMin < min {
min = r.SleepMin
}
max := s.SleepMax
if r.SleepMax > max {
max = r.SleepMax
}
return &Signature{
State: s.State, // Drop right side.
CreatedBy: s.CreatedBy, // Drop right side.
SleepMin: min,
SleepMax: max,
Stack: *s.Stack.Merge(&r.Stack),
Locked: s.Locked || r.Locked, // TODO(maruel): This is weirdo.
}
}
// Less compares two Signature, where the ones that are less are more
// important, so they come up front. A Signature with more private functions is
// 'less' so it is at the top. Inversely, a Signature with only public
// functions is 'more' so it is at the bottom.
func (s *Signature) Less(r *Signature) bool {
if s.Stack.Less(&r.Stack) {
return true
}
if r.Stack.Less(&s.Stack) {
return false
}
if s.Locked && !r.Locked {
return true
}
if r.Locked && !s.Locked {
return false
}
if s.State < r.State {
return true
}
if s.State > r.State {
return false
}
return false
}
// Goroutine represents the state of one goroutine, including the stack trace.
type Goroutine struct {
Signature // It's stack trace, internal bits, state, which call site created it, etc.
ID int // Goroutine ID.
First bool // First is the goroutine first printed, normally the one that crashed.
}
// Bucketize returns the number of similar goroutines.
func Bucketize(goroutines []Goroutine, similar Similarity) map[*Signature][]Goroutine {
out := map[*Signature][]Goroutine{}
// O(n²). Fix eventually.
for _, routine := range goroutines {
found := false
for key := range out {
// When a match is found, this effectively drops the other goroutine ID.
if key.Similar(&routine.Signature, similar) {
found = true
if !key.Equal(&routine.Signature) {
// Almost but not quite equal. There's different pointers passed
// around but the same values. Zap out the different values.
newKey := key.Merge(&routine.Signature)
out[newKey] = append(out[key], routine)
delete(out, key)
} else {
out[key] = append(out[key], routine)
}
break
}
}
if !found {
key := &Signature{}
*key = routine.Signature
out[key] = []Goroutine{routine}
}
}
return out
}
// Bucket is a stack trace signature and the list of goroutines that fits this
// signature.
type Bucket struct {
Signature
Routines []Goroutine
}
// First returns true if it contains the first goroutine, e.g. the ones that
// likely generated the panic() call, if any.
func (b *Bucket) First() bool {
for _, r := range b.Routines {
if r.First {
return true
}
}
return false
}
// Less does reverse sort.
func (b *Bucket) Less(r *Bucket) bool {
if b.First() {
return true
}
if r.First() {
return false
}
return b.Signature.Less(&r.Signature)
}
// Buckets is a list of Bucket sorted by repeation count.
type Buckets []Bucket
func (b Buckets) Len() int {
return len(b)
}
func (b Buckets) Less(i, j int) bool {
return b[i].Less(&b[j])
}
func (b Buckets) Swap(i, j int) {
b[j], b[i] = b[i], b[j]
}
// SortBuckets creates a list of Bucket from each goroutine stack trace count.
func SortBuckets(buckets map[*Signature][]Goroutine) Buckets {
out := make(Buckets, 0, len(buckets))
for signature, count := range buckets {
out = append(out, Bucket{*signature, count})
}
sort.Sort(out)
return out
}
// scanLines is similar to bufio.ScanLines except that it:
// - doesn't drop '\n'
// - doesn't strip '\r'
// - returns when the data is bufio.MaxScanTokenSize bytes
func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
return i + 1, data[0 : i+1], nil
}
if atEOF {
return len(data), data, nil
}
if len(data) >= bufio.MaxScanTokenSize {
// Returns the line even if it is not at EOF nor has a '\n', otherwise the
// scanner will return bufio.ErrTooLong which is definitely not what we
// want.
return len(data), data, nil
}
return 0, nil, nil
}
// ParseDump processes the output from runtime.Stack().
//
// It supports piping from another command and assumes there is junk before the
// actual stack trace. The junk is streamed to out.
func ParseDump(r io.Reader, out io.Writer) ([]Goroutine, error) {
goroutines := make([]Goroutine, 0, 16)
var goroutine *Goroutine
scanner := bufio.NewScanner(r)
scanner.Split(scanLines)
// TODO(maruel): Use a formal state machine. Patterns follows:
// - reRoutineHeader
// Either:
// - reUnavail
// - reFunc + reFile in a loop
// - reElided
// Optionally ends with:
// - reCreated + reFile
// Between each goroutine stack dump: an empty line
created := false
// firstLine is the first line after the reRoutineHeader header line.
firstLine := false
for scanner.Scan() {
line := scanner.Text()
if line == "\n" {
if goroutine != nil {
goroutine = nil
continue
}
} else if line[len(line)-1] == '\n' {
if goroutine == nil {
if match := reRoutineHeader.FindStringSubmatch(line); match != nil {
if id, err := strconv.Atoi(match[1]); err == nil {
// See runtime/traceback.go.
// "<state>, \d+ minutes, locked to thread"
items := strings.Split(match[2], ", ")
sleep := 0
locked := false
for i := 1; i < len(items); i++ {
if items[i] == lockedToThread {
locked = true
continue
}
// Look for duration, if any.
if match2 := reMinutes.FindStringSubmatch(items[i]); match2 != nil {
sleep, _ = strconv.Atoi(match2[1])
}
}
goroutines = append(goroutines, Goroutine{
Signature: Signature{
State: items[0],
SleepMin: sleep,
SleepMax: sleep,
Locked: locked,
},
ID: id,
First: len(goroutines) == 0,
})
goroutine = &goroutines[len(goroutines)-1]
firstLine = true
continue
}
}
} else {
if firstLine {
firstLine = false
if match := reUnavail.FindStringSubmatch(line); match != nil {
// Generate a fake stack entry.
goroutine.Stack.Calls = []Call{{SourcePath: "<unavailable>"}}
continue
}
}
if match := reFile.FindStringSubmatch(line); match != nil {
// Triggers after a reFunc or a reCreated.
num, err := strconv.Atoi(match[2])
if err != nil {
return goroutines, fmt.Errorf("failed to parse int on line: \"%s\"", line)
}
if created {
created = false
goroutine.CreatedBy.SourcePath = match[1]
goroutine.CreatedBy.Line = num
} else {
i := len(goroutine.Stack.Calls) - 1
if i < 0 {
return goroutines, errors.New("unexpected order")
}
goroutine.Stack.Calls[i].SourcePath = match[1]
goroutine.Stack.Calls[i].Line = num
}
continue
}
if match := reCreated.FindStringSubmatch(line); match != nil {
created = true
goroutine.CreatedBy.Func.Raw = match[1]
continue
}
if match := reFunc.FindStringSubmatch(line); match != nil {
args := Args{}
for _, a := range strings.Split(match[2], ", ") {
if a == "..." {
args.Elided = true
continue
}
if a == "" {
// Remaining values were dropped.
break
}
v, err := strconv.ParseUint(a, 0, 64)
if err != nil {
return goroutines, fmt.Errorf("failed to parse int on line: \"%s\"", line)
}
args.Values = append(args.Values, Arg{Value: v})
}
goroutine.Stack.Calls = append(goroutine.Stack.Calls, Call{Func: Function{match[1]}, Args: args})
continue
}
if match := reElided.FindStringSubmatch(line); match != nil {
goroutine.Stack.Elided = true
continue
}
}
}
_, _ = io.WriteString(out, line)
goroutine = nil
}
nameArguments(goroutines)
return goroutines, scanner.Err()
}
// Private stuff.
func nameArguments(goroutines []Goroutine) {
// Set a name for any pointer occuring more than once.
type object struct {
args []*Arg
inPrimary bool
id int
}
objects := map[uint64]object{}
// Enumerate all the arguments.
for i := range goroutines {
for j := range goroutines[i].Stack.Calls {
for k := range goroutines[i].Stack.Calls[j].Args.Values {
arg := goroutines[i].Stack.Calls[j].Args.Values[k]
if arg.IsPtr() {
objects[arg.Value] = object{
args: append(objects[arg.Value].args, &goroutines[i].Stack.Calls[j].Args.Values[k]),
inPrimary: objects[arg.Value].inPrimary || i == 0,
}
}
}
}
// CreatedBy.Args is never set.
}
order := uint64Slice{}
for k, obj := range objects {
if len(obj.args) > 1 && obj.inPrimary {
order = append(order, k)
}
}
sort.Sort(order)
nextID := 1
for _, k := range order {
for _, arg := range objects[k].args {
arg.Name = fmt.Sprintf("#%d", nextID)
}
nextID++
}
// Now do the rest. This is done so the output is deterministic.
order = uint64Slice{}
for k := range objects {
order = append(order, k)
}
sort.Sort(order)
for _, k := range order {
// Process the remaining pointers, they were not referenced by primary
// thread so will have higher IDs.
if objects[k].inPrimary {
continue
}
for _, arg := range objects[k].args {
arg.Name = fmt.Sprintf("#%d", nextID)
}
nextID++
}
}
type uint64Slice []uint64
func (a uint64Slice) Len() int { return len(a) }
func (a uint64Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a uint64Slice) Less(i, j int) bool { return a[i] < a[j] }

139
vendor/github.com/maruel/panicparse/stack/ui.go generated vendored Normal file
View File

@ -0,0 +1,139 @@
// Copyright 2016 Marc-Antoine Ruel. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package stack
import (
"fmt"
"strings"
)
// Palette defines the color used.
//
// An empty object Palette{} can be used to disable coloring.
type Palette struct {
EOLReset string
// Routine header.
RoutineFirst string // The first routine printed.
Routine string // Following routines.
CreatedBy string
// Call line.
Package string
SourceFile string
FunctionStdLib string
FunctionStdLibExported string
FunctionMain string
FunctionOther string
FunctionOtherExported string
Arguments string
}
// CalcLengths returns the maximum length of the source lines and package names.
func CalcLengths(buckets Buckets, fullPath bool) (int, int) {
srcLen := 0
pkgLen := 0
for _, bucket := range buckets {
for _, line := range bucket.Signature.Stack.Calls {
l := 0
if fullPath {
l = len(line.FullSourceLine())
} else {
l = len(line.SourceLine())
}
if l > srcLen {
srcLen = l
}
l = len(line.Func.PkgName())
if l > pkgLen {
pkgLen = l
}
}
}
return srcLen, pkgLen
}
// functionColor returns the color to be used for the function name based on
// the type of package the function is in.
func (p *Palette) functionColor(line *Call) string {
if line.IsStdlib() {
if line.Func.IsExported() {
return p.FunctionStdLibExported
}
return p.FunctionStdLib
} else if line.IsPkgMain() {
return p.FunctionMain
} else if line.Func.IsExported() {
return p.FunctionOtherExported
}
return p.FunctionOther
}
// routineColor returns the color for the header of the goroutines bucket.
func (p *Palette) routineColor(bucket *Bucket, multipleBuckets bool) string {
if bucket.First() && multipleBuckets {
return p.RoutineFirst
}
return p.Routine
}
// BucketHeader prints the header of a goroutine signature.
func (p *Palette) BucketHeader(bucket *Bucket, fullPath, multipleBuckets bool) string {
extra := ""
if bucket.SleepMax != 0 {
if bucket.SleepMin != bucket.SleepMax {
extra += fmt.Sprintf(" [%d~%d minutes]", bucket.SleepMin, bucket.SleepMax)
} else {
extra += fmt.Sprintf(" [%d minutes]", bucket.SleepMax)
}
}
if bucket.Locked {
extra += " [locked]"
}
created := bucket.CreatedBy.Func.PkgDotName()
if created != "" {
created += " @ "
if fullPath {
created += bucket.CreatedBy.FullSourceLine()
} else {
created += bucket.CreatedBy.SourceLine()
}
extra += p.CreatedBy + " [Created by " + created + "]"
}
return fmt.Sprintf(
"%s%d: %s%s%s\n",
p.routineColor(bucket, multipleBuckets), len(bucket.Routines),
bucket.State, extra,
p.EOLReset)
}
// callLine prints one stack line.
func (p *Palette) callLine(line *Call, srcLen, pkgLen int, fullPath bool) string {
src := ""
if fullPath {
src = line.FullSourceLine()
} else {
src = line.SourceLine()
}
return fmt.Sprintf(
" %s%-*s %s%-*s %s%s%s(%s)%s",
p.Package, pkgLen, line.Func.PkgName(),
p.SourceFile, srcLen, src,
p.functionColor(line), line.Func.Name(),
p.Arguments, line.Args,
p.EOLReset)
}
// StackLines prints one complete stack trace, without the header.
func (p *Palette) StackLines(signature *Signature, srcLen, pkgLen int, fullPath bool) string {
out := make([]string, len(signature.Stack.Calls))
for i := range signature.Stack.Calls {
out[i] = p.callLine(&signature.Stack.Calls[i], srcLen, pkgLen, fullPath)
}
if signature.Stack.Elided {
out = append(out, " (...)")
}
return strings.Join(out, "\n") + "\n"
}

21
vendor/github.com/mattn/go-runewidth/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Yasuhiro Matsumoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
vendor/github.com/mattn/go-runewidth/README.mkd generated vendored Normal file
View File

@ -0,0 +1,27 @@
go-runewidth
============
[![Build Status](https://travis-ci.org/mattn/go-runewidth.png?branch=master)](https://travis-ci.org/mattn/go-runewidth)
[![Coverage Status](https://coveralls.io/repos/mattn/go-runewidth/badge.png?branch=HEAD)](https://coveralls.io/r/mattn/go-runewidth?branch=HEAD)
[![GoDoc](https://godoc.org/github.com/mattn/go-runewidth?status.svg)](http://godoc.org/github.com/mattn/go-runewidth)
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-runewidth)](https://goreportcard.com/report/github.com/mattn/go-runewidth)
Provides functions to get fixed width of the character or string.
Usage
-----
```go
runewidth.StringWidth("つのだ☆HIRO") == 12
```
Author
------
Yasuhiro Matsumoto
License
-------
under the MIT License: http://mattn.mit-license.org/2013

1223
vendor/github.com/mattn/go-runewidth/runewidth.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

8
vendor/github.com/mattn/go-runewidth/runewidth_js.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
// +build js
package runewidth
func IsEastAsian() bool {
// TODO: Implement this for the web. Detect east asian in a compatible way, and return true.
return false
}

View File

@ -0,0 +1,77 @@
// +build !windows,!js
package runewidth
import (
"os"
"regexp"
"strings"
)
var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`)
var mblenTable = map[string]int{
"utf-8": 6,
"utf8": 6,
"jis": 8,
"eucjp": 3,
"euckr": 2,
"euccn": 2,
"sjis": 2,
"cp932": 2,
"cp51932": 2,
"cp936": 2,
"cp949": 2,
"cp950": 2,
"big5": 2,
"gbk": 2,
"gb2312": 2,
}
func isEastAsian(locale string) bool {
charset := strings.ToLower(locale)
r := reLoc.FindStringSubmatch(locale)
if len(r) == 2 {
charset = strings.ToLower(r[1])
}
if strings.HasSuffix(charset, "@cjk_narrow") {
return false
}
for pos, b := range []byte(charset) {
if b == '@' {
charset = charset[:pos]
break
}
}
max := 1
if m, ok := mblenTable[charset]; ok {
max = m
}
if max > 1 && (charset[0] != 'u' ||
strings.HasPrefix(locale, "ja") ||
strings.HasPrefix(locale, "ko") ||
strings.HasPrefix(locale, "zh")) {
return true
}
return false
}
// IsEastAsian return true if the current locale is CJK
func IsEastAsian() bool {
locale := os.Getenv("LC_CTYPE")
if locale == "" {
locale = os.Getenv("LANG")
}
// ignore C locale
if locale == "POSIX" || locale == "C" {
return false
}
if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') {
return false
}
return isEastAsian(locale)
}

View File

@ -0,0 +1,25 @@
package runewidth
import (
"syscall"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32")
procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP")
)
// IsEastAsian return true if the current locale is CJK
func IsEastAsian() bool {
r1, _, _ := procGetConsoleOutputCP.Call()
if r1 == 0 {
return false
}
switch int(r1) {
case 932, 51932, 936, 949, 950:
return true
}
return false
}

21
vendor/github.com/mitchellh/go-wordwrap/LICENSE.md generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

39
vendor/github.com/mitchellh/go-wordwrap/README.md generated vendored Normal file
View File

@ -0,0 +1,39 @@
# go-wordwrap
`go-wordwrap` (Golang package: `wordwrap`) is a package for Go that
automatically wraps words into multiple lines. The primary use case for this
is in formatting CLI output, but of course word wrapping is a generally useful
thing to do.
## Installation and Usage
Install using `go get github.com/mitchellh/go-wordwrap`.
Full documentation is available at
http://godoc.org/github.com/mitchellh/go-wordwrap
Below is an example of its usage ignoring errors:
```go
wrapped := wordwrap.WrapString("foo bar baz", 3)
fmt.Println(wrapped)
```
Would output:
```
foo
bar
baz
```
## Word Wrap Algorithm
This library doesn't use any clever algorithm for word wrapping. The wrapping
is actually very naive: whenever there is whitespace or an explicit linebreak.
The goal of this library is for word wrapping CLI output, so the input is
typically pretty well controlled human language. Because of this, the naive
approach typically works just fine.
In the future, we'd like to make the algorithm more advanced. We would do
so without breaking the API.

73
vendor/github.com/mitchellh/go-wordwrap/wordwrap.go generated vendored Normal file
View File

@ -0,0 +1,73 @@
package wordwrap
import (
"bytes"
"unicode"
)
// WrapString wraps the given string within lim width in characters.
//
// Wrapping is currently naive and only happens at white-space. A future
// version of the library will implement smarter wrapping. This means that
// pathological cases can dramatically reach past the limit, such as a very
// long word.
func WrapString(s string, lim uint) string {
// Initialize a buffer with a slightly larger size to account for breaks
init := make([]byte, 0, len(s))
buf := bytes.NewBuffer(init)
var current uint
var wordBuf, spaceBuf bytes.Buffer
for _, char := range s {
if char == '\n' {
if wordBuf.Len() == 0 {
if current+uint(spaceBuf.Len()) > lim {
current = 0
} else {
current += uint(spaceBuf.Len())
spaceBuf.WriteTo(buf)
}
spaceBuf.Reset()
} else {
current += uint(spaceBuf.Len() + wordBuf.Len())
spaceBuf.WriteTo(buf)
spaceBuf.Reset()
wordBuf.WriteTo(buf)
wordBuf.Reset()
}
buf.WriteRune(char)
current = 0
} else if unicode.IsSpace(char) {
if spaceBuf.Len() == 0 || wordBuf.Len() > 0 {
current += uint(spaceBuf.Len() + wordBuf.Len())
spaceBuf.WriteTo(buf)
spaceBuf.Reset()
wordBuf.WriteTo(buf)
wordBuf.Reset()
}
spaceBuf.WriteRune(char)
} else {
wordBuf.WriteRune(char)
if current+uint(spaceBuf.Len()+wordBuf.Len()) > lim && uint(wordBuf.Len()) < lim {
buf.WriteRune('\n')
current = 0
spaceBuf.Reset()
}
}
}
if wordBuf.Len() == 0 {
if current+uint(spaceBuf.Len()) <= lim {
spaceBuf.WriteTo(buf)
}
} else {
spaceBuf.WriteTo(buf)
wordBuf.WriteTo(buf)
}
return buf.String()
}

23
vendor/github.com/nlopes/slack/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2015, Norberto Lopes
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

76
vendor/github.com/nlopes/slack/README.md generated vendored Normal file
View File

@ -0,0 +1,76 @@
Slack API in Go [![GoDoc](https://godoc.org/github.com/nlopes/slack?status.svg)](https://godoc.org/github.com/nlopes/slack) [![Build Status](https://travis-ci.org/nlopes/slack.svg)](https://travis-ci.org/nlopes/slack)
===============
This library supports most if not all of the `api.slack.com` REST
calls, as well as the Real-Time Messaging protocol over websocket, in
a fully managed way.
Note: If you just updated from master and it broke your implementation, please check [0.0.1](https://github.com/nlopes/slack/releases/tag/v0.0.1)
## Installing
### *go get*
$ go get github.com/nlopes/slack
## Example
### Getting all groups
```golang
import (
"fmt"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
// If you set debugging, it will log all requests to the console
// Useful when encountering issues
// api.SetDebug(true)
groups, err := api.GetGroups(false)
if err != nil {
fmt.Printf("%s\n", err)
return
}
for _, group := range groups {
fmt.Printf("ID: %s, Name: %s\n", group.ID, group.Name)
}
}
```
### Getting User Information
```golang
import (
"fmt"
"github.com/nlopes/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
user, err := api.GetUserInfo("U023BECGF")
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("ID: %s, Fullname: %s, Email: %s\n", user.ID, user.Profile.RealName, user.Profile.Email)
}
```
## Minimal RTM usage:
See https://github.com/nlopes/slack/blob/master/examples/websocket/websocket.go
## Contributing
You are more than welcome to contribute to this project. Fork and
make a Pull Request, or create an Issue if you see any problem.
## License
BSD 2 Clause license

3
vendor/github.com/nlopes/slack/TODO.txt generated vendored Normal file
View File

@ -0,0 +1,3 @@
- Add more tests!!!
- Add support to have markdown hints
- See section Message Formatting at https://api.slack.com/docs/formatting

190
vendor/github.com/nlopes/slack/admin.go generated vendored Normal file
View File

@ -0,0 +1,190 @@
package slack
import (
"errors"
"fmt"
"net/url"
)
type adminResponse struct {
OK bool `json:"ok"`
Error string `json:"error"`
}
func adminRequest(method string, teamName string, values url.Values, debug bool) (*adminResponse, error) {
adminResponse := &adminResponse{}
err := parseAdminResponse(method, teamName, values, adminResponse, debug)
if err != nil {
return nil, err
}
if !adminResponse.OK {
return nil, errors.New(adminResponse.Error)
}
return adminResponse, nil
}
// DisableUser disabled a user account, given a user ID
func (api *Client) DisableUser(teamName string, uid string) error {
values := url.Values{
"user": {uid},
"token": {api.config.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest("setInactive", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to disable user with id '%s': %s", uid, err)
}
return nil
}
// InviteGuest invites a user to Slack as a single-channel guest
func (api *Client) InviteGuest(
teamName string,
channel string,
firstName string,
lastName string,
emailAddress string,
) error {
values := url.Values{
"email": {emailAddress},
"channels": {channel},
"first_name": {firstName},
"last_name": {lastName},
"ultra_restricted": {"1"},
"token": {api.config.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest("invite", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to invite single-channel guest: %s", err)
}
return nil
}
// InviteRestricted invites a user to Slack as a restricted account
func (api *Client) InviteRestricted(
teamName string,
channel string,
firstName string,
lastName string,
emailAddress string,
) error {
values := url.Values{
"email": {emailAddress},
"channels": {channel},
"first_name": {firstName},
"last_name": {lastName},
"restricted": {"1"},
"token": {api.config.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest("invite", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to restricted account: %s", err)
}
return nil
}
// InviteToTeam invites a user to a Slack team
func (api *Client) InviteToTeam(
teamName string,
firstName string,
lastName string,
emailAddress string,
) error {
values := url.Values{
"email": {emailAddress},
"first_name": {firstName},
"last_name": {lastName},
"token": {api.config.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest("invite", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to invite to team: %s", err)
}
return nil
}
// SetRegular enables the specified user
func (api *Client) SetRegular(teamName string, user string) error {
values := url.Values{
"user": {user},
"token": {api.config.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest("setRegular", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to change the user (%s) to a regular user: %s", user, err)
}
return nil
}
// SendSSOBindingEmail sends an SSO binding email to the specified user
func (api *Client) SendSSOBindingEmail(teamName string, user string) error {
values := url.Values{
"user": {user},
"token": {api.config.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest("sendSSOBind", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to send SSO binding email for user (%s): %s", user, err)
}
return nil
}
// SetUltraRestricted converts a user into a single-channel guest
func (api *Client) SetUltraRestricted(teamName, uid, channel string) error {
values := url.Values{
"user": {uid},
"channel": {channel},
"token": {api.config.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest("setUltraRestricted", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to ultra-restrict account: %s", err)
}
return nil
}
// SetRestricted converts a user into a restricted account
func (api *Client) SetRestricted(teamName, uid string) error {
values := url.Values{
"user": {uid},
"token": {api.config.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest("setRestricted", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to restrict account: %s", err)
}
return nil
}

78
vendor/github.com/nlopes/slack/attachments.go generated vendored Normal file
View File

@ -0,0 +1,78 @@
package slack
import "encoding/json"
// AttachmentField contains information for an attachment field
// An Attachment can contain multiple of these
type AttachmentField struct {
Title string `json:"title"`
Value string `json:"value"`
Short bool `json:"short"`
}
// AttachmentAction is a button to be included in the attachment. Required when
// using message buttons and otherwise not useful. A maximum of 5 actions may be
// provided per attachment.
type AttachmentAction struct {
Name string `json:"name"` // Required.
Text string `json:"text"` // Required.
Style string `json:"style,omitempty"` // Optional. Allowed values: "default", "primary", "danger"
Type string `json:"type"` // Required. Must be set to "button"
Value string `json:"value,omitempty"` // Optional.
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
}
// AttachmentActionCallback is sent from Slack when a user clicks a button in an interactive message (aka AttachmentAction)
type AttachmentActionCallback struct {
Actions []AttachmentAction `json:"actions"`
CallbackID string `json:"callback_id"`
Team Team `json:"team"`
Channel Channel `json:"channel"`
User User `json:"user"`
OriginalMessage Message `json:"original_message"`
ActionTs string `json:"action_ts"`
MessageTs string `json:"message_ts"`
AttachmentID string `json:"attachment_id"`
Token string `json:"token"`
ResponseURL string `json:"response_url"`
}
// ConfirmationField are used to ask users to confirm actions
type ConfirmationField struct {
Title string `json:"title,omitempty"` // Optional.
Text string `json:"text"` // Required.
OkText string `json:"ok_text,omitempty"` // Optional. Defaults to "Okay"
DismissText string `json:"dismiss_text,omitempty"` // Optional. Defaults to "Cancel"
}
// Attachment contains all the information for an attachment
type Attachment struct {
Color string `json:"color,omitempty"`
Fallback string `json:"fallback"`
CallbackID string `json:"callback_id,omitempty"`
AuthorName string `json:"author_name,omitempty"`
AuthorSubname string `json:"author_subname,omitempty"`
AuthorLink string `json:"author_link,omitempty"`
AuthorIcon string `json:"author_icon,omitempty"`
Title string `json:"title,omitempty"`
TitleLink string `json:"title_link,omitempty"`
Pretext string `json:"pretext,omitempty"`
Text string `json:"text"`
ImageURL string `json:"image_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
Fields []AttachmentField `json:"fields,omitempty"`
Actions []AttachmentAction `json:"actions,omitempty"`
MarkdownIn []string `json:"mrkdwn_in,omitempty"`
Footer string `json:"footer,omitempty"`
FooterIcon string `json:"footer_icon,omitempty"`
Ts json.Number `json:"ts,omitempty"`
}

57
vendor/github.com/nlopes/slack/backoff.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
package slack
import (
"math"
"math/rand"
"time"
)
// This one was ripped from https://github.com/jpillora/backoff/blob/master/backoff.go
// Backoff is a time.Duration counter. It starts at Min. After every
// call to Duration() it is multiplied by Factor. It is capped at
// Max. It returns to Min on every call to Reset(). Used in
// conjunction with the time package.
type backoff struct {
attempts int
//Factor is the multiplying factor for each increment step
Factor float64
//Jitter eases contention by randomizing backoff steps
Jitter bool
//Min and Max are the minimum and maximum values of the counter
Min, Max time.Duration
}
// Returns the current value of the counter and then multiplies it
// Factor
func (b *backoff) Duration() time.Duration {
//Zero-values are nonsensical, so we use
//them to apply defaults
if b.Min == 0 {
b.Min = 100 * time.Millisecond
}
if b.Max == 0 {
b.Max = 10 * time.Second
}
if b.Factor == 0 {
b.Factor = 2
}
//calculate this duration
dur := float64(b.Min) * math.Pow(b.Factor, float64(b.attempts))
if b.Jitter == true {
dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min)
}
//cap!
if dur > float64(b.Max) {
return b.Max
}
//bump attempts count
b.attempts++
//return as a time.Duration
return time.Duration(dur)
}
//Resets the current value of the counter back to Min
func (b *backoff) Reset() {
b.attempts = 0
}

44
vendor/github.com/nlopes/slack/bots.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
package slack
import (
"errors"
"net/url"
)
// Bot contains information about a bot
type Bot struct {
ID string `json:"id"`
Name string `json:"name"`
Deleted bool `json:"deleted"`
Icons Icons `json:"icons"`
}
type botResponseFull struct {
Bot `json:"bot,omitempty"` // GetBotInfo
SlackResponse
}
func botRequest(path string, values url.Values, debug bool) (*botResponseFull, error) {
response := &botResponseFull{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
// GetBotInfo will retrieve the complete bot information
func (api *Client) GetBotInfo(bot string) (*Bot, error) {
values := url.Values{
"token": {api.config.token},
"bot": {bot},
}
response, err := botRequest("bots.info", values, api.debug)
if err != nil {
return nil, err
}
return &response.Bot, nil
}

261
vendor/github.com/nlopes/slack/channels.go generated vendored Normal file
View File

@ -0,0 +1,261 @@
package slack
import (
"errors"
"net/url"
"strconv"
)
type channelResponseFull struct {
Channel Channel `json:"channel"`
Channels []Channel `json:"channels"`
Purpose string `json:"purpose"`
Topic string `json:"topic"`
NotInChannel bool `json:"not_in_channel"`
History
SlackResponse
}
// Channel contains information about the channel
type Channel struct {
groupConversation
IsChannel bool `json:"is_channel"`
IsGeneral bool `json:"is_general"`
IsMember bool `json:"is_member"`
}
func channelRequest(path string, values url.Values, debug bool) (*channelResponseFull, error) {
response := &channelResponseFull{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
// ArchiveChannel archives the given channel
func (api *Client) ArchiveChannel(channel string) error {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
}
_, err := channelRequest("channels.archive", values, api.debug)
if err != nil {
return err
}
return nil
}
// UnarchiveChannel unarchives the given channel
func (api *Client) UnarchiveChannel(channel string) error {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
}
_, err := channelRequest("channels.unarchive", values, api.debug)
if err != nil {
return err
}
return nil
}
// CreateChannel creates a channel with the given name and returns a *Channel
func (api *Client) CreateChannel(channel string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"name": {channel},
}
response, err := channelRequest("channels.create", values, api.debug)
if err != nil {
return nil, err
}
return &response.Channel, nil
}
// GetChannelHistory retrieves the channel history
func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
}
if params.Latest != DEFAULT_HISTORY_LATEST {
values.Add("latest", params.Latest)
}
if params.Oldest != DEFAULT_HISTORY_OLDEST {
values.Add("oldest", params.Oldest)
}
if params.Count != DEFAULT_HISTORY_COUNT {
values.Add("count", strconv.Itoa(params.Count))
}
if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
if params.Inclusive {
values.Add("inclusive", "1")
} else {
values.Add("inclusive", "0")
}
}
if params.Unreads != DEFAULT_HISTORY_UNREADS {
if params.Unreads {
values.Add("unreads", "1")
} else {
values.Add("unreads", "0")
}
}
response, err := channelRequest("channels.history", values, api.debug)
if err != nil {
return nil, err
}
return &response.History, nil
}
// GetChannelInfo retrieves the given channel
func (api *Client) GetChannelInfo(channel string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
}
response, err := channelRequest("channels.info", values, api.debug)
if err != nil {
return nil, err
}
return &response.Channel, nil
}
// InviteUserToChannel invites a user to a given channel and returns a *Channel
func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"user": {user},
}
response, err := channelRequest("channels.invite", values, api.debug)
if err != nil {
return nil, err
}
return &response.Channel, nil
}
// JoinChannel joins the currently authenticated user to a channel
func (api *Client) JoinChannel(channel string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"name": {channel},
}
response, err := channelRequest("channels.join", values, api.debug)
if err != nil {
return nil, err
}
return &response.Channel, nil
}
// LeaveChannel makes the authenticated user leave the given channel
func (api *Client) LeaveChannel(channel string) (bool, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
}
response, err := channelRequest("channels.leave", values, api.debug)
if err != nil {
return false, err
}
if response.NotInChannel {
return response.NotInChannel, nil
}
return false, nil
}
// KickUserFromChannel kicks a user from a given channel
func (api *Client) KickUserFromChannel(channel, user string) error {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"user": {user},
}
_, err := channelRequest("channels.kick", values, api.debug)
if err != nil {
return err
}
return nil
}
// GetChannels retrieves all the channels
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
values := url.Values{
"token": {api.config.token},
}
if excludeArchived {
values.Add("exclude_archived", "1")
}
response, err := channelRequest("channels.list", values, api.debug)
if err != nil {
return nil, err
}
return response.Channels, nil
}
// SetChannelReadMark sets the read mark of a given channel to a specific point
// Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a
// timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls
// (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A
// timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
func (api *Client) SetChannelReadMark(channel, ts string) error {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"ts": {ts},
}
_, err := channelRequest("channels.mark", values, api.debug)
if err != nil {
return err
}
return nil
}
// RenameChannel renames a given channel
func (api *Client) RenameChannel(channel, name string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"name": {name},
}
// XXX: the created entry in this call returns a string instead of a number
// so I may have to do some workaround to solve it.
response, err := channelRequest("channels.rename", values, api.debug)
if err != nil {
return nil, err
}
return &response.Channel, nil
}
// SetChannelPurpose sets the channel purpose and returns the purpose that was
// successfully set
func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"purpose": {purpose},
}
response, err := channelRequest("channels.setPurpose", values, api.debug)
if err != nil {
return "", err
}
return response.Purpose, nil
}
// SetChannelTopic sets the channel topic and returns the topic that was successfully set
func (api *Client) SetChannelTopic(channel, topic string) (string, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"topic": {topic},
}
response, err := channelRequest("channels.setTopic", values, api.debug)
if err != nil {
return "", err
}
return response.Topic, nil
}

171
vendor/github.com/nlopes/slack/chat.go generated vendored Normal file
View File

@ -0,0 +1,171 @@
package slack
import (
"encoding/json"
"errors"
"net/url"
"strings"
)
const (
DEFAULT_MESSAGE_USERNAME = ""
DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
DEFAULT_MESSAGE_ASUSER = false
DEFAULT_MESSAGE_PARSE = ""
DEFAULT_MESSAGE_LINK_NAMES = 0
DEFAULT_MESSAGE_UNFURL_LINKS = false
DEFAULT_MESSAGE_UNFURL_MEDIA = true
DEFAULT_MESSAGE_ICON_URL = ""
DEFAULT_MESSAGE_ICON_EMOJI = ""
DEFAULT_MESSAGE_MARKDOWN = true
DEFAULT_MESSAGE_ESCAPE_TEXT = true
)
type chatResponseFull struct {
Channel string `json:"channel"`
Timestamp string `json:"ts"`
Text string `json:"text"`
SlackResponse
}
// PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request
type PostMessageParameters struct {
Text string `json:"text"`
Username string `json:"user_name"`
AsUser bool `json:"as_user"`
Parse string `json:"parse"`
ThreadTimestamp string `json:"thread_ts"`
LinkNames int `json:"link_names"`
Attachments []Attachment `json:"attachments"`
UnfurlLinks bool `json:"unfurl_links"`
UnfurlMedia bool `json:"unfurl_media"`
IconURL string `json:"icon_url"`
IconEmoji string `json:"icon_emoji"`
Markdown bool `json:"mrkdwn,omitempty"`
EscapeText bool `json:"escape_text"`
}
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
func NewPostMessageParameters() PostMessageParameters {
return PostMessageParameters{
Username: DEFAULT_MESSAGE_USERNAME,
AsUser: DEFAULT_MESSAGE_ASUSER,
Parse: DEFAULT_MESSAGE_PARSE,
LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
Attachments: nil,
UnfurlLinks: DEFAULT_MESSAGE_UNFURL_LINKS,
UnfurlMedia: DEFAULT_MESSAGE_UNFURL_MEDIA,
IconURL: DEFAULT_MESSAGE_ICON_URL,
IconEmoji: DEFAULT_MESSAGE_ICON_EMOJI,
Markdown: DEFAULT_MESSAGE_MARKDOWN,
EscapeText: DEFAULT_MESSAGE_ESCAPE_TEXT,
}
}
func chatRequest(path string, values url.Values, debug bool) (*chatResponseFull, error) {
response := &chatResponseFull{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
// DeleteMessage deletes a message in a channel
func (api *Client) DeleteMessage(channel, messageTimestamp string) (string, string, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"ts": {messageTimestamp},
}
response, err := chatRequest("chat.delete", values, api.debug)
if err != nil {
return "", "", err
}
return response.Channel, response.Timestamp, nil
}
func escapeMessage(message string) string {
replacer := strings.NewReplacer("&", "&amp;", "<", "&lt;", ">", "&gt;")
return replacer.Replace(message)
}
// PostMessage sends a message to a channel.
// Message is escaped by default according to https://api.slack.com/docs/formatting
// Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
func (api *Client) PostMessage(channel, text string, params PostMessageParameters) (string, string, error) {
if params.EscapeText {
text = escapeMessage(text)
}
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"text": {text},
}
if params.Username != DEFAULT_MESSAGE_USERNAME {
values.Set("username", string(params.Username))
}
if params.AsUser != DEFAULT_MESSAGE_ASUSER {
values.Set("as_user", "true")
}
if params.Parse != DEFAULT_MESSAGE_PARSE {
values.Set("parse", string(params.Parse))
}
if params.LinkNames != DEFAULT_MESSAGE_LINK_NAMES {
values.Set("link_names", "1")
}
if params.Attachments != nil {
attachments, err := json.Marshal(params.Attachments)
if err != nil {
return "", "", err
}
values.Set("attachments", string(attachments))
}
if params.UnfurlLinks != DEFAULT_MESSAGE_UNFURL_LINKS {
values.Set("unfurl_links", "true")
}
// I want to send a message with explicit `as_user` `true` and `unfurl_links` `false` in request.
// Because setting `as_user` to `true` will change the default value for `unfurl_links` to `true` on Slack API side.
if params.AsUser != DEFAULT_MESSAGE_ASUSER && params.UnfurlLinks == DEFAULT_MESSAGE_UNFURL_LINKS {
values.Set("unfurl_links", "false")
}
if params.UnfurlMedia != DEFAULT_MESSAGE_UNFURL_MEDIA {
values.Set("unfurl_media", "false")
}
if params.IconURL != DEFAULT_MESSAGE_ICON_URL {
values.Set("icon_url", params.IconURL)
}
if params.IconEmoji != DEFAULT_MESSAGE_ICON_EMOJI {
values.Set("icon_emoji", params.IconEmoji)
}
if params.Markdown != DEFAULT_MESSAGE_MARKDOWN {
values.Set("mrkdwn", "false")
}
if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
values.Set("thread_ts", params.ThreadTimestamp)
}
response, err := chatRequest("chat.postMessage", values, api.debug)
if err != nil {
return "", "", err
}
return response.Channel, response.Timestamp, nil
}
// UpdateMessage updates a message in a channel
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"text": {escapeMessage(text)},
"ts": {timestamp},
}
response, err := chatRequest("chat.update", values, api.debug)
if err != nil {
return "", "", "", err
}
return response.Channel, response.Timestamp, response.Text, nil
}

10
vendor/github.com/nlopes/slack/comment.go generated vendored Normal file
View File

@ -0,0 +1,10 @@
package slack
// Comment contains all the information relative to a comment
type Comment struct {
ID string `json:"id,omitempty"`
Created JSONTime `json:"created,omitempty"`
Timestamp JSONTime `json:"timestamp,omitempty"`
User string `json:"user,omitempty"`
Comment string `json:"comment,omitempty"`
}

37
vendor/github.com/nlopes/slack/conversation.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
package slack
// Conversation is the foundation for IM and BaseGroupConversation
type conversation struct {
ID string `json:"id"`
Created JSONTime `json:"created"`
IsOpen bool `json:"is_open"`
LastRead string `json:"last_read,omitempty"`
Latest *Message `json:"latest,omitempty"`
UnreadCount int `json:"unread_count,omitempty"`
UnreadCountDisplay int `json:"unread_count_display,omitempty"`
}
// GroupConversation is the foundation for Group and Channel
type groupConversation struct {
conversation
Name string `json:"name"`
Creator string `json:"creator"`
IsArchived bool `json:"is_archived"`
Members []string `json:"members"`
Topic Topic `json:"topic"`
Purpose Purpose `json:"purpose"`
}
// Topic contains information about the topic
type Topic struct {
Value string `json:"value"`
Creator string `json:"creator"`
LastSet JSONTime `json:"last_set"`
}
// Purpose contains information about the purpose
type Purpose struct {
Value string `json:"value"`
Creator string `json:"creator"`
LastSet JSONTime `json:"last_set"`
}

123
vendor/github.com/nlopes/slack/dnd.go generated vendored Normal file
View File

@ -0,0 +1,123 @@
package slack
import (
"errors"
"net/url"
"strconv"
"strings"
)
type SnoozeDebug struct {
SnoozeEndDate string `json:"snooze_end_date"`
}
type SnoozeInfo struct {
SnoozeEnabled bool `json:"snooze_enabled,omitempty"`
SnoozeEndTime int `json:"snooze_endtime,omitempty"`
SnoozeRemaining int `json:"snooze_remaining,omitempty"`
SnoozeDebug SnoozeDebug `json:"snooze_debug,omitempty"`
}
type DNDStatus struct {
Enabled bool `json:"dnd_enabled"`
NextStartTimestamp int `json:"next_dnd_start_ts"`
NextEndTimestamp int `json:"next_dnd_end_ts"`
SnoozeInfo
}
type dndResponseFull struct {
DNDStatus
SlackResponse
}
type dndTeamInfoResponse struct {
Users map[string]DNDStatus `json:"users"`
SlackResponse
}
func dndRequest(path string, values url.Values, debug bool) (*dndResponseFull, error) {
response := &dndResponseFull{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
// EndDND ends the user's scheduled Do Not Disturb session
func (api *Client) EndDND() error {
values := url.Values{
"token": {api.config.token},
}
response := &SlackResponse{}
if err := post("dnd.endDnd", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// EndSnooze ends the current user's snooze mode
func (api *Client) EndSnooze() (*DNDStatus, error) {
values := url.Values{
"token": {api.config.token},
}
response, err := dndRequest("dnd.endSnooze", values, api.debug)
if err != nil {
return nil, err
}
return &response.DNDStatus, nil
}
// GetDNDInfo provides information about a user's current Do Not Disturb settings.
func (api *Client) GetDNDInfo(user *string) (*DNDStatus, error) {
values := url.Values{
"token": {api.config.token},
}
if user != nil {
values.Set("user", *user)
}
response, err := dndRequest("dnd.info", values, api.debug)
if err != nil {
return nil, err
}
return &response.DNDStatus, nil
}
// GetDNDTeamInfo provides information about a user's current Do Not Disturb settings.
func (api *Client) GetDNDTeamInfo(users []string) (map[string]DNDStatus, error) {
values := url.Values{
"token": {api.config.token},
"users": {strings.Join(users, ",")},
}
response := &dndTeamInfoResponse{}
if err := post("dnd.teamInfo", values, response, api.debug); err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response.Users, nil
}
// SetSnooze adjusts the snooze duration for a user's Do Not Disturb
// settings. If a snooze session is not already active for the user, invoking
// this method will begin one for the specified duration.
func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) {
values := url.Values{
"token": {api.config.token},
"num_minutes": {strconv.Itoa(minutes)},
}
response, err := dndRequest("dnd.setSnooze", values, api.debug)
if err != nil {
return nil, err
}
return &response.DNDStatus, nil
}

27
vendor/github.com/nlopes/slack/emoji.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
package slack
import (
"errors"
"net/url"
)
type emojiResponseFull struct {
Emoji map[string]string `json:"emoji"`
SlackResponse
}
// GetEmoji retrieves all the emojis
func (api *Client) GetEmoji() (map[string]string, error) {
values := url.Values{
"token": {api.config.token},
}
response := &emojiResponseFull{}
err := post("emoji.list", values, response, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response.Emoji, nil
}

274
vendor/github.com/nlopes/slack/files.go generated vendored Normal file
View File

@ -0,0 +1,274 @@
package slack
import (
"errors"
"net/url"
"strconv"
"strings"
)
const (
// Add here the defaults in the siten
DEFAULT_FILES_USER = ""
DEFAULT_FILES_CHANNEL = ""
DEFAULT_FILES_TS_FROM = 0
DEFAULT_FILES_TS_TO = -1
DEFAULT_FILES_TYPES = "all"
DEFAULT_FILES_COUNT = 100
DEFAULT_FILES_PAGE = 1
)
// File contains all the information for a file
type File struct {
ID string `json:"id"`
Created JSONTime `json:"created"`
Timestamp JSONTime `json:"timestamp"`
Name string `json:"name"`
Title string `json:"title"`
Mimetype string `json:"mimetype"`
ImageExifRotation int `json:"image_exif_rotation"`
Filetype string `json:"filetype"`
PrettyType string `json:"pretty_type"`
User string `json:"user"`
Mode string `json:"mode"`
Editable bool `json:"editable"`
IsExternal bool `json:"is_external"`
ExternalType string `json:"external_type"`
Size int `json:"size"`
URL string `json:"url"` // Deprecated - never set
URLDownload string `json:"url_download"` // Deprecated - never set
URLPrivate string `json:"url_private"`
URLPrivateDownload string `json:"url_private_download"`
OriginalH int `json:"original_h"`
OriginalW int `json:"original_w"`
Thumb64 string `json:"thumb_64"`
Thumb80 string `json:"thumb_80"`
Thumb160 string `json:"thumb_160"`
Thumb360 string `json:"thumb_360"`
Thumb360Gif string `json:"thumb_360_gif"`
Thumb360W int `json:"thumb_360_w"`
Thumb360H int `json:"thumb_360_h"`
Thumb480 string `json:"thumb_480"`
Thumb480W int `json:"thumb_480_w"`
Thumb480H int `json:"thumb_480_h"`
Thumb720 string `json:"thumb_720"`
Thumb720W int `json:"thumb_720_w"`
Thumb720H int `json:"thumb_720_h"`
Thumb960 string `json:"thumb_960"`
Thumb960W int `json:"thumb_960_w"`
Thumb960H int `json:"thumb_960_h"`
Thumb1024 string `json:"thumb_1024"`
Thumb1024W int `json:"thumb_1024_w"`
Thumb1024H int `json:"thumb_1024_h"`
Permalink string `json:"permalink"`
PermalinkPublic string `json:"permalink_public"`
EditLink string `json:"edit_link"`
Preview string `json:"preview"`
PreviewHighlight string `json:"preview_highlight"`
Lines int `json:"lines"`
LinesMore int `json:"lines_more"`
IsPublic bool `json:"is_public"`
PublicURLShared bool `json:"public_url_shared"`
Channels []string `json:"channels"`
Groups []string `json:"groups"`
IMs []string `json:"ims"`
InitialComment Comment `json:"initial_comment"`
CommentsCount int `json:"comments_count"`
NumStars int `json:"num_stars"`
IsStarred bool `json:"is_starred"`
}
// FileUploadParameters contains all the parameters necessary (including the optional ones) for an UploadFile() request
type FileUploadParameters struct {
File string
Content string
Filetype string
Filename string
Title string
InitialComment string
Channels []string
}
// GetFilesParameters contains all the parameters necessary (including the optional ones) for a GetFiles() request
type GetFilesParameters struct {
User string
Channel string
TimestampFrom JSONTime
TimestampTo JSONTime
Types string
Count int
Page int
}
type fileResponseFull struct {
File `json:"file"`
Paging `json:"paging"`
Comments []Comment `json:"comments"`
Files []File `json:"files"`
SlackResponse
}
// NewGetFilesParameters provides an instance of GetFilesParameters with all the sane default values set
func NewGetFilesParameters() GetFilesParameters {
return GetFilesParameters{
User: DEFAULT_FILES_USER,
Channel: DEFAULT_FILES_CHANNEL,
TimestampFrom: DEFAULT_FILES_TS_FROM,
TimestampTo: DEFAULT_FILES_TS_TO,
Types: DEFAULT_FILES_TYPES,
Count: DEFAULT_FILES_COUNT,
Page: DEFAULT_FILES_PAGE,
}
}
func fileRequest(path string, values url.Values, debug bool) (*fileResponseFull, error) {
response := &fileResponseFull{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
// GetFileInfo retrieves a file and related comments
func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) {
values := url.Values{
"token": {api.config.token},
"file": {fileID},
"count": {strconv.Itoa(count)},
"page": {strconv.Itoa(page)},
}
response, err := fileRequest("files.info", values, api.debug)
if err != nil {
return nil, nil, nil, err
}
return &response.File, response.Comments, &response.Paging, nil
}
// GetFiles retrieves all files according to the parameters given
func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) {
values := url.Values{
"token": {api.config.token},
}
if params.User != DEFAULT_FILES_USER {
values.Add("user", params.User)
}
if params.Channel != DEFAULT_FILES_CHANNEL {
values.Add("channel", params.Channel)
}
// XXX: this is broken. fix it with a proper unix timestamp
if params.TimestampFrom != DEFAULT_FILES_TS_FROM {
values.Add("ts_from", params.TimestampFrom.String())
}
if params.TimestampTo != DEFAULT_FILES_TS_TO {
values.Add("ts_to", params.TimestampTo.String())
}
if params.Types != DEFAULT_FILES_TYPES {
values.Add("types", params.Types)
}
if params.Count != DEFAULT_FILES_COUNT {
values.Add("count", strconv.Itoa(params.Count))
}
if params.Page != DEFAULT_FILES_PAGE {
values.Add("page", strconv.Itoa(params.Page))
}
response, err := fileRequest("files.list", values, api.debug)
if err != nil {
return nil, nil, err
}
return response.Files, &response.Paging, nil
}
// UploadFile uploads a file
func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) {
// Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More
// investigation needed, but for now this will do.
_, err = api.AuthTest()
if err != nil {
return nil, err
}
response := &fileResponseFull{}
values := url.Values{
"token": {api.config.token},
}
if params.Filetype != "" {
values.Add("filetype", params.Filetype)
}
if params.Filename != "" {
values.Add("filename", params.Filename)
}
if params.Title != "" {
values.Add("title", params.Title)
}
if params.InitialComment != "" {
values.Add("initial_comment", params.InitialComment)
}
if len(params.Channels) != 0 {
values.Add("channels", strings.Join(params.Channels, ","))
}
if params.Content != "" {
values.Add("content", params.Content)
err = post("files.upload", values, response, api.debug)
} else if params.File != "" {
err = postWithMultipartResponse("files.upload", params.File, values, response, api.debug)
}
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return &response.File, nil
}
// DeleteFile deletes a file
func (api *Client) DeleteFile(fileID string) error {
values := url.Values{
"token": {api.config.token},
"file": {fileID},
}
_, err := fileRequest("files.delete", values, api.debug)
if err != nil {
return err
}
return nil
}
// RevokeFilePublicURL disables public/external sharing for a file
func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) {
values := url.Values{
"token": {api.config.token},
"file": {fileID},
}
response, err := fileRequest("files.revokePublicURL", values, api.debug)
if err != nil {
return nil, err
}
return &response.File, nil
}
// ShareFilePublicURL enabled public/external sharing for a file
func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging, error) {
values := url.Values{
"token": {api.config.token},
"file": {fileID},
}
response, err := fileRequest("files.sharedPublicURL", values, api.debug)
if err != nil {
return nil, nil, nil, err
}
return &response.File, response.Comments, &response.Paging, nil
}

293
vendor/github.com/nlopes/slack/groups.go generated vendored Normal file
View File

@ -0,0 +1,293 @@
package slack
import (
"errors"
"net/url"
"strconv"
)
// Group contains all the information for a group
type Group struct {
groupConversation
IsGroup bool `json:"is_group"`
}
type groupResponseFull struct {
Group Group `json:"group"`
Groups []Group `json:"groups"`
Purpose string `json:"purpose"`
Topic string `json:"topic"`
NotInGroup bool `json:"not_in_group"`
NoOp bool `json:"no_op"`
AlreadyClosed bool `json:"already_closed"`
AlreadyOpen bool `json:"already_open"`
AlreadyInGroup bool `json:"already_in_group"`
Channel Channel `json:"channel"`
History
SlackResponse
}
func groupRequest(path string, values url.Values, debug bool) (*groupResponseFull, error) {
response := &groupResponseFull{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
// ArchiveGroup archives a private group
func (api *Client) ArchiveGroup(group string) error {
values := url.Values{
"token": {api.config.token},
"channel": {group},
}
_, err := groupRequest("groups.archive", values, api.debug)
if err != nil {
return err
}
return nil
}
// UnarchiveGroup unarchives a private group
func (api *Client) UnarchiveGroup(group string) error {
values := url.Values{
"token": {api.config.token},
"channel": {group},
}
_, err := groupRequest("groups.unarchive", values, api.debug)
if err != nil {
return err
}
return nil
}
// CreateGroup creates a private group
func (api *Client) CreateGroup(group string) (*Group, error) {
values := url.Values{
"token": {api.config.token},
"name": {group},
}
response, err := groupRequest("groups.create", values, api.debug)
if err != nil {
return nil, err
}
return &response.Group, nil
}
// CreateChildGroup creates a new private group archiving the old one
// This method takes an existing private group and performs the following steps:
// 1. Renames the existing group (from "example" to "example-archived").
// 2. Archives the existing group.
// 3. Creates a new group with the name of the existing group.
// 4. Adds all members of the existing group to the new group.
func (api *Client) CreateChildGroup(group string) (*Group, error) {
values := url.Values{
"token": {api.config.token},
"channel": {group},
}
response, err := groupRequest("groups.createChild", values, api.debug)
if err != nil {
return nil, err
}
return &response.Group, nil
}
// CloseGroup closes a private group
func (api *Client) CloseGroup(group string) (bool, bool, error) {
values := url.Values{
"token": {api.config.token},
"channel": {group},
}
response, err := imRequest("groups.close", values, api.debug)
if err != nil {
return false, false, err
}
return response.NoOp, response.AlreadyClosed, nil
}
// GetGroupHistory fetches all the history for a private group
func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*History, error) {
values := url.Values{
"token": {api.config.token},
"channel": {group},
}
if params.Latest != DEFAULT_HISTORY_LATEST {
values.Add("latest", params.Latest)
}
if params.Oldest != DEFAULT_HISTORY_OLDEST {
values.Add("oldest", params.Oldest)
}
if params.Count != DEFAULT_HISTORY_COUNT {
values.Add("count", strconv.Itoa(params.Count))
}
if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
if params.Inclusive {
values.Add("inclusive", "1")
} else {
values.Add("inclusive", "0")
}
}
if params.Unreads != DEFAULT_HISTORY_UNREADS {
if params.Unreads {
values.Add("unreads", "1")
} else {
values.Add("unreads", "0")
}
}
response, err := groupRequest("groups.history", values, api.debug)
if err != nil {
return nil, err
}
return &response.History, nil
}
// InviteUserToGroup invites a specific user to a private group
func (api *Client) InviteUserToGroup(group, user string) (*Group, bool, error) {
values := url.Values{
"token": {api.config.token},
"channel": {group},
"user": {user},
}
response, err := groupRequest("groups.invite", values, api.debug)
if err != nil {
return nil, false, err
}
return &response.Group, response.AlreadyInGroup, nil
}
// LeaveGroup makes authenticated user leave the group
func (api *Client) LeaveGroup(group string) error {
values := url.Values{
"token": {api.config.token},
"channel": {group},
}
_, err := groupRequest("groups.leave", values, api.debug)
if err != nil {
return err
}
return nil
}
// KickUserFromGroup kicks a user from a group
func (api *Client) KickUserFromGroup(group, user string) error {
values := url.Values{
"token": {api.config.token},
"channel": {group},
"user": {user},
}
_, err := groupRequest("groups.kick", values, api.debug)
if err != nil {
return err
}
return nil
}
// GetGroups retrieves all groups
func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) {
values := url.Values{
"token": {api.config.token},
}
if excludeArchived {
values.Add("exclude_archived", "1")
}
response, err := groupRequest("groups.list", values, api.debug)
if err != nil {
return nil, err
}
return response.Groups, nil
}
// GetGroupInfo retrieves the given group
func (api *Client) GetGroupInfo(group string) (*Group, error) {
values := url.Values{
"token": {api.config.token},
"channel": {group},
}
response, err := groupRequest("groups.info", values, api.debug)
if err != nil {
return nil, err
}
return &response.Group, nil
}
// SetGroupReadMark sets the read mark on a private group
// Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a
// timer before making the call. In this way, any further updates needed during the timeout will not generate extra
// calls (just one per channel). This is useful for when reading scroll-back history, or following a busy live
// channel. A timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
func (api *Client) SetGroupReadMark(group, ts string) error {
values := url.Values{
"token": {api.config.token},
"channel": {group},
"ts": {ts},
}
_, err := groupRequest("groups.mark", values, api.debug)
if err != nil {
return err
}
return nil
}
// OpenGroup opens a private group
func (api *Client) OpenGroup(group string) (bool, bool, error) {
values := url.Values{
"token": {api.config.token},
"channel": {group},
}
response, err := groupRequest("groups.open", values, api.debug)
if err != nil {
return false, false, err
}
return response.NoOp, response.AlreadyOpen, nil
}
// RenameGroup renames a group
// XXX: They return a channel, not a group. What is this crap? :(
// Inconsistent api it seems.
func (api *Client) RenameGroup(group, name string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"channel": {group},
"name": {name},
}
// XXX: the created entry in this call returns a string instead of a number
// so I may have to do some workaround to solve it.
response, err := groupRequest("groups.rename", values, api.debug)
if err != nil {
return nil, err
}
return &response.Channel, nil
}
// SetGroupPurpose sets the group purpose
func (api *Client) SetGroupPurpose(group, purpose string) (string, error) {
values := url.Values{
"token": {api.config.token},
"channel": {group},
"purpose": {purpose},
}
response, err := groupRequest("groups.setPurpose", values, api.debug)
if err != nil {
return "", err
}
return response.Purpose, nil
}
// SetGroupTopic sets the group topic
func (api *Client) SetGroupTopic(group, topic string) (string, error) {
values := url.Values{
"token": {api.config.token},
"channel": {group},
"topic": {topic},
}
response, err := groupRequest("groups.setTopic", values, api.debug)
if err != nil {
return "", err
}
return response.Topic, nil
}

36
vendor/github.com/nlopes/slack/history.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package slack
const (
DEFAULT_HISTORY_LATEST = ""
DEFAULT_HISTORY_OLDEST = "0"
DEFAULT_HISTORY_COUNT = 100
DEFAULT_HISTORY_INCLUSIVE = false
DEFAULT_HISTORY_UNREADS = false
)
// HistoryParameters contains all the necessary information to help in the retrieval of history for Channels/Groups/DMs
type HistoryParameters struct {
Latest string
Oldest string
Count int
Inclusive bool
Unreads bool
}
// History contains message history information needed to navigate a Channel / Group / DM history
type History struct {
Latest string `json:"latest"`
Messages []Message `json:"messages"`
HasMore bool `json:"has_more"`
}
// NewHistoryParameters provides an instance of HistoryParameters with all the sane default values set
func NewHistoryParameters() HistoryParameters {
return HistoryParameters{
Latest: DEFAULT_HISTORY_LATEST,
Oldest: DEFAULT_HISTORY_OLDEST,
Count: DEFAULT_HISTORY_COUNT,
Inclusive: DEFAULT_HISTORY_INCLUSIVE,
Unreads: DEFAULT_HISTORY_UNREADS,
}
}

130
vendor/github.com/nlopes/slack/im.go generated vendored Normal file
View File

@ -0,0 +1,130 @@
package slack
import (
"errors"
"net/url"
"strconv"
)
type imChannel struct {
ID string `json:"id"`
}
type imResponseFull struct {
NoOp bool `json:"no_op"`
AlreadyClosed bool `json:"already_closed"`
AlreadyOpen bool `json:"already_open"`
Channel imChannel `json:"channel"`
IMs []IM `json:"ims"`
History
SlackResponse
}
// IM contains information related to the Direct Message channel
type IM struct {
conversation
IsIM bool `json:"is_im"`
User string `json:"user"`
IsUserDeleted bool `json:"is_user_deleted"`
}
func imRequest(path string, values url.Values, debug bool) (*imResponseFull, error) {
response := &imResponseFull{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
// CloseIMChannel closes the direct message channel
func (api *Client) CloseIMChannel(channel string) (bool, bool, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
}
response, err := imRequest("im.close", values, api.debug)
if err != nil {
return false, false, err
}
return response.NoOp, response.AlreadyClosed, nil
}
// OpenIMChannel opens a direct message channel to the user provided as argument
// Returns some status and the channel ID
func (api *Client) OpenIMChannel(user string) (bool, bool, string, error) {
values := url.Values{
"token": {api.config.token},
"user": {user},
}
response, err := imRequest("im.open", values, api.debug)
if err != nil {
return false, false, "", err
}
return response.NoOp, response.AlreadyOpen, response.Channel.ID, nil
}
// MarkIMChannel sets the read mark of a direct message channel to a specific point
func (api *Client) MarkIMChannel(channel, ts string) (err error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"ts": {ts},
}
_, err = imRequest("im.mark", values, api.debug)
if err != nil {
return err
}
return
}
// GetIMHistory retrieves the direct message channel history
func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*History, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
}
if params.Latest != DEFAULT_HISTORY_LATEST {
values.Add("latest", params.Latest)
}
if params.Oldest != DEFAULT_HISTORY_OLDEST {
values.Add("oldest", params.Oldest)
}
if params.Count != DEFAULT_HISTORY_COUNT {
values.Add("count", strconv.Itoa(params.Count))
}
if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
if params.Inclusive {
values.Add("inclusive", "1")
} else {
values.Add("inclusive", "0")
}
}
if params.Unreads != DEFAULT_HISTORY_UNREADS {
if params.Unreads {
values.Add("unreads", "1")
} else {
values.Add("unreads", "0")
}
}
response, err := imRequest("im.history", values, api.debug)
if err != nil {
return nil, err
}
return &response.History, nil
}
// GetIMChannels returns the list of direct message channels
func (api *Client) GetIMChannels() ([]IM, error) {
values := url.Values{
"token": {api.config.token},
}
response, err := imRequest("im.list", values, api.debug)
if err != nil {
return nil, err
}
return response.IMs, nil
}

210
vendor/github.com/nlopes/slack/info.go generated vendored Normal file
View File

@ -0,0 +1,210 @@
package slack
import (
"fmt"
"time"
)
// UserPrefs needs to be implemented
type UserPrefs struct {
// "highlight_words":"",
// "user_colors":"",
// "color_names_in_list":true,
// "growls_enabled":true,
// "tz":"Europe\/London",
// "push_dm_alert":true,
// "push_mention_alert":true,
// "push_everything":true,
// "push_idle_wait":2,
// "push_sound":"b2.mp3",
// "push_loud_channels":"",
// "push_mention_channels":"",
// "push_loud_channels_set":"",
// "email_alerts":"instant",
// "email_alerts_sleep_until":0,
// "email_misc":false,
// "email_weekly":true,
// "welcome_message_hidden":false,
// "all_channels_loud":true,
// "loud_channels":"",
// "never_channels":"",
// "loud_channels_set":"",
// "show_member_presence":true,
// "search_sort":"timestamp",
// "expand_inline_imgs":true,
// "expand_internal_inline_imgs":true,
// "expand_snippets":false,
// "posts_formatting_guide":true,
// "seen_welcome_2":true,
// "seen_ssb_prompt":false,
// "search_only_my_channels":false,
// "emoji_mode":"default",
// "has_invited":true,
// "has_uploaded":false,
// "has_created_channel":true,
// "search_exclude_channels":"",
// "messages_theme":"default",
// "webapp_spellcheck":true,
// "no_joined_overlays":false,
// "no_created_overlays":true,
// "dropbox_enabled":false,
// "seen_user_menu_tip_card":true,
// "seen_team_menu_tip_card":true,
// "seen_channel_menu_tip_card":true,
// "seen_message_input_tip_card":true,
// "seen_channels_tip_card":true,
// "seen_domain_invite_reminder":false,
// "seen_member_invite_reminder":false,
// "seen_flexpane_tip_card":true,
// "seen_search_input_tip_card":true,
// "mute_sounds":false,
// "arrow_history":false,
// "tab_ui_return_selects":true,
// "obey_inline_img_limit":true,
// "new_msg_snd":"knock_brush.mp3",
// "collapsible":false,
// "collapsible_by_click":true,
// "require_at":false,
// "mac_ssb_bounce":"",
// "mac_ssb_bullet":true,
// "win_ssb_bullet":true,
// "expand_non_media_attachments":true,
// "show_typing":true,
// "pagekeys_handled":true,
// "last_snippet_type":"",
// "display_real_names_override":0,
// "time24":false,
// "enter_is_special_in_tbt":false,
// "graphic_emoticons":false,
// "convert_emoticons":true,
// "autoplay_chat_sounds":true,
// "ss_emojis":true,
// "sidebar_behavior":"",
// "mark_msgs_read_immediately":true,
// "start_scroll_at_oldest":true,
// "snippet_editor_wrap_long_lines":false,
// "ls_disabled":false,
// "sidebar_theme":"default",
// "sidebar_theme_custom_values":"",
// "f_key_search":false,
// "k_key_omnibox":true,
// "speak_growls":false,
// "mac_speak_voice":"com.apple.speech.synthesis.voice.Alex",
// "mac_speak_speed":250,
// "comma_key_prefs":false,
// "at_channel_suppressed_channels":"",
// "push_at_channel_suppressed_channels":"",
// "prompted_for_email_disabling":false,
// "full_text_extracts":false,
// "no_text_in_notifications":false,
// "muted_channels":"",
// "no_macssb1_banner":false,
// "privacy_policy_seen":true,
// "search_exclude_bots":false,
// "fuzzy_matching":false
}
// UserDetails contains user details coming in the initial response from StartRTM
type UserDetails struct {
ID string `json:"id"`
Name string `json:"name"`
Created JSONTime `json:"created"`
ManualPresence string `json:"manual_presence"`
Prefs UserPrefs `json:"prefs"`
}
// JSONTime exists so that we can have a String method converting the date
type JSONTime int64
// String converts the unix timestamp into a string
func (t JSONTime) String() string {
tm := t.Time()
return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
}
// Time returns a `time.Time` representation of this value.
func (t JSONTime) Time() time.Time {
return time.Unix(int64(t), 0)
}
// Team contains details about a team
type Team struct {
ID string `json:"id"`
Name string `json:"name"`
Domain string `json:"domain"`
}
// Icons XXX: needs further investigation
type Icons struct {
Image36 string `json:"image_36,omitempty"`
Image48 string `json:"image_48,omitempty"`
Image72 string `json:"image_72,omitempty"`
}
// Info contains various details about Users, Channels, Bots and the authenticated user.
// It is returned by StartRTM or included in the "ConnectedEvent" RTM event.
type Info struct {
URL string `json:"url,omitempty"`
User *UserDetails `json:"self,omitempty"`
Team *Team `json:"team,omitempty"`
Users []User `json:"users,omitempty"`
Channels []Channel `json:"channels,omitempty"`
Groups []Group `json:"groups,omitempty"`
Bots []Bot `json:"bots,omitempty"`
IMs []IM `json:"ims,omitempty"`
}
type infoResponseFull struct {
Info
WebResponse
}
// GetBotByID returns a bot given a bot id
func (info Info) GetBotByID(botID string) *Bot {
for _, bot := range info.Bots {
if bot.ID == botID {
return &bot
}
}
return nil
}
// GetUserByID returns a user given a user id
func (info Info) GetUserByID(userID string) *User {
for _, user := range info.Users {
if user.ID == userID {
return &user
}
}
return nil
}
// GetChannelByID returns a channel given a channel id
func (info Info) GetChannelByID(channelID string) *Channel {
for _, channel := range info.Channels {
if channel.ID == channelID {
return &channel
}
}
return nil
}
// GetGroupByID returns a group given a group id
func (info Info) GetGroupByID(groupID string) *Group {
for _, group := range info.Groups {
if group.ID == groupID {
return &group
}
}
return nil
}
// GetIMByID returns an IM given an IM id
func (info Info) GetIMByID(imID string) *IM {
for _, im := range info.IMs {
if im.ID == imID {
return &im
}
}
return nil
}

75
vendor/github.com/nlopes/slack/item.go generated vendored Normal file
View File

@ -0,0 +1,75 @@
package slack
const (
TYPE_MESSAGE = "message"
TYPE_FILE = "file"
TYPE_FILE_COMMENT = "file_comment"
TYPE_CHANNEL = "channel"
TYPE_IM = "im"
TYPE_GROUP = "group"
)
// Item is any type of slack message - message, file, or file comment.
type Item struct {
Type string `json:"type"`
Channel string `json:"channel,omitempty"`
Message *Message `json:"message,omitempty"`
File *File `json:"file,omitempty"`
Comment *Comment `json:"comment,omitempty"`
Timestamp string `json:"ts,omitempty"`
}
// NewMessageItem turns a message on a channel into a typed message struct.
func NewMessageItem(ch string, m *Message) Item {
return Item{Type: TYPE_MESSAGE, Channel: ch, Message: m}
}
// NewFileItem turns a file into a typed file struct.
func NewFileItem(f *File) Item {
return Item{Type: TYPE_FILE, File: f}
}
// NewFileCommentItem turns a file and comment into a typed file_comment struct.
func NewFileCommentItem(f *File, c *Comment) Item {
return Item{Type: TYPE_FILE_COMMENT, File: f, Comment: c}
}
// NewChannelItem turns a channel id into a typed channel struct.
func NewChannelItem(ch string) Item {
return Item{Type: TYPE_CHANNEL, Channel: ch}
}
// NewIMItem turns a channel id into a typed im struct.
func NewIMItem(ch string) Item {
return Item{Type: TYPE_IM, Channel: ch}
}
// NewGroupItem turns a channel id into a typed group struct.
func NewGroupItem(ch string) Item {
return Item{Type: TYPE_GROUP, Channel: ch}
}
// ItemRef is a reference to a message of any type. One of FileID,
// CommentId, or the combination of ChannelId and Timestamp must be
// specified.
type ItemRef struct {
Channel string `json:"channel"`
Timestamp string `json:"timestamp"`
File string `json:"file"`
Comment string `json:"file_comment"`
}
// NewRefToMessage initializes a reference to to a message.
func NewRefToMessage(channel, timestamp string) ItemRef {
return ItemRef{Channel: channel, Timestamp: timestamp}
}
// NewRefToFile initializes a reference to a file.
func NewRefToFile(file string) ItemRef {
return ItemRef{File: file}
}
// NewRefToComment initializes a reference to a file comment.
func NewRefToComment(comment string) ItemRef {
return ItemRef{Comment: comment}
}

30
vendor/github.com/nlopes/slack/messageID.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package slack
import "sync"
// IDGenerator provides an interface for generating integer ID values.
type IDGenerator interface {
Next() int
}
// NewSafeID returns a new instance of an IDGenerator which is safe for
// concurrent use by multiple goroutines.
func NewSafeID(startID int) IDGenerator {
return &safeID{
nextID: startID,
mutex: &sync.Mutex{},
}
}
type safeID struct {
nextID int
mutex *sync.Mutex
}
func (s *safeID) Next() int {
s.mutex.Lock()
defer s.mutex.Unlock()
id := s.nextID
s.nextID++
return id
}

131
vendor/github.com/nlopes/slack/messages.go generated vendored Normal file
View File

@ -0,0 +1,131 @@
package slack
// OutgoingMessage is used for the realtime API, and seems incomplete.
type OutgoingMessage struct {
ID int `json:"id"`
Channel string `json:"channel,omitempty"`
Text string `json:"text,omitempty"`
Type string `json:"type,omitempty"`
}
// Message is an auxiliary type to allow us to have a message containing sub messages
type Message struct {
Msg
SubMessage *Msg `json:"message,omitempty"`
}
// Msg contains information about a slack message
type Msg struct {
// Basic Message
Type string `json:"type,omitempty"`
Channel string `json:"channel,omitempty"`
User string `json:"user,omitempty"`
Text string `json:"text,omitempty"`
Timestamp string `json:"ts,omitempty"`
IsStarred bool `json:"is_starred,omitempty"`
PinnedTo []string `json:"pinned_to, omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
Edited *Edited `json:"edited,omitempty"`
// Message Subtypes
SubType string `json:"subtype,omitempty"`
// Hidden Subtypes
Hidden bool `json:"hidden,omitempty"` // message_changed, message_deleted, unpinned_item
DeletedTimestamp string `json:"deleted_ts,omitempty"` // message_deleted
EventTimestamp string `json:"event_ts,omitempty"`
// bot_message (https://api.slack.com/events/message/bot_message)
BotID string `json:"bot_id,omitempty"`
Username string `json:"username,omitempty"`
Icons *Icon `json:"icons,omitempty"`
// channel_join, group_join
Inviter string `json:"inviter,omitempty"`
// channel_topic, group_topic
Topic string `json:"topic,omitempty"`
// channel_purpose, group_purpose
Purpose string `json:"purpose,omitempty"`
// channel_name, group_name
Name string `json:"name,omitempty"`
OldName string `json:"old_name,omitempty"`
// channel_archive, group_archive
Members []string `json:"members,omitempty"`
// file_share, file_comment, file_mention
File *File `json:"file,omitempty"`
// file_share
Upload bool `json:"upload,omitempty"`
// file_comment
Comment *Comment `json:"comment,omitempty"`
// pinned_item
ItemType string `json:"item_type,omitempty"`
// https://api.slack.com/rtm
ReplyTo int `json:"reply_to,omitempty"`
Team string `json:"team,omitempty"`
// reactions
Reactions []ItemReaction `json:"reactions,omitempty"`
}
// Icon is used for bot messages
type Icon struct {
IconURL string `json:"icon_url,omitempty"`
IconEmoji string `json:"icon_emoji,omitempty"`
}
// Edited indicates that a message has been edited.
type Edited struct {
User string `json:"user,omitempty"`
Timestamp string `json:"ts,omitempty"`
}
// Event contains the event type
type Event struct {
Type string `json:"type,omitempty"`
}
// Ping contains information about a Ping Event
type Ping struct {
ID int `json:"id"`
Type string `json:"type"`
}
// Pong contains information about a Pong Event
type Pong struct {
Type string `json:"type"`
ReplyTo int `json:"reply_to"`
}
// NewOutgoingMessage prepares an OutgoingMessage that the user can
// use to send a message. Use this function to properly set the
// messageID.
func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
id := rtm.idGen.Next()
return &OutgoingMessage{
ID: id,
Type: "message",
Channel: channel,
Text: text,
}
}
// NewTypingMessage prepares an OutgoingMessage that the user can
// use to send as a typing indicator. Use this function to properly set the
// messageID.
func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage {
id := rtm.idGen.Next()
return &OutgoingMessage{
ID: id,
Type: "typing",
Channel: channel,
}
}

140
vendor/github.com/nlopes/slack/misc.go generated vendored Normal file
View File

@ -0,0 +1,140 @@
package slack
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"time"
)
var HTTPClient = &http.Client{}
type WebResponse struct {
Ok bool `json:"ok"`
Error *WebError `json:"error"`
}
type WebError string
func (s WebError) Error() string {
return string(s)
}
func fileUploadReq(path, fpath string, values url.Values) (*http.Request, error) {
fullpath, err := filepath.Abs(fpath)
if err != nil {
return nil, err
}
file, err := os.Open(fullpath)
if err != nil {
return nil, err
}
defer file.Close()
body := &bytes.Buffer{}
wr := multipart.NewWriter(body)
ioWriter, err := wr.CreateFormFile("file", filepath.Base(fullpath))
if err != nil {
wr.Close()
return nil, err
}
bytes, err := io.Copy(ioWriter, file)
if err != nil {
wr.Close()
return nil, err
}
// Close the multipart writer or the footer won't be written
wr.Close()
stat, err := file.Stat()
if err != nil {
return nil, err
}
if bytes != stat.Size() {
return nil, errors.New("could not read the whole file")
}
req, err := http.NewRequest("POST", path, body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", wr.FormDataContentType())
req.URL.RawQuery = (values).Encode()
return req, nil
}
func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error {
response, err := ioutil.ReadAll(body)
if err != nil {
return err
}
// FIXME: will be api.Debugf
if debug {
logger.Printf("parseResponseBody: %s\n", string(response))
}
err = json.Unmarshal(response, &intf)
if err != nil {
return err
}
return nil
}
func postWithMultipartResponse(path string, filepath string, values url.Values, intf interface{}, debug bool) error {
req, err := fileUploadReq(SLACK_API+path, filepath, values)
resp, err := HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
if resp.StatusCode != 200 {
logResponse(resp, debug)
return fmt.Errorf("Slack server error: %s.", resp.Status)
}
return parseResponseBody(resp.Body, &intf, debug)
}
func postForm(endpoint string, values url.Values, intf interface{}, debug bool) error {
resp, err := HTTPClient.PostForm(endpoint, values)
if err != nil {
return err
}
defer resp.Body.Close()
return parseResponseBody(resp.Body, &intf, debug)
}
func post(path string, values url.Values, intf interface{}, debug bool) error {
return postForm(SLACK_API+path, values, intf, debug)
}
func parseAdminResponse(method string, teamName string, values url.Values, intf interface{}, debug bool) error {
endpoint := fmt.Sprintf(SLACK_WEB_API_FORMAT, teamName, method, time.Now().Unix())
return postForm(endpoint, values, intf, debug)
}
func logResponse(resp *http.Response, debug bool) error {
if debug {
text, err := httputil.DumpResponse(resp, true)
if err != nil {
return err
}
logger.Print(text)
}
return nil
}

56
vendor/github.com/nlopes/slack/oauth.go generated vendored Normal file
View File

@ -0,0 +1,56 @@
package slack
import (
"errors"
"net/url"
)
type OAuthResponseIncomingWebhook struct {
URL string `json:"url"`
Channel string `json:"channel"`
ChannelID string `json:"channel_id,omitempty"`
ConfigurationURL string `json:"configuration_url"`
}
type OAuthResponseBot struct {
BotUserID string `json:"bot_user_id"`
BotAccessToken string `json:"bot_access_token"`
}
type OAuthResponse struct {
AccessToken string `json:"access_token"`
Scope string `json:"scope"`
TeamName string `json:"team_name"`
TeamID string `json:"team_id"`
IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"`
Bot OAuthResponseBot `json:"bot"`
UserID string `json:"user_id,omitempty"`
SlackResponse
}
// GetOAuthToken retrieves an AccessToken
func GetOAuthToken(clientID, clientSecret, code, redirectURI string, debug bool) (accessToken string, scope string, err error) {
response, err := GetOAuthResponse(clientID, clientSecret, code, redirectURI, debug)
if err != nil {
return "", "", err
}
return response.AccessToken, response.Scope, nil
}
func GetOAuthResponse(clientID, clientSecret, code, redirectURI string, debug bool) (resp *OAuthResponse, err error) {
values := url.Values{
"client_id": {clientID},
"client_secret": {clientSecret},
"code": {code},
"redirect_uri": {redirectURI},
}
response := &OAuthResponse{}
err = post("oauth.access", values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}

20
vendor/github.com/nlopes/slack/pagination.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
package slack
// Paging contains paging information
type Paging struct {
Count int `json:"count"`
Total int `json:"total"`
Page int `json:"page"`
Pages int `json:"pages"`
}
// Pagination contains pagination information
// This is different from Paging in that it contains additional details
type Pagination struct {
TotalCount int `json:"total_count"`
Page int `json:"page"`
PerPage int `json:"per_page"`
PageCount int `json:"page_count"`
First int `json:"first"`
Last int `json:"last"`
}

79
vendor/github.com/nlopes/slack/pins.go generated vendored Normal file
View File

@ -0,0 +1,79 @@
package slack
import (
"errors"
"net/url"
)
type listPinsResponseFull struct {
Items []Item
Paging `json:"paging"`
SlackResponse
}
// AddPin pins an item in a channel
func (api *Client) AddPin(channel string, item ItemRef) error {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
}
if item.File != "" {
values.Set("file", string(item.File))
}
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post("pins.add", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// RemovePin un-pins an item from a channel
func (api *Client) RemovePin(channel string, item ItemRef) error {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
}
if item.File != "" {
values.Set("file", string(item.File))
}
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post("pins.remove", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// ListPins returns information about the items a user reacted to.
func (api *Client) ListPins(channel string) ([]Item, *Paging, error) {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
}
response := &listPinsResponseFull{}
err := post("pins.list", values, response, api.debug)
if err != nil {
return nil, nil, err
}
if !response.Ok {
return nil, nil, errors.New(response.Error)
}
return response.Items, &response.Paging, nil
}

246
vendor/github.com/nlopes/slack/reactions.go generated vendored Normal file
View File

@ -0,0 +1,246 @@
package slack
import (
"errors"
"net/url"
"strconv"
)
// ItemReaction is the reactions that have happened on an item.
type ItemReaction struct {
Name string `json:"name"`
Count int `json:"count"`
Users []string `json:"users"`
}
// ReactedItem is an item that was reacted to, and the details of the
// reactions.
type ReactedItem struct {
Item
Reactions []ItemReaction
}
// GetReactionsParameters is the inputs to get reactions to an item.
type GetReactionsParameters struct {
Full bool
}
// NewGetReactionsParameters initializes the inputs to get reactions to an item.
func NewGetReactionsParameters() GetReactionsParameters {
return GetReactionsParameters{
Full: false,
}
}
type getReactionsResponseFull struct {
Type string
M struct {
Reactions []ItemReaction
} `json:"message"`
F struct {
Reactions []ItemReaction
} `json:"file"`
FC struct {
Reactions []ItemReaction
} `json:"comment"`
SlackResponse
}
func (res getReactionsResponseFull) extractReactions() []ItemReaction {
switch res.Type {
case "message":
return res.M.Reactions
case "file":
return res.F.Reactions
case "file_comment":
return res.FC.Reactions
}
return []ItemReaction{}
}
const (
DEFAULT_REACTIONS_USER = ""
DEFAULT_REACTIONS_COUNT = 100
DEFAULT_REACTIONS_PAGE = 1
DEFAULT_REACTIONS_FULL = false
)
// ListReactionsParameters is the inputs to find all reactions by a user.
type ListReactionsParameters struct {
User string
Count int
Page int
Full bool
}
// NewListReactionsParameters initializes the inputs to find all reactions
// performed by a user.
func NewListReactionsParameters() ListReactionsParameters {
return ListReactionsParameters{
User: DEFAULT_REACTIONS_USER,
Count: DEFAULT_REACTIONS_COUNT,
Page: DEFAULT_REACTIONS_PAGE,
Full: DEFAULT_REACTIONS_FULL,
}
}
type listReactionsResponseFull struct {
Items []struct {
Type string
Channel string
M struct {
*Message
} `json:"message"`
F struct {
*File
Reactions []ItemReaction
} `json:"file"`
FC struct {
*Comment
Reactions []ItemReaction
} `json:"comment"`
}
Paging `json:"paging"`
SlackResponse
}
func (res listReactionsResponseFull) extractReactedItems() []ReactedItem {
items := make([]ReactedItem, len(res.Items))
for i, input := range res.Items {
item := ReactedItem{}
item.Type = input.Type
switch input.Type {
case "message":
item.Channel = input.Channel
item.Message = input.M.Message
item.Reactions = input.M.Reactions
case "file":
item.File = input.F.File
item.Reactions = input.F.Reactions
case "file_comment":
item.File = input.F.File
item.Comment = input.FC.Comment
item.Reactions = input.FC.Reactions
}
items[i] = item
}
return items
}
// AddReaction adds a reaction emoji to a message, file or file comment.
func (api *Client) AddReaction(name string, item ItemRef) error {
values := url.Values{
"token": {api.config.token},
}
if name != "" {
values.Set("name", name)
}
if item.Channel != "" {
values.Set("channel", string(item.Channel))
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
}
if item.File != "" {
values.Set("file", string(item.File))
}
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post("reactions.add", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// RemoveReaction removes a reaction emoji from a message, file or file comment.
func (api *Client) RemoveReaction(name string, item ItemRef) error {
values := url.Values{
"token": {api.config.token},
}
if name != "" {
values.Set("name", name)
}
if item.Channel != "" {
values.Set("channel", string(item.Channel))
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
}
if item.File != "" {
values.Set("file", string(item.File))
}
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post("reactions.remove", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// GetReactions returns details about the reactions on an item.
func (api *Client) GetReactions(item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
values := url.Values{
"token": {api.config.token},
}
if item.Channel != "" {
values.Set("channel", string(item.Channel))
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
}
if item.File != "" {
values.Set("file", string(item.File))
}
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
if params.Full != DEFAULT_REACTIONS_FULL {
values.Set("full", strconv.FormatBool(params.Full))
}
response := &getReactionsResponseFull{}
if err := post("reactions.get", values, response, api.debug); err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response.extractReactions(), nil
}
// ListReactions returns information about the items a user reacted to.
func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
values := url.Values{
"token": {api.config.token},
}
if params.User != DEFAULT_REACTIONS_USER {
values.Add("user", params.User)
}
if params.Count != DEFAULT_REACTIONS_COUNT {
values.Add("count", strconv.Itoa(params.Count))
}
if params.Page != DEFAULT_REACTIONS_PAGE {
values.Add("page", strconv.Itoa(params.Page))
}
if params.Full != DEFAULT_REACTIONS_FULL {
values.Add("full", strconv.FormatBool(params.Full))
}
response := &listReactionsResponseFull{}
err := post("reactions.list", values, response, api.debug)
if err != nil {
return nil, nil, err
}
if !response.Ok {
return nil, nil, errors.New(response.Error)
}
return response.extractReactedItems(), &response.Paging, nil
}

39
vendor/github.com/nlopes/slack/rtm.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
package slack
import (
"fmt"
"net/url"
)
// StartRTM calls the "rtm.start" endpoint and returns the provided URL and the full Info
// block.
//
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()`
// on it.
func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
response := &infoResponseFull{}
err = post("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
if err != nil {
return nil, "", fmt.Errorf("post: %s", err)
}
if !response.Ok {
return nil, "", response.Error
}
// websocket.Dial does not accept url without the port (yet)
// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
// but slack returns the address with no port, so we have to fix it
api.Debugln("Using URL:", response.Info.URL)
websocketURL, err = websocketizeURLPort(response.Info.URL)
if err != nil {
return nil, "", fmt.Errorf("parsing response URL: %s", err)
}
return &response.Info, websocketURL, nil
}
// NewRTM returns a RTM, which provides a fully managed connection to
// Slack's websocket-based Real-Time Messaging protocol./
func (api *Client) NewRTM() *RTM {
return newRTM(api)
}

137
vendor/github.com/nlopes/slack/search.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
package slack
import (
"errors"
"net/url"
"strconv"
)
const (
DEFAULT_SEARCH_SORT = "score"
DEFAULT_SEARCH_SORT_DIR = "desc"
DEFAULT_SEARCH_HIGHLIGHT = false
DEFAULT_SEARCH_COUNT = 100
DEFAULT_SEARCH_PAGE = 1
)
type SearchParameters struct {
Sort string
SortDirection string
Highlight bool
Count int
Page int
}
type CtxChannel struct {
ID string `json:"id"`
Name string `json:"name"`
}
type CtxMessage struct {
User string `json:"user"`
Username string `json:"username"`
Text string `json:"text"`
Timestamp string `json:"ts"`
Type string `json:"type"`
}
type SearchMessage struct {
Type string `json:"type"`
Channel CtxChannel `json:"channel"`
User string `json:"user"`
Username string `json:"username"`
Timestamp string `json:"ts"`
Text string `json:"text"`
Permalink string `json:"permalink"`
Previous CtxMessage `json:"previous"`
Previous2 CtxMessage `json:"previous_2"`
Next CtxMessage `json:"next"`
Next2 CtxMessage `json:"next_2"`
}
type SearchMessages struct {
Matches []SearchMessage `json:"matches"`
Paging `json:"paging"`
Pagination `json:"pagination"`
Total int `json:"total"`
}
type SearchFiles struct {
Matches []File `json:"matches"`
Paging `json:"paging"`
Pagination `json:"pagination"`
Total int `json:"total"`
}
type searchResponseFull struct {
Query string `json:"query"`
SearchMessages `json:"messages"`
SearchFiles `json:"files"`
SlackResponse
}
func NewSearchParameters() SearchParameters {
return SearchParameters{
Sort: DEFAULT_SEARCH_SORT,
SortDirection: DEFAULT_SEARCH_SORT_DIR,
Highlight: DEFAULT_SEARCH_HIGHLIGHT,
Count: DEFAULT_SEARCH_COUNT,
Page: DEFAULT_SEARCH_PAGE,
}
}
func (api *Client) _search(path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
values := url.Values{
"token": {api.config.token},
"query": {query},
}
if params.Sort != DEFAULT_SEARCH_SORT {
values.Add("sort", params.Sort)
}
if params.SortDirection != DEFAULT_SEARCH_SORT_DIR {
values.Add("sort_dir", params.SortDirection)
}
if params.Highlight != DEFAULT_SEARCH_HIGHLIGHT {
values.Add("highlight", strconv.Itoa(1))
}
if params.Count != DEFAULT_SEARCH_COUNT {
values.Add("count", strconv.Itoa(params.Count))
}
if params.Page != DEFAULT_SEARCH_PAGE {
values.Add("page", strconv.Itoa(params.Page))
}
response = &searchResponseFull{}
err := post(path, values, response, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
func (api *Client) Search(query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) {
response, err := api._search("search.all", query, params, true, true)
if err != nil {
return nil, nil, err
}
return &response.SearchMessages, &response.SearchFiles, nil
}
func (api *Client) SearchFiles(query string, params SearchParameters) (*SearchFiles, error) {
response, err := api._search("search.files", query, params, true, false)
if err != nil {
return nil, err
}
return &response.SearchFiles, nil
}
func (api *Client) SearchMessages(query string, params SearchParameters) (*SearchMessages, error) {
response, err := api._search("search.messages", query, params, false, true)
if err != nil {
return nil, err
}
return &response.SearchMessages, nil
}

88
vendor/github.com/nlopes/slack/slack.go generated vendored Normal file
View File

@ -0,0 +1,88 @@
package slack
import (
"errors"
"log"
"net/url"
"os"
)
var logger *log.Logger // A logger that can be set by consumers
/*
Added as a var so that we can change this for testing purposes
*/
var SLACK_API string = "https://slack.com/api/"
var SLACK_WEB_API_FORMAT string = "https://%s.slack.com/api/users.admin.%s?t=%s"
type SlackResponse struct {
Ok bool `json:"ok"`
Error string `json:"error"`
}
type AuthTestResponse struct {
URL string `json:"url"`
Team string `json:"team"`
User string `json:"user"`
TeamID string `json:"team_id"`
UserID string `json:"user_id"`
}
type authTestResponseFull struct {
SlackResponse
AuthTestResponse
}
type Client struct {
config struct {
token string
}
info Info
debug bool
}
// SetLogger let's library users supply a logger, so that api debugging
// can be logged along with the application's debugging info.
func SetLogger(l *log.Logger) {
logger = l
}
func New(token string) *Client {
s := &Client{}
s.config.token = token
return s
}
// AuthTest tests if the user is able to do authenticated requests or not
func (api *Client) AuthTest() (response *AuthTestResponse, error error) {
responseFull := &authTestResponseFull{}
err := post("auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug)
if err != nil {
return nil, err
}
if !responseFull.Ok {
return nil, errors.New(responseFull.Error)
}
return &responseFull.AuthTestResponse, nil
}
// SetDebug switches the api into debug mode
// When in debug mode, it logs various info about what its doing
// If you ever use this in production, don't call SetDebug(true)
func (api *Client) SetDebug(debug bool) {
api.debug = debug
if debug && logger == nil {
logger = log.New(os.Stdout, "nlopes/slack", log.LstdFlags | log.Lshortfile)
}
}
func (api *Client) Debugf(format string, v ...interface{}) {
if api.debug {
logger.Printf(format, v...)
}
}
func (api *Client) Debugln(v ...interface{}) {
if api.debug {
logger.Println(v...)
}
}

135
vendor/github.com/nlopes/slack/stars.go generated vendored Normal file
View File

@ -0,0 +1,135 @@
package slack
import (
"errors"
"net/url"
"strconv"
)
const (
DEFAULT_STARS_USER = ""
DEFAULT_STARS_COUNT = 100
DEFAULT_STARS_PAGE = 1
)
type StarsParameters struct {
User string
Count int
Page int
}
type StarredItem Item
type listResponseFull struct {
Items []Item `json:"items"`
Paging `json:"paging"`
SlackResponse
}
// NewStarsParameters initialises StarsParameters with default values
func NewStarsParameters() StarsParameters {
return StarsParameters{
User: DEFAULT_STARS_USER,
Count: DEFAULT_STARS_COUNT,
Page: DEFAULT_STARS_PAGE,
}
}
// AddStar stars an item in a channel
func (api *Client) AddStar(channel string, item ItemRef) error {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
}
if item.File != "" {
values.Set("file", string(item.File))
}
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post("stars.add", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// RemoveStar removes a starred item from a channel
func (api *Client) RemoveStar(channel string, item ItemRef) error {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
}
if item.File != "" {
values.Set("file", string(item.File))
}
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post("stars.remove", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// ListStars returns information about the stars a user added
func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
values := url.Values{
"token": {api.config.token},
}
if params.User != DEFAULT_STARS_USER {
values.Add("user", params.User)
}
if params.Count != DEFAULT_STARS_COUNT {
values.Add("count", strconv.Itoa(params.Count))
}
if params.Page != DEFAULT_STARS_PAGE {
values.Add("page", strconv.Itoa(params.Page))
}
response := &listResponseFull{}
err := post("stars.list", values, response, api.debug)
if err != nil {
return nil, nil, err
}
if !response.Ok {
return nil, nil, errors.New(response.Error)
}
return response.Items, &response.Paging, nil
}
// GetStarred returns a list of StarredItem items. The user then has to iterate over them and figure out what they should
// be looking at according to what is in the Type.
// for _, item := range items {
// switch c.Type {
// case "file_comment":
// log.Println(c.Comment)
// case "file":
// ...
//
// }
// This function still exists to maintain backwards compatibility.
// I exposed it as returning []StarredItem, so it shall stay as StarredItem
func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) {
items, paging, err := api.ListStars(params)
if err != nil {
return nil, nil, err
}
starredItems := make([]StarredItem, len(items))
for i, item := range items {
starredItems[i] = StarredItem(item)
}
return starredItems, paging, nil
}

160
vendor/github.com/nlopes/slack/team.go generated vendored Normal file
View File

@ -0,0 +1,160 @@
package slack
import (
"errors"
"net/url"
"strconv"
)
const (
DEFAULT_LOGINS_COUNT = 100
DEFAULT_LOGINS_PAGE = 1
)
type TeamResponse struct {
Team TeamInfo `json:"team"`
SlackResponse
}
type TeamInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Domain string `json:"domain"`
EmailDomain string `json:"email_domain"`
Icon map[string]interface{} `json:"icon"`
}
type LoginResponse struct {
Logins []Login `json:"logins"`
Paging `json:"paging"`
SlackResponse
}
type Login struct {
UserID string `json:"user_id"`
Username string `json:"username"`
DateFirst int `json:"date_first"`
DateLast int `json:"date_last"`
Count int `json:"count"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
ISP string `json:"isp"`
Country string `json:"country"`
Region string `json:"region"`
}
type BillableInfoResponse struct {
BillableInfo map[string]BillingActive `json:"billable_info"`
SlackResponse
}
type BillingActive struct {
BillingActive bool `json:"billing_active"`
}
// AccessLogParameters contains all the parameters necessary (including the optional ones) for a GetAccessLogs() request
type AccessLogParameters struct {
Count int
Page int
}
// NewAccessLogParameters provides an instance of AccessLogParameters with all the sane default values set
func NewAccessLogParameters() AccessLogParameters {
return AccessLogParameters{
Count: DEFAULT_LOGINS_COUNT,
Page: DEFAULT_LOGINS_PAGE,
}
}
func teamRequest(path string, values url.Values, debug bool) (*TeamResponse, error) {
response := &TeamResponse{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
func billableInfoRequest(path string, values url.Values, debug bool) (map[string]BillingActive, error) {
response := &BillableInfoResponse{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response.BillableInfo, nil
}
func accessLogsRequest(path string, values url.Values, debug bool) (*LoginResponse, error) {
response := &LoginResponse{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
// GetTeamInfo gets the Team Information of the user
func (api *Client) GetTeamInfo() (*TeamInfo, error) {
values := url.Values{
"token": {api.config.token},
}
response, err := teamRequest("team.info", values, api.debug)
if err != nil {
return nil, err
}
return &response.Team, nil
}
// GetAccessLogs retrieves a page of logins according to the parameters given
func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, error) {
values := url.Values{
"token": {api.config.token},
}
if params.Count != DEFAULT_LOGINS_COUNT {
values.Add("count", strconv.Itoa(params.Count))
}
if params.Page != DEFAULT_LOGINS_PAGE {
values.Add("page", strconv.Itoa(params.Page))
}
response, err := accessLogsRequest("team.accessLogs", values, api.debug)
if err != nil {
return nil, nil, err
}
return response.Logins, &response.Paging, nil
}
func (api *Client) GetBillableInfo(user string) (map[string]BillingActive, error) {
values := url.Values{
"token": {api.config.token},
"user": {user},
}
return billableInfoRequest("team.billableInfo", values, api.debug)
}
// GetBillableInfoForTeam returns the billing_active status of all users on the team.
func (api *Client) GetBillableInfoForTeam() (map[string]BillingActive, error) {
values := url.Values{
"token": {api.config.token},
}
return billableInfoRequest("team.billableInfo", values, api.debug)
}

191
vendor/github.com/nlopes/slack/users.go generated vendored Normal file
View File

@ -0,0 +1,191 @@
package slack
import (
"errors"
"net/url"
)
// UserProfile contains all the information details of a given user
type UserProfile struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
RealName string `json:"real_name"`
RealNameNormalized string `json:"real_name_normalized"`
Email string `json:"email"`
Skype string `json:"skype"`
Phone string `json:"phone"`
Image24 string `json:"image_24"`
Image32 string `json:"image_32"`
Image48 string `json:"image_48"`
Image72 string `json:"image_72"`
Image192 string `json:"image_192"`
ImageOriginal string `json:"image_original"`
Title string `json:"title"`
BotID string `json:"bot_id,omitempty"`
ApiAppID string `json:"api_app_id,omitempty"`
}
// User contains all the information of a user
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Deleted bool `json:"deleted"`
Color string `json:"color"`
RealName string `json:"real_name"`
TZ string `json:"tz,omitempty"`
TZLabel string `json:"tz_label"`
TZOffset int `json:"tz_offset"`
Profile UserProfile `json:"profile"`
IsBot bool `json:"is_bot"`
IsAdmin bool `json:"is_admin"`
IsOwner bool `json:"is_owner"`
IsPrimaryOwner bool `json:"is_primary_owner"`
IsRestricted bool `json:"is_restricted"`
IsUltraRestricted bool `json:"is_ultra_restricted"`
Has2FA bool `json:"has_2fa"`
HasFiles bool `json:"has_files"`
Presence string `json:"presence"`
}
// UserPresence contains details about a user online status
type UserPresence struct {
Presence string `json:"presence,omitempty"`
Online bool `json:"online,omitempty"`
AutoAway bool `json:"auto_away,omitempty"`
ManualAway bool `json:"manual_away,omitempty"`
ConnectionCount int `json:"connection_count,omitempty"`
LastActivity JSONTime `json:"last_activity,omitempty"`
}
type UserIdentityResponse struct {
User UserIdentity `json:"user"`
Team TeamIdentity `json:"team"`
SlackResponse
}
type UserIdentity struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Image24 string `json:"image_24"`
Image32 string `json:"image_32"`
Image48 string `json:"image_48"`
Image72 string `json:"image_72"`
Image192 string `json:"image_192"`
Image512 string `json:"image_512"`
}
type TeamIdentity struct {
ID string `json:"id"`
Name string `json:"name"`
Domain string `json:"domain"`
Image34 string `json:"image_34"`
Image44 string `json:"image_44"`
Image68 string `json:"image_68"`
Image88 string `json:"image_88"`
Image102 string `json:"image_102"`
Image132 string `json:"image_132"`
Image230 string `json:"image_230"`
ImageDefault bool `json:"image_default"`
ImageOriginal string `json:"image_original"`
}
type userResponseFull struct {
Members []User `json:"members,omitempty"` // ListUsers
User `json:"user,omitempty"` // GetUserInfo
UserPresence // GetUserPresence
SlackResponse
}
func userRequest(path string, values url.Values, debug bool) (*userResponseFull, error) {
response := &userResponseFull{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}
// GetUserPresence will retrieve the current presence status of given user.
func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
values := url.Values{
"token": {api.config.token},
"user": {user},
}
response, err := userRequest("users.getPresence", values, api.debug)
if err != nil {
return nil, err
}
return &response.UserPresence, nil
}
// GetUserInfo will retrieve the complete user information
func (api *Client) GetUserInfo(user string) (*User, error) {
values := url.Values{
"token": {api.config.token},
"user": {user},
}
response, err := userRequest("users.info", values, api.debug)
if err != nil {
return nil, err
}
return &response.User, nil
}
// GetUsers returns the list of users (with their detailed information)
func (api *Client) GetUsers() ([]User, error) {
values := url.Values{
"token": {api.config.token},
"presence": {"1"},
}
response, err := userRequest("users.list", values, api.debug)
if err != nil {
return nil, err
}
return response.Members, nil
}
// SetUserAsActive marks the currently authenticated user as active
func (api *Client) SetUserAsActive() error {
values := url.Values{
"token": {api.config.token},
}
_, err := userRequest("users.setActive", values, api.debug)
if err != nil {
return err
}
return nil
}
// SetUserPresence changes the currently authenticated user presence
func (api *Client) SetUserPresence(presence string) error {
values := url.Values{
"token": {api.config.token},
"presence": {presence},
}
_, err := userRequest("users.setPresence", values, api.debug)
if err != nil {
return err
}
return nil
}
// GetUserIdentity will retrieve user info available per identity scopes
func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) {
values := url.Values{
"token": {api.config.token},
}
response := &UserIdentityResponse{}
err := post("users.identity", values, response, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response, nil
}

93
vendor/github.com/nlopes/slack/websocket.go generated vendored Normal file
View File

@ -0,0 +1,93 @@
package slack
import (
"encoding/json"
"errors"
"time"
"golang.org/x/net/websocket"
)
const (
// MaxMessageTextLength is the current maximum message length in number of characters as defined here
// https://api.slack.com/rtm#limits
MaxMessageTextLength = 4000
)
// RTM represents a managed websocket connection. It also supports
// all the methods of the `Client` type.
//
// Create this element with Client's NewRTM().
type RTM struct {
idGen IDGenerator
pings map[int]time.Time
// Connection life-cycle
conn *websocket.Conn
IncomingEvents chan RTMEvent
outgoingMessages chan OutgoingMessage
killChannel chan bool
forcePing chan bool
rawEvents chan json.RawMessage
wasIntentional bool
isConnected bool
// Client is the main API, embedded
Client
websocketURL string
// UserDetails upon connection
info *Info
}
// NewRTM returns a RTM, which provides a fully managed connection to
// Slack's websocket-based Real-Time Messaging protocol.
func newRTM(api *Client) *RTM {
return &RTM{
Client: *api,
IncomingEvents: make(chan RTMEvent, 50),
outgoingMessages: make(chan OutgoingMessage, 20),
pings: make(map[int]time.Time),
isConnected: false,
wasIntentional: true,
killChannel: make(chan bool),
forcePing: make(chan bool),
rawEvents: make(chan json.RawMessage),
idGen: NewSafeID(1),
}
}
// Disconnect and wait, blocking until a successful disconnection.
func (rtm *RTM) Disconnect() error {
if !rtm.isConnected {
return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
}
rtm.killChannel <- true
return nil
}
// Reconnect only makes sense if you've successfully disconnectd with Disconnect().
func (rtm *RTM) Reconnect() error {
logger.Println("RTM::Reconnect not implemented!")
return nil
}
// GetInfo returns the info structure received when calling
// "startrtm", holding all channels, groups and other metadata needed
// to implement a full chat client. It will be non-nil after a call to
// StartRTM().
func (rtm *RTM) GetInfo() *Info {
return rtm.info
}
// SendMessage submits a simple message through the websocket. For
// more complicated messages, use `rtm.PostMessage` with a complete
// struct describing your attachments and all.
func (rtm *RTM) SendMessage(msg *OutgoingMessage) {
if msg == nil {
rtm.Debugln("Error: Attempted to SendMessage(nil)")
return
}
rtm.outgoingMessages <- *msg
}

72
vendor/github.com/nlopes/slack/websocket_channels.go generated vendored Normal file
View File

@ -0,0 +1,72 @@
package slack
// ChannelCreatedEvent represents the Channel created event
type ChannelCreatedEvent struct {
Type string `json:"type"`
Channel ChannelCreatedInfo `json:"channel"`
EventTimestamp string `json:"event_ts"`
}
// ChannelCreatedInfo represents the information associated with the Channel created event
type ChannelCreatedInfo struct {
ID string `json:"id"`
IsChannel bool `json:"is_channel"`
Name string `json:"name"`
Created int `json:"created"`
Creator string `json:"creator"`
}
// ChannelJoinedEvent represents the Channel joined event
type ChannelJoinedEvent struct {
Type string `json:"type"`
Channel Channel `json:"channel"`
}
// ChannelInfoEvent represents the Channel info event
type ChannelInfoEvent struct {
// channel_left
// channel_deleted
// channel_archive
// channel_unarchive
Type string `json:"type"`
Channel string `json:"channel"`
User string `json:"user,omitempty"`
Timestamp string `json:"ts,omitempty"`
}
// ChannelRenameEvent represents the Channel rename event
type ChannelRenameEvent struct {
Type string `json:"type"`
Channel ChannelRenameInfo `json:"channel"`
Timestamp string `json:"event_ts"`
}
// ChannelRenameInfo represents the information associated with a Channel rename event
type ChannelRenameInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Created string `json:"created"`
}
// ChannelHistoryChangedEvent represents the Channel history changed event
type ChannelHistoryChangedEvent struct {
Type string `json:"type"`
Latest string `json:"latest"`
Timestamp string `json:"ts"`
EventTimestamp string `json:"event_ts"`
}
// ChannelMarkedEvent represents the Channel marked event
type ChannelMarkedEvent ChannelInfoEvent
// ChannelLeftEvent represents the Channel left event
type ChannelLeftEvent ChannelInfoEvent
// ChannelDeletedEvent represents the Channel deleted event
type ChannelDeletedEvent ChannelInfoEvent
// ChannelArchiveEvent represents the Channel archive event
type ChannelArchiveEvent ChannelInfoEvent
// ChannelUnarchiveEvent represents the Channel unarchive event
type ChannelUnarchiveEvent ChannelInfoEvent

23
vendor/github.com/nlopes/slack/websocket_dm.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
package slack
// IMCreatedEvent represents the IM created event
type IMCreatedEvent struct {
Type string `json:"type"`
User string `json:"user"`
Channel ChannelCreatedInfo `json:"channel"`
}
// IMHistoryChangedEvent represents the IM history changed event
type IMHistoryChangedEvent ChannelHistoryChangedEvent
// IMOpenEvent represents the IM open event
type IMOpenEvent ChannelInfoEvent
// IMCloseEvent represents the IM close event
type IMCloseEvent ChannelInfoEvent
// IMMarkedEvent represents the IM marked event
type IMMarkedEvent ChannelInfoEvent
// IMMarkedHistoryChanged represents the IM marked history changed event
type IMMarkedHistoryChanged ChannelInfoEvent

8
vendor/github.com/nlopes/slack/websocket_dnd.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
package slack
// DNDUpdatedEvent represents the update event for Do Not Disturb
type DNDUpdatedEvent struct {
Type string `json:"type"`
User string `json:"user"`
Status DNDStatus `json:"dnd_status"`
}

49
vendor/github.com/nlopes/slack/websocket_files.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package slack
// FileActionEvent represents the File action event
type fileActionEvent struct {
Type string `json:"type"`
EventTimestamp string `json:"event_ts"`
File File `json:"file"`
// FileID is used for FileDeletedEvent
FileID string `json:"file_id,omitempty"`
}
// FileCreatedEvent represents the File created event
type FileCreatedEvent fileActionEvent
// FileSharedEvent represents the File shared event
type FileSharedEvent fileActionEvent
// FilePublicEvent represents the File public event
type FilePublicEvent fileActionEvent
// FileUnsharedEvent represents the File unshared event
type FileUnsharedEvent fileActionEvent
// FileChangeEvent represents the File change event
type FileChangeEvent fileActionEvent
// FileDeletedEvent represents the File deleted event
type FileDeletedEvent fileActionEvent
// FilePrivateEvent represents the File private event
type FilePrivateEvent fileActionEvent
// FileCommentAddedEvent represents the File comment added event
type FileCommentAddedEvent struct {
fileActionEvent
Comment Comment `json:"comment"`
}
// FileCommentEditedEvent represents the File comment edited event
type FileCommentEditedEvent struct {
fileActionEvent
Comment Comment `json:"comment"`
}
// FileCommentDeletedEvent represents the File comment deleted event
type FileCommentDeletedEvent struct {
fileActionEvent
Comment string `json:"comment"`
}

49
vendor/github.com/nlopes/slack/websocket_groups.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package slack
// GroupCreatedEvent represents the Group created event
type GroupCreatedEvent struct {
Type string `json:"type"`
User string `json:"user"`
Channel ChannelCreatedInfo `json:"channel"`
}
// XXX: Should we really do this? event.Group is probably nicer than event.Channel
// even though the api returns "channel"
// GroupMarkedEvent represents the Group marked event
type GroupMarkedEvent ChannelInfoEvent
// GroupOpenEvent represents the Group open event
type GroupOpenEvent ChannelInfoEvent
// GroupCloseEvent represents the Group close event
type GroupCloseEvent ChannelInfoEvent
// GroupArchiveEvent represents the Group archive event
type GroupArchiveEvent ChannelInfoEvent
// GroupUnarchiveEvent represents the Group unarchive event
type GroupUnarchiveEvent ChannelInfoEvent
// GroupLeftEvent represents the Group left event
type GroupLeftEvent ChannelInfoEvent
// GroupJoinedEvent represents the Group joined event
type GroupJoinedEvent ChannelJoinedEvent
// GroupRenameEvent represents the Group rename event
type GroupRenameEvent struct {
Type string `json:"type"`
Group GroupRenameInfo `json:"channel"`
Timestamp string `json:"ts"`
}
// GroupRenameInfo represents the group info related to the renamed group
type GroupRenameInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Created string `json:"created"`
}
// GroupHistoryChangedEvent represents the Group history changed event
type GroupHistoryChangedEvent ChannelHistoryChangedEvent

92
vendor/github.com/nlopes/slack/websocket_internals.go generated vendored Normal file
View File

@ -0,0 +1,92 @@
package slack
import (
"fmt"
"time"
)
/**
* Internal events, created by this lib and not mapped to Slack APIs.
*/
// ConnectedEvent is used for when we connect to Slack
type ConnectedEvent struct {
ConnectionCount int // 1 = first time, 2 = second time
Info *Info
}
// ConnectionErrorEvent contains information about a connection error
type ConnectionErrorEvent struct {
Attempt int
ErrorObj error
}
func (c *ConnectionErrorEvent) Error() string {
return c.ErrorObj.Error()
}
// ConnectingEvent contains information about our connection attempt
type ConnectingEvent struct {
Attempt int // 1 = first attempt, 2 = second attempt
ConnectionCount int
}
// DisconnectedEvent contains information about how we disconnected
type DisconnectedEvent struct {
Intentional bool
}
// LatencyReport contains information about connection latency
type LatencyReport struct {
Value time.Duration
}
// InvalidAuthEvent is used in case we can't even authenticate with the API
type InvalidAuthEvent struct{}
// UnmarshallingErrorEvent is used when there are issues deconstructing a response
type UnmarshallingErrorEvent struct {
ErrorObj error
}
func (u UnmarshallingErrorEvent) Error() string {
return u.ErrorObj.Error()
}
// MessageTooLongEvent is used when sending a message that is too long
type MessageTooLongEvent struct {
Message OutgoingMessage
MaxLength int
}
func (m *MessageTooLongEvent) Error() string {
return fmt.Sprintf("Message too long (max %d characters)", m.MaxLength)
}
// OutgoingErrorEvent contains information in case there were errors sending messages
type OutgoingErrorEvent struct {
Message OutgoingMessage
ErrorObj error
}
func (o OutgoingErrorEvent) Error() string {
return o.ErrorObj.Error()
}
// IncomingEventError contains information about an unexpected error receiving a websocket event
type IncomingEventError struct {
ErrorObj error
}
func (i *IncomingEventError) Error() string {
return i.ErrorObj.Error()
}
// AckErrorEvent i
type AckErrorEvent struct {
ErrorObj error
}
func (a *AckErrorEvent) Error() string {
return a.ErrorObj.Error()
}

View File

@ -0,0 +1,440 @@
package slack
import (
"encoding/json"
"fmt"
"io"
"reflect"
"time"
"golang.org/x/net/websocket"
)
// ManageConnection can be called on a Slack RTM instance returned by the
// NewRTM method. It will connect to the slack RTM API and handle all incoming
// and outgoing events. If a connection fails then it will attempt to reconnect
// and will notify any listeners through an error event on the IncomingEvents
// channel.
//
// If the connection ends and the disconnect was unintentional then this will
// attempt to reconnect.
//
// This should only be called once per slack API! Otherwise expect undefined
// behavior.
//
// The defined error events are located in websocket_internals.go.
func (rtm *RTM) ManageConnection() {
var connectionCount int
for {
connectionCount++
// start trying to connect
// the returned err is already passed onto the IncomingEvents channel
info, conn, err := rtm.connect(connectionCount)
// if err != nil then the connection is sucessful - otherwise it is
// fatal
if err != nil {
return
}
rtm.info = info
rtm.IncomingEvents <- RTMEvent{"connected", &ConnectedEvent{
ConnectionCount: connectionCount,
Info: info,
}}
rtm.conn = conn
rtm.isConnected = true
keepRunning := make(chan bool)
// we're now connected (or have failed fatally) so we can set up
// listeners
go rtm.handleIncomingEvents(keepRunning)
// this should be a blocking call until the connection has ended
rtm.handleEvents(keepRunning, 30*time.Second)
// after being disconnected we need to check if it was intentional
// if not then we should try to reconnect
if rtm.wasIntentional {
return
}
// else continue and run the loop again to connect
}
}
// connect attempts to connect to the slack websocket API. It handles any
// errors that occur while connecting and will return once a connection
// has been successfully opened.
func (rtm *RTM) connect(connectionCount int) (*Info, *websocket.Conn, error) {
// used to provide exponential backoff wait time with jitter before trying
// to connect to slack again
boff := &backoff{
Min: 100 * time.Millisecond,
Max: 5 * time.Minute,
Factor: 2,
Jitter: true,
}
for {
// send connecting event
rtm.IncomingEvents <- RTMEvent{"connecting", &ConnectingEvent{
Attempt: boff.attempts + 1,
ConnectionCount: connectionCount,
}}
// attempt to start the connection
info, conn, err := rtm.startRTMAndDial()
if err == nil {
return info, conn, nil
}
// check for fatal errors - currently only invalid_auth
if sErr, ok := err.(*WebError); ok && (sErr.Error() == "invalid_auth" || sErr.Error() == "account_inactive") {
rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}}
return nil, nil, sErr
}
// any other errors are treated as recoverable and we try again after
// sending the event along the IncomingEvents channel
rtm.IncomingEvents <- RTMEvent{"connection_error", &ConnectionErrorEvent{
Attempt: boff.attempts,
ErrorObj: err,
}}
// get time we should wait before attempting to connect again
dur := boff.Duration()
rtm.Debugf("reconnection %d failed: %s", boff.attempts+1, err)
rtm.Debugln(" -> reconnecting in", dur)
time.Sleep(dur)
}
}
// startRTMAndDial attemps to connect to the slack websocket. It returns the
// full information returned by the "rtm.start" method on the slack API.
func (rtm *RTM) startRTMAndDial() (*Info, *websocket.Conn, error) {
info, url, err := rtm.StartRTM()
if err != nil {
return nil, nil, err
}
conn, err := websocketProxyDial(url, "http://api.slack.com")
if err != nil {
return nil, nil, err
}
return info, conn, err
}
// killConnection stops the websocket connection and signals to all goroutines
// that they should cease listening to the connection for events.
//
// This should not be called directly! Instead a boolean value (true for
// intentional, false otherwise) should be sent to the killChannel on the RTM.
func (rtm *RTM) killConnection(keepRunning chan bool, intentional bool) error {
rtm.Debugln("killing connection")
if rtm.isConnected {
close(keepRunning)
}
rtm.isConnected = false
rtm.wasIntentional = intentional
err := rtm.conn.Close()
rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{intentional}}
return err
}
// handleEvents is a blocking function that handles all events. This sends
// pings when asked to (on rtm.forcePing) and upon every given elapsed
// interval. This also sends outgoing messages that are received from the RTM's
// outgoingMessages channel. This also handles incoming raw events from the RTM
// rawEvents channel.
func (rtm *RTM) handleEvents(keepRunning chan bool, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
// catch "stop" signal on channel close
case intentional := <-rtm.killChannel:
_ = rtm.killConnection(keepRunning, intentional)
return
// send pings on ticker interval
case <-ticker.C:
err := rtm.ping()
if err != nil {
_ = rtm.killConnection(keepRunning, false)
return
}
case <-rtm.forcePing:
err := rtm.ping()
if err != nil {
_ = rtm.killConnection(keepRunning, false)
return
}
// listen for messages that need to be sent
case msg := <-rtm.outgoingMessages:
rtm.sendOutgoingMessage(msg)
// listen for incoming messages that need to be parsed
case rawEvent := <-rtm.rawEvents:
rtm.handleRawEvent(rawEvent)
}
}
}
// handleIncomingEvents monitors the RTM's opened websocket for any incoming
// events. It pushes the raw events onto the RTM channel rawEvents.
//
// This will stop executing once the RTM's keepRunning channel has been closed
// or has anything sent to it.
func (rtm *RTM) handleIncomingEvents(keepRunning <-chan bool) {
for {
// non-blocking listen to see if channel is closed
select {
// catch "stop" signal on channel close
case <-keepRunning:
return
default:
rtm.receiveIncomingEvent()
}
}
}
func (rtm *RTM) sendWithDeadline(msg interface{}) error {
// set a write deadline on the connection
if err := rtm.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)); err != nil {
return err
}
if err := websocket.JSON.Send(rtm.conn, msg); err != nil {
return err
}
// remove write deadline
return rtm.conn.SetWriteDeadline(time.Time{})
}
// sendOutgoingMessage sends the given OutgoingMessage to the slack websocket.
//
// It does not currently detect if a outgoing message fails due to a disconnect
// and instead lets a future failed 'PING' detect the failed connection.
func (rtm *RTM) sendOutgoingMessage(msg OutgoingMessage) {
rtm.Debugln("Sending message:", msg)
if len(msg.Text) > MaxMessageTextLength {
rtm.IncomingEvents <- RTMEvent{"outgoing_error", &MessageTooLongEvent{
Message: msg,
MaxLength: MaxMessageTextLength,
}}
return
}
if err := rtm.sendWithDeadline(msg); err != nil {
rtm.IncomingEvents <- RTMEvent{"outgoing_error", &OutgoingErrorEvent{
Message: msg,
ErrorObj: err,
}}
// TODO force ping?
}
}
// ping sends a 'PING' message to the RTM's websocket. If the 'PING' message
// fails to send then this returns an error signifying that the connection
// should be considered disconnected.
//
// This does not handle incoming 'PONG' responses but does store the time of
// each successful 'PING' send so latency can be detected upon a 'PONG'
// response.
func (rtm *RTM) ping() error {
id := rtm.idGen.Next()
rtm.Debugln("Sending PING ", id)
rtm.pings[id] = time.Now()
msg := &Ping{ID: id, Type: "ping"}
if err := rtm.sendWithDeadline(msg); err != nil {
rtm.Debugf("RTM Error sending 'PING %d': %s", id, err.Error())
return err
}
return nil
}
// receiveIncomingEvent attempts to receive an event from the RTM's websocket.
// This will block until a frame is available from the websocket.
func (rtm *RTM) receiveIncomingEvent() {
event := json.RawMessage{}
err := websocket.JSON.Receive(rtm.conn, &event)
if err == io.EOF {
// EOF's don't seem to signify a failed connection so instead we ignore
// them here and detect a failed connection upon attempting to send a
// 'PING' message
// trigger a 'PING' to detect pontential websocket disconnect
rtm.forcePing <- true
return
} else if err != nil {
rtm.IncomingEvents <- RTMEvent{"incoming_error", &IncomingEventError{
ErrorObj: err,
}}
// force a ping here too?
return
} else if len(event) == 0 {
rtm.Debugln("Received empty event")
return
}
rtm.Debugln("Incoming Event:", string(event[:]))
rtm.rawEvents <- event
}
// handleRawEvent takes a raw JSON message received from the slack websocket
// and handles the encoded event.
func (rtm *RTM) handleRawEvent(rawEvent json.RawMessage) {
event := &Event{}
err := json.Unmarshal(rawEvent, event)
if err != nil {
rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
return
}
switch event.Type {
case "":
rtm.handleAck(rawEvent)
case "hello":
rtm.IncomingEvents <- RTMEvent{"hello", &HelloEvent{}}
case "pong":
rtm.handlePong(rawEvent)
default:
rtm.handleEvent(event.Type, rawEvent)
}
}
// handleAck handles an incoming 'ACK' message.
func (rtm *RTM) handleAck(event json.RawMessage) {
ack := &AckMessage{}
if err := json.Unmarshal(event, ack); err != nil {
rtm.Debugln("RTM Error unmarshalling 'ack' event:", err)
rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
return
}
if ack.Ok {
rtm.IncomingEvents <- RTMEvent{"ack", ack}
} else {
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
}
}
// handlePong handles an incoming 'PONG' message which should be in response to
// a previously sent 'PING' message. This is then used to compute the
// connection's latency.
func (rtm *RTM) handlePong(event json.RawMessage) {
pong := &Pong{}
if err := json.Unmarshal(event, pong); err != nil {
rtm.Debugln("RTM Error unmarshalling 'pong' event:", err)
rtm.Debugln(" -> Erroneous 'ping' event:", string(event))
return
}
if pingTime, exists := rtm.pings[pong.ReplyTo]; exists {
latency := time.Since(pingTime)
rtm.IncomingEvents <- RTMEvent{"latency_report", &LatencyReport{Value: latency}}
delete(rtm.pings, pong.ReplyTo)
} else {
rtm.Debugln("RTM Error - unmatched 'pong' event:", string(event))
}
}
// handleEvent is the "default" response to an event that does not have a
// special case. It matches the command's name to a mapping of defined events
// and then sends the corresponding event struct to the IncomingEvents channel.
// If the event type is not found or the event cannot be unmarshalled into the
// correct struct then this sends an UnmarshallingErrorEvent to the
// IncomingEvents channel.
func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) {
v, exists := eventMapping[typeStr]
if !exists {
rtm.Debugf("RTM Error, received unmapped event %q: %s\n", typeStr, string(event))
err := fmt.Errorf("RTM Error: Received unmapped event %q: %s\n", typeStr, string(event))
rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
return
}
t := reflect.TypeOf(v)
recvEvent := reflect.New(t).Interface()
err := json.Unmarshal(event, recvEvent)
if err != nil {
rtm.Debugf("RTM Error, could not unmarshall event %q: %s\n", typeStr, string(event))
err := fmt.Errorf("RTM Error: Could not unmarshall event %q: %s\n", typeStr, string(event))
rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
return
}
rtm.IncomingEvents <- RTMEvent{typeStr, recvEvent}
}
// eventMapping holds a mapping of event names to their corresponding struct
// implementations. The structs should be instances of the unmarshalling
// target for the matching event type.
var eventMapping = map[string]interface{}{
"message": MessageEvent{},
"presence_change": PresenceChangeEvent{},
"user_typing": UserTypingEvent{},
"channel_marked": ChannelMarkedEvent{},
"channel_created": ChannelCreatedEvent{},
"channel_joined": ChannelJoinedEvent{},
"channel_left": ChannelLeftEvent{},
"channel_deleted": ChannelDeletedEvent{},
"channel_rename": ChannelRenameEvent{},
"channel_archive": ChannelArchiveEvent{},
"channel_unarchive": ChannelUnarchiveEvent{},
"channel_history_changed": ChannelHistoryChangedEvent{},
"dnd_updated": DNDUpdatedEvent{},
"dnd_updated_user": DNDUpdatedEvent{},
"im_created": IMCreatedEvent{},
"im_open": IMOpenEvent{},
"im_close": IMCloseEvent{},
"im_marked": IMMarkedEvent{},
"im_history_changed": IMHistoryChangedEvent{},
"group_marked": GroupMarkedEvent{},
"group_open": GroupOpenEvent{},
"group_joined": GroupJoinedEvent{},
"group_left": GroupLeftEvent{},
"group_close": GroupCloseEvent{},
"group_rename": GroupRenameEvent{},
"group_archive": GroupArchiveEvent{},
"group_unarchive": GroupUnarchiveEvent{},
"group_history_changed": GroupHistoryChangedEvent{},
"file_created": FileCreatedEvent{},
"file_shared": FileSharedEvent{},
"file_unshared": FileUnsharedEvent{},
"file_public": FilePublicEvent{},
"file_private": FilePrivateEvent{},
"file_change": FileChangeEvent{},
"file_deleted": FileDeletedEvent{},
"file_comment_added": FileCommentAddedEvent{},
"file_comment_edited": FileCommentEditedEvent{},
"file_comment_deleted": FileCommentDeletedEvent{},
"pin_added": PinAddedEvent{},
"pin_removed": PinRemovedEvent{},
"star_added": StarAddedEvent{},
"star_removed": StarRemovedEvent{},
"reaction_added": ReactionAddedEvent{},
"reaction_removed": ReactionRemovedEvent{},
"pref_change": PrefChangeEvent{},
"team_join": TeamJoinEvent{},
"team_rename": TeamRenameEvent{},
"team_pref_change": TeamPrefChangeEvent{},
"team_domain_change": TeamDomainChangeEvent{},
"team_migration_started": TeamMigrationStartedEvent{},
"manual_presence_change": ManualPresenceChangeEvent{},
"user_change": UserChangeEvent{},
"emoji_changed": EmojiChangedEvent{},
"commands_changed": CommandsChangedEvent{},
"email_domain_changed": EmailDomainChangedEvent{},
"bot_added": BotAddedEvent{},
"bot_changed": BotChangedEvent{},
"accounts_changed": AccountsChangedEvent{},
"reconnect_url": ReconnectUrlEvent{},
}

117
vendor/github.com/nlopes/slack/websocket_misc.go generated vendored Normal file
View File

@ -0,0 +1,117 @@
package slack
import (
"encoding/json"
"fmt"
)
// AckMessage is used for messages received in reply to other messages
type AckMessage struct {
ReplyTo int `json:"reply_to"`
Timestamp string `json:"ts"`
Text string `json:"text"`
RTMResponse
}
// RTMResponse encapsulates response details as returned by the Slack API
type RTMResponse struct {
Ok bool `json:"ok"`
Error *RTMError `json:"error"`
}
// RTMError encapsulates error information as returned by the Slack API
type RTMError struct {
Code int
Msg string
}
func (s RTMError) Error() string {
return fmt.Sprintf("Code %d - %s", s.Code, s.Msg)
}
// MessageEvent represents a Slack Message (used as the event type for an incoming message)
type MessageEvent Message
// RTMEvent is the main wrapper. You will find all the other messages attached
type RTMEvent struct {
Type string
Data interface{}
}
// HelloEvent represents the hello event
type HelloEvent struct{}
// PresenceChangeEvent represents the presence change event
type PresenceChangeEvent struct {
Type string `json:"type"`
Presence string `json:"presence"`
User string `json:"user"`
}
// UserTypingEvent represents the user typing event
type UserTypingEvent struct {
Type string `json:"type"`
User string `json:"user"`
Channel string `json:"channel"`
}
// PrefChangeEvent represents a user preferences change event
type PrefChangeEvent struct {
Type string `json:"type"`
Name string `json:"name"`
Value json.RawMessage `json:"value"`
}
// ManualPresenceChangeEvent represents the manual presence change event
type ManualPresenceChangeEvent struct {
Type string `json:"type"`
Presence string `json:"presence"`
}
// UserChangeEvent represents the user change event
type UserChangeEvent struct {
Type string `json:"type"`
User User `json:"user"`
}
// EmojiChangedEvent represents the emoji changed event
type EmojiChangedEvent struct {
Type string `json:"type"`
EventTimestamp string `json:"event_ts"`
}
// CommandsChangedEvent represents the commands changed event
type CommandsChangedEvent struct {
Type string `json:"type"`
EventTimestamp string `json:"event_ts"`
}
// EmailDomainChangedEvent represents the email domain changed event
type EmailDomainChangedEvent struct {
Type string `json:"type"`
EventTimestamp string `json:"event_ts"`
EmailDomain string `json:"email_domain"`
}
// BotAddedEvent represents the bot added event
type BotAddedEvent struct {
Type string `json:"type"`
Bot Bot `json:"bot"`
}
// BotChangedEvent represents the bot changed event
type BotChangedEvent struct {
Type string `json:"type"`
Bot Bot `json:"bot"`
}
// AccountsChangedEvent represents the accounts changed event
type AccountsChangedEvent struct {
Type string `json:"type"`
}
// ReconnectUrlEvent represents the receiving reconnect url event
type ReconnectUrlEvent struct {
Type string `json:"type"`
URL string `json:"url"`
}

16
vendor/github.com/nlopes/slack/websocket_pins.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
package slack
type pinEvent struct {
Type string `json:"type"`
User string `json:"user"`
Item Item `json:"item"`
Channel string `json:"channel_id"`
EventTimestamp string `json:"event_ts"`
HasPins bool `json:"has_pins,omitempty"`
}
// PinAddedEvent represents the Pin added event
type PinAddedEvent pinEvent
// PinRemovedEvent represents the Pin removed event
type PinRemovedEvent pinEvent

83
vendor/github.com/nlopes/slack/websocket_proxy.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
package slack
import (
"crypto/tls"
"errors"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"golang.org/x/net/websocket"
)
// Taken and reworked from: https://gist.github.com/madmo/8548738
func websocketHTTPConnect(proxy, urlString string) (net.Conn, error) {
p, err := net.Dial("tcp", proxy)
if err != nil {
return nil, err
}
turl, err := url.Parse(urlString)
if err != nil {
return nil, err
}
req := http.Request{
Method: "CONNECT",
URL: &url.URL{},
Host: turl.Host,
}
cc := httputil.NewProxyClientConn(p, nil)
cc.Do(&req)
if err != nil && err != httputil.ErrPersistEOF {
return nil, err
}
rwc, _ := cc.Hijack()
return rwc, nil
}
func websocketProxyDial(urlString, origin string) (ws *websocket.Conn, err error) {
if os.Getenv("HTTP_PROXY") == "" {
return websocket.Dial(urlString, "", origin)
}
purl, err := url.Parse(os.Getenv("HTTP_PROXY"))
if err != nil {
return nil, err
}
config, err := websocket.NewConfig(urlString, origin)
if err != nil {
return nil, err
}
client, err := websocketHTTPConnect(purl.Host, urlString)
if err != nil {
return nil, err
}
switch config.Location.Scheme {
case "ws":
case "wss":
tlsClient := tls.Client(client, &tls.Config{
ServerName: strings.Split(config.Location.Host, ":")[0],
})
err := tlsClient.Handshake()
if err != nil {
tlsClient.Close()
return nil, err
}
client = tlsClient
default:
return nil, errors.New("invalid websocket schema")
}
return websocket.NewClient(config, client)
}

25
vendor/github.com/nlopes/slack/websocket_reactions.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package slack
// reactionItem is a lighter-weight item than is returned by the reactions list.
type reactionItem struct {
Type string `json:"type"`
Channel string `json:"channel,omitempty"`
File string `json:"file,omitempty"`
FileComment string `json:"file_comment,omitempty"`
Timestamp string `json:"ts,omitempty"`
}
type reactionEvent struct {
Type string `json:"type"`
User string `json:"user"`
ItemUser string `json:"item_user"`
Item reactionItem `json:"item"`
Reaction string `json:"reaction"`
EventTimestamp string `json:"event_ts"`
}
// ReactionAddedEvent represents the Reaction added event
type ReactionAddedEvent reactionEvent
// ReactionRemovedEvent represents the Reaction removed event
type ReactionRemovedEvent reactionEvent

14
vendor/github.com/nlopes/slack/websocket_stars.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
package slack
type starEvent struct {
Type string `json:"type"`
User string `json:"user"`
Item StarredItem `json:"item"`
EventTimestamp string `json:"event_ts"`
}
// StarAddedEvent represents the Star added event
type StarAddedEvent starEvent
// StarRemovedEvent represents the Star removed event
type StarRemovedEvent starEvent

33
vendor/github.com/nlopes/slack/websocket_teams.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
package slack
// TeamJoinEvent represents the Team join event
type TeamJoinEvent struct {
Type string `json:"type"`
User User `json:"user"`
}
// TeamRenameEvent represents the Team rename event
type TeamRenameEvent struct {
Type string `json:"type"`
Name string `json:"name,omitempty"`
EventTimestamp string `json:"event_ts,omitempty"`
}
// TeamPrefChangeEvent represents the Team preference change event
type TeamPrefChangeEvent struct {
Type string `json:"type"`
Name string `json:"name,omitempty"`
Value []string `json:"value,omitempty"`
}
// TeamDomainChangeEvent represents the Team domain change event
type TeamDomainChangeEvent struct {
Type string `json:"type"`
URL string `json:"url"`
Domain string `json:"domain"`
}
// TeamMigrationStartedEvent represents the Team migration started event
type TeamMigrationStartedEvent struct {
Type string `json:"type"`
}

20
vendor/github.com/nlopes/slack/websocket_utils.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
package slack
import (
"net"
"net/url"
)
var portMapping = map[string]string{"ws": "80", "wss": "443"}
func websocketizeURLPort(orig string) (string, error) {
urlObj, err := url.ParseRequestURI(orig)
if err != nil {
return "", err
}
_, _, err = net.SplitHostPort(urlObj.Host)
if err != nil {
return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
}
return orig, nil
}

4
vendor/github.com/nsf/termbox-go/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,4 @@
# Please keep this file sorted.
Georg Reinke <guelfey@googlemail.com>
nsf <no.smile.face@gmail.com>

19
vendor/github.com/nsf/termbox-go/LICENSE generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (C) 2012 termbox-go authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

32
vendor/github.com/nsf/termbox-go/README.md generated vendored Normal file
View File

@ -0,0 +1,32 @@
## Termbox
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.
### Installation
Install and update this go package with `go get -u github.com/nsf/termbox-go`
### Examples
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`
There are also some interesting projects using termbox-go:
- [godit](https://github.com/nsf/godit) is an emacsish lightweight text editor written using termbox.
- [gomatrix](https://github.com/GeertJohan/gomatrix) connects to The Matrix and displays its data streams in your terminal.
- [gotetris](https://github.com/jjinux/gotetris) is an implementation of Tetris.
- [sokoban-go](https://github.com/rn2dy/sokoban-go) is an implementation of sokoban game.
- [hecate](https://github.com/evanmiller/hecate) is a hex editor designed by Satan.
- [httopd](https://github.com/verdverm/httopd) is top for httpd logs.
- [mop](https://github.com/michaeldv/mop) is stock market tracker for hackers.
- [termui](https://github.com/gizak/termui) is a terminal dashboard.
- [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine.
- [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart.
- [gocui](https://github.com/jroimartin/gocui) is a minimalist Go library aimed at creating console user interfaces.
- [dry](https://github.com/moncho/dry) is an interactive cli to manage Docker containers.
- [pxl](https://github.com/ichinaski/pxl) displays images in the terminal.
- [snake-game](https://github.com/DyegoCosta/snake-game) is an implementation of the Snake game.
- [gone](https://github.com/guillaumebreton/gone) is a CLI pomodoro® timer.
- [Spoof.go](https://github.com/sabey/spoofgo) controllable movement spoofing from the cli
- [lf](https://github.com/gokcehan/lf) is a terminal file manager
- [rat](https://github.com/ericfreese/rat) lets you compose shell commands to build terminal applications.
- [httplab](https://github.com/gchaincl/httplab) An interactive web server.
### API reference
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)

457
vendor/github.com/nsf/termbox-go/api.go generated vendored Normal file
View File

@ -0,0 +1,457 @@
// +build !windows
package termbox
import "github.com/mattn/go-runewidth"
import "fmt"
import "os"
import "os/signal"
import "syscall"
import "runtime"
// public API
// Initializes termbox library. This function should be called before any other functions.
// After successful initialization, the library must be finalized using 'Close' function.
//
// Example usage:
// err := termbox.Init()
// if err != nil {
// panic(err)
// }
// defer termbox.Close()
func Init() error {
var err error
out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
if err != nil {
return err
}
in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
if err != nil {
return err
}
err = setup_term()
if err != nil {
return fmt.Errorf("termbox: error while reading terminfo data: %v", err)
}
signal.Notify(sigwinch, syscall.SIGWINCH)
signal.Notify(sigio, syscall.SIGIO)
_, err = fcntl(in, syscall.F_SETFL, syscall.O_ASYNC|syscall.O_NONBLOCK)
if err != nil {
return err
}
_, err = fcntl(in, syscall.F_SETOWN, syscall.Getpid())
if runtime.GOOS != "darwin" && err != nil {
return err
}
err = tcgetattr(out.Fd(), &orig_tios)
if err != nil {
return err
}
tios := orig_tios
tios.Iflag &^= syscall_IGNBRK | syscall_BRKINT | syscall_PARMRK |
syscall_ISTRIP | syscall_INLCR | syscall_IGNCR |
syscall_ICRNL | syscall_IXON
tios.Lflag &^= syscall_ECHO | syscall_ECHONL | syscall_ICANON |
syscall_ISIG | syscall_IEXTEN
tios.Cflag &^= syscall_CSIZE | syscall_PARENB
tios.Cflag |= syscall_CS8
tios.Cc[syscall_VMIN] = 1
tios.Cc[syscall_VTIME] = 0
err = tcsetattr(out.Fd(), &tios)
if err != nil {
return err
}
out.WriteString(funcs[t_enter_ca])
out.WriteString(funcs[t_enter_keypad])
out.WriteString(funcs[t_hide_cursor])
out.WriteString(funcs[t_clear_screen])
termw, termh = get_term_size(out.Fd())
back_buffer.init(termw, termh)
front_buffer.init(termw, termh)
back_buffer.clear()
front_buffer.clear()
go func() {
buf := make([]byte, 128)
for {
select {
case <-sigio:
for {
n, err := syscall.Read(in, buf)
if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK {
break
}
select {
case input_comm <- input_event{buf[:n], err}:
ie := <-input_comm
buf = ie.data[:128]
case <-quit:
return
}
}
case <-quit:
return
}
}
}()
IsInit = true
return nil
}
// Interrupt an in-progress call to PollEvent by causing it to return
// EventInterrupt. Note that this function will block until the PollEvent
// function has successfully been interrupted.
func Interrupt() {
interrupt_comm <- struct{}{}
}
// Finalizes termbox library, should be called after successful initialization
// when termbox's functionality isn't required anymore.
func Close() {
quit <- 1
out.WriteString(funcs[t_show_cursor])
out.WriteString(funcs[t_sgr0])
out.WriteString(funcs[t_clear_screen])
out.WriteString(funcs[t_exit_ca])
out.WriteString(funcs[t_exit_keypad])
out.WriteString(funcs[t_exit_mouse])
tcsetattr(out.Fd(), &orig_tios)
out.Close()
syscall.Close(in)
// reset the state, so that on next Init() it will work again
termw = 0
termh = 0
input_mode = InputEsc
out = nil
in = 0
lastfg = attr_invalid
lastbg = attr_invalid
lastx = coord_invalid
lasty = coord_invalid
cursor_x = cursor_hidden
cursor_y = cursor_hidden
foreground = ColorDefault
background = ColorDefault
IsInit = false
}
// Synchronizes the internal back buffer with the terminal.
func Flush() error {
// invalidate cursor position
lastx = coord_invalid
lasty = coord_invalid
update_size_maybe()
for y := 0; y < front_buffer.height; y++ {
line_offset := y * front_buffer.width
for x := 0; x < front_buffer.width; {
cell_offset := line_offset + x
back := &back_buffer.cells[cell_offset]
front := &front_buffer.cells[cell_offset]
if back.Ch < ' ' {
back.Ch = ' '
}
w := runewidth.RuneWidth(back.Ch)
if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) {
w = 1
}
if *back == *front {
x += w
continue
}
*front = *back
send_attr(back.Fg, back.Bg)
if w == 2 && x == front_buffer.width-1 {
// there's not enough space for 2-cells rune,
// let's just put a space in there
send_char(x, y, ' ')
} else {
send_char(x, y, back.Ch)
if w == 2 {
next := cell_offset + 1
front_buffer.cells[next] = Cell{
Ch: 0,
Fg: back.Fg,
Bg: back.Bg,
}
}
}
x += w
}
}
if !is_cursor_hidden(cursor_x, cursor_y) {
write_cursor(cursor_x, cursor_y)
}
return flush()
}
// Sets the position of the cursor. See also HideCursor().
func SetCursor(x, y int) {
if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) {
outbuf.WriteString(funcs[t_show_cursor])
}
if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) {
outbuf.WriteString(funcs[t_hide_cursor])
}
cursor_x, cursor_y = x, y
if !is_cursor_hidden(cursor_x, cursor_y) {
write_cursor(cursor_x, cursor_y)
}
}
// The shortcut for SetCursor(-1, -1).
func HideCursor() {
SetCursor(cursor_hidden, cursor_hidden)
}
// Changes cell's parameters in the internal back buffer at the specified
// position.
func SetCell(x, y int, ch rune, fg, bg Attribute) {
if x < 0 || x >= back_buffer.width {
return
}
if y < 0 || y >= back_buffer.height {
return
}
back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
}
// Returns a slice into the termbox's back buffer. You can get its dimensions
// using 'Size' function. The slice remains valid as long as no 'Clear' or
// 'Flush' function calls were made after call to this function.
func CellBuffer() []Cell {
return back_buffer.cells
}
// After getting a raw event from PollRawEvent function call, you can parse it
// again into an ordinary one using termbox logic. That is parse an event as
// termbox would do it. Returned event in addition to usual Event struct fields
// sets N field to the amount of bytes used within 'data' slice. If the length
// of 'data' slice is zero or event cannot be parsed for some other reason, the
// function will return a special event type: EventNone.
//
// IMPORTANT: EventNone may contain a non-zero N, which means you should skip
// these bytes, because termbox cannot recognize them.
//
// NOTE: This API is experimental and may change in future.
func ParseEvent(data []byte) Event {
event := Event{Type: EventKey}
ok := extract_event(data, &event)
if !ok {
return Event{Type: EventNone, N: event.N}
}
return event
}
// Wait for an event and return it. This is a blocking function call. Instead
// of EventKey and EventMouse it returns EventRaw events. Raw event is written
// into `data` slice and Event's N field is set to the amount of bytes written.
// The minimum required length of the 'data' slice is 1. This requirement may
// vary on different platforms.
//
// NOTE: This API is experimental and may change in future.
func PollRawEvent(data []byte) Event {
if len(data) == 0 {
panic("len(data) >= 1 is a requirement")
}
var event Event
if extract_raw_event(data, &event) {
return event
}
for {
select {
case ev := <-input_comm:
if ev.err != nil {
return Event{Type: EventError, Err: ev.err}
}
inbuf = append(inbuf, ev.data...)
input_comm <- ev
if extract_raw_event(data, &event) {
return event
}
case <-interrupt_comm:
event.Type = EventInterrupt
return event
case <-sigwinch:
event.Type = EventResize
event.Width, event.Height = get_term_size(out.Fd())
return event
}
}
}
// Wait for an event and return it. This is a blocking function call.
func PollEvent() Event {
var event Event
// try to extract event from input buffer, return on success
event.Type = EventKey
ok := extract_event(inbuf, &event)
if event.N != 0 {
copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N]
}
if ok {
return event
}
for {
select {
case ev := <-input_comm:
if ev.err != nil {
return Event{Type: EventError, Err: ev.err}
}
inbuf = append(inbuf, ev.data...)
input_comm <- ev
ok := extract_event(inbuf, &event)
if event.N != 0 {
copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N]
}
if ok {
return event
}
case <-interrupt_comm:
event.Type = EventInterrupt
return event
case <-sigwinch:
event.Type = EventResize
event.Width, event.Height = get_term_size(out.Fd())
return event
}
}
}
// Returns the size of the internal back buffer (which is mostly the same as
// terminal's window size in characters). But it doesn't always match the size
// of the terminal window, after the terminal size has changed, the internal
// back buffer will get in sync only after Clear or Flush function calls.
func Size() (width int, height int) {
return termw, termh
}
// Clears the internal back buffer.
func Clear(fg, bg Attribute) error {
foreground, background = fg, bg
err := update_size_maybe()
back_buffer.clear()
return err
}
// Sets termbox input mode. Termbox has two input modes:
//
// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
// any known sequence. ESC means KeyEsc. This is the default input mode.
//
// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
// any known sequence. ESC enables ModAlt modifier for the next keyboard event.
//
// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
// enable mouse button press/release and drag events.
//
// If 'mode' is InputCurrent, returns the current input mode. See also Input*
// constants.
func SetInputMode(mode InputMode) InputMode {
if mode == InputCurrent {
return input_mode
}
if mode&(InputEsc|InputAlt) == 0 {
mode |= InputEsc
}
if mode&(InputEsc|InputAlt) == InputEsc|InputAlt {
mode &^= InputAlt
}
if mode&InputMouse != 0 {
out.WriteString(funcs[t_enter_mouse])
} else {
out.WriteString(funcs[t_exit_mouse])
}
input_mode = mode
return input_mode
}
// Sets the termbox output mode. Termbox has four output options:
//
// 1. OutputNormal => [1..8]
// This mode provides 8 different colors:
// black, red, green, yellow, blue, magenta, cyan, white
// Shortcut: ColorBlack, ColorRed, ...
// Attributes: AttrBold, AttrUnderline, AttrReverse
//
// Example usage:
// SetCell(x, y, '@', ColorBlack | AttrBold, ColorRed);
//
// 2. Output256 => [1..256]
// In this mode you can leverage the 256 terminal mode:
// 0x01 - 0x08: the 8 colors as in OutputNormal
// 0x09 - 0x10: Color* | AttrBold
// 0x11 - 0xe8: 216 different colors
// 0xe9 - 0x1ff: 24 different shades of grey
//
// Example usage:
// SetCell(x, y, '@', 184, 240);
// SetCell(x, y, '@', 0xb8, 0xf0);
//
// 3. Output216 => [1..216]
// This mode supports the 3rd range of the 256 mode only.
// But you dont need to provide an offset.
//
// 4. OutputGrayscale => [1..26]
// This mode supports the 4th range of the 256 mode
// and black and white colors from 3th range of the 256 mode
// But you dont need to provide an offset.
//
// In all modes, 0x00 represents the default color.
//
// `go run _demos/output.go` to see its impact on your terminal.
//
// If 'mode' is OutputCurrent, it returns the current output mode.
//
// Note that this may return a different OutputMode than the one requested,
// as the requested mode may not be available on the target platform.
func SetOutputMode(mode OutputMode) OutputMode {
if mode == OutputCurrent {
return output_mode
}
output_mode = mode
return output_mode
}
// Sync comes handy when something causes desync between termbox's understanding
// of a terminal buffer and the reality. Such as a third party process. Sync
// forces a complete resync between the termbox and a terminal, it may not be
// visually pretty though.
func Sync() error {
front_buffer.clear()
err := send_clear()
if err != nil {
return err
}
return Flush()
}

187
vendor/github.com/nsf/termbox-go/api_common.go generated vendored Normal file
View File

@ -0,0 +1,187 @@
// termbox is a library for creating cross-platform text-based interfaces
package termbox
// public API, common OS agnostic part
type (
InputMode int
OutputMode int
EventType uint8
Modifier uint8
Key uint16
Attribute uint16
)
// This type represents a termbox event. The 'Mod', 'Key' and 'Ch' fields are
// valid if 'Type' is EventKey. The 'Width' and 'Height' fields are valid if
// 'Type' is EventResize. The 'Err' field is valid if 'Type' is EventError.
type Event struct {
Type EventType // one of Event* constants
Mod Modifier // one of Mod* constants or 0
Key Key // one of Key* constants, invalid if 'Ch' is not 0
Ch rune // a unicode character
Width int // width of the screen
Height int // height of the screen
Err error // error in case if input failed
MouseX int // x coord of mouse
MouseY int // y coord of mouse
N int // number of bytes written when getting a raw event
}
// A cell, single conceptual entity on the screen. The screen is basically a 2d
// array of cells. 'Ch' is a unicode character, 'Fg' and 'Bg' are foreground
// and background attributes respectively.
type Cell struct {
Ch rune
Fg Attribute
Bg Attribute
}
// To know if termbox has been initialized or not
var (
IsInit bool = false
)
// Key constants, see Event.Key field.
const (
KeyF1 Key = 0xFFFF - iota
KeyF2
KeyF3
KeyF4
KeyF5
KeyF6
KeyF7
KeyF8
KeyF9
KeyF10
KeyF11
KeyF12
KeyInsert
KeyDelete
KeyHome
KeyEnd
KeyPgup
KeyPgdn
KeyArrowUp
KeyArrowDown
KeyArrowLeft
KeyArrowRight
key_min // see terminfo
MouseLeft
MouseMiddle
MouseRight
MouseRelease
MouseWheelUp
MouseWheelDown
)
const (
KeyCtrlTilde Key = 0x00
KeyCtrl2 Key = 0x00
KeyCtrlSpace Key = 0x00
KeyCtrlA Key = 0x01
KeyCtrlB Key = 0x02
KeyCtrlC Key = 0x03
KeyCtrlD Key = 0x04
KeyCtrlE Key = 0x05
KeyCtrlF Key = 0x06
KeyCtrlG Key = 0x07
KeyBackspace Key = 0x08
KeyCtrlH Key = 0x08
KeyTab Key = 0x09
KeyCtrlI Key = 0x09
KeyCtrlJ Key = 0x0A
KeyCtrlK Key = 0x0B
KeyCtrlL Key = 0x0C
KeyEnter Key = 0x0D
KeyCtrlM Key = 0x0D
KeyCtrlN Key = 0x0E
KeyCtrlO Key = 0x0F
KeyCtrlP Key = 0x10
KeyCtrlQ Key = 0x11
KeyCtrlR Key = 0x12
KeyCtrlS Key = 0x13
KeyCtrlT Key = 0x14
KeyCtrlU Key = 0x15
KeyCtrlV Key = 0x16
KeyCtrlW Key = 0x17
KeyCtrlX Key = 0x18
KeyCtrlY Key = 0x19
KeyCtrlZ Key = 0x1A
KeyEsc Key = 0x1B
KeyCtrlLsqBracket Key = 0x1B
KeyCtrl3 Key = 0x1B
KeyCtrl4 Key = 0x1C
KeyCtrlBackslash Key = 0x1C
KeyCtrl5 Key = 0x1D
KeyCtrlRsqBracket Key = 0x1D
KeyCtrl6 Key = 0x1E
KeyCtrl7 Key = 0x1F
KeyCtrlSlash Key = 0x1F
KeyCtrlUnderscore Key = 0x1F
KeySpace Key = 0x20
KeyBackspace2 Key = 0x7F
KeyCtrl8 Key = 0x7F
)
// Alt modifier constant, see Event.Mod field and SetInputMode function.
const (
ModAlt Modifier = 1 << iota
ModMotion
)
// Cell colors, you can combine a color with multiple attributes using bitwise
// OR ('|').
const (
ColorDefault Attribute = iota
ColorBlack
ColorRed
ColorGreen
ColorYellow
ColorBlue
ColorMagenta
ColorCyan
ColorWhite
)
// Cell attributes, it is possible to use multiple attributes by combining them
// using bitwise OR ('|'). Although, colors cannot be combined. But you can
// combine attributes and a single color.
//
// It's worth mentioning that some platforms don't support certain attibutes.
// For example windows console doesn't support AttrUnderline. And on some
// terminals applying AttrBold to background may result in blinking text. Use
// them with caution and test your code on various terminals.
const (
AttrBold Attribute = 1 << (iota + 9)
AttrUnderline
AttrReverse
)
// Input mode. See SetInputMode function.
const (
InputEsc InputMode = 1 << iota
InputAlt
InputMouse
InputCurrent InputMode = 0
)
// Output mode. See SetOutputMode function.
const (
OutputCurrent OutputMode = iota
OutputNormal
Output256
Output216
OutputGrayscale
)
// Event type. See Event.Type field.
const (
EventKey EventType = iota
EventResize
EventMouse
EventError
EventInterrupt
EventRaw
EventNone
)

239
vendor/github.com/nsf/termbox-go/api_windows.go generated vendored Normal file
View File

@ -0,0 +1,239 @@
package termbox
import (
"syscall"
)
// public API
// Initializes termbox library. This function should be called before any other functions.
// After successful initialization, the library must be finalized using 'Close' function.
//
// Example usage:
// err := termbox.Init()
// if err != nil {
// panic(err)
// }
// defer termbox.Close()
func Init() error {
var err error
interrupt, err = create_event()
if err != nil {
return err
}
in, err = syscall.Open("CONIN$", syscall.O_RDWR, 0)
if err != nil {
return err
}
out, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0)
if err != nil {
return err
}
err = get_console_mode(in, &orig_mode)
if err != nil {
return err
}
err = set_console_mode(in, enable_window_input)
if err != nil {
return err
}
orig_size = get_term_size(out)
win_size := get_win_size(out)
err = set_console_screen_buffer_size(out, win_size)
if err != nil {
return err
}
err = get_console_cursor_info(out, &orig_cursor_info)
if err != nil {
return err
}
show_cursor(false)
term_size = get_term_size(out)
back_buffer.init(int(term_size.x), int(term_size.y))
front_buffer.init(int(term_size.x), int(term_size.y))
back_buffer.clear()
front_buffer.clear()
clear()
diffbuf = make([]diff_msg, 0, 32)
go input_event_producer()
IsInit = true
return nil
}
// Finalizes termbox library, should be called after successful initialization
// when termbox's functionality isn't required anymore.
func Close() {
// we ignore errors here, because we can't really do anything about them
Clear(0, 0)
Flush()
// stop event producer
cancel_comm <- true
set_event(interrupt)
select {
case <-input_comm:
default:
}
<-cancel_done_comm
set_console_cursor_info(out, &orig_cursor_info)
set_console_cursor_position(out, coord{})
set_console_screen_buffer_size(out, orig_size)
set_console_mode(in, orig_mode)
syscall.Close(in)
syscall.Close(out)
syscall.Close(interrupt)
IsInit = false
}
// Interrupt an in-progress call to PollEvent by causing it to return
// EventInterrupt. Note that this function will block until the PollEvent
// function has successfully been interrupted.
func Interrupt() {
interrupt_comm <- struct{}{}
}
// Synchronizes the internal back buffer with the terminal.
func Flush() error {
update_size_maybe()
prepare_diff_messages()
for _, diff := range diffbuf {
r := small_rect{
left: 0,
top: diff.pos,
right: term_size.x - 1,
bottom: diff.pos + diff.lines - 1,
}
write_console_output(out, diff.chars, r)
}
if !is_cursor_hidden(cursor_x, cursor_y) {
move_cursor(cursor_x, cursor_y)
}
return nil
}
// Sets the position of the cursor. See also HideCursor().
func SetCursor(x, y int) {
if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) {
show_cursor(true)
}
if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) {
show_cursor(false)
}
cursor_x, cursor_y = x, y
if !is_cursor_hidden(cursor_x, cursor_y) {
move_cursor(cursor_x, cursor_y)
}
}
// The shortcut for SetCursor(-1, -1).
func HideCursor() {
SetCursor(cursor_hidden, cursor_hidden)
}
// Changes cell's parameters in the internal back buffer at the specified
// position.
func SetCell(x, y int, ch rune, fg, bg Attribute) {
if x < 0 || x >= back_buffer.width {
return
}
if y < 0 || y >= back_buffer.height {
return
}
back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
}
// Returns a slice into the termbox's back buffer. You can get its dimensions
// using 'Size' function. The slice remains valid as long as no 'Clear' or
// 'Flush' function calls were made after call to this function.
func CellBuffer() []Cell {
return back_buffer.cells
}
// Wait for an event and return it. This is a blocking function call.
func PollEvent() Event {
select {
case ev := <-input_comm:
return ev
case <-interrupt_comm:
return Event{Type: EventInterrupt}
}
}
// Returns the size of the internal back buffer (which is mostly the same as
// console's window size in characters). But it doesn't always match the size
// of the console window, after the console size has changed, the internal back
// buffer will get in sync only after Clear or Flush function calls.
func Size() (int, int) {
return int(term_size.x), int(term_size.y)
}
// Clears the internal back buffer.
func Clear(fg, bg Attribute) error {
foreground, background = fg, bg
update_size_maybe()
back_buffer.clear()
return nil
}
// Sets termbox input mode. Termbox has two input modes:
//
// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
// any known sequence. ESC means KeyEsc. This is the default input mode.
//
// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
// any known sequence. ESC enables ModAlt modifier for the next keyboard event.
//
// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
// enable mouse button press/release and drag events.
//
// If 'mode' is InputCurrent, returns the current input mode. See also Input*
// constants.
func SetInputMode(mode InputMode) InputMode {
if mode == InputCurrent {
return input_mode
}
if mode&InputMouse != 0 {
err := set_console_mode(in, enable_window_input|enable_mouse_input|enable_extended_flags)
if err != nil {
panic(err)
}
} else {
err := set_console_mode(in, enable_window_input)
if err != nil {
panic(err)
}
}
input_mode = mode
return input_mode
}
// Sets the termbox output mode.
//
// Windows console does not support extra colour modes,
// so this will always set and return OutputNormal.
func SetOutputMode(mode OutputMode) OutputMode {
return OutputNormal
}
// Sync comes handy when something causes desync between termbox's understanding
// of a terminal buffer and the reality. Such as a third party process. Sync
// forces a complete resync between the termbox and a terminal, it may not be
// visually pretty though. At the moment on Windows it does nothing.
func Sync() error {
return nil
}

110
vendor/github.com/nsf/termbox-go/collect_terminfo.py generated vendored Executable file
View File

@ -0,0 +1,110 @@
#!/usr/bin/env python
import sys, os, subprocess
def escaped(s):
return repr(s)[1:-1]
def tput(term, name):
try:
return subprocess.check_output(['tput', '-T%s' % term, name]).decode()
except subprocess.CalledProcessError as e:
return e.output.decode()
def w(s):
if s == None:
return
sys.stdout.write(s)
terminals = {
'xterm' : 'xterm',
'rxvt-256color' : 'rxvt_256color',
'rxvt-unicode' : 'rxvt_unicode',
'linux' : 'linux',
'Eterm' : 'eterm',
'screen' : 'screen'
}
keys = [
"F1", "kf1",
"F2", "kf2",
"F3", "kf3",
"F4", "kf4",
"F5", "kf5",
"F6", "kf6",
"F7", "kf7",
"F8", "kf8",
"F9", "kf9",
"F10", "kf10",
"F11", "kf11",
"F12", "kf12",
"INSERT", "kich1",
"DELETE", "kdch1",
"HOME", "khome",
"END", "kend",
"PGUP", "kpp",
"PGDN", "knp",
"KEY_UP", "kcuu1",
"KEY_DOWN", "kcud1",
"KEY_LEFT", "kcub1",
"KEY_RIGHT", "kcuf1"
]
funcs = [
"T_ENTER_CA", "smcup",
"T_EXIT_CA", "rmcup",
"T_SHOW_CURSOR", "cnorm",
"T_HIDE_CURSOR", "civis",
"T_CLEAR_SCREEN", "clear",
"T_SGR0", "sgr0",
"T_UNDERLINE", "smul",
"T_BOLD", "bold",
"T_BLINK", "blink",
"T_REVERSE", "rev",
"T_ENTER_KEYPAD", "smkx",
"T_EXIT_KEYPAD", "rmkx"
]
def iter_pairs(iterable):
iterable = iter(iterable)
while True:
yield (next(iterable), next(iterable))
def do_term(term, nick):
w("// %s\n" % term)
w("var %s_keys = []string{\n\t" % nick)
for k, v in iter_pairs(keys):
w('"')
w(escaped(tput(term, v)))
w('",')
w("\n}\n")
w("var %s_funcs = []string{\n\t" % nick)
for k,v in iter_pairs(funcs):
w('"')
if v == "sgr":
w("\\033[3%d;4%dm")
elif v == "cup":
w("\\033[%d;%dH")
else:
w(escaped(tput(term, v)))
w('", ')
w("\n}\n\n")
def do_terms(d):
w("var terms = []struct {\n")
w("\tname string\n")
w("\tkeys []string\n")
w("\tfuncs []string\n")
w("}{\n")
for k, v in d.items():
w('\t{"%s", %s_keys, %s_funcs},\n' % (k, v, v))
w("}\n\n")
w("// +build !windows\n\npackage termbox\n\n")
for k,v in terminals.items():
do_term(k, v)
do_terms(terminals)

41
vendor/github.com/nsf/termbox-go/syscalls_darwin.go generated vendored Normal file
View File

@ -0,0 +1,41 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs syscalls.go
// +build !amd64
package termbox
type syscall_Termios struct {
Iflag uint32
Oflag uint32
Cflag uint32
Lflag uint32
Cc [20]uint8
Ispeed uint32
Ospeed uint32
}
const (
syscall_IGNBRK = 0x1
syscall_BRKINT = 0x2
syscall_PARMRK = 0x8
syscall_ISTRIP = 0x20
syscall_INLCR = 0x40
syscall_IGNCR = 0x80
syscall_ICRNL = 0x100
syscall_IXON = 0x200
syscall_OPOST = 0x1
syscall_ECHO = 0x8
syscall_ECHONL = 0x10
syscall_ICANON = 0x100
syscall_ISIG = 0x80
syscall_IEXTEN = 0x400
syscall_CSIZE = 0x300
syscall_PARENB = 0x1000
syscall_CS8 = 0x300
syscall_VMIN = 0x10
syscall_VTIME = 0x11
syscall_TCGETS = 0x402c7413
syscall_TCSETS = 0x802c7414
)

View File

@ -0,0 +1,40 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs syscalls.go
package termbox
type syscall_Termios struct {
Iflag uint64
Oflag uint64
Cflag uint64
Lflag uint64
Cc [20]uint8
Pad_cgo_0 [4]byte
Ispeed uint64
Ospeed uint64
}
const (
syscall_IGNBRK = 0x1
syscall_BRKINT = 0x2
syscall_PARMRK = 0x8
syscall_ISTRIP = 0x20
syscall_INLCR = 0x40
syscall_IGNCR = 0x80
syscall_ICRNL = 0x100
syscall_IXON = 0x200
syscall_OPOST = 0x1
syscall_ECHO = 0x8
syscall_ECHONL = 0x10
syscall_ICANON = 0x100
syscall_ISIG = 0x80
syscall_IEXTEN = 0x400
syscall_CSIZE = 0x300
syscall_PARENB = 0x1000
syscall_CS8 = 0x300
syscall_VMIN = 0x10
syscall_VTIME = 0x11
syscall_TCGETS = 0x40487413
syscall_TCSETS = 0x80487414
)

39
vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs syscalls.go
package termbox
type syscall_Termios struct {
Iflag uint32
Oflag uint32
Cflag uint32
Lflag uint32
Cc [20]uint8
Ispeed uint32
Ospeed uint32
}
const (
syscall_IGNBRK = 0x1
syscall_BRKINT = 0x2
syscall_PARMRK = 0x8
syscall_ISTRIP = 0x20
syscall_INLCR = 0x40
syscall_IGNCR = 0x80
syscall_ICRNL = 0x100
syscall_IXON = 0x200
syscall_OPOST = 0x1
syscall_ECHO = 0x8
syscall_ECHONL = 0x10
syscall_ICANON = 0x100
syscall_ISIG = 0x80
syscall_IEXTEN = 0x400
syscall_CSIZE = 0x300
syscall_PARENB = 0x1000
syscall_CS8 = 0x300
syscall_VMIN = 0x10
syscall_VTIME = 0x11
syscall_TCGETS = 0x402c7413
syscall_TCSETS = 0x802c7414
)

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