rm vendor
|
@ -1,25 +0,0 @@
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
||||||
|
|
||||||
.idea
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2014, 0xAX
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
* Neither the name of the {organization} nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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.
|
|
|
@ -1,49 +0,0 @@
|
||||||
notificator
|
|
||||||
===========================
|
|
||||||
|
|
||||||
Desktop notification with Golang for:
|
|
||||||
|
|
||||||
* Windows with `growlnotify`;
|
|
||||||
* Mac OS X with `terminal-notifier` (if installed) or `osascript` (native, 10.9 Mavericks or Up.);
|
|
||||||
* Linux with `notify-send` for Gnome and `kdialog` for Kde.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
------
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/0xAX/notificator"
|
|
||||||
)
|
|
||||||
|
|
||||||
var notify *notificator.Notificator
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
notify = notificator.New(notificator.Options{
|
|
||||||
DefaultIcon: "icon/default.png",
|
|
||||||
AppName: "My test App",
|
|
||||||
})
|
|
||||||
|
|
||||||
notify.Push("title", "text", "/home/user/icon.png", notificator.UR_CRITICAL)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
TODO
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Add more options for different notificators.
|
|
||||||
|
|
||||||
Сontribution
|
|
||||||
------------
|
|
||||||
|
|
||||||
* Fork;
|
|
||||||
* Make changes;
|
|
||||||
* Send pull request;
|
|
||||||
* Thank you.
|
|
||||||
|
|
||||||
author
|
|
||||||
----------
|
|
||||||
|
|
||||||
[@0xAX](https://twitter.com/0xAX)
|
|
|
@ -1,24 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"../../notificator"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var notify *notificator.Notificator
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
notify = notificator.New(notificator.Options{
|
|
||||||
DefaultIcon: "icon/default.png",
|
|
||||||
AppName: "My test App",
|
|
||||||
})
|
|
||||||
|
|
||||||
notify.Push("title", "text", "/home/user/icon.png", notificator.UR_NORMAL)
|
|
||||||
|
|
||||||
// Check errors
|
|
||||||
err := notify.Push("error", "ops =(", "/home/user/icon.png", notificator.UR_CRITICAL)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 37 KiB |
|
@ -1,166 +0,0 @@
|
||||||
package notificator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
DefaultIcon string
|
|
||||||
AppName string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
UR_NORMAL = "normal"
|
|
||||||
UR_CRITICAL = "critical"
|
|
||||||
)
|
|
||||||
|
|
||||||
type notifier interface {
|
|
||||||
push(title string, text string, iconPath string) *exec.Cmd
|
|
||||||
pushCritical(title string, text string, iconPath string) *exec.Cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
type Notificator struct {
|
|
||||||
notifier notifier
|
|
||||||
defaultIcon string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Notificator) Push(title string, text string, iconPath string, urgency string) error {
|
|
||||||
icon := n.defaultIcon
|
|
||||||
|
|
||||||
if iconPath != "" {
|
|
||||||
icon = iconPath
|
|
||||||
}
|
|
||||||
|
|
||||||
if urgency == UR_CRITICAL {
|
|
||||||
return n.notifier.pushCritical(title, text, icon).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
return n.notifier.push(title, text, icon).Run()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type osxNotificator struct {
|
|
||||||
AppName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o osxNotificator) push(title string, text string, iconPath string) *exec.Cmd {
|
|
||||||
|
|
||||||
// Checks if terminal-notifier exists, and is accessible.
|
|
||||||
|
|
||||||
term_notif := CheckTermNotif()
|
|
||||||
os_version_check := CheckMacOSVersion()
|
|
||||||
|
|
||||||
// if terminal-notifier exists, use it.
|
|
||||||
// else, fall back to osascript. (Mavericks and later.)
|
|
||||||
|
|
||||||
if term_notif == true {
|
|
||||||
return exec.Command("terminal-notifier", "-title", o.AppName, "-message", text, "-subtitle", title, "-appIcon", iconPath)
|
|
||||||
} else if os_version_check == true {
|
|
||||||
title = strings.Replace(title, `"`, `\"`, -1)
|
|
||||||
text = strings.Replace(text, `"`, `\"`, -1)
|
|
||||||
|
|
||||||
notification := fmt.Sprintf("display notification \"%s\" with title \"%s\" subtitle \"%s\"", text, o.AppName, title)
|
|
||||||
return exec.Command("osascript", "-e", notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally falls back to growlnotify.
|
|
||||||
|
|
||||||
return exec.Command("growlnotify", "-n", o.AppName, "--image", iconPath, "-m", title)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Causes the notification to stick around until clicked.
|
|
||||||
func (o osxNotificator) pushCritical(title string, text string, iconPath string) *exec.Cmd {
|
|
||||||
|
|
||||||
// same function as above...
|
|
||||||
|
|
||||||
term_notif := CheckTermNotif()
|
|
||||||
os_version_check := CheckMacOSVersion()
|
|
||||||
|
|
||||||
if term_notif == true {
|
|
||||||
// timeout set to 30 seconds, to show the importance of the notification
|
|
||||||
return exec.Command("terminal-notifier", "-title", o.AppName, "-message", text, "-subtitle", title, "-timeout", "30")
|
|
||||||
} else if os_version_check == true {
|
|
||||||
notification := fmt.Sprintf("display notification \"%s\" with title \"%s\" subtitle \"%s\"", text, o.AppName, title)
|
|
||||||
return exec.Command("osascript", "-e", notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
return exec.Command("growlnotify", "-n", o.AppName, "--image", iconPath, "-m", title)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type linuxNotificator struct{}
|
|
||||||
|
|
||||||
func (l linuxNotificator) push(title string, text string, iconPath string) *exec.Cmd {
|
|
||||||
return exec.Command("notify-send", "-i", iconPath, title, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Causes the notification to stick around until clicked.
|
|
||||||
func (l linuxNotificator) pushCritical(title string, text string, iconPath string) *exec.Cmd {
|
|
||||||
return exec.Command("notify-send", "-i", iconPath, title, text, "-u", "critical")
|
|
||||||
}
|
|
||||||
|
|
||||||
type windowsNotificator struct{}
|
|
||||||
|
|
||||||
func (w windowsNotificator) push(title string, text string, iconPath string) *exec.Cmd {
|
|
||||||
return exec.Command("growlnotify", "/i:", iconPath, "/t:", title, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Causes the notification to stick around until clicked.
|
|
||||||
func (w windowsNotificator) pushCritical(title string, text string, iconPath string) *exec.Cmd {
|
|
||||||
return exec.Command("notify-send", "-i", iconPath, title, text, "/s", "true", "/p", "2")
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(o Options) *Notificator {
|
|
||||||
|
|
||||||
var Notifier notifier
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
|
||||||
|
|
||||||
case "darwin":
|
|
||||||
Notifier = osxNotificator{AppName: o.AppName}
|
|
||||||
case "linux":
|
|
||||||
Notifier = linuxNotificator{}
|
|
||||||
case "windows":
|
|
||||||
Notifier = windowsNotificator{}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Notificator{notifier: Notifier, defaultIcon: o.DefaultIcon}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function for macOS
|
|
||||||
|
|
||||||
func CheckTermNotif() bool {
|
|
||||||
// Checks if terminal-notifier exists, and is accessible.
|
|
||||||
if err := exec.Command("which", "terminal-notifier").Run(); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// no error, so return true. (terminal-notifier exists)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckMacOSVersion() bool {
|
|
||||||
// Checks if the version of macOS is 10.9 or Higher (osascript support for notifications.)
|
|
||||||
|
|
||||||
cmd := exec.Command("sw_vers", "-productVersion")
|
|
||||||
check, _ := cmd.Output()
|
|
||||||
|
|
||||||
version := strings.Split(strings.TrimSpace(string(check)), ".")
|
|
||||||
|
|
||||||
// semantic versioning of macOS
|
|
||||||
|
|
||||||
major, _ := strconv.Atoi(version[0])
|
|
||||||
minor, _ := strconv.Atoi(version[1])
|
|
||||||
|
|
||||||
if major < 10 {
|
|
||||||
return false
|
|
||||||
} else if major == 10 && minor < 9 {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.6
|
|
||||||
- tip
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2013 Fatih Arslan
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,177 +0,0 @@
|
||||||
# Color [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/color) [![Build Status](http://img.shields.io/travis/fatih/color.svg?style=flat-square)](https://travis-ci.org/fatih/color)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Color lets you use colorized outputs in terms of [ANSI Escape
|
|
||||||
Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
|
|
||||||
has support for Windows too! The API can be used in several ways, pick one that
|
|
||||||
suits you.
|
|
||||||
|
|
||||||
|
|
||||||
![Color](http://i.imgur.com/c1JI0lA.png)
|
|
||||||
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get github.com/fatih/color
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the `vendor` folder is here for stability. Remove the folder if you
|
|
||||||
already have the dependencies in your GOPATH.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Standard colors
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Print with default helper functions
|
|
||||||
color.Cyan("Prints text in cyan.")
|
|
||||||
|
|
||||||
// A newline will be appended automatically
|
|
||||||
color.Blue("Prints %s in blue.", "text")
|
|
||||||
|
|
||||||
// These are using the default foreground colors
|
|
||||||
color.Red("We have red")
|
|
||||||
color.Magenta("And many others ..")
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mix and reuse colors
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Create a new color object
|
|
||||||
c := color.New(color.FgCyan).Add(color.Underline)
|
|
||||||
c.Println("Prints cyan text with an underline.")
|
|
||||||
|
|
||||||
// Or just add them to New()
|
|
||||||
d := color.New(color.FgCyan, color.Bold)
|
|
||||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
|
||||||
|
|
||||||
// Mix up foreground and background colors, create new mixes!
|
|
||||||
red := color.New(color.FgRed)
|
|
||||||
|
|
||||||
boldRed := red.Add(color.Bold)
|
|
||||||
boldRed.Println("This will print text in bold red.")
|
|
||||||
|
|
||||||
whiteBackground := red.Add(color.BgWhite)
|
|
||||||
whiteBackground.Println("Red text with white background.")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use your own output (io.Writer)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Use your own io.Writer output
|
|
||||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
|
||||||
|
|
||||||
blue := color.New(color.FgBlue)
|
|
||||||
blue.Fprint(writer, "This will print text in blue.")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom print functions (PrintFunc)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Create a custom print function for convenience
|
|
||||||
red := color.New(color.FgRed).PrintfFunc()
|
|
||||||
red("Warning")
|
|
||||||
red("Error: %s", err)
|
|
||||||
|
|
||||||
// Mix up multiple attributes
|
|
||||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
|
||||||
notice("Don't forget this...")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom fprint functions (FprintFunc)
|
|
||||||
|
|
||||||
```go
|
|
||||||
blue := color.New(FgBlue).FprintfFunc()
|
|
||||||
blue(myWriter, "important notice: %s", stars)
|
|
||||||
|
|
||||||
// Mix up with multiple attributes
|
|
||||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
|
||||||
success(myWriter, "Don't forget this...")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Insert into noncolor strings (SprintFunc)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Create SprintXxx functions to mix strings with other non-colorized strings:
|
|
||||||
yellow := color.New(color.FgYellow).SprintFunc()
|
|
||||||
red := color.New(color.FgRed).SprintFunc()
|
|
||||||
fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
|
|
||||||
|
|
||||||
info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
|
|
||||||
fmt.Printf("This %s rocks!\n", info("package"))
|
|
||||||
|
|
||||||
// Use helper functions
|
|
||||||
fmt.Println("This", color.RedString("warning"), "should be not neglected.")
|
|
||||||
fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
|
|
||||||
|
|
||||||
// Windows supported too! Just don't forget to change the output to color.Output
|
|
||||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Plug into existing code
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Use handy standard colors
|
|
||||||
color.Set(color.FgYellow)
|
|
||||||
|
|
||||||
fmt.Println("Existing text will now be in yellow")
|
|
||||||
fmt.Printf("This one %s\n", "too")
|
|
||||||
|
|
||||||
color.Unset() // Don't forget to unset
|
|
||||||
|
|
||||||
// You can mix up parameters
|
|
||||||
color.Set(color.FgMagenta, color.Bold)
|
|
||||||
defer color.Unset() // Use it in your function
|
|
||||||
|
|
||||||
fmt.Println("All text will now be bold magenta.")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Disable color
|
|
||||||
|
|
||||||
There might be a case where you want to disable color output (for example to
|
|
||||||
pipe the standard output of your app to somewhere else). `Color` has support to
|
|
||||||
disable colors both globally and for single color definition. For example
|
|
||||||
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
|
|
||||||
the color output with:
|
|
||||||
|
|
||||||
```go
|
|
||||||
|
|
||||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
|
||||||
|
|
||||||
if *flagNoColor {
|
|
||||||
color.NoColor = true // disables colorized output
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
It also has support for single color definitions (local). You can
|
|
||||||
disable/enable color output on the fly:
|
|
||||||
|
|
||||||
```go
|
|
||||||
c := color.New(color.FgCyan)
|
|
||||||
c.Println("Prints cyan text")
|
|
||||||
|
|
||||||
c.DisableColor()
|
|
||||||
c.Println("This is printed without any color")
|
|
||||||
|
|
||||||
c.EnableColor()
|
|
||||||
c.Println("This prints again cyan...")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Todo
|
|
||||||
|
|
||||||
* Save/Return previous values
|
|
||||||
* Evaluate fmt.Formatter interface
|
|
||||||
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
* [Fatih Arslan](https://github.com/fatih)
|
|
||||||
* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details
|
|
||||||
|
|
|
@ -1,600 +0,0 @@
|
||||||
package color
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/mattn/go-colorable"
|
|
||||||
"github.com/mattn/go-isatty"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// NoColor defines if the output is colorized or not. It's dynamically set to
|
|
||||||
// false or true based on the stdout's file descriptor referring to a terminal
|
|
||||||
// or not. This is a global option and affects all colors. For more control
|
|
||||||
// over each color block use the methods DisableColor() individually.
|
|
||||||
NoColor = os.Getenv("TERM") == "dumb" ||
|
|
||||||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
|
|
||||||
|
|
||||||
// Output defines the standard output of the print functions. By default
|
|
||||||
// os.Stdout is used.
|
|
||||||
Output = colorable.NewColorableStdout()
|
|
||||||
|
|
||||||
// colorsCache is used to reduce the count of created Color objects and
|
|
||||||
// allows to reuse already created objects with required Attribute.
|
|
||||||
colorsCache = make(map[Attribute]*Color)
|
|
||||||
colorsCacheMu sync.Mutex // protects colorsCache
|
|
||||||
)
|
|
||||||
|
|
||||||
// Color defines a custom color object which is defined by SGR parameters.
|
|
||||||
type Color struct {
|
|
||||||
params []Attribute
|
|
||||||
noColor *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute defines a single SGR Code
|
|
||||||
type Attribute int
|
|
||||||
|
|
||||||
const escape = "\x1b"
|
|
||||||
|
|
||||||
// Base attributes
|
|
||||||
const (
|
|
||||||
Reset Attribute = iota
|
|
||||||
Bold
|
|
||||||
Faint
|
|
||||||
Italic
|
|
||||||
Underline
|
|
||||||
BlinkSlow
|
|
||||||
BlinkRapid
|
|
||||||
ReverseVideo
|
|
||||||
Concealed
|
|
||||||
CrossedOut
|
|
||||||
)
|
|
||||||
|
|
||||||
// Foreground text colors
|
|
||||||
const (
|
|
||||||
FgBlack Attribute = iota + 30
|
|
||||||
FgRed
|
|
||||||
FgGreen
|
|
||||||
FgYellow
|
|
||||||
FgBlue
|
|
||||||
FgMagenta
|
|
||||||
FgCyan
|
|
||||||
FgWhite
|
|
||||||
)
|
|
||||||
|
|
||||||
// Foreground Hi-Intensity text colors
|
|
||||||
const (
|
|
||||||
FgHiBlack Attribute = iota + 90
|
|
||||||
FgHiRed
|
|
||||||
FgHiGreen
|
|
||||||
FgHiYellow
|
|
||||||
FgHiBlue
|
|
||||||
FgHiMagenta
|
|
||||||
FgHiCyan
|
|
||||||
FgHiWhite
|
|
||||||
)
|
|
||||||
|
|
||||||
// Background text colors
|
|
||||||
const (
|
|
||||||
BgBlack Attribute = iota + 40
|
|
||||||
BgRed
|
|
||||||
BgGreen
|
|
||||||
BgYellow
|
|
||||||
BgBlue
|
|
||||||
BgMagenta
|
|
||||||
BgCyan
|
|
||||||
BgWhite
|
|
||||||
)
|
|
||||||
|
|
||||||
// Background Hi-Intensity text colors
|
|
||||||
const (
|
|
||||||
BgHiBlack Attribute = iota + 100
|
|
||||||
BgHiRed
|
|
||||||
BgHiGreen
|
|
||||||
BgHiYellow
|
|
||||||
BgHiBlue
|
|
||||||
BgHiMagenta
|
|
||||||
BgHiCyan
|
|
||||||
BgHiWhite
|
|
||||||
)
|
|
||||||
|
|
||||||
// New returns a newly created color object.
|
|
||||||
func New(value ...Attribute) *Color {
|
|
||||||
c := &Color{params: make([]Attribute, 0)}
|
|
||||||
c.Add(value...)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets the given parameters immediately. It will change the color of
|
|
||||||
// output with the given SGR parameters until color.Unset() is called.
|
|
||||||
func Set(p ...Attribute) *Color {
|
|
||||||
c := New(p...)
|
|
||||||
c.Set()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unset resets all escape attributes and clears the output. Usually should
|
|
||||||
// be called after Set().
|
|
||||||
func Unset() {
|
|
||||||
if NoColor {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets the SGR sequence.
|
|
||||||
func (c *Color) Set() *Color {
|
|
||||||
if c.isNoColorSet() {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(Output, c.format())
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Color) unset() {
|
|
||||||
if c.isNoColorSet() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Unset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Color) setWriter(w io.Writer) *Color {
|
|
||||||
if c.isNoColorSet() {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(w, c.format())
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Color) unsetWriter(w io.Writer) {
|
|
||||||
if c.isNoColorSet() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if NoColor {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "%s[%dm", escape, Reset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add is used to chain SGR parameters. Use as many as parameters to combine
|
|
||||||
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
|
|
||||||
func (c *Color) Add(value ...Attribute) *Color {
|
|
||||||
c.params = append(c.params, value...)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Color) prepend(value Attribute) {
|
|
||||||
c.params = append(c.params, 0)
|
|
||||||
copy(c.params[1:], c.params[0:])
|
|
||||||
c.params[0] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint formats using the default formats for its operands and writes to w.
|
|
||||||
// Spaces are added between operands when neither is a string.
|
|
||||||
// It returns the number of bytes written and any write error encountered.
|
|
||||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
|
||||||
// type *os.File.
|
|
||||||
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
c.setWriter(w)
|
|
||||||
defer c.unsetWriter(w)
|
|
||||||
|
|
||||||
return fmt.Fprint(w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print formats using the default formats for its operands and writes to
|
|
||||||
// standard output. Spaces are added between operands when neither is a
|
|
||||||
// string. It returns the number of bytes written and any write error
|
|
||||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
|
||||||
// color.
|
|
||||||
func (c *Color) Print(a ...interface{}) (n int, err error) {
|
|
||||||
c.Set()
|
|
||||||
defer c.unset()
|
|
||||||
|
|
||||||
return fmt.Fprint(Output, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf formats according to a format specifier and writes to w.
|
|
||||||
// It returns the number of bytes written and any write error encountered.
|
|
||||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
|
||||||
// type *os.File.
|
|
||||||
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
c.setWriter(w)
|
|
||||||
defer c.unsetWriter(w)
|
|
||||||
|
|
||||||
return fmt.Fprintf(w, format, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf formats according to a format specifier and writes to standard output.
|
|
||||||
// It returns the number of bytes written and any write error encountered.
|
|
||||||
// This is the standard fmt.Printf() method wrapped with the given color.
|
|
||||||
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
c.Set()
|
|
||||||
defer c.unset()
|
|
||||||
|
|
||||||
return fmt.Fprintf(Output, format, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln formats using the default formats for its operands and writes to w.
|
|
||||||
// Spaces are always added between operands and a newline is appended.
|
|
||||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
|
||||||
// type *os.File.
|
|
||||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
c.setWriter(w)
|
|
||||||
defer c.unsetWriter(w)
|
|
||||||
|
|
||||||
return fmt.Fprintln(w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println formats using the default formats for its operands and writes to
|
|
||||||
// standard output. Spaces are always added between operands and a newline is
|
|
||||||
// appended. It returns the number of bytes written and any write error
|
|
||||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
|
||||||
// color.
|
|
||||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
|
||||||
c.Set()
|
|
||||||
defer c.unset()
|
|
||||||
|
|
||||||
return fmt.Fprintln(Output, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is just like Print, but returns a string instead of printing it.
|
|
||||||
func (c *Color) Sprint(a ...interface{}) string {
|
|
||||||
return c.wrap(fmt.Sprint(a...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
|
||||||
func (c *Color) Sprintln(a ...interface{}) string {
|
|
||||||
return c.wrap(fmt.Sprintln(a...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
|
||||||
func (c *Color) Sprintf(format string, a ...interface{}) string {
|
|
||||||
return c.wrap(fmt.Sprintf(format, a...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// FprintFunc returns a new function that prints the passed arguments as
|
|
||||||
// colorized with color.Fprint().
|
|
||||||
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
|
|
||||||
return func(w io.Writer, a ...interface{}) {
|
|
||||||
c.Fprint(w, a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintFunc returns a new function that prints the passed arguments as
|
|
||||||
// colorized with color.Print().
|
|
||||||
func (c *Color) PrintFunc() func(a ...interface{}) {
|
|
||||||
return func(a ...interface{}) {
|
|
||||||
c.Print(a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FprintfFunc returns a new function that prints the passed arguments as
|
|
||||||
// colorized with color.Fprintf().
|
|
||||||
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
|
|
||||||
return func(w io.Writer, format string, a ...interface{}) {
|
|
||||||
c.Fprintf(w, format, a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintfFunc returns a new function that prints the passed arguments as
|
|
||||||
// colorized with color.Printf().
|
|
||||||
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
|
|
||||||
return func(format string, a ...interface{}) {
|
|
||||||
c.Printf(format, a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FprintlnFunc returns a new function that prints the passed arguments as
|
|
||||||
// colorized with color.Fprintln().
|
|
||||||
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
|
|
||||||
return func(w io.Writer, a ...interface{}) {
|
|
||||||
c.Fprintln(w, a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintlnFunc returns a new function that prints the passed arguments as
|
|
||||||
// colorized with color.Println().
|
|
||||||
func (c *Color) PrintlnFunc() func(a ...interface{}) {
|
|
||||||
return func(a ...interface{}) {
|
|
||||||
c.Println(a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SprintFunc returns a new function that returns colorized strings for the
|
|
||||||
// given arguments with fmt.Sprint(). Useful to put into or mix into other
|
|
||||||
// string. Windows users should use this in conjunction with color.Output, example:
|
|
||||||
//
|
|
||||||
// put := New(FgYellow).SprintFunc()
|
|
||||||
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
|
|
||||||
func (c *Color) SprintFunc() func(a ...interface{}) string {
|
|
||||||
return func(a ...interface{}) string {
|
|
||||||
return c.wrap(fmt.Sprint(a...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SprintfFunc returns a new function that returns colorized strings for the
|
|
||||||
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
|
|
||||||
// string. Windows users should use this in conjunction with color.Output.
|
|
||||||
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
|
||||||
return func(format string, a ...interface{}) string {
|
|
||||||
return c.wrap(fmt.Sprintf(format, a...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SprintlnFunc returns a new function that returns colorized strings for the
|
|
||||||
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
|
|
||||||
// string. Windows users should use this in conjunction with color.Output.
|
|
||||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
|
||||||
return func(a ...interface{}) string {
|
|
||||||
return c.wrap(fmt.Sprintln(a...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sequence returns a formated SGR sequence to be plugged into a "\x1b[...m"
|
|
||||||
// an example output might be: "1;36" -> bold cyan
|
|
||||||
func (c *Color) sequence() string {
|
|
||||||
format := make([]string, len(c.params))
|
|
||||||
for i, v := range c.params {
|
|
||||||
format[i] = strconv.Itoa(int(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(format, ";")
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrap wraps the s string with the colors attributes. The string is ready to
|
|
||||||
// be printed.
|
|
||||||
func (c *Color) wrap(s string) string {
|
|
||||||
if c.isNoColorSet() {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.format() + s + c.unformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Color) format() string {
|
|
||||||
return fmt.Sprintf("%s[%sm", escape, c.sequence())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Color) unformat() string {
|
|
||||||
return fmt.Sprintf("%s[%dm", escape, Reset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableColor disables the color output. Useful to not change any existing
|
|
||||||
// code and still being able to output. Can be used for flags like
|
|
||||||
// "--no-color". To enable back use EnableColor() method.
|
|
||||||
func (c *Color) DisableColor() {
|
|
||||||
c.noColor = boolPtr(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableColor enables the color output. Use it in conjunction with
|
|
||||||
// DisableColor(). Otherwise this method has no side effects.
|
|
||||||
func (c *Color) EnableColor() {
|
|
||||||
c.noColor = boolPtr(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Color) isNoColorSet() bool {
|
|
||||||
// check first if we have user setted action
|
|
||||||
if c.noColor != nil {
|
|
||||||
return *c.noColor
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not return the global option, which is disabled by default
|
|
||||||
return NoColor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals returns a boolean value indicating whether two colors are equal.
|
|
||||||
func (c *Color) Equals(c2 *Color) bool {
|
|
||||||
if len(c.params) != len(c2.params) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, attr := range c.params {
|
|
||||||
if !c2.attrExists(attr) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Color) attrExists(a Attribute) bool {
|
|
||||||
for _, attr := range c.params {
|
|
||||||
if attr == a {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func boolPtr(v bool) *bool {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCachedColor(p Attribute) *Color {
|
|
||||||
colorsCacheMu.Lock()
|
|
||||||
defer colorsCacheMu.Unlock()
|
|
||||||
|
|
||||||
c, ok := colorsCache[p]
|
|
||||||
if !ok {
|
|
||||||
c = New(p)
|
|
||||||
colorsCache[p] = c
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func colorPrint(format string, p Attribute, a ...interface{}) {
|
|
||||||
c := getCachedColor(p)
|
|
||||||
|
|
||||||
if !strings.HasSuffix(format, "\n") {
|
|
||||||
format += "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(a) == 0 {
|
|
||||||
c.Print(format)
|
|
||||||
} else {
|
|
||||||
c.Printf(format, a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func colorString(format string, p Attribute, a ...interface{}) string {
|
|
||||||
c := getCachedColor(p)
|
|
||||||
|
|
||||||
if len(a) == 0 {
|
|
||||||
return c.SprintFunc()(format)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.SprintfFunc()(format, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Black is a convenient helper function to print with black foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
|
|
||||||
|
|
||||||
// Red is a convenient helper function to print with red foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
|
|
||||||
|
|
||||||
// Green is a convenient helper function to print with green foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
|
|
||||||
|
|
||||||
// Yellow is a convenient helper function to print with yellow foreground.
|
|
||||||
// A newline is appended to format by default.
|
|
||||||
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
|
|
||||||
|
|
||||||
// Blue is a convenient helper function to print with blue foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
|
|
||||||
|
|
||||||
// Magenta is a convenient helper function to print with magenta foreground.
|
|
||||||
// A newline is appended to format by default.
|
|
||||||
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
|
|
||||||
|
|
||||||
// Cyan is a convenient helper function to print with cyan foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
|
|
||||||
|
|
||||||
// White is a convenient helper function to print with white foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
|
|
||||||
|
|
||||||
// BlackString is a convenient helper function to return a string with black
|
|
||||||
// foreground.
|
|
||||||
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
|
|
||||||
|
|
||||||
// RedString is a convenient helper function to return a string with red
|
|
||||||
// foreground.
|
|
||||||
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
|
|
||||||
|
|
||||||
// GreenString is a convenient helper function to return a string with green
|
|
||||||
// foreground.
|
|
||||||
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
|
|
||||||
|
|
||||||
// YellowString is a convenient helper function to return a string with yellow
|
|
||||||
// foreground.
|
|
||||||
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
|
|
||||||
|
|
||||||
// BlueString is a convenient helper function to return a string with blue
|
|
||||||
// foreground.
|
|
||||||
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
|
|
||||||
|
|
||||||
// MagentaString is a convenient helper function to return a string with magenta
|
|
||||||
// foreground.
|
|
||||||
func MagentaString(format string, a ...interface{}) string {
|
|
||||||
return colorString(format, FgMagenta, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CyanString is a convenient helper function to return a string with cyan
|
|
||||||
// foreground.
|
|
||||||
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
|
|
||||||
|
|
||||||
// WhiteString is a convenient helper function to return a string with white
|
|
||||||
// foreground.
|
|
||||||
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
|
|
||||||
|
|
||||||
// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
|
|
||||||
|
|
||||||
// HiRed is a convenient helper function to print with hi-intensity red foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
|
|
||||||
|
|
||||||
// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
|
|
||||||
|
|
||||||
// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
|
|
||||||
// A newline is appended to format by default.
|
|
||||||
func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
|
|
||||||
|
|
||||||
// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
|
|
||||||
|
|
||||||
// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
|
|
||||||
// A newline is appended to format by default.
|
|
||||||
func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
|
|
||||||
|
|
||||||
// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
|
|
||||||
|
|
||||||
// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
|
|
||||||
// newline is appended to format by default.
|
|
||||||
func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
|
|
||||||
|
|
||||||
// HiBlackString is a convenient helper function to return a string with hi-intensity black
|
|
||||||
// foreground.
|
|
||||||
func HiBlackString(format string, a ...interface{}) string {
|
|
||||||
return colorString(format, FgHiBlack, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HiRedString is a convenient helper function to return a string with hi-intensity red
|
|
||||||
// foreground.
|
|
||||||
func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
|
|
||||||
|
|
||||||
// HiGreenString is a convenient helper function to return a string with hi-intensity green
|
|
||||||
// foreground.
|
|
||||||
func HiGreenString(format string, a ...interface{}) string {
|
|
||||||
return colorString(format, FgHiGreen, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
|
|
||||||
// foreground.
|
|
||||||
func HiYellowString(format string, a ...interface{}) string {
|
|
||||||
return colorString(format, FgHiYellow, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HiBlueString is a convenient helper function to return a string with hi-intensity blue
|
|
||||||
// foreground.
|
|
||||||
func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
|
|
||||||
|
|
||||||
// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
|
|
||||||
// foreground.
|
|
||||||
func HiMagentaString(format string, a ...interface{}) string {
|
|
||||||
return colorString(format, FgHiMagenta, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
|
|
||||||
// foreground.
|
|
||||||
func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
|
|
||||||
|
|
||||||
// HiWhiteString is a convenient helper function to return a string with hi-intensity white
|
|
||||||
// foreground.
|
|
||||||
func HiWhiteString(format string, a ...interface{}) string {
|
|
||||||
return colorString(format, FgHiWhite, a...)
|
|
||||||
}
|
|
|
@ -1,342 +0,0 @@
|
||||||
package color
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mattn/go-colorable"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Testing colors is kinda different. First we test for given colors and their
|
|
||||||
// escaped formatted results. Next we create some visual tests to be tested.
|
|
||||||
// Each visual test includes the color name to be compared.
|
|
||||||
func TestColor(t *testing.T) {
|
|
||||||
rb := new(bytes.Buffer)
|
|
||||||
Output = rb
|
|
||||||
|
|
||||||
NoColor = false
|
|
||||||
|
|
||||||
testColors := []struct {
|
|
||||||
text string
|
|
||||||
code Attribute
|
|
||||||
}{
|
|
||||||
{text: "black", code: FgBlack},
|
|
||||||
{text: "red", code: FgRed},
|
|
||||||
{text: "green", code: FgGreen},
|
|
||||||
{text: "yellow", code: FgYellow},
|
|
||||||
{text: "blue", code: FgBlue},
|
|
||||||
{text: "magent", code: FgMagenta},
|
|
||||||
{text: "cyan", code: FgCyan},
|
|
||||||
{text: "white", code: FgWhite},
|
|
||||||
{text: "hblack", code: FgHiBlack},
|
|
||||||
{text: "hred", code: FgHiRed},
|
|
||||||
{text: "hgreen", code: FgHiGreen},
|
|
||||||
{text: "hyellow", code: FgHiYellow},
|
|
||||||
{text: "hblue", code: FgHiBlue},
|
|
||||||
{text: "hmagent", code: FgHiMagenta},
|
|
||||||
{text: "hcyan", code: FgHiCyan},
|
|
||||||
{text: "hwhite", code: FgHiWhite},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range testColors {
|
|
||||||
New(c.code).Print(c.text)
|
|
||||||
|
|
||||||
line, _ := rb.ReadString('\n')
|
|
||||||
scannedLine := fmt.Sprintf("%q", line)
|
|
||||||
colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text)
|
|
||||||
escapedForm := fmt.Sprintf("%q", colored)
|
|
||||||
|
|
||||||
fmt.Printf("%s\t: %s\n", c.text, line)
|
|
||||||
|
|
||||||
if scannedLine != escapedForm {
|
|
||||||
t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range testColors {
|
|
||||||
line := New(c.code).Sprintf("%s", c.text)
|
|
||||||
scannedLine := fmt.Sprintf("%q", line)
|
|
||||||
colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text)
|
|
||||||
escapedForm := fmt.Sprintf("%q", colored)
|
|
||||||
|
|
||||||
fmt.Printf("%s\t: %s\n", c.text, line)
|
|
||||||
|
|
||||||
if scannedLine != escapedForm {
|
|
||||||
t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestColorEquals(t *testing.T) {
|
|
||||||
fgblack1 := New(FgBlack)
|
|
||||||
fgblack2 := New(FgBlack)
|
|
||||||
bgblack := New(BgBlack)
|
|
||||||
fgbgblack := New(FgBlack, BgBlack)
|
|
||||||
fgblackbgred := New(FgBlack, BgRed)
|
|
||||||
fgred := New(FgRed)
|
|
||||||
bgred := New(BgRed)
|
|
||||||
|
|
||||||
if !fgblack1.Equals(fgblack2) {
|
|
||||||
t.Error("Two black colors are not equal")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(bgblack) {
|
|
||||||
t.Error("Fg and bg black colors are equal")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(fgbgblack) {
|
|
||||||
t.Error("Fg black equals fg/bg black color")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(fgred) {
|
|
||||||
t.Error("Fg black equals Fg red")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(bgred) {
|
|
||||||
t.Error("Fg black equals Bg red")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fgblack1.Equals(fgblackbgred) {
|
|
||||||
t.Error("Fg black equals fg black bg red")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoColor(t *testing.T) {
|
|
||||||
rb := new(bytes.Buffer)
|
|
||||||
Output = rb
|
|
||||||
|
|
||||||
testColors := []struct {
|
|
||||||
text string
|
|
||||||
code Attribute
|
|
||||||
}{
|
|
||||||
{text: "black", code: FgBlack},
|
|
||||||
{text: "red", code: FgRed},
|
|
||||||
{text: "green", code: FgGreen},
|
|
||||||
{text: "yellow", code: FgYellow},
|
|
||||||
{text: "blue", code: FgBlue},
|
|
||||||
{text: "magent", code: FgMagenta},
|
|
||||||
{text: "cyan", code: FgCyan},
|
|
||||||
{text: "white", code: FgWhite},
|
|
||||||
{text: "hblack", code: FgHiBlack},
|
|
||||||
{text: "hred", code: FgHiRed},
|
|
||||||
{text: "hgreen", code: FgHiGreen},
|
|
||||||
{text: "hyellow", code: FgHiYellow},
|
|
||||||
{text: "hblue", code: FgHiBlue},
|
|
||||||
{text: "hmagent", code: FgHiMagenta},
|
|
||||||
{text: "hcyan", code: FgHiCyan},
|
|
||||||
{text: "hwhite", code: FgHiWhite},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range testColors {
|
|
||||||
p := New(c.code)
|
|
||||||
p.DisableColor()
|
|
||||||
p.Print(c.text)
|
|
||||||
|
|
||||||
line, _ := rb.ReadString('\n')
|
|
||||||
if line != c.text {
|
|
||||||
t.Errorf("Expecting %s, got '%s'\n", c.text, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// global check
|
|
||||||
NoColor = true
|
|
||||||
defer func() {
|
|
||||||
NoColor = false
|
|
||||||
}()
|
|
||||||
for _, c := range testColors {
|
|
||||||
p := New(c.code)
|
|
||||||
p.Print(c.text)
|
|
||||||
|
|
||||||
line, _ := rb.ReadString('\n')
|
|
||||||
if line != c.text {
|
|
||||||
t.Errorf("Expecting %s, got '%s'\n", c.text, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestColorVisual(t *testing.T) {
|
|
||||||
// First Visual Test
|
|
||||||
Output = colorable.NewColorableStdout()
|
|
||||||
|
|
||||||
New(FgRed).Printf("red\t")
|
|
||||||
New(BgRed).Print(" ")
|
|
||||||
New(FgRed, Bold).Println(" red")
|
|
||||||
|
|
||||||
New(FgGreen).Printf("green\t")
|
|
||||||
New(BgGreen).Print(" ")
|
|
||||||
New(FgGreen, Bold).Println(" green")
|
|
||||||
|
|
||||||
New(FgYellow).Printf("yellow\t")
|
|
||||||
New(BgYellow).Print(" ")
|
|
||||||
New(FgYellow, Bold).Println(" yellow")
|
|
||||||
|
|
||||||
New(FgBlue).Printf("blue\t")
|
|
||||||
New(BgBlue).Print(" ")
|
|
||||||
New(FgBlue, Bold).Println(" blue")
|
|
||||||
|
|
||||||
New(FgMagenta).Printf("magenta\t")
|
|
||||||
New(BgMagenta).Print(" ")
|
|
||||||
New(FgMagenta, Bold).Println(" magenta")
|
|
||||||
|
|
||||||
New(FgCyan).Printf("cyan\t")
|
|
||||||
New(BgCyan).Print(" ")
|
|
||||||
New(FgCyan, Bold).Println(" cyan")
|
|
||||||
|
|
||||||
New(FgWhite).Printf("white\t")
|
|
||||||
New(BgWhite).Print(" ")
|
|
||||||
New(FgWhite, Bold).Println(" white")
|
|
||||||
fmt.Println("")
|
|
||||||
|
|
||||||
// Second Visual test
|
|
||||||
Black("black")
|
|
||||||
Red("red")
|
|
||||||
Green("green")
|
|
||||||
Yellow("yellow")
|
|
||||||
Blue("blue")
|
|
||||||
Magenta("magenta")
|
|
||||||
Cyan("cyan")
|
|
||||||
White("white")
|
|
||||||
HiBlack("hblack")
|
|
||||||
HiRed("hred")
|
|
||||||
HiGreen("hgreen")
|
|
||||||
HiYellow("hyellow")
|
|
||||||
HiBlue("hblue")
|
|
||||||
HiMagenta("hmagenta")
|
|
||||||
HiCyan("hcyan")
|
|
||||||
HiWhite("hwhite")
|
|
||||||
|
|
||||||
// Third visual test
|
|
||||||
fmt.Println()
|
|
||||||
Set(FgBlue)
|
|
||||||
fmt.Println("is this blue?")
|
|
||||||
Unset()
|
|
||||||
|
|
||||||
Set(FgMagenta)
|
|
||||||
fmt.Println("and this magenta?")
|
|
||||||
Unset()
|
|
||||||
|
|
||||||
// Fourth Visual test
|
|
||||||
fmt.Println()
|
|
||||||
blue := New(FgBlue).PrintlnFunc()
|
|
||||||
blue("blue text with custom print func")
|
|
||||||
|
|
||||||
red := New(FgRed).PrintfFunc()
|
|
||||||
red("red text with a printf func: %d\n", 123)
|
|
||||||
|
|
||||||
put := New(FgYellow).SprintFunc()
|
|
||||||
warn := New(FgRed).SprintFunc()
|
|
||||||
|
|
||||||
fmt.Fprintf(Output, "this is a %s and this is %s.\n", put("warning"), warn("error"))
|
|
||||||
|
|
||||||
info := New(FgWhite, BgGreen).SprintFunc()
|
|
||||||
fmt.Fprintf(Output, "this %s rocks!\n", info("package"))
|
|
||||||
|
|
||||||
notice := New(FgBlue).FprintFunc()
|
|
||||||
notice(os.Stderr, "just a blue notice to stderr")
|
|
||||||
|
|
||||||
// Fifth Visual Test
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
fmt.Fprintln(Output, BlackString("black"))
|
|
||||||
fmt.Fprintln(Output, RedString("red"))
|
|
||||||
fmt.Fprintln(Output, GreenString("green"))
|
|
||||||
fmt.Fprintln(Output, YellowString("yellow"))
|
|
||||||
fmt.Fprintln(Output, BlueString("blue"))
|
|
||||||
fmt.Fprintln(Output, MagentaString("magenta"))
|
|
||||||
fmt.Fprintln(Output, CyanString("cyan"))
|
|
||||||
fmt.Fprintln(Output, WhiteString("white"))
|
|
||||||
fmt.Fprintln(Output, HiBlackString("hblack"))
|
|
||||||
fmt.Fprintln(Output, HiRedString("hred"))
|
|
||||||
fmt.Fprintln(Output, HiGreenString("hgreen"))
|
|
||||||
fmt.Fprintln(Output, HiYellowString("hyellow"))
|
|
||||||
fmt.Fprintln(Output, HiBlueString("hblue"))
|
|
||||||
fmt.Fprintln(Output, HiMagentaString("hmagenta"))
|
|
||||||
fmt.Fprintln(Output, HiCyanString("hcyan"))
|
|
||||||
fmt.Fprintln(Output, HiWhiteString("hwhite"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoFormat(t *testing.T) {
|
|
||||||
fmt.Printf("%s %%s = ", BlackString("Black"))
|
|
||||||
Black("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", RedString("Red"))
|
|
||||||
Red("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", GreenString("Green"))
|
|
||||||
Green("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", YellowString("Yellow"))
|
|
||||||
Yellow("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", BlueString("Blue"))
|
|
||||||
Blue("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", MagentaString("Magenta"))
|
|
||||||
Magenta("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", CyanString("Cyan"))
|
|
||||||
Cyan("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", WhiteString("White"))
|
|
||||||
White("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiBlackString("HiBlack"))
|
|
||||||
HiBlack("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiRedString("HiRed"))
|
|
||||||
HiRed("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiGreenString("HiGreen"))
|
|
||||||
HiGreen("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiYellowString("HiYellow"))
|
|
||||||
HiYellow("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiBlueString("HiBlue"))
|
|
||||||
HiBlue("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiMagentaString("HiMagenta"))
|
|
||||||
HiMagenta("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiCyanString("HiCyan"))
|
|
||||||
HiCyan("%s")
|
|
||||||
|
|
||||||
fmt.Printf("%s %%s = ", HiWhiteString("HiWhite"))
|
|
||||||
HiWhite("%s")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoFormatString(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
f func(string, ...interface{}) string
|
|
||||||
format string
|
|
||||||
args []interface{}
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{BlackString, "%s", nil, "\x1b[30m%s\x1b[0m"},
|
|
||||||
{RedString, "%s", nil, "\x1b[31m%s\x1b[0m"},
|
|
||||||
{GreenString, "%s", nil, "\x1b[32m%s\x1b[0m"},
|
|
||||||
{YellowString, "%s", nil, "\x1b[33m%s\x1b[0m"},
|
|
||||||
{BlueString, "%s", nil, "\x1b[34m%s\x1b[0m"},
|
|
||||||
{MagentaString, "%s", nil, "\x1b[35m%s\x1b[0m"},
|
|
||||||
{CyanString, "%s", nil, "\x1b[36m%s\x1b[0m"},
|
|
||||||
{WhiteString, "%s", nil, "\x1b[37m%s\x1b[0m"},
|
|
||||||
{HiBlackString, "%s", nil, "\x1b[90m%s\x1b[0m"},
|
|
||||||
{HiRedString, "%s", nil, "\x1b[91m%s\x1b[0m"},
|
|
||||||
{HiGreenString, "%s", nil, "\x1b[92m%s\x1b[0m"},
|
|
||||||
{HiYellowString, "%s", nil, "\x1b[93m%s\x1b[0m"},
|
|
||||||
{HiBlueString, "%s", nil, "\x1b[94m%s\x1b[0m"},
|
|
||||||
{HiMagentaString, "%s", nil, "\x1b[95m%s\x1b[0m"},
|
|
||||||
{HiCyanString, "%s", nil, "\x1b[96m%s\x1b[0m"},
|
|
||||||
{HiWhiteString, "%s", nil, "\x1b[97m%s\x1b[0m"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
s := fmt.Sprintf("%s", test.f(test.format, test.args...))
|
|
||||||
if s != test.want {
|
|
||||||
t.Errorf("[%d] want: %q, got: %q", i, test.want, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
/*
|
|
||||||
Package color is an ANSI color package to output colorized or SGR defined
|
|
||||||
output to the standard output. The API can be used in several way, pick one
|
|
||||||
that suits you.
|
|
||||||
|
|
||||||
Use simple and default helper functions with predefined foreground colors:
|
|
||||||
|
|
||||||
color.Cyan("Prints text in cyan.")
|
|
||||||
|
|
||||||
// a newline will be appended automatically
|
|
||||||
color.Blue("Prints %s in blue.", "text")
|
|
||||||
|
|
||||||
// More default foreground colors..
|
|
||||||
color.Red("We have red")
|
|
||||||
color.Yellow("Yellow color too!")
|
|
||||||
color.Magenta("And many others ..")
|
|
||||||
|
|
||||||
// Hi-intensity colors
|
|
||||||
color.HiGreen("Bright green color.")
|
|
||||||
color.HiBlack("Bright black means gray..")
|
|
||||||
color.HiWhite("Shiny white color!")
|
|
||||||
|
|
||||||
However there are times where custom color mixes are required. Below are some
|
|
||||||
examples to create custom color objects and use the print functions of each
|
|
||||||
separate color object.
|
|
||||||
|
|
||||||
// Create a new color object
|
|
||||||
c := color.New(color.FgCyan).Add(color.Underline)
|
|
||||||
c.Println("Prints cyan text with an underline.")
|
|
||||||
|
|
||||||
// Or just add them to New()
|
|
||||||
d := color.New(color.FgCyan, color.Bold)
|
|
||||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
|
||||||
|
|
||||||
|
|
||||||
// Mix up foreground and background colors, create new mixes!
|
|
||||||
red := color.New(color.FgRed)
|
|
||||||
|
|
||||||
boldRed := red.Add(color.Bold)
|
|
||||||
boldRed.Println("This will print text in bold red.")
|
|
||||||
|
|
||||||
whiteBackground := red.Add(color.BgWhite)
|
|
||||||
whiteBackground.Println("Red text with White background.")
|
|
||||||
|
|
||||||
// Use your own io.Writer output
|
|
||||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
|
||||||
|
|
||||||
blue := color.New(color.FgBlue)
|
|
||||||
blue.Fprint(myWriter, "This will print text in blue.")
|
|
||||||
|
|
||||||
You can create PrintXxx functions to simplify even more:
|
|
||||||
|
|
||||||
// Create a custom print function for convenient
|
|
||||||
red := color.New(color.FgRed).PrintfFunc()
|
|
||||||
red("warning")
|
|
||||||
red("error: %s", err)
|
|
||||||
|
|
||||||
// Mix up multiple attributes
|
|
||||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
|
||||||
notice("don't forget this...")
|
|
||||||
|
|
||||||
You can also FprintXxx functions to pass your own io.Writer:
|
|
||||||
|
|
||||||
blue := color.New(FgBlue).FprintfFunc()
|
|
||||||
blue(myWriter, "important notice: %s", stars)
|
|
||||||
|
|
||||||
// Mix up with multiple attributes
|
|
||||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
|
||||||
success(myWriter, don't forget this...")
|
|
||||||
|
|
||||||
|
|
||||||
Or create SprintXxx functions to mix strings with other non-colorized strings:
|
|
||||||
|
|
||||||
yellow := New(FgYellow).SprintFunc()
|
|
||||||
red := New(FgRed).SprintFunc()
|
|
||||||
|
|
||||||
fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
|
|
||||||
|
|
||||||
info := New(FgWhite, BgGreen).SprintFunc()
|
|
||||||
fmt.Printf("this %s rocks!\n", info("package"))
|
|
||||||
|
|
||||||
Windows support is enabled by default. All Print functions work as intended.
|
|
||||||
However only for color.SprintXXX functions, user should use fmt.FprintXXX and
|
|
||||||
set the output to color.Output:
|
|
||||||
|
|
||||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
|
||||||
|
|
||||||
info := New(FgWhite, BgGreen).SprintFunc()
|
|
||||||
fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
|
|
||||||
|
|
||||||
Using with existing code is possible. Just use the Set() method to set the
|
|
||||||
standard output to the given parameters. That way a rewrite of an existing
|
|
||||||
code is not required.
|
|
||||||
|
|
||||||
// Use handy standard colors.
|
|
||||||
color.Set(color.FgYellow)
|
|
||||||
|
|
||||||
fmt.Println("Existing text will be now in Yellow")
|
|
||||||
fmt.Printf("This one %s\n", "too")
|
|
||||||
|
|
||||||
color.Unset() // don't forget to unset
|
|
||||||
|
|
||||||
// You can mix up parameters
|
|
||||||
color.Set(color.FgMagenta, color.Bold)
|
|
||||||
defer color.Unset() // use it in your function
|
|
||||||
|
|
||||||
fmt.Println("All text will be now bold magenta.")
|
|
||||||
|
|
||||||
There might be a case where you want to disable color output (for example to
|
|
||||||
pipe the standard output of your app to somewhere else). `Color` has support to
|
|
||||||
disable colors both globally and for single color definition. For example
|
|
||||||
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
|
|
||||||
the color output with:
|
|
||||||
|
|
||||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
|
||||||
|
|
||||||
if *flagNoColor {
|
|
||||||
color.NoColor = true // disables colorized output
|
|
||||||
}
|
|
||||||
|
|
||||||
It also has support for single color definitions (local). You can
|
|
||||||
disable/enable color output on the fly:
|
|
||||||
|
|
||||||
c := color.New(color.FgCyan)
|
|
||||||
c.Println("Prints cyan text")
|
|
||||||
|
|
||||||
c.DisableColor()
|
|
||||||
c.Println("This is printed without any color")
|
|
||||||
|
|
||||||
c.EnableColor()
|
|
||||||
c.Println("This prints again cyan...")
|
|
||||||
*/
|
|
||||||
package color
|
|
|
@ -1,26 +0,0 @@
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
||||||
*.prof
|
|
||||||
.DS_Store
|
|
||||||
/vendor
|
|
|
@ -1,6 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- tip
|
|
||||||
|
|
||||||
script: go test -v ./
|
|
|
@ -1,22 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,32 +0,0 @@
|
||||||
Overview
|
|
||||||
---
|
|
||||||
|
|
||||||
Bufferer
|
|
||||||
---
|
|
||||||
|
|
||||||
Block
|
|
||||||
---
|
|
||||||
|
|
||||||
BarChart
|
|
||||||
---
|
|
||||||
|
|
||||||
Canvas
|
|
||||||
---
|
|
||||||
|
|
||||||
Gauge
|
|
||||||
---
|
|
||||||
|
|
||||||
LineChart
|
|
||||||
---
|
|
||||||
|
|
||||||
MBarChart
|
|
||||||
---
|
|
||||||
|
|
||||||
Par
|
|
||||||
---
|
|
||||||
|
|
||||||
Sparkline
|
|
||||||
---
|
|
||||||
|
|
||||||
Sparklines
|
|
||||||
---
|
|
|
@ -1,14 +0,0 @@
|
||||||
Event System
|
|
||||||
---
|
|
||||||
|
|
||||||
Keyboard Events
|
|
||||||
---
|
|
||||||
|
|
||||||
Mouse Events
|
|
||||||
---
|
|
||||||
|
|
||||||
Window Events
|
|
||||||
---
|
|
||||||
|
|
||||||
Custom Events
|
|
||||||
---
|
|
Before Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 125 KiB |
|
@ -1,15 +0,0 @@
|
||||||
[termui]() is a cross-platform, easy-to-compile, and fully-customizable terminal dashboard. It aims to provide a terminal front end for your applications with less struggle:
|
|
||||||
|
|
||||||
> ![dashboard](img/dashboard.gif)
|
|
||||||
>
|
|
||||||
> _cast under osx 10.10; Terminal.app; Menlo Regular 12pt._
|
|
||||||
|
|
||||||
This guide describes the essential parts used to build a interface, which includes:
|
|
||||||
|
|
||||||
- Installation & Usage
|
|
||||||
- Layout System
|
|
||||||
- Event System
|
|
||||||
- Theming
|
|
||||||
- Components
|
|
||||||
|
|
||||||
[Quickstart](quickstart.md) is the way to go for starters and [Recipes](recipes.md) contains some practical resolutions you might need.
|
|
|
@ -1,26 +0,0 @@
|
||||||
Overview
|
|
||||||
---
|
|
||||||
|
|
||||||
termui offers two layout system: [Absolute]() and [Grid](). The two concept actually spawned from Web:
|
|
||||||
|
|
||||||
- The __Absolute layout__ is a plain coordination system, like [CSS position property](https://developer.mozilla.org/en/docs/Web/CSS/position) `position: absolute`. You will need manually assign `.X`, `.Y`, `.Width` and `.Height` to a component.
|
|
||||||
- The __Grid system__ actually is a simplified version of [the 12 columns CSS grid system](http://www.w3schools.com/bootstrap/bootstrap_grid_system.asp) on terminal. You do not need to bother setting positions and width properties, these values will be synced up according to their containers.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
`Align` property can help you set your component position based on terminal window. Find more at [Magic Variables](#magic-variables)
|
|
||||||
|
|
||||||
__Cons and pros:__
|
|
||||||
|
|
||||||
- Use of Absolute layout gives you maximum control over how to arrange your components, while you have
|
|
||||||
to put a little more effort to set things up. Fortunately there are some "magic variables" may help you out.
|
|
||||||
- Grid layout can save you some time, it adjusts components location and size based on it's container. But note that you do need to set `.Height` property to each components because termui can not decide it for you.
|
|
||||||
|
|
||||||
|
|
||||||
Absolute Layout
|
|
||||||
---
|
|
||||||
|
|
||||||
Grid Layout
|
|
||||||
---
|
|
||||||
|
|
||||||
Magic Variables
|
|
||||||
---
|
|
|
@ -1,80 +0,0 @@
|
||||||
Installation
|
|
||||||
---
|
|
||||||
|
|
||||||
Since [termui](https://github.com/gizak/termui) is a Go lib, we will need a working Go environment to begin with. If you have not set it up, there is a great intro you can follow up: [How to write Go code](https://golang.org/doc/code.html).
|
|
||||||
|
|
||||||
Once you have the environment set up, you can proceed to install termui by the following command:
|
|
||||||
|
|
||||||
`go get github.com/gizak/termui`
|
|
||||||
|
|
||||||
The current version of termui is v2. If you are working with the old version of termui or the new version does not seem right to you, you can always go back to v1 version by:
|
|
||||||
|
|
||||||
`go get gopkg.in/gizak/termui.v1`
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
v2 has many features implemented which you can not find in v1, such as new event system and asynchronous rendering. To find more about versions difference in section [Versions](versions.md).
|
|
||||||
|
|
||||||
|
|
||||||
Usage
|
|
||||||
---
|
|
||||||
|
|
||||||
Let's throw an simple example to get our feet wet:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import ui "github.com/gizak/termui" // use ui as an alias
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
ui.Render(p) // feel free to call Render, it's async and non-block
|
|
||||||
|
|
||||||
ui.Handle("/sys/kbd/q",func(e ui.Event){
|
|
||||||
ui.StopLoop()
|
|
||||||
})
|
|
||||||
|
|
||||||
ui.Loop()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
There are only around 20 lines for the main function. Break this down into 4 parts:
|
|
||||||
|
|
||||||
1. __Init termui__:
|
|
||||||
`ui.Init()` initializes the termui. From this point, termui will take over your terminal display.
|
|
||||||
`ui.Close()` closes resources and cleans up your terminal content. Make sure it is called before exit or you will end up with a messed up looking terminal.
|
|
||||||
|
|
||||||
2. __Build your component__:
|
|
||||||
`ui.NewPar(:PRESS q TO QUIT DEMO)` returns a structure representing a paragraph component. You can assign position, size, text colour, border and many other properties to a component.
|
|
||||||
|
|
||||||
3. __Draw your component on display__:
|
|
||||||
`ui.Render(p)` renders p onto terminal display.
|
|
||||||
|
|
||||||
4. __Handle events__:
|
|
||||||
`ui.Handle("/sys/kbd/q", func(e Event))` registers an event handler for event: key q is pressed.
|
|
||||||
`ui.StopLoop()` exits the event listening loop invoked by `ui.Loop()`.
|
|
||||||
`ui.Loop()` makes the program stops at here and start listening & handling events. Call
|
|
||||||
`ui.StopLoop()` to leave the circle.
|
|
||||||
|
|
||||||
The example code gives us:
|
|
||||||
|
|
||||||
> ![example screenshot](img/demo1.png)
|
|
||||||
|
|
||||||
Now you can press q to quit the program.
|
|
||||||
|
|
||||||
After knowing of some basics, next we can discover more about:
|
|
||||||
|
|
||||||
1. how to set component location in [Layouts](layouts.md)
|
|
||||||
2. how to capture and handle events in [Events](events.md)
|
|
||||||
3. the different [components](components.md)
|
|
||||||
4. check out some real world examples in [recipes](recipes.md)
|
|
|
@ -1 +0,0 @@
|
||||||
_Sorry, it is still Work in Progress..._
|
|
|
@ -1,36 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/gizak/termui"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := termui.Init(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
bc := termui.NewBarChart()
|
|
||||||
data := []int{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6}
|
|
||||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
|
|
||||||
bc.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
|
|
||||||
|
|
||||||
termui.Render(bc)
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
termui.Loop()
|
|
||||||
|
|
||||||
}
|
|
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 152 KiB |
|
@ -1,142 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import ui "github.com/gizak/termui"
|
|
||||||
import "math"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := ui.Init(); 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
|
|
||||||
p.Handle("/timer/1s", func(e ui.Event) {
|
|
||||||
cnt := e.Data.(ui.EvtTimer)
|
|
||||||
if cnt.Count%2 == 0 {
|
|
||||||
p.TextFgColor = ui.ColorRed
|
|
||||||
} else {
|
|
||||||
p.TextFgColor = ui.ColorWhite
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
strs := []string{"[0] gizak/termui", "[1] editbox.go", "[2] iterrupt.go", "[3] keyboard.go", "[4] output.go", "[5] random_out.go", "[6] dashboard.go", "[7] nsf/termbox-go"}
|
|
||||||
list := ui.NewList()
|
|
||||||
list.Items = strs
|
|
||||||
list.ItemFgColor = ui.ColorYellow
|
|
||||||
list.BorderLabel = "List"
|
|
||||||
list.Height = 7
|
|
||||||
list.Width = 25
|
|
||||||
list.Y = 4
|
|
||||||
|
|
||||||
g := ui.NewGauge()
|
|
||||||
g.Percent = 50
|
|
||||||
g.Width = 50
|
|
||||||
g.Height = 3
|
|
||||||
g.Y = 11
|
|
||||||
g.BorderLabel = "Gauge"
|
|
||||||
g.BarColor = ui.ColorRed
|
|
||||||
g.BorderFg = ui.ColorWhite
|
|
||||||
g.BorderLabelFg = ui.ColorCyan
|
|
||||||
|
|
||||||
spark := ui.Sparkline{}
|
|
||||||
spark.Height = 1
|
|
||||||
spark.Title = "srv 0:"
|
|
||||||
spdata := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6, 4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6}
|
|
||||||
spark.Data = spdata
|
|
||||||
spark.LineColor = ui.ColorCyan
|
|
||||||
spark.TitleColor = ui.ColorWhite
|
|
||||||
|
|
||||||
spark1 := ui.Sparkline{}
|
|
||||||
spark1.Height = 1
|
|
||||||
spark1.Title = "srv 1:"
|
|
||||||
spark1.Data = spdata
|
|
||||||
spark1.TitleColor = ui.ColorWhite
|
|
||||||
spark1.LineColor = ui.ColorRed
|
|
||||||
|
|
||||||
sp := ui.NewSparklines(spark, spark1)
|
|
||||||
sp.Width = 25
|
|
||||||
sp.Height = 7
|
|
||||||
sp.BorderLabel = "Sparkline"
|
|
||||||
sp.Y = 4
|
|
||||||
sp.X = 25
|
|
||||||
|
|
||||||
sinps := (func() []float64 {
|
|
||||||
n := 220
|
|
||||||
ps := make([]float64, n)
|
|
||||||
for i := range ps {
|
|
||||||
ps[i] = 1 + math.Sin(float64(i)/5)
|
|
||||||
}
|
|
||||||
return ps
|
|
||||||
})()
|
|
||||||
|
|
||||||
lc := ui.NewLineChart()
|
|
||||||
lc.BorderLabel = "dot-mode Line Chart"
|
|
||||||
lc.Data = sinps
|
|
||||||
lc.Width = 50
|
|
||||||
lc.Height = 11
|
|
||||||
lc.X = 0
|
|
||||||
lc.Y = 14
|
|
||||||
lc.AxesColor = ui.ColorWhite
|
|
||||||
lc.LineColor = ui.ColorRed | ui.AttrBold
|
|
||||||
lc.Mode = "dot"
|
|
||||||
|
|
||||||
bc := ui.NewBarChart()
|
|
||||||
bcdata := []int{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6}
|
|
||||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
|
|
||||||
bc.BorderLabel = "Bar Chart"
|
|
||||||
bc.Width = 26
|
|
||||||
bc.Height = 10
|
|
||||||
bc.X = 51
|
|
||||||
bc.Y = 0
|
|
||||||
bc.DataLabels = bclabels
|
|
||||||
bc.BarColor = ui.ColorGreen
|
|
||||||
bc.NumColor = ui.ColorBlack
|
|
||||||
|
|
||||||
lc1 := ui.NewLineChart()
|
|
||||||
lc1.BorderLabel = "braille-mode Line Chart"
|
|
||||||
lc1.Data = sinps
|
|
||||||
lc1.Width = 26
|
|
||||||
lc1.Height = 11
|
|
||||||
lc1.X = 51
|
|
||||||
lc1.Y = 14
|
|
||||||
lc1.AxesColor = ui.ColorWhite
|
|
||||||
lc1.LineColor = ui.ColorYellow | ui.AttrBold
|
|
||||||
|
|
||||||
p1 := ui.NewPar("Hey!\nI am a borderless block!")
|
|
||||||
p1.Border = false
|
|
||||||
p1.Width = 26
|
|
||||||
p1.Height = 2
|
|
||||||
p1.TextFgColor = ui.ColorMagenta
|
|
||||||
p1.X = 52
|
|
||||||
p1.Y = 11
|
|
||||||
|
|
||||||
draw := func(t int) {
|
|
||||||
g.Percent = t % 101
|
|
||||||
list.Items = strs[t%9:]
|
|
||||||
sp.Lines[0].Data = spdata[:30+t%50]
|
|
||||||
sp.Lines[1].Data = spdata[:35+t%50]
|
|
||||||
lc.Data = sinps[t/2%220:]
|
|
||||||
lc1.Data = sinps[2*t%220:]
|
|
||||||
bc.Data = bcdata[t/2%10:]
|
|
||||||
ui.Render(p, list, g, sp, lc, bc, lc1, p1)
|
|
||||||
}
|
|
||||||
ui.Handle("/sys/kbd/q", func(ui.Event) {
|
|
||||||
ui.StopLoop()
|
|
||||||
})
|
|
||||||
ui.Handle("/timer/1s", func(e ui.Event) {
|
|
||||||
t := e.Data.(ui.EvtTimer)
|
|
||||||
draw(int(t.Count))
|
|
||||||
})
|
|
||||||
ui.Loop()
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/gizak/termui"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := termui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
//termui.UseTheme("helloworld")
|
|
||||||
|
|
||||||
g0 := termui.NewGauge()
|
|
||||||
g0.Percent = 40
|
|
||||||
g0.Width = 50
|
|
||||||
g0.Height = 3
|
|
||||||
g0.BorderLabel = "Slim Gauge"
|
|
||||||
g0.BarColor = termui.ColorRed
|
|
||||||
g0.BorderFg = termui.ColorWhite
|
|
||||||
g0.BorderLabelFg = termui.ColorCyan
|
|
||||||
|
|
||||||
gg := termui.NewBlock()
|
|
||||||
gg.Width = 50
|
|
||||||
gg.Height = 5
|
|
||||||
gg.Y = 12
|
|
||||||
gg.BorderLabel = "TEST"
|
|
||||||
gg.Align()
|
|
||||||
|
|
||||||
g2 := termui.NewGauge()
|
|
||||||
g2.Percent = 60
|
|
||||||
g2.Width = 50
|
|
||||||
g2.Height = 3
|
|
||||||
g2.PercentColor = termui.ColorBlue
|
|
||||||
g2.Y = 3
|
|
||||||
g2.BorderLabel = "Slim Gauge"
|
|
||||||
g2.BarColor = termui.ColorYellow
|
|
||||||
g2.BorderFg = termui.ColorWhite
|
|
||||||
|
|
||||||
g1 := termui.NewGauge()
|
|
||||||
g1.Percent = 30
|
|
||||||
g1.Width = 50
|
|
||||||
g1.Height = 5
|
|
||||||
g1.Y = 6
|
|
||||||
g1.BorderLabel = "Big Gauge"
|
|
||||||
g1.PercentColor = termui.ColorYellow
|
|
||||||
g1.BarColor = termui.ColorGreen
|
|
||||||
g1.BorderFg = termui.ColorWhite
|
|
||||||
g1.BorderLabelFg = termui.ColorMagenta
|
|
||||||
|
|
||||||
g3 := termui.NewGauge()
|
|
||||||
g3.Percent = 50
|
|
||||||
g3.Width = 50
|
|
||||||
g3.Height = 3
|
|
||||||
g3.Y = 11
|
|
||||||
g3.BorderLabel = "Gauge with custom label"
|
|
||||||
g3.Label = "{{percent}}% (100MBs free)"
|
|
||||||
g3.LabelAlign = termui.AlignRight
|
|
||||||
|
|
||||||
g4 := termui.NewGauge()
|
|
||||||
g4.Percent = 50
|
|
||||||
g4.Width = 50
|
|
||||||
g4.Height = 3
|
|
||||||
g4.Y = 14
|
|
||||||
g4.BorderLabel = "Gauge"
|
|
||||||
g4.Label = "Gauge with custom highlighted label"
|
|
||||||
g4.PercentColor = termui.ColorYellow
|
|
||||||
g4.BarColor = termui.ColorGreen
|
|
||||||
g4.PercentColorHighlighted = termui.ColorBlack
|
|
||||||
|
|
||||||
termui.Render(g0, g1, g2, g3, g4)
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Loop()
|
|
||||||
}
|
|
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 782 KiB |
|
@ -1,122 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import ui "github.com/gizak/termui"
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := ui.Init(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer ui.Close()
|
|
||||||
|
|
||||||
sinps := (func() []float64 {
|
|
||||||
n := 400
|
|
||||||
ps := make([]float64, n)
|
|
||||||
for i := range ps {
|
|
||||||
ps[i] = 1 + math.Sin(float64(i)/5)
|
|
||||||
}
|
|
||||||
return ps
|
|
||||||
})()
|
|
||||||
sinpsint := (func() []int {
|
|
||||||
ps := make([]int, len(sinps))
|
|
||||||
for i, v := range sinps {
|
|
||||||
ps[i] = int(100*v + 10)
|
|
||||||
}
|
|
||||||
return ps
|
|
||||||
})()
|
|
||||||
|
|
||||||
spark := ui.Sparkline{}
|
|
||||||
spark.Height = 8
|
|
||||||
spdata := sinpsint
|
|
||||||
spark.Data = spdata[:100]
|
|
||||||
spark.LineColor = ui.ColorCyan
|
|
||||||
spark.TitleColor = ui.ColorWhite
|
|
||||||
|
|
||||||
sp := ui.NewSparklines(spark)
|
|
||||||
sp.Height = 11
|
|
||||||
sp.BorderLabel = "Sparkline"
|
|
||||||
|
|
||||||
lc := ui.NewLineChart()
|
|
||||||
lc.BorderLabel = "braille-mode Line Chart"
|
|
||||||
lc.Data = sinps
|
|
||||||
lc.Height = 11
|
|
||||||
lc.AxesColor = ui.ColorWhite
|
|
||||||
lc.LineColor = ui.ColorYellow | ui.AttrBold
|
|
||||||
|
|
||||||
gs := make([]*ui.Gauge, 3)
|
|
||||||
for i := range gs {
|
|
||||||
gs[i] = ui.NewGauge()
|
|
||||||
//gs[i].LabelAlign = ui.AlignCenter
|
|
||||||
gs[i].Height = 2
|
|
||||||
gs[i].Border = false
|
|
||||||
gs[i].Percent = i * 10
|
|
||||||
gs[i].PaddingBottom = 1
|
|
||||||
gs[i].BarColor = ui.ColorRed
|
|
||||||
}
|
|
||||||
|
|
||||||
ls := ui.NewList()
|
|
||||||
ls.Border = false
|
|
||||||
ls.Items = []string{
|
|
||||||
"[1] Downloading File 1",
|
|
||||||
"", // == \newline
|
|
||||||
"[2] Downloading File 2",
|
|
||||||
"",
|
|
||||||
"[3] Uploading File 3",
|
|
||||||
}
|
|
||||||
ls.Height = 5
|
|
||||||
|
|
||||||
par := ui.NewPar("<> This row has 3 columns\n<- Widgets can be stacked up like left side\n<- Stacked widgets are treated as a single widget")
|
|
||||||
par.Height = 5
|
|
||||||
par.BorderLabel = "Demonstration"
|
|
||||||
|
|
||||||
// build layout
|
|
||||||
ui.Body.AddRows(
|
|
||||||
ui.NewRow(
|
|
||||||
ui.NewCol(6, 0, sp),
|
|
||||||
ui.NewCol(6, 0, lc)),
|
|
||||||
ui.NewRow(
|
|
||||||
ui.NewCol(3, 0, ls),
|
|
||||||
ui.NewCol(3, 0, gs[0], gs[1], gs[2]),
|
|
||||||
ui.NewCol(6, 0, par)))
|
|
||||||
|
|
||||||
// calculate layout
|
|
||||||
ui.Body.Align()
|
|
||||||
|
|
||||||
ui.Render(ui.Body)
|
|
||||||
|
|
||||||
ui.Handle("/sys/kbd/q", func(ui.Event) {
|
|
||||||
ui.StopLoop()
|
|
||||||
})
|
|
||||||
ui.Handle("/timer/1s", func(e ui.Event) {
|
|
||||||
t := e.Data.(ui.EvtTimer)
|
|
||||||
i := t.Count
|
|
||||||
if i > 103 {
|
|
||||||
ui.StopLoop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, g := range gs {
|
|
||||||
g.Percent = (g.Percent + 3) % 100
|
|
||||||
}
|
|
||||||
|
|
||||||
sp.Lines[0].Data = spdata[:100+i]
|
|
||||||
lc.Data = sinps[2*i:]
|
|
||||||
ui.Render(ui.Body)
|
|
||||||
})
|
|
||||||
|
|
||||||
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
|
||||||
ui.Body.Width = ui.TermWidth()
|
|
||||||
ui.Body.Align()
|
|
||||||
ui.Clear()
|
|
||||||
ui.Render(ui.Body)
|
|
||||||
})
|
|
||||||
|
|
||||||
ui.Loop()
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/gizak/termui"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := termui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
//termui.UseTheme("helloworld")
|
|
||||||
|
|
||||||
sinps := (func() []float64 {
|
|
||||||
n := 220
|
|
||||||
ps := make([]float64, n)
|
|
||||||
for i := range ps {
|
|
||||||
ps[i] = 1 + math.Sin(float64(i)/5)
|
|
||||||
}
|
|
||||||
return ps
|
|
||||||
})()
|
|
||||||
|
|
||||||
lc0 := termui.NewLineChart()
|
|
||||||
lc0.BorderLabel = "braille-mode Line Chart"
|
|
||||||
lc0.Data = sinps
|
|
||||||
lc0.Width = 50
|
|
||||||
lc0.Height = 12
|
|
||||||
lc0.X = 0
|
|
||||||
lc0.Y = 0
|
|
||||||
lc0.AxesColor = termui.ColorWhite
|
|
||||||
lc0.LineColor = termui.ColorGreen | termui.AttrBold
|
|
||||||
|
|
||||||
lc1 := termui.NewLineChart()
|
|
||||||
lc1.BorderLabel = "dot-mode Line Chart"
|
|
||||||
lc1.Mode = "dot"
|
|
||||||
lc1.Data = sinps
|
|
||||||
lc1.Width = 26
|
|
||||||
lc1.Height = 12
|
|
||||||
lc1.X = 51
|
|
||||||
lc1.DotStyle = '+'
|
|
||||||
lc1.AxesColor = termui.ColorWhite
|
|
||||||
lc1.LineColor = termui.ColorYellow | termui.AttrBold
|
|
||||||
|
|
||||||
lc2 := termui.NewLineChart()
|
|
||||||
lc2.BorderLabel = "dot-mode Line Chart"
|
|
||||||
lc2.Mode = "dot"
|
|
||||||
lc2.Data = sinps[4:]
|
|
||||||
lc2.Width = 77
|
|
||||||
lc2.Height = 16
|
|
||||||
lc2.X = 0
|
|
||||||
lc2.Y = 12
|
|
||||||
lc2.AxesColor = termui.ColorWhite
|
|
||||||
lc2.LineColor = termui.ColorCyan | termui.AttrBold
|
|
||||||
|
|
||||||
termui.Render(lc0, lc1, lc2)
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
termui.Loop()
|
|
||||||
|
|
||||||
}
|
|
Before Width: | Height: | Size: 85 KiB |
|
@ -1,44 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/gizak/termui"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := termui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
//termui.UseTheme("helloworld")
|
|
||||||
|
|
||||||
strs := []string{
|
|
||||||
"[0] github.com/gizak/termui",
|
|
||||||
"[1] [你好,世界](fg-blue)",
|
|
||||||
"[2] [こんにちは世界](fg-red)",
|
|
||||||
"[3] [color output](fg-white,bg-green)",
|
|
||||||
"[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
|
|
||||||
|
|
||||||
termui.Render(ls)
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
termui.Loop()
|
|
||||||
|
|
||||||
}
|
|
Before Width: | Height: | Size: 28 KiB |
|
@ -1,54 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/gizak/termui"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := termui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
//termui.UseTheme("helloworld")
|
|
||||||
|
|
||||||
bc := termui.NewMBarChart()
|
|
||||||
math := []int{90, 85, 90, 80}
|
|
||||||
english := []int{70, 85, 75, 60}
|
|
||||||
science := []int{75, 60, 80, 85}
|
|
||||||
compsci := []int{100, 100, 100, 100}
|
|
||||||
bc.Data[0] = math
|
|
||||||
bc.Data[1] = english
|
|
||||||
bc.Data[2] = science
|
|
||||||
bc.Data[3] = compsci
|
|
||||||
studentsName := []string{"Ken", "Rob", "Dennis", "Linus"}
|
|
||||||
bc.BorderLabel = "Student's Marks X-Axis=Name Y-Axis=Marks[Math,English,Science,ComputerScience] in %"
|
|
||||||
bc.Width = 100
|
|
||||||
bc.Height = 30
|
|
||||||
bc.Y = 0
|
|
||||||
bc.BarWidth = 10
|
|
||||||
bc.DataLabels = studentsName
|
|
||||||
bc.ShowScale = true //Show y_axis scale value (min and max)
|
|
||||||
bc.SetMax(400)
|
|
||||||
|
|
||||||
bc.TextColor = termui.ColorGreen //this is color for label (x-axis)
|
|
||||||
bc.BarColor[3] = termui.ColorGreen //BarColor for computerscience
|
|
||||||
bc.BarColor[1] = termui.ColorYellow //Bar Color for english
|
|
||||||
bc.NumColor[3] = termui.ColorRed // Num color for computerscience
|
|
||||||
bc.NumColor[1] = termui.ColorRed // num color for english
|
|
||||||
|
|
||||||
//Other colors are automatically populated, btw All the students seems do well in computerscience. :p
|
|
||||||
|
|
||||||
termui.Render(bc)
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
termui.Loop()
|
|
||||||
|
|
||||||
}
|
|
Before Width: | Height: | Size: 71 KiB |
|
@ -1,52 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/gizak/termui"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := termui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
//termui.UseTheme("helloworld")
|
|
||||||
|
|
||||||
par0 := termui.NewPar("Borderless Text")
|
|
||||||
par0.Height = 1
|
|
||||||
par0.Width = 20
|
|
||||||
par0.Y = 1
|
|
||||||
par0.Border = false
|
|
||||||
|
|
||||||
par1 := termui.NewPar("你好,世界。")
|
|
||||||
par1.Height = 3
|
|
||||||
par1.Width = 17
|
|
||||||
par1.X = 20
|
|
||||||
par1.BorderLabel = "标签"
|
|
||||||
|
|
||||||
par2 := termui.NewPar("Simple colored text\nwith label. It [can be](fg-red) multilined with \\n or [break automatically](fg-red,fg-bold)")
|
|
||||||
par2.Height = 5
|
|
||||||
par2.Width = 37
|
|
||||||
par2.Y = 4
|
|
||||||
par2.BorderLabel = "Multiline"
|
|
||||||
par2.BorderFg = termui.ColorYellow
|
|
||||||
|
|
||||||
par3 := termui.NewPar("Long text with label and it is auto trimmed.")
|
|
||||||
par3.Height = 3
|
|
||||||
par3.Width = 37
|
|
||||||
par3.Y = 9
|
|
||||||
par3.BorderLabel = "Auto Trim"
|
|
||||||
|
|
||||||
termui.Render(par0, par1, par2, par3)
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
termui.Loop()
|
|
||||||
|
|
||||||
}
|
|
Before Width: | Height: | Size: 45 KiB |
|
@ -1,69 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/gizak/termui"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := termui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
//termui.UseTheme("helloworld")
|
|
||||||
|
|
||||||
data := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6}
|
|
||||||
spl0 := termui.NewSparkline()
|
|
||||||
spl0.Data = data[3:]
|
|
||||||
spl0.Title = "Sparkline 0"
|
|
||||||
spl0.LineColor = termui.ColorGreen
|
|
||||||
|
|
||||||
// single
|
|
||||||
spls0 := termui.NewSparklines(spl0)
|
|
||||||
spls0.Height = 2
|
|
||||||
spls0.Width = 20
|
|
||||||
spls0.Border = false
|
|
||||||
|
|
||||||
spl1 := termui.NewSparkline()
|
|
||||||
spl1.Data = data
|
|
||||||
spl1.Title = "Sparkline 1"
|
|
||||||
spl1.LineColor = termui.ColorRed
|
|
||||||
|
|
||||||
spl2 := termui.NewSparkline()
|
|
||||||
spl2.Data = data[5:]
|
|
||||||
spl2.Title = "Sparkline 2"
|
|
||||||
spl2.LineColor = termui.ColorMagenta
|
|
||||||
|
|
||||||
// group
|
|
||||||
spls1 := termui.NewSparklines(spl0, spl1, spl2)
|
|
||||||
spls1.Height = 8
|
|
||||||
spls1.Width = 20
|
|
||||||
spls1.Y = 3
|
|
||||||
spls1.BorderLabel = "Group Sparklines"
|
|
||||||
|
|
||||||
spl3 := termui.NewSparkline()
|
|
||||||
spl3.Data = data
|
|
||||||
spl3.Title = "Enlarged Sparkline"
|
|
||||||
spl3.Height = 8
|
|
||||||
spl3.LineColor = termui.ColorYellow
|
|
||||||
|
|
||||||
spls2 := termui.NewSparklines(spl3)
|
|
||||||
spls2.Height = 11
|
|
||||||
spls2.Width = 30
|
|
||||||
spls2.BorderFg = termui.ColorCyan
|
|
||||||
spls2.X = 21
|
|
||||||
spls2.BorderLabel = "Tweeked Sparkline"
|
|
||||||
|
|
||||||
termui.Render(spls0, spls1, spls2)
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
termui.Loop()
|
|
||||||
|
|
||||||
}
|
|
Before Width: | Height: | Size: 48 KiB |
|
@ -1,56 +0,0 @@
|
||||||
// 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 main
|
|
||||||
|
|
||||||
import "github.com/gizak/termui"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := termui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
rows1 := [][]string{
|
|
||||||
[]string{"header1", "header2", "header3"},
|
|
||||||
[]string{"你好吗", "Go-lang is so cool", "Im working on Ruby"},
|
|
||||||
[]string{"2016", "10", "11"},
|
|
||||||
}
|
|
||||||
|
|
||||||
table1 := termui.NewTable()
|
|
||||||
table1.Rows = rows1
|
|
||||||
table1.FgColor = termui.ColorWhite
|
|
||||||
table1.BgColor = termui.ColorDefault
|
|
||||||
table1.Y = 0
|
|
||||||
table1.X = 0
|
|
||||||
table1.Width = 62
|
|
||||||
table1.Height = 7
|
|
||||||
|
|
||||||
termui.Render(table1)
|
|
||||||
|
|
||||||
rows2 := [][]string{
|
|
||||||
[]string{"header1", "header2", "header3"},
|
|
||||||
[]string{"Foundations", "Go-lang is so cool", "Im working on Ruby"},
|
|
||||||
[]string{"2016", "11", "11"},
|
|
||||||
}
|
|
||||||
|
|
||||||
table2 := termui.NewTable()
|
|
||||||
table2.Rows = rows2
|
|
||||||
table2.FgColor = termui.ColorWhite
|
|
||||||
table2.BgColor = termui.ColorDefault
|
|
||||||
table2.TextAlign = termui.AlignCenter
|
|
||||||
table2.Separator = false
|
|
||||||
table2.Analysis()
|
|
||||||
table2.SetSize()
|
|
||||||
table2.BgColors[2] = termui.ColorRed
|
|
||||||
table2.Y = 10
|
|
||||||
table2.X = 0
|
|
||||||
table2.Border = true
|
|
||||||
|
|
||||||
termui.Render(table2)
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
termui.Loop()
|
|
||||||
}
|
|
Before Width: | Height: | Size: 46 KiB |
|
@ -1,83 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gizak/termui"
|
|
||||||
"github.com/gizak/termui/extra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := termui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
//termui.UseTheme("helloworld")
|
|
||||||
|
|
||||||
header := termui.NewPar("Press q to quit, Press j or k to switch tabs")
|
|
||||||
header.Height = 1
|
|
||||||
header.Width = 50
|
|
||||||
header.Border = false
|
|
||||||
header.TextBgColor = termui.ColorBlue
|
|
||||||
|
|
||||||
tab1 := extra.NewTab("pierwszy")
|
|
||||||
par2 := termui.NewPar("Press q to quit\nPress j or k to switch tabs\n")
|
|
||||||
par2.Height = 5
|
|
||||||
par2.Width = 37
|
|
||||||
par2.Y = 0
|
|
||||||
par2.BorderLabel = "Keys"
|
|
||||||
par2.BorderFg = termui.ColorYellow
|
|
||||||
tab1.AddBlocks(par2)
|
|
||||||
|
|
||||||
tab2 := extra.NewTab("drugi")
|
|
||||||
bc := termui.NewBarChart()
|
|
||||||
data := []int{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6}
|
|
||||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
|
|
||||||
bc.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
|
|
||||||
tab2.AddBlocks(bc)
|
|
||||||
|
|
||||||
tab3 := extra.NewTab("trzeci")
|
|
||||||
tab4 := extra.NewTab("żółw")
|
|
||||||
tab5 := extra.NewTab("four")
|
|
||||||
tab6 := extra.NewTab("five")
|
|
||||||
|
|
||||||
tabpane := extra.NewTabpane()
|
|
||||||
tabpane.Y = 1
|
|
||||||
tabpane.Width = 30
|
|
||||||
tabpane.Border = true
|
|
||||||
|
|
||||||
tabpane.SetTabs(*tab1, *tab2, *tab3, *tab4, *tab5, *tab6)
|
|
||||||
|
|
||||||
termui.Render(header, tabpane)
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/j", func(termui.Event) {
|
|
||||||
tabpane.SetActiveLeft()
|
|
||||||
termui.Clear()
|
|
||||||
termui.Render(header, tabpane)
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/k", func(termui.Event) {
|
|
||||||
tabpane.SetActiveRight()
|
|
||||||
termui.Clear()
|
|
||||||
termui.Render(header, tabpane)
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Loop()
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import ui "github.com/gizak/termui"
|
|
||||||
import "math"
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := ui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer ui.Close()
|
|
||||||
|
|
||||||
ui.UseTheme("helloworld")
|
|
||||||
|
|
||||||
p := ui.NewPar(":PRESS q TO QUIT DEMO")
|
|
||||||
p.Height = 3
|
|
||||||
p.Width = 50
|
|
||||||
p.BorderLabel = "Text Box"
|
|
||||||
|
|
||||||
strs := []string{"[0] gizak/termui", "[1] editbox.go", "[2] iterrupt.go", "[3] keyboard.go", "[4] output.go", "[5] random_out.go", "[6] dashboard.go", "[7] nsf/termbox-go"}
|
|
||||||
list := ui.NewList()
|
|
||||||
list.Items = strs
|
|
||||||
list.BorderLabel = "List"
|
|
||||||
list.Height = 7
|
|
||||||
list.Width = 25
|
|
||||||
list.Y = 4
|
|
||||||
|
|
||||||
g := ui.NewGauge()
|
|
||||||
g.Percent = 50
|
|
||||||
g.Width = 50
|
|
||||||
g.Height = 3
|
|
||||||
g.Y = 11
|
|
||||||
g.BorderLabel = "Gauge"
|
|
||||||
|
|
||||||
spark := ui.NewSparkline()
|
|
||||||
spark.Title = "srv 0:"
|
|
||||||
spdata := []int{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1, 7, 10, 10, 14, 13, 6}
|
|
||||||
spark.Data = spdata
|
|
||||||
|
|
||||||
spark1 := ui.NewSparkline()
|
|
||||||
spark1.Title = "srv 1:"
|
|
||||||
spark1.Data = spdata
|
|
||||||
|
|
||||||
sp := ui.NewSparklines(spark, spark1)
|
|
||||||
sp.Width = 25
|
|
||||||
sp.Height = 7
|
|
||||||
sp.BorderLabel = "Sparkline"
|
|
||||||
sp.Y = 4
|
|
||||||
sp.X = 25
|
|
||||||
|
|
||||||
lc := ui.NewLineChart()
|
|
||||||
sinps := (func() []float64 {
|
|
||||||
n := 100
|
|
||||||
ps := make([]float64, n)
|
|
||||||
for i := range ps {
|
|
||||||
ps[i] = 1 + math.Sin(float64(i)/4)
|
|
||||||
}
|
|
||||||
return ps
|
|
||||||
})()
|
|
||||||
|
|
||||||
lc.BorderLabel = "Line Chart"
|
|
||||||
lc.Data = sinps
|
|
||||||
lc.Width = 50
|
|
||||||
lc.Height = 11
|
|
||||||
lc.X = 0
|
|
||||||
lc.Y = 14
|
|
||||||
lc.Mode = "dot"
|
|
||||||
|
|
||||||
bc := ui.NewBarChart()
|
|
||||||
bcdata := []int{3, 2, 5, 3, 9, 5, 3, 2, 5, 8, 3, 2, 4, 5, 3, 2, 5, 7, 5, 3, 2, 6, 7, 4, 6, 3, 6, 7, 8, 3, 6, 4, 5, 3, 2, 4, 6, 4, 8, 5, 9, 4, 3, 6, 5, 3, 6}
|
|
||||||
bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
|
|
||||||
bc.BorderLabel = "Bar Chart"
|
|
||||||
bc.Width = 26
|
|
||||||
bc.Height = 10
|
|
||||||
bc.X = 51
|
|
||||||
bc.Y = 0
|
|
||||||
bc.DataLabels = bclabels
|
|
||||||
|
|
||||||
lc1 := ui.NewLineChart()
|
|
||||||
lc1.BorderLabel = "Line Chart"
|
|
||||||
rndwalk := (func() []float64 {
|
|
||||||
n := 150
|
|
||||||
d := make([]float64, n)
|
|
||||||
for i := 1; i < n; i++ {
|
|
||||||
if i < 20 {
|
|
||||||
d[i] = d[i-1] + 0.01
|
|
||||||
}
|
|
||||||
if i > 20 {
|
|
||||||
d[i] = d[i-1] - 0.05
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
})()
|
|
||||||
lc1.Data = rndwalk
|
|
||||||
lc1.Width = 26
|
|
||||||
lc1.Height = 11
|
|
||||||
lc1.X = 51
|
|
||||||
lc1.Y = 14
|
|
||||||
|
|
||||||
p1 := ui.NewPar("Hey!\nI am a borderless block!")
|
|
||||||
p1.HasBorder = false
|
|
||||||
p1.Width = 26
|
|
||||||
p1.Height = 2
|
|
||||||
p1.X = 52
|
|
||||||
p1.Y = 11
|
|
||||||
|
|
||||||
draw := func(t int) {
|
|
||||||
g.Percent = t % 101
|
|
||||||
list.Items = strs[t%9:]
|
|
||||||
sp.Lines[0].Data = spdata[t%10:]
|
|
||||||
sp.Lines[1].Data = spdata[t/2%10:]
|
|
||||||
lc.Data = sinps[t/2:]
|
|
||||||
lc1.Data = rndwalk[t:]
|
|
||||||
bc.Data = bcdata[t/2%10:]
|
|
||||||
ui.Render(p, list, g, sp, lc, bc, lc1, p1)
|
|
||||||
}
|
|
||||||
|
|
||||||
evt := ui.EventCh()
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case e := <-evt:
|
|
||||||
if e.Type == ui.EventKey && e.Ch == 'q' {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
draw(i)
|
|
||||||
i++
|
|
||||||
if i == 102 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second / 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 88 KiB |
|
@ -1,369 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gizak/termui"
|
|
||||||
"github.com/gizak/termui/extra"
|
|
||||||
)
|
|
||||||
|
|
||||||
const statFilePath = "/proc/stat"
|
|
||||||
const meminfoFilePath = "/proc/meminfo"
|
|
||||||
|
|
||||||
type CpuStat struct {
|
|
||||||
user float32
|
|
||||||
nice float32
|
|
||||||
system float32
|
|
||||||
idle float32
|
|
||||||
}
|
|
||||||
|
|
||||||
type CpusStats struct {
|
|
||||||
stat map[string]CpuStat
|
|
||||||
proc map[string]CpuStat
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCpusStats(s map[string]CpuStat) *CpusStats {
|
|
||||||
return &CpusStats{stat: s, proc: make(map[string]CpuStat)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *CpusStats) String() (ret string) {
|
|
||||||
for key, _ := range cs.proc {
|
|
||||||
ret += fmt.Sprintf("%s: %.2f %.2f %.2f %.2f\n", key, cs.proc[key].user, cs.proc[key].nice, cs.proc[key].system, cs.proc[key].idle)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func subCpuStat(m CpuStat, s CpuStat) CpuStat {
|
|
||||||
return CpuStat{user: m.user - s.user,
|
|
||||||
nice: m.nice - s.nice,
|
|
||||||
system: m.system - s.system,
|
|
||||||
idle: m.idle - s.idle}
|
|
||||||
}
|
|
||||||
|
|
||||||
func procCpuStat(c CpuStat) CpuStat {
|
|
||||||
sum := c.user + c.nice + c.system + c.idle
|
|
||||||
return CpuStat{user: c.user / sum * 100,
|
|
||||||
nice: c.nice / sum * 100,
|
|
||||||
system: c.system / sum * 100,
|
|
||||||
idle: c.idle / sum * 100}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *CpusStats) tick(ns map[string]CpuStat) {
|
|
||||||
for key, _ := range cs.stat {
|
|
||||||
proc := subCpuStat(ns[key], cs.stat[key])
|
|
||||||
cs.proc[key] = procCpuStat(proc)
|
|
||||||
cs.stat[key] = ns[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type errIntParser struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (eip *errIntParser) parse(s string) (ret int64) {
|
|
||||||
if eip.err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
ret, eip.err = strconv.ParseInt(s, 10, 0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type LineProcessor interface {
|
|
||||||
process(string) error
|
|
||||||
finalize() interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type CpuLineProcessor struct {
|
|
||||||
m map[string]CpuStat
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clp *CpuLineProcessor) process(line string) (err error) {
|
|
||||||
r := regexp.MustCompile("^cpu([0-9]*)")
|
|
||||||
|
|
||||||
if r.MatchString(line) {
|
|
||||||
tab := strings.Fields(line)
|
|
||||||
if len(tab) < 5 {
|
|
||||||
err = errors.New("cpu info line has not enough fields")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parser := errIntParser{}
|
|
||||||
cs := CpuStat{user: float32(parser.parse(tab[1])),
|
|
||||||
nice: float32(parser.parse(tab[2])),
|
|
||||||
system: float32(parser.parse(tab[3])),
|
|
||||||
idle: float32(parser.parse(tab[4]))}
|
|
||||||
clp.m[tab[0]] = cs
|
|
||||||
err = parser.err
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clp *CpuLineProcessor) finalize() interface{} {
|
|
||||||
return clp.m
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemStat struct {
|
|
||||||
total int64
|
|
||||||
free int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms MemStat) String() (ret string) {
|
|
||||||
ret = fmt.Sprintf("TotalMem: %d, FreeMem: %d\n", ms.total, ms.free)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MemStat) process(line string) (err error) {
|
|
||||||
rtotal := regexp.MustCompile("^MemTotal:")
|
|
||||||
rfree := regexp.MustCompile("^MemFree:")
|
|
||||||
var aux int64
|
|
||||||
if rtotal.MatchString(line) || rfree.MatchString(line) {
|
|
||||||
tab := strings.Fields(line)
|
|
||||||
if len(tab) < 3 {
|
|
||||||
err = errors.New("mem info line has not enough fields")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aux, err = strconv.ParseInt(tab[1], 10, 0)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if rtotal.MatchString(line) {
|
|
||||||
ms.total = aux
|
|
||||||
}
|
|
||||||
if rfree.MatchString(line) {
|
|
||||||
ms.free = aux
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MemStat) finalize() interface{} {
|
|
||||||
return *ms
|
|
||||||
}
|
|
||||||
|
|
||||||
func processFileLines(filePath string, lp LineProcessor) (ret interface{}, err error) {
|
|
||||||
var statFile *os.File
|
|
||||||
statFile, err = os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("open: %v\n", err)
|
|
||||||
}
|
|
||||||
defer statFile.Close()
|
|
||||||
|
|
||||||
statFileReader := bufio.NewReader(statFile)
|
|
||||||
|
|
||||||
for {
|
|
||||||
var line string
|
|
||||||
line, err = statFileReader.ReadString('\n')
|
|
||||||
if err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("open: %v\n", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
|
|
||||||
err = lp.process(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = lp.finalize()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCpusStatsMap() (m map[string]CpuStat, err error) {
|
|
||||||
var aux interface{}
|
|
||||||
aux, err = processFileLines(statFilePath, &CpuLineProcessor{m: make(map[string]CpuStat)})
|
|
||||||
return aux.(map[string]CpuStat), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMemStats() (ms MemStat, err error) {
|
|
||||||
var aux interface{}
|
|
||||||
aux, err = processFileLines(meminfoFilePath, &MemStat{})
|
|
||||||
return aux.(MemStat), err
|
|
||||||
}
|
|
||||||
|
|
||||||
type CpuTabElems struct {
|
|
||||||
GMap map[string]*termui.Gauge
|
|
||||||
LChart *termui.LineChart
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCpuTabElems(width int) *CpuTabElems {
|
|
||||||
lc := termui.NewLineChart()
|
|
||||||
lc.Width = width
|
|
||||||
lc.Height = 12
|
|
||||||
lc.X = 0
|
|
||||||
lc.Mode = "dot"
|
|
||||||
lc.BorderLabel = "CPU"
|
|
||||||
return &CpuTabElems{GMap: make(map[string]*termui.Gauge),
|
|
||||||
LChart: lc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cte *CpuTabElems) AddGauge(key string, Y int, width int) *termui.Gauge {
|
|
||||||
cte.GMap[key] = termui.NewGauge()
|
|
||||||
cte.GMap[key].Width = width
|
|
||||||
cte.GMap[key].Height = 3
|
|
||||||
cte.GMap[key].Y = Y
|
|
||||||
cte.GMap[key].BorderLabel = key
|
|
||||||
cte.GMap[key].Percent = 0 //int(val.user + val.nice + val.system)
|
|
||||||
return cte.GMap[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cte *CpuTabElems) Update(cs CpusStats) {
|
|
||||||
for key, val := range cs.proc {
|
|
||||||
p := int(val.user + val.nice + val.system)
|
|
||||||
cte.GMap[key].Percent = p
|
|
||||||
if key == "cpu" {
|
|
||||||
cte.LChart.Data = append(cte.LChart.Data, 0)
|
|
||||||
copy(cte.LChart.Data[1:], cte.LChart.Data[0:])
|
|
||||||
cte.LChart.Data[0] = float64(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemTabElems struct {
|
|
||||||
Gauge *termui.Gauge
|
|
||||||
SLines *termui.Sparklines
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMemTabElems(width int) *MemTabElems {
|
|
||||||
g := termui.NewGauge()
|
|
||||||
g.Width = width
|
|
||||||
g.Height = 3
|
|
||||||
g.Y = 0
|
|
||||||
|
|
||||||
sline := termui.NewSparkline()
|
|
||||||
sline.Title = "MEM"
|
|
||||||
sline.Height = 8
|
|
||||||
|
|
||||||
sls := termui.NewSparklines(sline)
|
|
||||||
sls.Width = width
|
|
||||||
sls.Height = 12
|
|
||||||
sls.Y = 3
|
|
||||||
return &MemTabElems{Gauge: g, SLines: sls}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mte *MemTabElems) Update(ms MemStat) {
|
|
||||||
used := int((ms.total - ms.free) * 100 / ms.total)
|
|
||||||
mte.Gauge.Percent = used
|
|
||||||
mte.SLines.Lines[0].Data = append(mte.SLines.Lines[0].Data, 0)
|
|
||||||
copy(mte.SLines.Lines[0].Data[1:], mte.SLines.Lines[0].Data[0:])
|
|
||||||
mte.SLines.Lines[0].Data[0] = used
|
|
||||||
if len(mte.SLines.Lines[0].Data) > mte.SLines.Width-2 {
|
|
||||||
mte.SLines.Lines[0].Data = mte.SLines.Lines[0].Data[0 : mte.SLines.Width-2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
panic("Currently works only on Linux")
|
|
||||||
}
|
|
||||||
err := termui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
termWidth := 70
|
|
||||||
|
|
||||||
//termui.UseTheme("helloworld")
|
|
||||||
|
|
||||||
header := termui.NewPar("Press q to quit, Press j or k to switch tabs")
|
|
||||||
header.Height = 1
|
|
||||||
header.Width = 50
|
|
||||||
header.Border = false
|
|
||||||
header.TextBgColor = termui.ColorBlue
|
|
||||||
|
|
||||||
tabCpu := extra.NewTab("CPU")
|
|
||||||
tabMem := extra.NewTab("MEM")
|
|
||||||
|
|
||||||
tabpane := extra.NewTabpane()
|
|
||||||
tabpane.Y = 1
|
|
||||||
tabpane.Width = 30
|
|
||||||
tabpane.Border = false
|
|
||||||
|
|
||||||
cs, errcs := getCpusStatsMap()
|
|
||||||
cpusStats := NewCpusStats(cs)
|
|
||||||
|
|
||||||
if errcs != nil {
|
|
||||||
panic("error")
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuTabElems := NewCpuTabElems(termWidth)
|
|
||||||
|
|
||||||
Y := 0
|
|
||||||
cpuKeys := make([]string, 0, len(cs))
|
|
||||||
for key := range cs {
|
|
||||||
cpuKeys = append(cpuKeys, key)
|
|
||||||
}
|
|
||||||
sort.Strings(cpuKeys)
|
|
||||||
for _, key := range cpuKeys {
|
|
||||||
g := cpuTabElems.AddGauge(key, Y, termWidth)
|
|
||||||
Y += 3
|
|
||||||
tabCpu.AddBlocks(g)
|
|
||||||
}
|
|
||||||
cpuTabElems.LChart.Y = Y
|
|
||||||
tabCpu.AddBlocks(cpuTabElems.LChart)
|
|
||||||
|
|
||||||
memTabElems := NewMemTabElems(termWidth)
|
|
||||||
ms, errm := getMemStats()
|
|
||||||
if errm != nil {
|
|
||||||
panic(errm)
|
|
||||||
}
|
|
||||||
memTabElems.Update(ms)
|
|
||||||
tabMem.AddBlocks(memTabElems.Gauge)
|
|
||||||
tabMem.AddBlocks(memTabElems.SLines)
|
|
||||||
|
|
||||||
tabpane.SetTabs(*tabCpu, *tabMem)
|
|
||||||
|
|
||||||
termui.Render(header, tabpane)
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/q", func(termui.Event) {
|
|
||||||
termui.StopLoop()
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/j", func(termui.Event) {
|
|
||||||
tabpane.SetActiveLeft()
|
|
||||||
termui.Render(header, tabpane)
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Handle("/sys/kbd/k", func(termui.Event) {
|
|
||||||
tabpane.SetActiveRight()
|
|
||||||
termui.Render(header, tabpane)
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Handle("/timer/1s", func(e termui.Event) {
|
|
||||||
cs, errcs := getCpusStatsMap()
|
|
||||||
if errcs != nil {
|
|
||||||
panic(errcs)
|
|
||||||
}
|
|
||||||
cpusStats.tick(cs)
|
|
||||||
cpuTabElems.Update(*cpusStats)
|
|
||||||
|
|
||||||
ms, errm := getMemStats()
|
|
||||||
if errm != nil {
|
|
||||||
panic(errm)
|
|
||||||
}
|
|
||||||
memTabElems.Update(ms)
|
|
||||||
termui.Render(header, tabpane)
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Loop()
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
// 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 main
|
|
||||||
|
|
||||||
import ui "github.com/gizak/termui"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
err := ui.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer ui.Close()
|
|
||||||
|
|
||||||
p := ui.NewPar("Press q to QUIT THE DEMO. [There](fg-blue) are other things [that](fg-red) are going to fit in here I think. What do you think? Now is the time for all good [men to](bg-blue) come to the aid of their country. [This is going to be one really really really long line](fg-green) that is going to go together and stuffs and things. Let's see how this thing renders out.\n Here is a new paragraph and stuffs and things. There should be a tab indent at the beginning of the paragraph. Let's see if that worked as well.")
|
|
||||||
p.WrapLength = 48 // this should be at least p.Width - 2
|
|
||||||
p.Height = 20
|
|
||||||
p.Width = 50
|
|
||||||
p.Y = 2
|
|
||||||
p.X = 20
|
|
||||||
p.TextFgColor = ui.ColorWhite
|
|
||||||
p.BorderLabel = "Text Box with Wrapping"
|
|
||||||
p.BorderFg = ui.ColorCyan
|
|
||||||
//p.Border = false
|
|
||||||
|
|
||||||
ui.Render(p)
|
|
||||||
|
|
||||||
ui.Handle("/sys/kbd/q", func(ui.Event) {
|
|
||||||
ui.StopLoop()
|
|
||||||
})
|
|
||||||
|
|
||||||
ui.Loop()
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,240 +0,0 @@
|
||||||
// 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 }
|
|
|
@ -1,20 +0,0 @@
|
||||||
// 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 = '»'
|
|
|
@ -1,72 +0,0 @@
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBlockFloat(t *testing.T) {
|
|
||||||
Init()
|
|
||||||
defer Close()
|
|
||||||
|
|
||||||
b := NewBlock()
|
|
||||||
b.X = 10
|
|
||||||
b.Y = 20
|
|
||||||
|
|
||||||
b.Float = AlignCenter
|
|
||||||
b.Align()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBlockInnerBounds(t *testing.T) {
|
|
||||||
Init()
|
|
||||||
defer Close()
|
|
||||||
|
|
||||||
b := NewBlock()
|
|
||||||
b.X = 10
|
|
||||||
b.Y = 11
|
|
||||||
b.Width = 12
|
|
||||||
b.Height = 13
|
|
||||||
|
|
||||||
assert := func(name string, x, y, w, h int) {
|
|
||||||
t.Log(name)
|
|
||||||
area := b.InnerBounds()
|
|
||||||
cx := area.Min.X
|
|
||||||
cy := area.Min.Y
|
|
||||||
cw := area.Dx()
|
|
||||||
ch := area.Dy()
|
|
||||||
|
|
||||||
if cx != x {
|
|
||||||
t.Errorf("expected x to be %d but got %d", x, cx)
|
|
||||||
}
|
|
||||||
if cy != y {
|
|
||||||
t.Errorf("expected y to be %d but got %d\n%+v", y, cy, area)
|
|
||||||
}
|
|
||||||
if cw != w {
|
|
||||||
t.Errorf("expected width to be %d but got %d", w, cw)
|
|
||||||
}
|
|
||||||
if ch != h {
|
|
||||||
t.Errorf("expected height to be %d but got %d", h, ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Border = false
|
|
||||||
assert("no border, no padding", 10, 11, 12, 13)
|
|
||||||
|
|
||||||
b.Border = true
|
|
||||||
assert("border, no padding", 11, 12, 10, 11)
|
|
||||||
|
|
||||||
b.PaddingBottom = 2
|
|
||||||
assert("border, 2b padding", 11, 12, 10, 9)
|
|
||||||
|
|
||||||
b.PaddingTop = 3
|
|
||||||
assert("border, 2b 3t padding", 11, 15, 10, 6)
|
|
||||||
|
|
||||||
b.PaddingLeft = 4
|
|
||||||
assert("border, 2b 3t 4l padding", 15, 15, 6, 6)
|
|
||||||
|
|
||||||
b.PaddingRight = 5
|
|
||||||
assert("border, 2b 3t 4l 5r padding", 15, 15, 1, 6)
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
// 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 = '+'
|
|
|
@ -1,106 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
// 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"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBufferUnion(t *testing.T) {
|
|
||||||
b0 := NewBuffer()
|
|
||||||
b1 := NewBuffer()
|
|
||||||
|
|
||||||
b1.Area.Max.X = 100
|
|
||||||
b1.Area.Max.Y = 100
|
|
||||||
b0.Area.Max.X = 50
|
|
||||||
b0.Merge(b1)
|
|
||||||
if b0.Area.Max.X != 100 {
|
|
||||||
t.Errorf("Buffer.Merge unions Area failed: should:%v, actual %v,%v", image.Rect(0, 0, 50, 0).Union(image.Rect(0, 0, 100, 100)), b1.Area, b0.Area)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
// 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 ignore
|
|
||||||
|
|
||||||
package termui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCanvasSet(t *testing.T) {
|
|
||||||
c := NewCanvas()
|
|
||||||
c.Set(0, 0)
|
|
||||||
c.Set(0, 1)
|
|
||||||
c.Set(0, 2)
|
|
||||||
c.Set(0, 3)
|
|
||||||
c.Set(1, 3)
|
|
||||||
c.Set(2, 3)
|
|
||||||
c.Set(3, 3)
|
|
||||||
c.Set(4, 3)
|
|
||||||
c.Set(5, 3)
|
|
||||||
spew.Dump(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCanvasUnset(t *testing.T) {
|
|
||||||
c := NewCanvas()
|
|
||||||
c.Set(0, 0)
|
|
||||||
c.Set(0, 1)
|
|
||||||
c.Set(0, 2)
|
|
||||||
c.Unset(0, 2)
|
|
||||||
spew.Dump(c)
|
|
||||||
c.Unset(0, 3)
|
|
||||||
spew.Dump(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCanvasBuffer(t *testing.T) {
|
|
||||||
c := NewCanvas()
|
|
||||||
c.Set(0, 0)
|
|
||||||
c.Set(0, 1)
|
|
||||||
c.Set(0, 2)
|
|
||||||
c.Set(0, 3)
|
|
||||||
c.Set(1, 3)
|
|
||||||
c.Set(2, 3)
|
|
||||||
c.Set(3, 3)
|
|
||||||
c.Set(4, 3)
|
|
||||||
c.Set(5, 3)
|
|
||||||
c.Set(6, 3)
|
|
||||||
c.Set(7, 2)
|
|
||||||
c.Set(8, 1)
|
|
||||||
c.Set(9, 0)
|
|
||||||
bufs := c.Buffer()
|
|
||||||
spew.Dump(bufs)
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
#!/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()
|
|
|
@ -1,117 +0,0 @@
|
||||||
// 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 debug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
Port string
|
|
||||||
Addr string
|
|
||||||
Path string
|
|
||||||
Msg chan string
|
|
||||||
chs []chan string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
Port string
|
|
||||||
Addr string
|
|
||||||
Path string
|
|
||||||
ws *websocket.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultPort = ":8080"
|
|
||||||
|
|
||||||
func NewServer() *Server {
|
|
||||||
return &Server{
|
|
||||||
Port: defaultPort,
|
|
||||||
Addr: "localhost",
|
|
||||||
Path: "/echo",
|
|
||||||
Msg: make(chan string),
|
|
||||||
chs: make([]chan string, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient() Client {
|
|
||||||
return Client{
|
|
||||||
Port: defaultPort,
|
|
||||||
Addr: "localhost",
|
|
||||||
Path: "/echo",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Client) ConnectAndListen() error {
|
|
||||||
ws, err := websocket.Dial("ws://"+c.Addr+c.Port+c.Path, "", "http://"+c.Addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer ws.Close()
|
|
||||||
|
|
||||||
var m string
|
|
||||||
for {
|
|
||||||
err := websocket.Message.Receive(ws, &m)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Print(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Print(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) ListenAndServe() error {
|
|
||||||
http.Handle(s.Path, websocket.Handler(func(ws *websocket.Conn) {
|
|
||||||
defer ws.Close()
|
|
||||||
|
|
||||||
mc := make(chan string)
|
|
||||||
s.chs = append(s.chs, mc)
|
|
||||||
|
|
||||||
for m := range mc {
|
|
||||||
websocket.Message.Send(ws, m)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for msg := range s.Msg {
|
|
||||||
for _, c := range s.chs {
|
|
||||||
go func(a chan string) {
|
|
||||||
a <- msg
|
|
||||||
}(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return http.ListenAndServe(s.Port, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Log(msg string) {
|
|
||||||
go func() { s.Msg <- msg }()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Logf(format string, a ...interface{}) {
|
|
||||||
s.Log(fmt.Sprintf(format, a...))
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultServer = NewServer()
|
|
||||||
var DefaultClient = NewClient()
|
|
||||||
|
|
||||||
func ListenAndServe() error {
|
|
||||||
return DefaultServer.ListenAndServe()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConnectAndListen() error {
|
|
||||||
return DefaultClient.ConnectAndListen()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Log(msg string) {
|
|
||||||
DefaultServer.Log(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Logf(format string, a ...interface{}) {
|
|
||||||
DefaultServer.Logf(format, a...)
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
// 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
|
|
|
@ -1,323 +0,0 @@
|
||||||
// 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 DefualtHandler = func(e Event) {
|
|
||||||
}
|
|
||||||
|
|
||||||
var usrEvtCh = make(chan Event)
|
|
||||||
|
|
||||||
func SendCustomEvt(path string, data interface{}) {
|
|
||||||
e := Event{}
|
|
||||||
e.Path = path
|
|
||||||
e.Data = data
|
|
||||||
e.Time = time.Now().Unix()
|
|
||||||
usrEvtCh <- e
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
// 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 "testing"
|
|
||||||
|
|
||||||
var ps = []string{
|
|
||||||
"",
|
|
||||||
"/",
|
|
||||||
"/a",
|
|
||||||
"/b",
|
|
||||||
"/a/c",
|
|
||||||
"/a/b",
|
|
||||||
"/a/b/c",
|
|
||||||
"/a/b/c/d",
|
|
||||||
"/a/b/c/d/"}
|
|
||||||
|
|
||||||
func TestMatchScore(t *testing.T) {
|
|
||||||
chk := func(a, b string, s bool) {
|
|
||||||
if c := isPathMatch(a, b); c != s {
|
|
||||||
t.Errorf("\na:%s\nb:%s\nshould:%t\nactual:%t", a, b, s, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chk(ps[1], ps[1], true)
|
|
||||||
chk(ps[1], ps[2], true)
|
|
||||||
chk(ps[2], ps[1], false)
|
|
||||||
chk(ps[4], ps[1], false)
|
|
||||||
chk(ps[6], ps[2], false)
|
|
||||||
chk(ps[4], ps[5], false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCrtEvt(t *testing.T) {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,265 +0,0 @@
|
||||||
// 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 extra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
. "github.com/gizak/termui"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Tab struct {
|
|
||||||
Label string
|
|
||||||
RuneLen int
|
|
||||||
Blocks []Bufferer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTab(label string) *Tab {
|
|
||||||
return &Tab{
|
|
||||||
Label: label,
|
|
||||||
RuneLen: utf8.RuneCount([]byte(label))}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tab *Tab) AddBlocks(rs ...Bufferer) {
|
|
||||||
for _, r := range rs {
|
|
||||||
tab.Blocks = append(tab.Blocks, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tab *Tab) Buffer() Buffer {
|
|
||||||
buf := NewBuffer()
|
|
||||||
for blockNum := 0; blockNum < len(tab.Blocks); blockNum++ {
|
|
||||||
b := tab.Blocks[blockNum]
|
|
||||||
buf.Merge(b.Buffer())
|
|
||||||
}
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tabpane struct {
|
|
||||||
Block
|
|
||||||
Tabs []Tab
|
|
||||||
activeTabIndex int
|
|
||||||
ActiveTabBg Attribute
|
|
||||||
posTabText []int
|
|
||||||
offTabText int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTabpane() *Tabpane {
|
|
||||||
tp := Tabpane{
|
|
||||||
Block: *NewBlock(),
|
|
||||||
activeTabIndex: 0,
|
|
||||||
offTabText: 0,
|
|
||||||
ActiveTabBg: ThemeAttr("bg.tab.active")}
|
|
||||||
return &tp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tp *Tabpane) SetTabs(tabs ...Tab) {
|
|
||||||
tp.Tabs = make([]Tab, len(tabs))
|
|
||||||
tp.posTabText = make([]int, len(tabs)+1)
|
|
||||||
off := 0
|
|
||||||
for i := 0; i < len(tp.Tabs); i++ {
|
|
||||||
tp.Tabs[i] = tabs[i]
|
|
||||||
tp.posTabText[i] = off
|
|
||||||
off += tp.Tabs[i].RuneLen + 1 //+1 for space between tabs
|
|
||||||
}
|
|
||||||
tp.posTabText[len(tabs)] = off - 1 //total length of Tab's text
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tp *Tabpane) SetActiveLeft() {
|
|
||||||
if tp.activeTabIndex == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tp.activeTabIndex -= 1
|
|
||||||
if tp.posTabText[tp.activeTabIndex] < tp.offTabText {
|
|
||||||
tp.offTabText = tp.posTabText[tp.activeTabIndex]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tp *Tabpane) SetActiveRight() {
|
|
||||||
if tp.activeTabIndex == len(tp.Tabs)-1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tp.activeTabIndex += 1
|
|
||||||
endOffset := tp.posTabText[tp.activeTabIndex] + tp.Tabs[tp.activeTabIndex].RuneLen
|
|
||||||
if endOffset+tp.offTabText > tp.InnerWidth() {
|
|
||||||
tp.offTabText = endOffset - tp.InnerWidth()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if left and right tabs are fully visible
|
|
||||||
// if only left tabs are not visible return -1
|
|
||||||
// if only right tabs are not visible return 1
|
|
||||||
// if both return 0
|
|
||||||
// use only if fitsWidth() returns false
|
|
||||||
func (tp *Tabpane) checkAlignment() int {
|
|
||||||
ret := 0
|
|
||||||
if tp.offTabText > 0 {
|
|
||||||
ret = -1
|
|
||||||
}
|
|
||||||
if tp.offTabText+tp.InnerWidth() < tp.posTabText[len(tp.Tabs)] {
|
|
||||||
ret += 1
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if all tabs fits innerWidth of Tabpane
|
|
||||||
func (tp *Tabpane) fitsWidth() bool {
|
|
||||||
return tp.InnerWidth() >= tp.posTabText[len(tp.Tabs)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tp *Tabpane) align() {
|
|
||||||
if !tp.fitsWidth() && !tp.Border {
|
|
||||||
tp.PaddingLeft += 1
|
|
||||||
tp.PaddingRight += 1
|
|
||||||
tp.Block.Align()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// bridge the old Point stuct
|
|
||||||
type point struct {
|
|
||||||
X int
|
|
||||||
Y int
|
|
||||||
Ch rune
|
|
||||||
Fg Attribute
|
|
||||||
Bg Attribute
|
|
||||||
}
|
|
||||||
|
|
||||||
func buf2pt(b Buffer) []point {
|
|
||||||
ps := make([]point, 0, len(b.CellMap))
|
|
||||||
for k, c := range b.CellMap {
|
|
||||||
ps = append(ps, point{X: k.X, Y: k.Y, Ch: c.Ch, Fg: c.Fg, Bg: c.Bg})
|
|
||||||
}
|
|
||||||
|
|
||||||
return ps
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds the point only if it is visible in Tabpane.
|
|
||||||
// Point can be invisible if concatenation of Tab's texts is widther then
|
|
||||||
// innerWidth of Tabpane
|
|
||||||
func (tp *Tabpane) addPoint(ptab []point, charOffset *int, oftX *int, points ...point) []point {
|
|
||||||
if *charOffset < tp.offTabText || tp.offTabText+tp.InnerWidth() < *charOffset {
|
|
||||||
*charOffset++
|
|
||||||
return ptab
|
|
||||||
}
|
|
||||||
for _, p := range points {
|
|
||||||
p.X = *oftX
|
|
||||||
ptab = append(ptab, p)
|
|
||||||
}
|
|
||||||
*oftX++
|
|
||||||
*charOffset++
|
|
||||||
return ptab
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draws the point and redraws upper and lower border points (if it has one)
|
|
||||||
func (tp *Tabpane) drawPointWithBorder(p point, ch rune, chbord rune, chdown rune, chup rune) []point {
|
|
||||||
var addp []point
|
|
||||||
p.Ch = ch
|
|
||||||
if tp.Border {
|
|
||||||
p.Ch = chdown
|
|
||||||
p.Y = tp.InnerY() - 1
|
|
||||||
addp = append(addp, p)
|
|
||||||
p.Ch = chup
|
|
||||||
p.Y = tp.InnerY() + 1
|
|
||||||
addp = append(addp, p)
|
|
||||||
p.Ch = chbord
|
|
||||||
}
|
|
||||||
p.Y = tp.InnerY()
|
|
||||||
return append(addp, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tp *Tabpane) Buffer() Buffer {
|
|
||||||
if tp.Border {
|
|
||||||
tp.Height = 3
|
|
||||||
} else {
|
|
||||||
tp.Height = 1
|
|
||||||
}
|
|
||||||
if tp.Width > tp.posTabText[len(tp.Tabs)]+2 {
|
|
||||||
tp.Width = tp.posTabText[len(tp.Tabs)] + 2
|
|
||||||
}
|
|
||||||
buf := tp.Block.Buffer()
|
|
||||||
ps := []point{}
|
|
||||||
|
|
||||||
tp.align()
|
|
||||||
if tp.InnerHeight() <= 0 || tp.InnerWidth() <= 0 {
|
|
||||||
return NewBuffer()
|
|
||||||
}
|
|
||||||
oftX := tp.InnerX()
|
|
||||||
charOffset := 0
|
|
||||||
pt := point{Bg: tp.BorderBg, Fg: tp.BorderFg}
|
|
||||||
for i, tab := range tp.Tabs {
|
|
||||||
|
|
||||||
if i != 0 {
|
|
||||||
pt.X = oftX
|
|
||||||
pt.Y = tp.InnerY()
|
|
||||||
addp := tp.drawPointWithBorder(pt, ' ', VERTICAL_LINE, HORIZONTAL_DOWN, HORIZONTAL_UP)
|
|
||||||
ps = tp.addPoint(ps, &charOffset, &oftX, addp...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == tp.activeTabIndex {
|
|
||||||
pt.Bg = tp.ActiveTabBg
|
|
||||||
}
|
|
||||||
rs := []rune(tab.Label)
|
|
||||||
for k := 0; k < len(rs); k++ {
|
|
||||||
|
|
||||||
addp := make([]point, 0, 2)
|
|
||||||
if i == tp.activeTabIndex && tp.Border {
|
|
||||||
pt.Ch = ' '
|
|
||||||
pt.Y = tp.InnerY() + 1
|
|
||||||
pt.Bg = tp.BorderBg
|
|
||||||
addp = append(addp, pt)
|
|
||||||
pt.Bg = tp.ActiveTabBg
|
|
||||||
}
|
|
||||||
|
|
||||||
pt.Y = tp.InnerY()
|
|
||||||
pt.Ch = rs[k]
|
|
||||||
|
|
||||||
addp = append(addp, pt)
|
|
||||||
ps = tp.addPoint(ps, &charOffset, &oftX, addp...)
|
|
||||||
}
|
|
||||||
pt.Bg = tp.BorderBg
|
|
||||||
|
|
||||||
if !tp.fitsWidth() {
|
|
||||||
all := tp.checkAlignment()
|
|
||||||
pt.X = tp.InnerX() - 1
|
|
||||||
|
|
||||||
pt.Ch = '*'
|
|
||||||
if tp.Border {
|
|
||||||
pt.Ch = VERTICAL_LINE
|
|
||||||
}
|
|
||||||
ps = append(ps, pt)
|
|
||||||
|
|
||||||
if all <= 0 {
|
|
||||||
addp := tp.drawPointWithBorder(pt, '<', '«', HORIZONTAL_LINE, HORIZONTAL_LINE)
|
|
||||||
ps = append(ps, addp...)
|
|
||||||
}
|
|
||||||
|
|
||||||
pt.X = tp.InnerX() + tp.InnerWidth()
|
|
||||||
pt.Ch = '*'
|
|
||||||
if tp.Border {
|
|
||||||
pt.Ch = VERTICAL_LINE
|
|
||||||
}
|
|
||||||
ps = append(ps, pt)
|
|
||||||
if all >= 0 {
|
|
||||||
addp := tp.drawPointWithBorder(pt, '>', '»', HORIZONTAL_LINE, HORIZONTAL_LINE)
|
|
||||||
ps = append(ps, addp...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//draw tab content below the Tabpane
|
|
||||||
if i == tp.activeTabIndex {
|
|
||||||
blockPoints := buf2pt(tab.Buffer())
|
|
||||||
for i := 0; i < len(blockPoints); i++ {
|
|
||||||
blockPoints[i].Y += tp.Height + tp.Y
|
|
||||||
}
|
|
||||||
ps = append(ps, blockPoints...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range ps {
|
|
||||||
buf.Set(v.X, v.Y, NewCell(v.Ch, v.Fg, v.Bg))
|
|
||||||
}
|
|
||||||
buf.Sync()
|
|
||||||
return buf
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
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
|
|
|
@ -1,9 +0,0 @@
|
||||||
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
|
|
|
@ -1,279 +0,0 @@
|
||||||
// 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
|
|
|
@ -1,80 +0,0 @@
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
var r *Row
|
|
||||||
|
|
||||||
func TestRowWidth(t *testing.T) {
|
|
||||||
p0 := NewBlock()
|
|
||||||
p0.Height = 1
|
|
||||||
p1 := NewBlock()
|
|
||||||
p1.Height = 1
|
|
||||||
p2 := NewBlock()
|
|
||||||
p2.Height = 1
|
|
||||||
p3 := NewBlock()
|
|
||||||
p3.Height = 1
|
|
||||||
|
|
||||||
/* test against tree:
|
|
||||||
|
|
||||||
r
|
|
||||||
/ \
|
|
||||||
0:w 1
|
|
||||||
/ \
|
|
||||||
10:w 11
|
|
||||||
/
|
|
||||||
110:w
|
|
||||||
/
|
|
||||||
1100:w
|
|
||||||
*/
|
|
||||||
|
|
||||||
r = NewRow(
|
|
||||||
NewCol(6, 0, p0),
|
|
||||||
NewCol(6, 0,
|
|
||||||
NewRow(
|
|
||||||
NewCol(6, 0, p1),
|
|
||||||
NewCol(6, 0, p2, p3))))
|
|
||||||
|
|
||||||
r.assignWidth(100)
|
|
||||||
if r.Width != 100 ||
|
|
||||||
(r.Cols[0].Width) != 50 ||
|
|
||||||
(r.Cols[1].Width) != 50 ||
|
|
||||||
(r.Cols[1].Cols[0].Width) != 25 ||
|
|
||||||
(r.Cols[1].Cols[1].Width) != 25 ||
|
|
||||||
(r.Cols[1].Cols[1].Cols[0].Width) != 25 ||
|
|
||||||
(r.Cols[1].Cols[1].Cols[0].Cols[0].Width) != 25 {
|
|
||||||
t.Error("assignWidth fails")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRowHeight(t *testing.T) {
|
|
||||||
spew.Dump()
|
|
||||||
|
|
||||||
if (r.solveHeight()) != 2 ||
|
|
||||||
(r.Cols[1].Cols[1].Height) != 2 ||
|
|
||||||
(r.Cols[1].Cols[1].Cols[0].Height) != 2 ||
|
|
||||||
(r.Cols[1].Cols[0].Height) != 1 {
|
|
||||||
t.Error("solveHeight fails")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAssignXY(t *testing.T) {
|
|
||||||
r.assignX(0)
|
|
||||||
r.assignY(0)
|
|
||||||
if (r.Cols[0].X) != 0 ||
|
|
||||||
(r.Cols[1].Cols[0].X) != 50 ||
|
|
||||||
(r.Cols[1].Cols[1].X) != 75 ||
|
|
||||||
(r.Cols[1].Cols[1].Cols[0].X) != 75 ||
|
|
||||||
(r.Cols[1].Cols[0].Y) != 0 ||
|
|
||||||
(r.Cols[1].Cols[1].Cols[0].Y) != 0 ||
|
|
||||||
(r.Cols[1].Cols[1].Cols[0].Cols[0].Y) != 1 {
|
|
||||||
t.Error("assignXY fails")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStr2Rune(t *testing.T) {
|
|
||||||
s := "你好,世界."
|
|
||||||
rs := str2runes(s)
|
|
||||||
if len(rs) != 6 {
|
|
||||||
t.Error(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWidth(t *testing.T) {
|
|
||||||
s0 := "つのだ☆HIRO"
|
|
||||||
s1 := "11111111111"
|
|
||||||
// above not align for setting East Asian Ambiguous to wide!!
|
|
||||||
|
|
||||||
if strWidth(s0) != strWidth(s1) {
|
|
||||||
t.Error("str len failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
len1 := []rune{'a', '2', '&', '「', 'オ', '。'} //will false: 'ᆵ', 'ᄚ', 'ᄒ'
|
|
||||||
for _, v := range len1 {
|
|
||||||
if charWidth(v) != 1 {
|
|
||||||
t.Error("len1 failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
len2 := []rune{'漢', '字', '한', '자', '你', '好', 'だ', '。', '%', 's', 'E', 'ョ', '、', 'ヲ'}
|
|
||||||
for _, v := range len2 {
|
|
||||||
if charWidth(v) != 2 {
|
|
||||||
t.Error("len2 failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrim(t *testing.T) {
|
|
||||||
s := "つのだ☆HIRO"
|
|
||||||
if string(trimStr2Runes(s, 10)) != "つのだ☆HI"+dot {
|
|
||||||
t.Error("trim failed")
|
|
||||||
}
|
|
||||||
if string(trimStr2Runes(s, 11)) != "つのだ☆HIRO" {
|
|
||||||
t.Error("avoid tail trim failed")
|
|
||||||
}
|
|
||||||
if string(trimStr2Runes(s, 15)) != "つのだ☆HIRO" {
|
|
||||||
t.Error("avoid trim failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrimStrIfAppropriate_NoTrim(t *testing.T) {
|
|
||||||
assert.Equal(t, "hello", TrimStrIfAppropriate("hello", 5))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrimStrIfAppropriate(t *testing.T) {
|
|
||||||
assert.Equal(t, "hel…", TrimStrIfAppropriate("hello", 4))
|
|
||||||
assert.Equal(t, "h…", TrimStrIfAppropriate("hello", 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringToAttribute(t *testing.T) {
|
|
||||||
assert.Equal(t, ColorRed, StringToAttribute("ReD"))
|
|
||||||
assert.Equal(t, ColorRed|AttrBold, StringToAttribute("RED, bold"))
|
|
||||||
}
|
|
|
@ -1,331 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// 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 = '└'
|
|
|
@ -1,11 +0,0 @@
|
||||||
// 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 = '+'
|
|
|
@ -1,89 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,242 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
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
|
|
|
@ -1,73 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
// 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 "testing"
|
|
||||||
|
|
||||||
func TestPar_NoBorderBackground(t *testing.T) {
|
|
||||||
par := NewPar("a")
|
|
||||||
par.Border = false
|
|
||||||
par.Bg = ColorBlue
|
|
||||||
par.TextBgColor = ColorBlue
|
|
||||||
par.Width = 2
|
|
||||||
par.Height = 2
|
|
||||||
|
|
||||||
pts := par.Buffer()
|
|
||||||
for _, p := range pts.CellMap {
|
|
||||||
t.Log(p)
|
|
||||||
if p.Bg != par.Bg {
|
|
||||||
t.Errorf("expected color to be %v but got %v", par.Bg, p.Bg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
// 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"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAlignArea(t *testing.T) {
|
|
||||||
p := image.Rect(0, 0, 100, 100)
|
|
||||||
c := image.Rect(10, 10, 20, 20)
|
|
||||||
|
|
||||||
nc := AlignArea(p, c, AlignLeft)
|
|
||||||
if nc.Min.X != 0 || nc.Max.Y != 20 {
|
|
||||||
t.Errorf("AlignLeft failed:\n%+v", nc)
|
|
||||||
}
|
|
||||||
|
|
||||||
nc = AlignArea(p, c, AlignCenter)
|
|
||||||
if nc.Min.X != 45 || nc.Max.Y != 55 {
|
|
||||||
t.Error("AlignCenter failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
nc = AlignArea(p, c, AlignBottom|AlignRight)
|
|
||||||
if nc.Min.X != 90 || nc.Max.Y != 100 {
|
|
||||||
t.Errorf("AlignBottom|AlignRight failed\n%+v", nc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMoveArea(t *testing.T) {
|
|
||||||
a := image.Rect(10, 10, 20, 20)
|
|
||||||
a = MoveArea(a, 5, 10)
|
|
||||||
if a.Min.X != 15 || a.Min.Y != 20 || a.Max.X != 25 || a.Max.Y != 30 {
|
|
||||||
t.Error("MoveArea failed")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
// 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("/", DefualtHandler)
|
|
||||||
DefaultEvtStream.Handle("/sys/wnd/resize", func(e Event) {
|
|
||||||
w := e.Data.(EvtWnd)
|
|
||||||
Body.Width = w.Width
|
|
||||||
})
|
|
||||||
|
|
||||||
DefaultWgtMgr = NewWgtMgr()
|
|
||||||
DefaultEvtStream.Hook(DefaultWgtMgr.WgtHandlersHook())
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for bs := range renderJobs {
|
|
||||||
render(bs...)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return 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
|
|
||||||
}
|
|
|
@ -1,167 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
// 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 main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/gizak/termui"
|
|
||||||
"github.com/gizak/termui/debug"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// run as client
|
|
||||||
if len(os.Args) > 1 {
|
|
||||||
fmt.Print(debug.ConnectAndListen())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// run as server
|
|
||||||
go func() { panic(debug.ListenAndServe()) }()
|
|
||||||
|
|
||||||
if err := termui.Init(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termui.Close()
|
|
||||||
|
|
||||||
//termui.UseTheme("helloworld")
|
|
||||||
b := termui.NewBlock()
|
|
||||||
b.Width = 20
|
|
||||||
b.Height = 20
|
|
||||||
b.Float = termui.AlignCenter
|
|
||||||
b.BorderLabel = "[HELLO](fg-red,bg-white) [WORLD](fg-blue,bg-green)"
|
|
||||||
|
|
||||||
termui.Render(b)
|
|
||||||
|
|
||||||
termui.Handle("/sys", func(e termui.Event) {
|
|
||||||
k, ok := e.Data.(termui.EvtKbd)
|
|
||||||
debug.Logf("->%v\n", e)
|
|
||||||
if ok && k.KeyStr == "q" {
|
|
||||||
termui.StopLoop()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Handle(("/usr"), func(e termui.Event) {
|
|
||||||
debug.Logf("->%v\n", e)
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Handle("/timer/1s", func(e termui.Event) {
|
|
||||||
t := e.Data.(termui.EvtTimer)
|
|
||||||
termui.SendCustomEvt("/usr/t", t.Count)
|
|
||||||
|
|
||||||
if t.Count%2 == 0 {
|
|
||||||
b.BorderLabel = "[HELLO](fg-red,bg-green) [WORLD](fg-blue,bg-white)"
|
|
||||||
} else {
|
|
||||||
b.BorderLabel = "[HELLO](fg-blue,bg-white) [WORLD](fg-red,bg-green)"
|
|
||||||
}
|
|
||||||
|
|
||||||
termui.Render(b)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
termui.Loop()
|
|
||||||
}
|
|
|
@ -1,278 +0,0 @@
|
||||||
// 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{}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
// 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 "testing"
|
|
||||||
|
|
||||||
func TestReadAttr(t *testing.T) {
|
|
||||||
m := MarkdownTxBuilder{}
|
|
||||||
m.baseFg = ColorCyan | AttrUnderline
|
|
||||||
m.baseBg = ColorBlue | AttrBold
|
|
||||||
fg, bg := m.readAttr("fg-red,bg-reverse")
|
|
||||||
if fg != ColorRed|AttrUnderline || bg != ColorBlue|AttrBold|AttrReverse {
|
|
||||||
t.Error("readAttr failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMTBParse(t *testing.T) {
|
|
||||||
/*
|
|
||||||
str := func(cs []Cell) string {
|
|
||||||
rs := make([]rune, len(cs))
|
|
||||||
for i := range cs {
|
|
||||||
rs[i] = cs[i].Ch
|
|
||||||
}
|
|
||||||
return string(rs)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
tbls := [][]string{
|
|
||||||
{"hello world", "hello world"},
|
|
||||||
{"[hello](fg-red) world", "hello world"},
|
|
||||||
{"[[hello]](bg-red) world", "[hello] world"},
|
|
||||||
{"[1] hello world", "[1] hello world"},
|
|
||||||
{"[[1]](bg-white) [hello] world", "[1] [hello] world"},
|
|
||||||
{"[hello world]", "[hello world]"},
|
|
||||||
{"", ""},
|
|
||||||
{"[hello world)", "[hello world)"},
|
|
||||||
{"[0] [hello](bg-red)[ world](fg-blue)!", "[0] hello world!"},
|
|
||||||
}
|
|
||||||
|
|
||||||
m := MarkdownTxBuilder{}
|
|
||||||
m.baseFg = ColorWhite
|
|
||||||
m.baseBg = ColorDefault
|
|
||||||
for _, s := range tbls {
|
|
||||||
m.reset()
|
|
||||||
m.parse(s[0])
|
|
||||||
res := string(m.plainTx)
|
|
||||||
if s[1] != res {
|
|
||||||
t.Errorf("\ninput :%s\nshould:%s\noutput:%s", s[0], s[1], res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.reset()
|
|
||||||
m.parse("[0] [hello](bg-red)[ world](fg-blue)")
|
|
||||||
if len(m.markers) != 2 &&
|
|
||||||
m.markers[0].st == 4 &&
|
|
||||||
m.markers[0].ed == 11 &&
|
|
||||||
m.markers[0].fg == ColorWhite &&
|
|
||||||
m.markers[0].bg == ColorRed {
|
|
||||||
t.Error("markers dismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
m2 := NewMarkdownTxBuilder()
|
|
||||||
cs := m2.Build("[0] [hellob-e) wrd]fgblue)!", ColorWhite, ColorBlack)
|
|
||||||
cs = m2.Build("[0] [hello](bg-red) [world](fg-blue)!", ColorWhite, ColorBlack)
|
|
||||||
if cs[4].Ch != 'h' && cs[4].Bg != ColorRed && cs[4].Fg != ColorWhite {
|
|
||||||
t.Error("dismatch in Build")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
// 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 "testing"
|
|
||||||
|
|
||||||
var cmap = map[string]Attribute{
|
|
||||||
"fg": ColorWhite,
|
|
||||||
"bg": ColorDefault,
|
|
||||||
"border.fg": ColorWhite,
|
|
||||||
"label.fg": ColorGreen,
|
|
||||||
"par.fg": ColorYellow,
|
|
||||||
"par.label.bg": ColorWhite,
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoopUpAttr(t *testing.T) {
|
|
||||||
tbl := []struct {
|
|
||||||
name string
|
|
||||||
should Attribute
|
|
||||||
}{
|
|
||||||
{"par.label.bg", ColorWhite},
|
|
||||||
{"par.label.fg", ColorGreen},
|
|
||||||
{"par.bg", ColorDefault},
|
|
||||||
{"bar.border.fg", ColorWhite},
|
|
||||||
{"bar.label.bg", ColorDefault},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range tbl {
|
|
||||||
if lookUpAttr(cmap, v.name) != v.should {
|
|
||||||
t.Error(v.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
// 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)
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
testdata/*.golden
|
|
||||||
coverage.out
|
|
|
@ -1,19 +0,0 @@
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.8
|
|
||||||
- 1.7
|
|
||||||
- tip
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
install: make setup
|
|
||||||
|
|
||||||
script: make check test
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key:
|
|
||||||
secure: D2fwuS7n54ZkjfRmLMEAnwR2lQLa5xd+4s0l65pI6UN4ljVfQwQiFdZXaZWFr8PYSTKTm0UPj6Iqqw7VOl8I6iDzcaOqN3hoh5mDaJ7kyo7GZRPodwK9MKdOp5dPw459L2Atll8kxb4iYmfnjmcl0lDCUuWvMxXx+zgiFTB7BO0=
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
go: 1.8
|
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2014, Jawher Moussa
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
test:
|
|
||||||
go test -v ./...
|
|
||||||
|
|
||||||
check: lint vet fmtcheck ineffassign
|
|
||||||
|
|
||||||
lint:
|
|
||||||
golint -set_exit_status .
|
|
||||||
|
|
||||||
vet:
|
|
||||||
go vet
|
|
||||||
|
|
||||||
fmtcheck:
|
|
||||||
@ export output="$$(gofmt -s -d .)"; \
|
|
||||||
[ -n "$${output}" ] && echo "$${output}" && export status=1; \
|
|
||||||
exit $${status:-0}
|
|
||||||
|
|
||||||
ineffassign:
|
|
||||||
ineffassign .
|
|
||||||
|
|
||||||
setup:
|
|
||||||
go get github.com/gordonklaus/ineffassign
|
|
||||||
go get github.com/golang/lint/golint
|
|
||||||
go get -t -u ./...
|
|
||||||
|
|
||||||
.PHONY: test check lint vet fmtcheck ineffassign
|
|
|
@ -1,771 +0,0 @@
|
||||||
# mow.cli
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/jawher/mow.cli.svg?branch=master)](https://travis-ci.org/jawher/mow.cli)
|
|
||||||
[![GoDoc](https://godoc.org/github.com/jawher/mow.cli?status.svg)](https://godoc.org/github.com/jawher/mow.cli)
|
|
||||||
|
|
||||||
A framework to build command line applications in Go with most of the burden of arguments parsing and validation placed on the framework instead of the developer.
|
|
||||||
|
|
||||||
## First app
|
|
||||||
|
|
||||||
### A simple app
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/jawher/mow.cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := cli.App("cp", "Copy files around")
|
|
||||||
|
|
||||||
app.Spec = "[-r] SRC... DST"
|
|
||||||
|
|
||||||
var (
|
|
||||||
recursive = app.BoolOpt("r recursive", false, "Copy files recursively")
|
|
||||||
src = app.StringsArg("SRC", nil, "Source files to copy")
|
|
||||||
dst = app.StringArg("DST", "", "Destination where to copy files to")
|
|
||||||
)
|
|
||||||
|
|
||||||
app.Action = func() {
|
|
||||||
fmt.Printf("Copying %v to %s [recursively: %v]\n", *src, *dst, *recursive)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Run(os.Args)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### An app with multiple commands:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/jawher/mow.cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := cli.App("uman", "User Manager")
|
|
||||||
|
|
||||||
app.Spec = "[-v]"
|
|
||||||
|
|
||||||
var (
|
|
||||||
verbose = app.BoolOpt("v verbose", false, "Verbose debug mode")
|
|
||||||
)
|
|
||||||
|
|
||||||
app.Before = func() {
|
|
||||||
if *verbose {
|
|
||||||
// Here you can enable debug output in your logger for example
|
|
||||||
fmt.Println("Verbose mode enabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare a first command, invocable with "uman list"
|
|
||||||
app.Command("list", "list the users", func(cmd *cli.Cmd) {
|
|
||||||
// These are the command specific options and args, nicely scoped inside a func
|
|
||||||
var (
|
|
||||||
all = cmd.BoolOpt("all", false, "Display all users, including disabled ones")
|
|
||||||
)
|
|
||||||
|
|
||||||
// What to run when this command is called
|
|
||||||
cmd.Action = func() {
|
|
||||||
// Inside the action, and only inside, you can safely access the values of the options and arguments
|
|
||||||
fmt.Printf("user list (including disabled ones: %v)\n", *all)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// The second command, invocable with "uman get"
|
|
||||||
app.Command("get", "get a user details", func(cmd *cli.Cmd) {
|
|
||||||
var (
|
|
||||||
detailed = cmd.BoolOpt("detailed", false, "Disaply detailed information")
|
|
||||||
id = cmd.StringArg("ID", "", "The user id to display")
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd.Action = func() {
|
|
||||||
fmt.Printf("user %q details (detailed mode: %v)\n", *id, *detailed)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Now that the app is configured, execute it passing in the os.Args array
|
|
||||||
app.Run(os.Args)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Motivation
|
|
||||||
|
|
||||||
| | mow.cli | codegangsta/cli | flag |
|
|
||||||
|----------------------------------------------------------------------|---------|-----------------|------|
|
|
||||||
| Contextual help | ✓ | ✓ | |
|
|
||||||
| Commands | ✓ | ✓ | |
|
|
||||||
| Option folding `-xyz` | ✓ | | |
|
|
||||||
| Option Value folding `-fValue` | ✓ | | |
|
|
||||||
| Option exclusion: `--start ❘ --stop` | ✓ | | |
|
|
||||||
| Option dependency : `[-a -b]` or `[-a [-b]]` | ✓ | | |
|
|
||||||
| Arguments validation : `SRC DST` | ✓ | | |
|
|
||||||
| Argument optionality : `SRC [DST]` | ✓ | | |
|
|
||||||
| Argument repetition : `SRC... DST` | ✓ | | |
|
|
||||||
| Option/Argument dependency : `SRC [-f DST]` | ✓ | | |
|
|
||||||
| Any combination of the above: `[-d ❘ --rm] IMAGE [COMMAND [ARG...]]` | ✓ | | |
|
|
||||||
|
|
||||||
In the goland, docopt is another library with rich flags and arguments validation.
|
|
||||||
However, it falls short for many use cases:
|
|
||||||
|
|
||||||
| | mow.cli | docopt |
|
|
||||||
|-----------------------------|---------|--------|
|
|
||||||
| Contextual help | ✓ | |
|
|
||||||
| Backtracking: `SRC... DST` | ✓ | |
|
|
||||||
| Backtracking: `[SRC] DST` | ✓ | |
|
|
||||||
| Branching: `(SRC ❘ -f DST)` | ✓ | |
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
To install this library, simply run:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ go get github.com/jawher/mow.cli
|
|
||||||
```
|
|
||||||
|
|
||||||
## Basics
|
|
||||||
|
|
||||||
You start by creating an application by passing a name and a description:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cp := cli.App("cp", "Copy files around")
|
|
||||||
```
|
|
||||||
|
|
||||||
To attach the code to execute when the app is launched, assign a function to the Action field:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cp.Action = func() {
|
|
||||||
fmt.Printf("Hello world\n")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want you can add support for printing the app version (invoked by ```-v, --version```) like so:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cp.Version("v version", "cp 1.2.3")
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, in your main func, call Run on the app:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cp.Run(os.Args)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Options
|
|
||||||
|
|
||||||
To add a (global) option, call one of the (String[s]|Int[s]|Bool)Opt methods on the app:
|
|
||||||
|
|
||||||
```go
|
|
||||||
recursive := cp.BoolOpt("R recursive", false, "recursively copy the src to dst")
|
|
||||||
```
|
|
||||||
|
|
||||||
* The first argument is a space separated list of names (short and long) for the option without the dashes, e.g `"f force"`. While you can specify multiple short or long names, e.g. `"f x force force-push"`, only the first short name and the first long name will be displayed in the help messages
|
|
||||||
* The second parameter is the default value for the option
|
|
||||||
* The third and last parameter is the option description, as will be shown in the help messages
|
|
||||||
|
|
||||||
There is also a second set of methods Bool, String, Int, Strings and Ints, which accepts a struct describing the option:
|
|
||||||
|
|
||||||
```go
|
|
||||||
recursive = cp.Bool(cli.BoolOpt{
|
|
||||||
Name: "R recursive",
|
|
||||||
Value: false,
|
|
||||||
Desc: "copy src files recursively",
|
|
||||||
EnvVar: "VAR_RECURSIVE",
|
|
||||||
SetByUser: &recursiveSetByUser,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
`EnvVar` accepts a space separated list of environment variables names to be used to initialize the option.
|
|
||||||
|
|
||||||
If `SetByUser` is specified (by passing a pointer to a bool variable), it will be set to `true` if the user explicitly set the option.
|
|
||||||
|
|
||||||
The result is a pointer to a value which will be populated after parsing the command line arguments.
|
|
||||||
You can access the values in the Action func.
|
|
||||||
|
|
||||||
In the command line, mow.cli accepts the following syntaxes
|
|
||||||
|
|
||||||
### For boolean options:
|
|
||||||
|
|
||||||
|
|
||||||
* `-f` : a single dash for the one letter names
|
|
||||||
* `-f=false` : a single dash for the one letter names, equal sign followed by true or false
|
|
||||||
* `--force` : double dash for longer option names
|
|
||||||
* `-it` : mow.cli supports option folding, this is equivalent to: -i -t
|
|
||||||
|
|
||||||
### For string, int options:
|
|
||||||
|
|
||||||
|
|
||||||
* `-e=value` : single dash for one letter names, equal sign followed by the value
|
|
||||||
* `-e value` : single dash for one letter names, space followed by the value
|
|
||||||
* `-Ivalue` : single dash for one letter names immediately followed by the value
|
|
||||||
* `--extra=value` : double dash for longer option names, equal sign followed by the value
|
|
||||||
* `--extra value` : double dash for longer option names, space followed by the value
|
|
||||||
|
|
||||||
### For slice options (StringsOpt, IntsOpt):
|
|
||||||
repeat the option to accumulate the values in the resulting slice:
|
|
||||||
|
|
||||||
* `-e PATH:/bin -e PATH:/usr/bin` : resulting slice contains `["/bin", "/usr/bin"]`
|
|
||||||
* `-ePATH:/bin -ePATH:/usr/bin` : resulting slice contains `["/bin", "/usr/bin"]`
|
|
||||||
* `-e=PATH:/bin -e=PATH:/usr/bin` : resulting slice contains `["/bin", "/usr/bin"]`
|
|
||||||
* `--env PATH:/bin --env PATH:/usr/bin` : resulting slice contains `["/bin", "/usr/bin"]`
|
|
||||||
* `--env=PATH:/bin --env=PATH:/usr/bin` : resulting slice contains `["/bin", "/usr/bin"]`
|
|
||||||
|
|
||||||
|
|
||||||
## Arguments
|
|
||||||
|
|
||||||
To accept arguments, you need to explicitly declare them by calling one of the (String[s]|Int[s]|Bool)Arg methods on the app:
|
|
||||||
|
|
||||||
```go
|
|
||||||
src := cp.StringArg("SRC", "", "the file to copy")
|
|
||||||
dst := cp.StringArg("DST", "", "the destination")
|
|
||||||
```
|
|
||||||
|
|
||||||
* The first argument is the argument name as will be shown in the help messages
|
|
||||||
* The second parameter is the default value for the argument
|
|
||||||
* The third parameter is the argument description, as will be shown in the help messages
|
|
||||||
|
|
||||||
|
|
||||||
There is also a second set of methods Bool, String, Int, Strings and Ints, which accepts structs describing the argument:
|
|
||||||
|
|
||||||
```go
|
|
||||||
src = cp.Strings(cli.StringsArg{
|
|
||||||
Name: "SRC",
|
|
||||||
Desc: "The source files to copy",
|
|
||||||
Value: "default value",
|
|
||||||
EnvVar: "VAR1 VAR2",
|
|
||||||
SetByUser: &srcSetByUser,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
The Value field is where you can set the initial value for the argument.
|
|
||||||
|
|
||||||
`EnvVar` accepts a space separated list of environment variables names to be used to initialize the argument.
|
|
||||||
|
|
||||||
If `SetByUser` is specified (by passing a pointer to a bool variable), it will be set to `true` only if the user explicitly sets the argument.
|
|
||||||
|
|
||||||
The result is a pointer to a value that will be populated after parsing the command line arguments.
|
|
||||||
You can access the values in the Action func.
|
|
||||||
|
|
||||||
|
|
||||||
## Operators
|
|
||||||
|
|
||||||
The `--` operator marks the end of options.
|
|
||||||
Everything that follow will be treated as an argument,
|
|
||||||
even if starts with a dash.
|
|
||||||
|
|
||||||
For example, given the `touch` command which takes a filename as an argument (and possibly other options):
|
|
||||||
|
|
||||||
```go
|
|
||||||
file := cp.StringArg("FILE", "", "the file to create")
|
|
||||||
```
|
|
||||||
|
|
||||||
If we try to create a file named `-f` this way:
|
|
||||||
|
|
||||||
```
|
|
||||||
touch -f
|
|
||||||
```
|
|
||||||
|
|
||||||
Would fail, because `-f` will be parsed as an option not as an argument.
|
|
||||||
The fix is to prefix the filename with the `--` operator:
|
|
||||||
|
|
||||||
```
|
|
||||||
touch -- -f
|
|
||||||
```
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
mow.cli supports nesting commands and sub commands.
|
|
||||||
Declare a top level command by calling the Command func on the app struct, and a sub command by calling
|
|
||||||
the Command func on the command struct:
|
|
||||||
|
|
||||||
```go
|
|
||||||
docker := cli.App("docker", "A self-sufficient runtime for linux containers")
|
|
||||||
|
|
||||||
docker.Command("run", "Run a command in a new container", func(cmd *cli.Cmd) {
|
|
||||||
// initialize the run command here
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
* The first argument is the command name, as will be shown in the help messages and as will need to be input by the user in the command line to call the command
|
|
||||||
* The second argument is the command description as will be shown in the help messages
|
|
||||||
* The third argument is a CmdInitializer, a function that receives a pointer to a Cmd struct representing the command.
|
|
||||||
In this function, you can add options and arguments by calling the same methods as you would with an app struct (BoolOpt, StringArg, ...).
|
|
||||||
You would also assign a function to the Action field of the Cmd struct for it to be executed when the command is invoked.
|
|
||||||
|
|
||||||
```go
|
|
||||||
docker.Command("run", "Run a command in a new container", func(cmd *cli.Cmd) {
|
|
||||||
var (
|
|
||||||
detached = cmd.BoolOpt("d detach", false, "Detached mode: run the container in the background and print the new container ID")
|
|
||||||
memory = cmd.StringOpt("m memory", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
|
|
||||||
image = cmd.StringArg("IMAGE", "", "")
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd.Action = func() {
|
|
||||||
if *detached {
|
|
||||||
//do something
|
|
||||||
}
|
|
||||||
runContainer(*image, *detached, *memory)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
You can also add sub commands by calling Command on the Cmd struct:
|
|
||||||
|
|
||||||
```go
|
|
||||||
bzk.Command("job", "actions on jobs", func(cmd *cli.Cmd) {
|
|
||||||
cmd.Command("list", "list jobs", listJobs)
|
|
||||||
cmd.Command("start", "start a new job", startJob)
|
|
||||||
cmd.Command("log", "show a job log", nil)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
When you just want to set Action to cmd, you can use ActionCommand function for this:
|
|
||||||
|
|
||||||
```go
|
|
||||||
app.Command("list", "list all configs", cli.ActionCommand(func() { list() }))
|
|
||||||
```
|
|
||||||
|
|
||||||
is the same as:
|
|
||||||
|
|
||||||
```go
|
|
||||||
app.Command("list", "list all configs", func(cmd *cli.Cmd)) {
|
|
||||||
cmd.Action = func() {
|
|
||||||
list()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This could go on to any depth if need be.
|
|
||||||
|
|
||||||
mow.cli also supports command aliases. For example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
app.Command("start run r", "start doing things", cli.ActionCommand(func() { start() }))
|
|
||||||
```
|
|
||||||
|
|
||||||
will alias `start`, `run`, and `r` to the same action. Aliases also work for
|
|
||||||
subcommands:
|
|
||||||
|
|
||||||
```go
|
|
||||||
app.Command("job j", "actions on jobs", func(cmd *cli.Cmd) {
|
|
||||||
cmd.Command("list ls", "list jobs", func(cmd *cli.Cmd) {
|
|
||||||
cmd.Action = func() {
|
|
||||||
list()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
which then allows you to invoke the subcommand as `app job list`, `app job ls`,
|
|
||||||
`app j ls`, or `app j list`.
|
|
||||||
|
|
||||||
|
|
||||||
As a side-note: it may seem a bit weird the way mow.cli uses a function to initialize a command instead of just returning the command struct.
|
|
||||||
|
|
||||||
The motivation behind this choice is scoping: as with the standard flag package, adding an option or an argument returns a pointer to a value which will be populated when the app is run.
|
|
||||||
|
|
||||||
Since you'll want to store these pointers in variables, and to avoid having dozens of them in the same scope (the main func for example or as global variables),
|
|
||||||
mow.cli's API was specifically tailored to take a func parameter (called CmdInitializer) which accepts the command struct.
|
|
||||||
|
|
||||||
This way, the command specific variables scope is limited to this function.
|
|
||||||
|
|
||||||
## Custom types
|
|
||||||
|
|
||||||
Out of the box, mow.cli supports the following types for options and arguments:
|
|
||||||
|
|
||||||
* bool
|
|
||||||
* string
|
|
||||||
* int
|
|
||||||
* strings (slice of strings)
|
|
||||||
* ints (slice of ints)
|
|
||||||
|
|
||||||
You can however extend mow.cli to handle other types, e.g. `time.Duration`, `float64`, or even your own struct types for example.
|
|
||||||
|
|
||||||
To do so, you'll need to:
|
|
||||||
|
|
||||||
* implement the `flag.Value` interface for the custom type
|
|
||||||
* declare the option or the flag using `VarOpt`, `VarArg` for the short hands, and `Var` for the full form.
|
|
||||||
|
|
||||||
Here's an example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Declare your type
|
|
||||||
type Duration time.Duration
|
|
||||||
|
|
||||||
// Make it implement flag.Value
|
|
||||||
func (d *Duration) Set(v string) error {
|
|
||||||
parsed, err := time.ParseDuration(v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*d = Duration(parsed)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Duration) String() string {
|
|
||||||
duration := time.Duration(*d)
|
|
||||||
return duration.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
duration := Duration(0)
|
|
||||||
|
|
||||||
app := App("var", "")
|
|
||||||
|
|
||||||
app.VarArg("DURATION", &duration, "")
|
|
||||||
|
|
||||||
app.Run([]string{"cp", "1h31m42s"})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Boolean custom types
|
|
||||||
|
|
||||||
To make your custom type behave as a boolean option, i.e. doesn't take a value, it has to implement a `IsBoolFlag` method that returns true:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type BoolLike int
|
|
||||||
|
|
||||||
|
|
||||||
func (d *BoolLike) IsBoolFlag() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-valued custom type
|
|
||||||
|
|
||||||
To make your custom type behave as a multi-valued option or argument, i.e. takes multiple values,
|
|
||||||
it has to implement a `Clear` method which will be called whenever the value list needs to be cleared,
|
|
||||||
e.g. when the value was initially populated from an environment variable, and then explicitly set from the CLI:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Durations []time.Duration
|
|
||||||
|
|
||||||
// Make it implement flag.Value
|
|
||||||
func (d *Durations) Set(v string) error {
|
|
||||||
parsed, err := time.ParseDuration(v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*d = append(*d, Duration(parsed))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Durations) String() string {
|
|
||||||
return fmt.Sprintf("%v", *d)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Make it multi-valued
|
|
||||||
func (d *Durations) Clear() {
|
|
||||||
*d = []Duration{}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Hide default value of custom type
|
|
||||||
|
|
||||||
If your custom type implements a `IsDefault` method (returning a boolean), the help message generation will make use of it to decide whether or not to display the default value.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Action string
|
|
||||||
|
|
||||||
// Make it implement flag.Value
|
|
||||||
:
|
|
||||||
:
|
|
||||||
|
|
||||||
// Make it multi-valued
|
|
||||||
func (a *Action) IsDefault() bool {
|
|
||||||
return (*a) == "nop"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Interceptors
|
|
||||||
|
|
||||||
It is possible to define snippets of code to be executed before and after a command or any of its sub commands is executed.
|
|
||||||
|
|
||||||
For example, given an app with multiple commands but with a global flag which toggles a verbose mode:
|
|
||||||
|
|
||||||
```go
|
|
||||||
app := cli.App("app", "bla bla")
|
|
||||||
|
|
||||||
verbose := app.Bool(cli.BoolOpt{
|
|
||||||
Name: "verbose",
|
|
||||||
Value: false,
|
|
||||||
Desc: "Enable debug logs",
|
|
||||||
})
|
|
||||||
|
|
||||||
app.Command("command1", "...", func(cmd *cli.Cmd) {
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
app.Command("command2", "...", func(cmd *cli.Cmd) {
|
|
||||||
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Instead of repeating yourself by checking if the verbose flag is set or not, and setting the debug level in every command (and its sub-commands),
|
|
||||||
a before interceptor can be set on the `app` instead:
|
|
||||||
|
|
||||||
```go
|
|
||||||
app.Before = func() {
|
|
||||||
if (*verbose) {
|
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Whenever a valid command is called by the user, all the before interceptors defined on the app and the intermediate commands
|
|
||||||
will be called, in order from the root to the leaf.
|
|
||||||
|
|
||||||
Similarly, if you need to execute a code snippet after a command has been called, e.g. to cleanup resources allocated in before interceptors,
|
|
||||||
simply set the `After` field of the app struct or any other command.
|
|
||||||
`After` interceptors will be called, in order from the leaf up to the root (the opposite order of the `Before` interceptors).
|
|
||||||
|
|
||||||
Here's a diagram which shows in when and in which order multiple `Before` and `After` interceptors get executed:
|
|
||||||
|
|
||||||
![flow](http://i.imgur.com/oUEa8Sh.png)
|
|
||||||
|
|
||||||
## Spec
|
|
||||||
|
|
||||||
An app or command's call syntax can be customized using spec strings.
|
|
||||||
This can be useful to indicate that an argument is optional for example, or that 2 options are mutually exclusive.
|
|
||||||
|
|
||||||
You can set a spec string on:
|
|
||||||
|
|
||||||
* The app: to configure the syntax for global options and arguments
|
|
||||||
* A command: to configure the syntax for that command's options and arguments
|
|
||||||
|
|
||||||
In both cases, a spec string is assigned to the Spec field:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cp := cli.App("cp", "Copy files around")
|
|
||||||
cp.Spec = "[-R [-H | -L | -P]]"
|
|
||||||
```
|
|
||||||
|
|
||||||
And:
|
|
||||||
```go
|
|
||||||
docker := cli.App("docker", "A self-sufficient runtime for linux containers")
|
|
||||||
docker.Command("run", "Run a command in a new container", func(cmd *cli.Cmd) {
|
|
||||||
cmd.Spec = "[-d|--rm] IMAGE [COMMAND [ARG...]]"
|
|
||||||
:
|
|
||||||
:
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The spec syntax is mostly based on the conventions used in POSIX command line apps help messages and man pages:
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
You can use both short and long option names in spec strings:
|
|
||||||
```go
|
|
||||||
x.Spec="-f"
|
|
||||||
```
|
|
||||||
And:
|
|
||||||
```go
|
|
||||||
x.Spec="--force"
|
|
||||||
```
|
|
||||||
|
|
||||||
In both cases, we required that the f or force flag be set
|
|
||||||
|
|
||||||
Any option you reference in a spec string MUST be explicitly declared, otherwise mow.cli will panic:
|
|
||||||
```go
|
|
||||||
x.BoolOpt("f force", ...)
|
|
||||||
```
|
|
||||||
### Arguments
|
|
||||||
|
|
||||||
Arguments are all-uppercased words:
|
|
||||||
```go
|
|
||||||
x.Spec="SRC DST"
|
|
||||||
```
|
|
||||||
|
|
||||||
This spec string will force the user to pass exactly 2 arguments, SRC and DST
|
|
||||||
|
|
||||||
Any argument you reference in a spec string MUST be explicitly declared, otherwise mow.cli will panic:
|
|
||||||
|
|
||||||
```go
|
|
||||||
x.StringArg("SRC", ...)
|
|
||||||
x.StringArg("DST", ...)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ordering
|
|
||||||
|
|
||||||
Except for options, The order of the elements in a spec string is respected and enforced when parsing the command line arguments:
|
|
||||||
|
|
||||||
```go
|
|
||||||
x.Spec = "-f -g SRC -h DST"
|
|
||||||
```
|
|
||||||
|
|
||||||
Consecutive options (`-f` and `-g` for example) get parsed regardless of the order they are specified in (both `-f=5 -g=6` and `-g=6 -f=5` are valid).
|
|
||||||
|
|
||||||
Order between options and arguments is significant (`-f` and `-g` must appear before the `SRC` argument).
|
|
||||||
|
|
||||||
Same goes for arguments, where `SRC` must appear before `DST`.
|
|
||||||
|
|
||||||
### Optionality
|
|
||||||
|
|
||||||
You can mark items as optional in a spec string by enclosing them in square brackets :`[...]`
|
|
||||||
```go
|
|
||||||
x.Spec = "[-x]"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Choice
|
|
||||||
|
|
||||||
You can use the `|` operator to indicate a choice between two or more items
|
|
||||||
```go
|
|
||||||
x.Spec = "--rm | --daemon"
|
|
||||||
x.Spec = "-H | -L | -P"
|
|
||||||
x.Spec = "-t | DST"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Repetition
|
|
||||||
|
|
||||||
You can use the `...` postfix operator to mark an element as repeatable:
|
|
||||||
|
|
||||||
```go
|
|
||||||
x.Spec="SRC..."
|
|
||||||
x.Spec="-e..."
|
|
||||||
```
|
|
||||||
|
|
||||||
### Grouping
|
|
||||||
|
|
||||||
You can group items using parenthesis. This is useful in combination with the choice and repetition operators (`|` and `...`):
|
|
||||||
|
|
||||||
```go
|
|
||||||
x.Spec = "(-e COMMAND)... | (-x|-y)"
|
|
||||||
```
|
|
||||||
|
|
||||||
The parenthesis in the example above serve to mark that it is the sequence of a -e flag followed by an argument that is repeatable, and that
|
|
||||||
all that is mutually exclusive to a choice between -x and -y options.
|
|
||||||
|
|
||||||
### Option group
|
|
||||||
|
|
||||||
This is a shortcut to declare a choice between multiple options:
|
|
||||||
```go
|
|
||||||
x.Spec = "-abcd"
|
|
||||||
```
|
|
||||||
|
|
||||||
Is equivalent to:
|
|
||||||
|
|
||||||
```go
|
|
||||||
x.Spec = "(-a | -b | -c | -d)..."
|
|
||||||
```
|
|
||||||
|
|
||||||
I.e. any combination of the listed options in any order, with at least one option.
|
|
||||||
|
|
||||||
### All options
|
|
||||||
|
|
||||||
Another shortcut:
|
|
||||||
|
|
||||||
```go
|
|
||||||
x.Spec = "[OPTIONS]"
|
|
||||||
```
|
|
||||||
|
|
||||||
This is a special syntax (the square brackets are not for marking an optional item, and the uppercased word is not for an argument).
|
|
||||||
This is equivalent to a repeatable choice between all the available options.
|
|
||||||
For example, if an app or a command declares 4 options a, b, c and d, `[OPTIONS]` is equivalent to:
|
|
||||||
|
|
||||||
```go
|
|
||||||
x.Spec = "[-a | -b | -c | -d]..."
|
|
||||||
```
|
|
||||||
|
|
||||||
### Inline option values
|
|
||||||
|
|
||||||
You can use the `=<some-text>` notation right after an option (long or short form) to give an inline description or value.
|
|
||||||
|
|
||||||
An example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
x.Spec = "[ -a=<absolute-path> | --timeout=<in seconds> ] ARG"
|
|
||||||
```
|
|
||||||
|
|
||||||
The inline values are ignored by the spec parser and are just there for the final user as a contextual hint.
|
|
||||||
|
|
||||||
### Operators
|
|
||||||
|
|
||||||
The `--` operator can be used in a spec string to automatically treat everything following it as an options.
|
|
||||||
|
|
||||||
In other words, placing a `--` in the spec string automatically inserts a `--` in the same position in the program call arguments.
|
|
||||||
|
|
||||||
This lets you write programs like the `time` utility for example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
x.Spec = "time -lp [-- CMD [ARG...]]"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Grammar
|
|
||||||
|
|
||||||
Here's the (simplified) EBNF grammar for the Specs language:
|
|
||||||
|
|
||||||
```
|
|
||||||
spec -> sequence
|
|
||||||
sequence -> choice*
|
|
||||||
req_sequence -> choice+
|
|
||||||
choice -> atom ('|' atom)*
|
|
||||||
atom -> (shortOpt | longOpt | optSeq | allOpts | group | optional) rep? | optEnd
|
|
||||||
shortOp -> '-' [A-Za-z]
|
|
||||||
longOpt -> '--' [A-Za-z][A-Za-z0-9]*
|
|
||||||
optSeq -> '-' [A-Za-z]+
|
|
||||||
allOpts -> '[OPTIONS]'
|
|
||||||
group -> '(' req_sequence ')'
|
|
||||||
optional -> '[' req_sequence ']'
|
|
||||||
rep -> '...'
|
|
||||||
optEnd -> '--'
|
|
||||||
```
|
|
||||||
And that's it for the spec language.
|
|
||||||
You can combine these few building blocks in any way you want (while respecting the grammar above) to construct sophisticated validation constraints
|
|
||||||
(don't go too wild though).
|
|
||||||
|
|
||||||
Behind the scenes, mow.cli parses the spec string and constructs a finite state machine to be used to parse the command line arguments.
|
|
||||||
mow.cli also handles backtracking, and so it can handle tricky cases, or what I like to call "the cp test"
|
|
||||||
```
|
|
||||||
cp SRC... DST
|
|
||||||
```
|
|
||||||
|
|
||||||
Without backtracking, this deceptively simple spec string cannot be parsed correctly.
|
|
||||||
For instance, docopt can't handle this case, whereas mow.cli does.
|
|
||||||
|
|
||||||
## Default spec
|
|
||||||
|
|
||||||
By default, and unless a spec string is set by the user, mow.cli auto-generates one for the app and every command using this logic:
|
|
||||||
|
|
||||||
* Start with an empty spec string
|
|
||||||
* If at least one option was declared, append `[OPTIONS]` to the spec string
|
|
||||||
* For every declared argument, append it, in the order of declaration, to the spec string
|
|
||||||
|
|
||||||
For example, given this command declaration:
|
|
||||||
|
|
||||||
```go
|
|
||||||
docker.Command("run", "Run a command in a new container", func(cmd *cli.Cmd) {
|
|
||||||
var (
|
|
||||||
detached = cmd.BoolOpt("d detach", false, "Detached mode: run the container in the background and print the new container ID", nil)
|
|
||||||
memory = cmd.StringOpt("m memory", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)", nil)
|
|
||||||
image = cmd.StringArg("IMAGE", "", "", nil)
|
|
||||||
args = cmd.StringsArg("ARG", "", "", nil)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
The auto-generated spec string would be:
|
|
||||||
```go
|
|
||||||
[OPTIONS] IMAGE ARG
|
|
||||||
```
|
|
||||||
|
|
||||||
Which should suffice for simple cases. If not, the spec string has to be set explicitly.
|
|
||||||
|
|
||||||
## Exiting
|
|
||||||
|
|
||||||
`mow.cli` provides the `Exit` function which accepts an exit code and exits the app with the provided code.
|
|
||||||
|
|
||||||
You are highly encouraged to call `cli.Exit` instead of `os.Exit` for the `After` interceptors to be executed.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This work is published under the MIT license.
|
|
||||||
|
|
||||||
Please see the `LICENSE` file for details.
|
|
|
@ -1,224 +0,0 @@
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BoolArg describes a boolean argument
|
|
||||||
type BoolArg struct {
|
|
||||||
// The argument name as will be shown in help messages
|
|
||||||
Name string
|
|
||||||
// The argument description as will be shown in help messages
|
|
||||||
Desc string
|
|
||||||
// A space separated list of environment variables names to be used to initialize this argument
|
|
||||||
EnvVar string
|
|
||||||
// The argument's inital value
|
|
||||||
Value bool
|
|
||||||
// A boolean to display or not the current value of the argument in the help message
|
|
||||||
HideValue bool
|
|
||||||
// Set to true if this arg was set by the user (as opposed to being set from env or not set at all)
|
|
||||||
SetByUser *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a BoolArg) value() bool {
|
|
||||||
return a.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringArg describes a string argument
|
|
||||||
type StringArg struct {
|
|
||||||
// The argument name as will be shown in help messages
|
|
||||||
Name string
|
|
||||||
// The argument description as will be shown in help messages
|
|
||||||
Desc string
|
|
||||||
// A space separated list of environment variables names to be used to initialize this argument
|
|
||||||
EnvVar string
|
|
||||||
// The argument's initial value
|
|
||||||
Value string
|
|
||||||
// A boolean to display or not the current value of the argument in the help message
|
|
||||||
HideValue bool
|
|
||||||
// Set to true if this arg was set by the user (as opposed to being set from env or not set at all)
|
|
||||||
SetByUser *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a StringArg) value() string {
|
|
||||||
return a.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntArg describes an int argument
|
|
||||||
type IntArg struct {
|
|
||||||
// The argument name as will be shown in help messages
|
|
||||||
Name string
|
|
||||||
// The argument description as will be shown in help messages
|
|
||||||
Desc string
|
|
||||||
// A space separated list of environment variables names to be used to initialize this argument
|
|
||||||
EnvVar string
|
|
||||||
// The argument's initial value
|
|
||||||
Value int
|
|
||||||
// A boolean to display or not the current value of the argument in the help message
|
|
||||||
HideValue bool
|
|
||||||
// Set to true if this arg was set by the user (as opposed to being set from env or not set at all)
|
|
||||||
SetByUser *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a IntArg) value() int {
|
|
||||||
return a.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringsArg describes a string slice argument
|
|
||||||
type StringsArg struct {
|
|
||||||
// The argument name as will be shown in help messages
|
|
||||||
Name string
|
|
||||||
// The argument description as will be shown in help messages
|
|
||||||
Desc string
|
|
||||||
// A space separated list of environment variables names to be used to initialize this argument.
|
|
||||||
// The env variable should contain a comma separated list of values
|
|
||||||
EnvVar string
|
|
||||||
// The argument's initial value
|
|
||||||
Value []string
|
|
||||||
// A boolean to display or not the current value of the argument in the help message
|
|
||||||
HideValue bool
|
|
||||||
// Set to true if this arg was set by the user (as opposed to being set from env or not set at all)
|
|
||||||
SetByUser *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a StringsArg) value() []string {
|
|
||||||
return a.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntsArg describes an int slice argument
|
|
||||||
type IntsArg struct {
|
|
||||||
// The argument name as will be shown in help messages
|
|
||||||
Name string
|
|
||||||
// The argument description as will be shown in help messages
|
|
||||||
Desc string
|
|
||||||
// A space separated list of environment variables names to be used to initialize this argument.
|
|
||||||
// The env variable should contain a comma separated list of values
|
|
||||||
EnvVar string
|
|
||||||
// The argument's initial value
|
|
||||||
Value []int
|
|
||||||
// A boolean to display or not the current value of the argument in the help message
|
|
||||||
HideValue bool
|
|
||||||
// Set to true if this arg was set by the user (as opposed to being set from env or not set at all)
|
|
||||||
SetByUser *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a IntsArg) value() []int {
|
|
||||||
return a.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// VarArg describes an argument where the type and format of the value is controlled by the developer
|
|
||||||
type VarArg struct {
|
|
||||||
// A space separated list of the option names *WITHOUT* the dashes, e.g. `f force` and *NOT* `-f --force`.
|
|
||||||
// The one letter names will then be called with a single dash (short option), the others with two (long options).
|
|
||||||
Name string
|
|
||||||
// The option description as will be shown in help messages
|
|
||||||
Desc string
|
|
||||||
// A space separated list of environment variables names to be used to initialize this option
|
|
||||||
EnvVar string
|
|
||||||
// A value implementing the flag.Value type (will hold the final value)
|
|
||||||
Value flag.Value
|
|
||||||
// A boolean to display or not the current value of the option in the help message
|
|
||||||
HideValue bool
|
|
||||||
// Set to true if this arg was set by the user (as opposed to being set from env or not set at all)
|
|
||||||
SetByUser *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a VarArg) value() flag.Value {
|
|
||||||
return a.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
BoolArg defines a boolean argument on the command c named `name`, with an initial value of `value` and a description of `desc` which will be used in help messages.
|
|
||||||
|
|
||||||
The result should be stored in a variable (a pointer to a bool) which will be populated when the app is run and the call arguments get parsed
|
|
||||||
*/
|
|
||||||
func (c *Cmd) BoolArg(name string, value bool, desc string) *bool {
|
|
||||||
return c.Bool(BoolArg{
|
|
||||||
Name: name,
|
|
||||||
Value: value,
|
|
||||||
Desc: desc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
StringArg defines a string argument on the command c named `name`, with an initial value of `value` and a description of `desc` which will be used in help messages.
|
|
||||||
|
|
||||||
The result should be stored in a variable (a pointer to a string) which will be populated when the app is run and the call arguments get parsed
|
|
||||||
*/
|
|
||||||
func (c *Cmd) StringArg(name string, value string, desc string) *string {
|
|
||||||
return c.String(StringArg{
|
|
||||||
Name: name,
|
|
||||||
Value: value,
|
|
||||||
Desc: desc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
IntArg defines an int argument on the command c named `name`, with an initial value of `value` and a description of `desc` which will be used in help messages.
|
|
||||||
|
|
||||||
The result should be stored in a variable (a pointer to an int) which will be populated when the app is run and the call arguments get parsed
|
|
||||||
*/
|
|
||||||
func (c *Cmd) IntArg(name string, value int, desc string) *int {
|
|
||||||
return c.Int(IntArg{
|
|
||||||
Name: name,
|
|
||||||
Value: value,
|
|
||||||
Desc: desc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
StringsArg defines a string slice argument on the command c named `name`, with an initial value of `value` and a description of `desc` which will be used in help messages.
|
|
||||||
|
|
||||||
The result should be stored in a variable (a pointer to a string slice) which will be populated when the app is run and the call arguments get parsed
|
|
||||||
*/
|
|
||||||
func (c *Cmd) StringsArg(name string, value []string, desc string) *[]string {
|
|
||||||
return c.Strings(StringsArg{
|
|
||||||
Name: name,
|
|
||||||
Value: value,
|
|
||||||
Desc: desc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
IntsArg defines an int slice argument on the command c named `name`, with an initial value of `value` and a description of `desc` which will be used in help messages.
|
|
||||||
|
|
||||||
The result should be stored in a variable (a pointer to an int slice) which will be populated when the app is run and the call arguments get parsed
|
|
||||||
*/
|
|
||||||
func (c *Cmd) IntsArg(name string, value []int, desc string) *[]int {
|
|
||||||
return c.Ints(IntsArg{
|
|
||||||
Name: name,
|
|
||||||
Value: value,
|
|
||||||
Desc: desc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
VarArg defines an argument where the type and format is controlled by the developer on the command c named `name` and a description of `desc` which will be used in help messages.
|
|
||||||
|
|
||||||
The result will be stored in the value parameter (a value implementing the flag.Value interface) which will be populated when the app is run and the call arguments get parsed
|
|
||||||
*/
|
|
||||||
func (c *Cmd) VarArg(name string, value flag.Value, desc string) {
|
|
||||||
c.mkArg(arg{name: name, desc: desc, value: value})
|
|
||||||
}
|
|
||||||
|
|
||||||
type arg struct {
|
|
||||||
name string
|
|
||||||
desc string
|
|
||||||
envVar string
|
|
||||||
hideValue bool
|
|
||||||
valueSetFromEnv bool
|
|
||||||
valueSetByUser *bool
|
|
||||||
value flag.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *arg) String() string {
|
|
||||||
return fmt.Sprintf("ARG(%s)", a.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cmd) mkArg(arg arg) {
|
|
||||||
arg.valueSetFromEnv = setFromEnv(arg.value, arg.envVar)
|
|
||||||
|
|
||||||
c.args = append(c.args, &arg)
|
|
||||||
c.argsIdx[arg.name] = &arg
|
|
||||||
}
|
|