vendor in deps so CI goes fast
This commit is contained in:
parent
7a47873be3
commit
081028f3b2
|
@ -0,0 +1,31 @@
|
|||
### Go template
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
vendor/
|
||||
|
||||
### IntelliJ
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
gen
|
||||
|
||||
### VisualStudioCode
|
||||
.vscode
|
||||
*.code-workspace
|
||||
|
||||
### macOS
|
||||
# General
|
||||
.DS_Store
|
||||
experimenting
|
|
@ -0,0 +1,71 @@
|
|||
linters-settings:
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
disabled-checks:
|
||||
- dupImport
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
- whyNoLint
|
||||
- wrapperFunc
|
||||
- exitAfterDefer
|
||||
- hugeParam
|
||||
- ptrToRefParam
|
||||
- paramTypeCombine
|
||||
- unnamedResult
|
||||
misspell:
|
||||
locale: US
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- dupl
|
||||
- durationcheck
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- gci
|
||||
- gocognit
|
||||
- gocritic
|
||||
- godot
|
||||
- godox
|
||||
- goerr113
|
||||
- gofmt
|
||||
- goimports
|
||||
- goprintffuncname
|
||||
- misspell
|
||||
- nilerr
|
||||
- nlreturn
|
||||
- noctx
|
||||
- prealloc
|
||||
- predeclared
|
||||
- thelper
|
||||
- unconvert
|
||||
- unparam
|
||||
- wastedassign
|
||||
- wrapcheck
|
||||
issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
- dupl
|
||||
- gocritic
|
||||
- wrapcheck
|
||||
- goerr113
|
||||
# https://github.com/go-critic/go-critic/issues/926
|
||||
- linters:
|
||||
- gocritic
|
||||
text: "unnecessaryDefer:"
|
||||
service:
|
||||
golangci-lint-version: 1.39.x # use the fixed version to not introduce new linters unexpectedly
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Marvin Wendt (MarvinJWendt)
|
||||
|
||||
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.
|
|
@ -0,0 +1,237 @@
|
|||
<h1 align="center">AtomicGo | cursor</h1>
|
||||
|
||||
<p align="center">
|
||||
|
||||
<a href="https://github.com/atomicgo/cursor/releases">
|
||||
<img src="https://img.shields.io/github/v/release/atomicgo/cursor?style=flat-square" alt="Latest Release">
|
||||
</a>
|
||||
|
||||
<a href="https://codecov.io/gh/atomicgo/cursor" target="_blank">
|
||||
<img src="https://img.shields.io/github/workflow/status/atomicgo/cursor/Go?label=tests&style=flat-square" alt="Tests">
|
||||
</a>
|
||||
|
||||
<a href="https://codecov.io/gh/atomicgo/cursor" target="_blank">
|
||||
<img src="https://img.shields.io/codecov/c/gh/atomicgo/cursor?color=magenta&logo=codecov&style=flat-square" alt="Coverage">
|
||||
</a>
|
||||
|
||||
<a href="https://codecov.io/gh/atomicgo/cursor">
|
||||
<!-- unittestcount:start --><img src="https://img.shields.io/badge/Unit_Tests-2-magenta?style=flat-square" alt="Unit test count"><!-- unittestcount:end -->
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/atomicgo/cursor/issues">
|
||||
<img src="https://img.shields.io/github/issues/atomicgo/cursor.svg?style=flat-square" alt="Issues">
|
||||
</a>
|
||||
|
||||
<a href="https://opensource.org/licenses/MIT" target="_blank">
|
||||
<img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square" alt="License: MIT">
|
||||
</a>
|
||||
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<strong><a href="#install">Get The Module</a></strong>
|
||||
|
|
||||
<strong><a href="https://pkg.go.dev/github.com/atomicgo/cursor#section-documentation" target="_blank">Documentation</a></strong>
|
||||
|
|
||||
<strong><a href="https://github.com/atomicgo/atomicgo/blob/main/CONTRIBUTING.md" target="_blank">Contributing</a></strong>
|
||||
|
|
||||
<strong><a href="https://github.com/atomicgo/atomicgo/blob/main/CODE_OF_CONDUCT.md" target="_blank">Code of Conduct</a></strong>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/atomicgo/atomicgo/main/assets/header.png" alt="AtomicGo">
|
||||
</p>
|
||||
|
||||
## Description
|
||||
|
||||
Package cursor contains cross-platform methods to move the terminal cursor in
|
||||
different directions. This package can be used to create interactive CLI tools
|
||||
and games, live charts, algorithm visualizations and other updatable output of
|
||||
any kind.
|
||||
|
||||
Special thanks to github.com/k0kubun/go-ansi which this project is based on.
|
||||
|
||||
## Install
|
||||
|
||||
```console
|
||||
# Execute this command inside your project
|
||||
go get -u github.com/atomicgo/cursor
|
||||
```
|
||||
|
||||
```go
|
||||
// Add this to your imports
|
||||
import "github.com/atomicgo/cursor"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
#### func Bottom
|
||||
|
||||
```go
|
||||
func Bottom()
|
||||
```
|
||||
Bottom moves the cursor to the bottom of the terminal. This is done by
|
||||
calculating how many lines were moved by Up and Down.
|
||||
|
||||
#### func ClearLine
|
||||
|
||||
```go
|
||||
func ClearLine()
|
||||
```
|
||||
ClearLine clears the current line and moves the cursor to it's start position.
|
||||
|
||||
#### func ClearLinesDown
|
||||
|
||||
```go
|
||||
func ClearLinesDown(n int)
|
||||
```
|
||||
ClearLinesDown clears n lines downwards from the current position and moves the
|
||||
cursor.
|
||||
|
||||
#### func ClearLinesUp
|
||||
|
||||
```go
|
||||
func ClearLinesUp(n int)
|
||||
```
|
||||
ClearLinesUp clears n lines upwards from the current position and moves the
|
||||
cursor.
|
||||
|
||||
#### func Down
|
||||
|
||||
```go
|
||||
func Down(n int)
|
||||
```
|
||||
Down moves the cursor n lines down relative to the current position.
|
||||
|
||||
#### func DownAndClear
|
||||
|
||||
```go
|
||||
func DownAndClear(n int)
|
||||
```
|
||||
DownAndClear moves the cursor down by n lines, then clears the line.
|
||||
|
||||
#### func Hide
|
||||
|
||||
```go
|
||||
func Hide()
|
||||
```
|
||||
Hide the cursor. Don't forget to show the cursor at least at the end of your
|
||||
application with Show. Otherwise the user might have a terminal with a
|
||||
permanently hidden cursor, until he reopens the terminal.
|
||||
|
||||
#### func HorizontalAbsolute
|
||||
|
||||
```go
|
||||
func HorizontalAbsolute(n int)
|
||||
```
|
||||
HorizontalAbsolute moves the cursor to n horizontally. The position n is
|
||||
absolute to the start of the line.
|
||||
|
||||
#### func Left
|
||||
|
||||
```go
|
||||
func Left(n int)
|
||||
```
|
||||
Left moves the cursor n characters to the left relative to the current position.
|
||||
|
||||
#### func Move
|
||||
|
||||
```go
|
||||
func Move(x, y int)
|
||||
```
|
||||
Move moves the cursor relative by x and y.
|
||||
|
||||
#### func Right
|
||||
|
||||
```go
|
||||
func Right(n int)
|
||||
```
|
||||
Right moves the cursor n characters to the right relative to the current
|
||||
position.
|
||||
|
||||
#### func Show
|
||||
|
||||
```go
|
||||
func Show()
|
||||
```
|
||||
Show the cursor if it was hidden previously. Don't forget to show the cursor at
|
||||
least at the end of your application. Otherwise the user might have a terminal
|
||||
with a permanently hidden cursor, until he reopens the terminal.
|
||||
|
||||
#### func StartOfLine
|
||||
|
||||
```go
|
||||
func StartOfLine()
|
||||
```
|
||||
StartOfLine moves the cursor to the start of the current line.
|
||||
|
||||
#### func StartOfLineDown
|
||||
|
||||
```go
|
||||
func StartOfLineDown(n int)
|
||||
```
|
||||
StartOfLineDown moves the cursor down by n lines, then moves to cursor to the
|
||||
start of the line.
|
||||
|
||||
#### func StartOfLineUp
|
||||
|
||||
```go
|
||||
func StartOfLineUp(n int)
|
||||
```
|
||||
StartOfLineUp moves the cursor up by n lines, then moves to cursor to the start
|
||||
of the line.
|
||||
|
||||
#### func Up
|
||||
|
||||
```go
|
||||
func Up(n int)
|
||||
```
|
||||
Up moves the cursor n lines up relative to the current position.
|
||||
|
||||
#### func UpAndClear
|
||||
|
||||
```go
|
||||
func UpAndClear(n int)
|
||||
```
|
||||
UpAndClear moves the cursor up by n lines, then clears the line.
|
||||
|
||||
#### type Area
|
||||
|
||||
```go
|
||||
type Area struct {
|
||||
}
|
||||
```
|
||||
|
||||
Area displays content which can be updated on the fly. You can use this to
|
||||
create live output, charts, dropdowns, etc.
|
||||
|
||||
#### func NewArea
|
||||
|
||||
```go
|
||||
func NewArea() Area
|
||||
```
|
||||
NewArea returns a new Area.
|
||||
|
||||
#### func (*Area) Clear
|
||||
|
||||
```go
|
||||
func (area *Area) Clear()
|
||||
```
|
||||
Clear clears the content of the Area.
|
||||
|
||||
#### func (*Area) Update
|
||||
|
||||
```go
|
||||
func (area *Area) Update(content string)
|
||||
```
|
||||
Update overwrites the content of the Area.
|
||||
|
||||
---
|
||||
|
||||
> [AtomicGo.dev](https://atomicgo.dev) ·
|
||||
> with ❤️ by [@MarvinJWendt](https://github.com/MarvinJWendt) |
|
||||
> [MarvinJWendt.com](https://marvinjwendt.com)
|
|
@ -0,0 +1,45 @@
|
|||
package cursor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Area displays content which can be updated on the fly.
|
||||
// You can use this to create live output, charts, dropdowns, etc.
|
||||
type Area struct {
|
||||
height int
|
||||
}
|
||||
|
||||
// NewArea returns a new Area.
|
||||
func NewArea() Area {
|
||||
return Area{}
|
||||
}
|
||||
|
||||
// Clear clears the content of the Area.
|
||||
func (area *Area) Clear() {
|
||||
Bottom()
|
||||
if area.height > 0 {
|
||||
ClearLinesUp(area.height)
|
||||
}
|
||||
}
|
||||
|
||||
// Update overwrites the content of the Area.
|
||||
func (area *Area) Update(content string) {
|
||||
area.Clear()
|
||||
lines := strings.Split(content, "\n")
|
||||
if runtime.GOOS == "windows" {
|
||||
for _, line := range lines {
|
||||
fmt.Print(line)
|
||||
StartOfLineDown(1)
|
||||
}
|
||||
} else {
|
||||
for _, line := range lines {
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
height = 0
|
||||
|
||||
area.height = len(lines)
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// +build !windows
|
||||
|
||||
package cursor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Up moves the cursor n lines up relative to the current position.
|
||||
func Up(n int) {
|
||||
fmt.Printf("\x1b[%dA", n)
|
||||
height += n
|
||||
}
|
||||
|
||||
// Down moves the cursor n lines down relative to the current position.
|
||||
func Down(n int) {
|
||||
fmt.Printf("\x1b[%dB", n)
|
||||
if height-n <= 0 {
|
||||
height = 0
|
||||
} else {
|
||||
height -= n
|
||||
}
|
||||
}
|
||||
|
||||
// Right moves the cursor n characters to the right relative to the current position.
|
||||
func Right(n int) {
|
||||
fmt.Printf("\x1b[%dC", n)
|
||||
}
|
||||
|
||||
// Left moves the cursor n characters to the left relative to the current position.
|
||||
func Left(n int) {
|
||||
fmt.Printf("\x1b[%dD", n)
|
||||
}
|
||||
|
||||
// HorizontalAbsolute moves the cursor to n horizontally.
|
||||
// The position n is absolute to the start of the line.
|
||||
func HorizontalAbsolute(n int) {
|
||||
n += 1 // Moves the line to the character after n
|
||||
fmt.Printf("\x1b[%dG", n)
|
||||
}
|
||||
|
||||
// Show the cursor if it was hidden previously.
|
||||
// Don't forget to show the cursor at least at the end of your application.
|
||||
// Otherwise the user might have a terminal with a permanently hidden cursor, until he reopens the terminal.
|
||||
func Show() {
|
||||
fmt.Print("\x1b[?25h")
|
||||
}
|
||||
|
||||
// Hide the cursor.
|
||||
// Don't forget to show the cursor at least at the end of your application with Show.
|
||||
// Otherwise the user might have a terminal with a permanently hidden cursor, until he reopens the terminal.
|
||||
func Hide() {
|
||||
fmt.Print("\x1b[?25l")
|
||||
}
|
||||
|
||||
// ClearLine clears the current line and moves the cursor to it's start position.
|
||||
func ClearLine() {
|
||||
fmt.Print("\x1b[2K")
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package cursor
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Up moves the cursor n lines up relative to the current position.
|
||||
func Up(n int) {
|
||||
move(0, -n)
|
||||
height += n
|
||||
}
|
||||
|
||||
// Down moves the cursor n lines down relative to the current position.
|
||||
func Down(n int) {
|
||||
move(0, n)
|
||||
if height-n <= 0 {
|
||||
height = 0
|
||||
} else {
|
||||
height -= n
|
||||
}
|
||||
}
|
||||
|
||||
// Right moves the cursor n characters to the right relative to the current position.
|
||||
func Right(n int) {
|
||||
move(n, 0)
|
||||
}
|
||||
|
||||
// Left moves the cursor n characters to the left relative to the current position.
|
||||
func Left(n int) {
|
||||
move(-n, 0)
|
||||
}
|
||||
|
||||
func move(x int, y int) {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
_, _, _ = procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
var cursor coord
|
||||
cursor.x = csbi.cursorPosition.x + short(x)
|
||||
cursor.y = csbi.cursorPosition.y + short(y)
|
||||
|
||||
_, _, _ = procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
|
||||
}
|
||||
|
||||
// HorizontalAbsolute moves the cursor to n horizontally.
|
||||
// The position n is absolute to the start of the line.
|
||||
func HorizontalAbsolute(n int) {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
_, _, _ = procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
var cursor coord
|
||||
cursor.x = short(n)
|
||||
cursor.y = csbi.cursorPosition.y
|
||||
|
||||
if csbi.size.x < cursor.x {
|
||||
cursor.x = csbi.size.x
|
||||
}
|
||||
|
||||
_, _, _ = procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
|
||||
}
|
||||
|
||||
// Show the cursor if it was hidden previously.
|
||||
// Don't forget to show the cursor at least at the end of your application.
|
||||
// Otherwise the user might have a terminal with a permanently hidden cursor, until he reopens the terminal.
|
||||
func Show() {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var cci consoleCursorInfo
|
||||
_, _, _ = procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
cci.visible = 1
|
||||
|
||||
_, _, _ = procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
}
|
||||
|
||||
// Hide the cursor.
|
||||
// Don't forget to show the cursor at least at the end of your application with Show.
|
||||
// Otherwise the user might have a terminal with a permanently hidden cursor, until he reopens the terminal.
|
||||
func Hide() {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var cci consoleCursorInfo
|
||||
_, _, _ = procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
cci.visible = 0
|
||||
|
||||
_, _, _ = procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
}
|
||||
|
||||
// ClearLine clears the current line and moves the cursor to it's start position.
|
||||
func ClearLine() {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
_, _, _ = procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
var w uint32
|
||||
var x short
|
||||
cursor := csbi.cursorPosition
|
||||
x = csbi.size.x
|
||||
_, _, _ = procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w)))
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
Package cursor contains cross-platform methods to move the terminal cursor in different directions.
|
||||
This package can be used to create interactive CLI tools and games, live charts, algorithm visualizations and other updatable output of any kind.
|
||||
|
||||
Special thanks to github.com/k0kubun/go-ansi which this project is based on.
|
||||
*/
|
||||
package cursor
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/atomicgo/cursor
|
||||
|
||||
go 1.15
|
|
@ -0,0 +1,43 @@
|
|||
package cursor
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
|
||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||
)
|
||||
|
||||
type short int16
|
||||
type dword uint32
|
||||
type word uint16
|
||||
|
||||
type coord struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
|
||||
type smallRect struct {
|
||||
bottom short
|
||||
left short
|
||||
right short
|
||||
top short
|
||||
}
|
||||
|
||||
type consoleScreenBufferInfo struct {
|
||||
size coord
|
||||
cursorPosition coord
|
||||
attributes word
|
||||
window smallRect
|
||||
maximumWindowSize coord
|
||||
}
|
||||
|
||||
type consoleCursorInfo struct {
|
||||
size dword
|
||||
visible int32
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package cursor
|
||||
|
||||
var height int
|
||||
|
||||
// Bottom moves the cursor to the bottom of the terminal.
|
||||
// This is done by calculating how many lines were moved by Up and Down.
|
||||
func Bottom() {
|
||||
if height > 0 {
|
||||
Down(height)
|
||||
StartOfLine()
|
||||
height = 0
|
||||
}
|
||||
}
|
||||
|
||||
// StartOfLine moves the cursor to the start of the current line.
|
||||
func StartOfLine() {
|
||||
HorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
// StartOfLineDown moves the cursor down by n lines, then moves to cursor to the start of the line.
|
||||
func StartOfLineDown(n int) {
|
||||
Down(n)
|
||||
StartOfLine()
|
||||
}
|
||||
|
||||
// StartOfLineUp moves the cursor up by n lines, then moves to cursor to the start of the line.
|
||||
func StartOfLineUp(n int) {
|
||||
Up(n)
|
||||
StartOfLine()
|
||||
}
|
||||
|
||||
// UpAndClear moves the cursor up by n lines, then clears the line.
|
||||
func UpAndClear(n int) {
|
||||
Up(n)
|
||||
ClearLine()
|
||||
}
|
||||
|
||||
// DownAndClear moves the cursor down by n lines, then clears the line.
|
||||
func DownAndClear(n int) {
|
||||
Down(n)
|
||||
ClearLine()
|
||||
}
|
||||
|
||||
// Move moves the cursor relative by x and y.
|
||||
func Move(x, y int) {
|
||||
if x > 0 {
|
||||
Right(x)
|
||||
} else if x < 0 {
|
||||
x *= -1
|
||||
Left(x)
|
||||
}
|
||||
|
||||
if y > 0 {
|
||||
Up(y)
|
||||
} else if y < 0 {
|
||||
y *= -1
|
||||
Down(y)
|
||||
}
|
||||
}
|
||||
|
||||
// ClearLinesUp clears n lines upwards from the current position and moves the cursor.
|
||||
func ClearLinesUp(n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
UpAndClear(1)
|
||||
}
|
||||
}
|
||||
|
||||
// ClearLinesDown clears n lines downwards from the current position and moves the cursor.
|
||||
func ClearLinesDown(n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
DownAndClear(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Brian Goff
|
||||
|
||||
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.
|
|
@ -0,0 +1,14 @@
|
|||
package md2man
|
||||
|
||||
import (
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// Render converts a markdown document into a roff formatted document.
|
||||
func Render(doc []byte) []byte {
|
||||
renderer := NewRoffRenderer()
|
||||
|
||||
return blackfriday.Run(doc,
|
||||
[]blackfriday.Option{blackfriday.WithRenderer(renderer),
|
||||
blackfriday.WithExtensions(renderer.GetExtensions())}...)
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
package md2man
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// roffRenderer implements the blackfriday.Renderer interface for creating
|
||||
// roff format (manpages) from markdown text
|
||||
type roffRenderer struct {
|
||||
extensions blackfriday.Extensions
|
||||
listCounters []int
|
||||
firstHeader bool
|
||||
defineTerm bool
|
||||
listDepth int
|
||||
}
|
||||
|
||||
const (
|
||||
titleHeader = ".TH "
|
||||
topLevelHeader = "\n\n.SH "
|
||||
secondLevelHdr = "\n.SH "
|
||||
otherHeader = "\n.SS "
|
||||
crTag = "\n"
|
||||
emphTag = "\\fI"
|
||||
emphCloseTag = "\\fP"
|
||||
strongTag = "\\fB"
|
||||
strongCloseTag = "\\fP"
|
||||
breakTag = "\n.br\n"
|
||||
paraTag = "\n.PP\n"
|
||||
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
|
||||
linkTag = "\n\\[la]"
|
||||
linkCloseTag = "\\[ra]"
|
||||
codespanTag = "\\fB\\fC"
|
||||
codespanCloseTag = "\\fR"
|
||||
codeTag = "\n.PP\n.RS\n\n.nf\n"
|
||||
codeCloseTag = "\n.fi\n.RE\n"
|
||||
quoteTag = "\n.PP\n.RS\n"
|
||||
quoteCloseTag = "\n.RE\n"
|
||||
listTag = "\n.RS\n"
|
||||
listCloseTag = "\n.RE\n"
|
||||
arglistTag = "\n.TP\n"
|
||||
tableStart = "\n.TS\nallbox;\n"
|
||||
tableEnd = ".TE\n"
|
||||
tableCellStart = "T{\n"
|
||||
tableCellEnd = "\nT}\n"
|
||||
)
|
||||
|
||||
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
|
||||
// from markdown
|
||||
func NewRoffRenderer() *roffRenderer { // nolint: golint
|
||||
var extensions blackfriday.Extensions
|
||||
|
||||
extensions |= blackfriday.NoIntraEmphasis
|
||||
extensions |= blackfriday.Tables
|
||||
extensions |= blackfriday.FencedCode
|
||||
extensions |= blackfriday.SpaceHeadings
|
||||
extensions |= blackfriday.Footnotes
|
||||
extensions |= blackfriday.Titleblock
|
||||
extensions |= blackfriday.DefinitionLists
|
||||
return &roffRenderer{
|
||||
extensions: extensions,
|
||||
}
|
||||
}
|
||||
|
||||
// GetExtensions returns the list of extensions used by this renderer implementation
|
||||
func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
|
||||
return r.extensions
|
||||
}
|
||||
|
||||
// RenderHeader handles outputting the header at document start
|
||||
func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
|
||||
// disable hyphenation
|
||||
out(w, ".nh\n")
|
||||
}
|
||||
|
||||
// RenderFooter handles outputting the footer at the document end; the roff
|
||||
// renderer has no footer information
|
||||
func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
|
||||
}
|
||||
|
||||
// RenderNode is called for each node in a markdown document; based on the node
|
||||
// type the equivalent roff output is sent to the writer
|
||||
func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
|
||||
var walkAction = blackfriday.GoToNext
|
||||
|
||||
switch node.Type {
|
||||
case blackfriday.Text:
|
||||
r.handleText(w, node, entering)
|
||||
case blackfriday.Softbreak:
|
||||
out(w, crTag)
|
||||
case blackfriday.Hardbreak:
|
||||
out(w, breakTag)
|
||||
case blackfriday.Emph:
|
||||
if entering {
|
||||
out(w, emphTag)
|
||||
} else {
|
||||
out(w, emphCloseTag)
|
||||
}
|
||||
case blackfriday.Strong:
|
||||
if entering {
|
||||
out(w, strongTag)
|
||||
} else {
|
||||
out(w, strongCloseTag)
|
||||
}
|
||||
case blackfriday.Link:
|
||||
if !entering {
|
||||
out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag)
|
||||
}
|
||||
case blackfriday.Image:
|
||||
// ignore images
|
||||
walkAction = blackfriday.SkipChildren
|
||||
case blackfriday.Code:
|
||||
out(w, codespanTag)
|
||||
escapeSpecialChars(w, node.Literal)
|
||||
out(w, codespanCloseTag)
|
||||
case blackfriday.Document:
|
||||
break
|
||||
case blackfriday.Paragraph:
|
||||
// roff .PP markers break lists
|
||||
if r.listDepth > 0 {
|
||||
return blackfriday.GoToNext
|
||||
}
|
||||
if entering {
|
||||
out(w, paraTag)
|
||||
} else {
|
||||
out(w, crTag)
|
||||
}
|
||||
case blackfriday.BlockQuote:
|
||||
if entering {
|
||||
out(w, quoteTag)
|
||||
} else {
|
||||
out(w, quoteCloseTag)
|
||||
}
|
||||
case blackfriday.Heading:
|
||||
r.handleHeading(w, node, entering)
|
||||
case blackfriday.HorizontalRule:
|
||||
out(w, hruleTag)
|
||||
case blackfriday.List:
|
||||
r.handleList(w, node, entering)
|
||||
case blackfriday.Item:
|
||||
r.handleItem(w, node, entering)
|
||||
case blackfriday.CodeBlock:
|
||||
out(w, codeTag)
|
||||
escapeSpecialChars(w, node.Literal)
|
||||
out(w, codeCloseTag)
|
||||
case blackfriday.Table:
|
||||
r.handleTable(w, node, entering)
|
||||
case blackfriday.TableCell:
|
||||
r.handleTableCell(w, node, entering)
|
||||
case blackfriday.TableHead:
|
||||
case blackfriday.TableBody:
|
||||
case blackfriday.TableRow:
|
||||
// no action as cell entries do all the nroff formatting
|
||||
return blackfriday.GoToNext
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
|
||||
}
|
||||
return walkAction
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleText(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
var (
|
||||
start, end string
|
||||
)
|
||||
// handle special roff table cell text encapsulation
|
||||
if node.Parent.Type == blackfriday.TableCell {
|
||||
if len(node.Literal) > 30 {
|
||||
start = tableCellStart
|
||||
end = tableCellEnd
|
||||
} else {
|
||||
// end rows that aren't terminated by "tableCellEnd" with a cr if end of row
|
||||
if node.Parent.Next == nil && !node.Parent.IsHeader {
|
||||
end = crTag
|
||||
}
|
||||
}
|
||||
}
|
||||
out(w, start)
|
||||
escapeSpecialChars(w, node.Literal)
|
||||
out(w, end)
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
if entering {
|
||||
switch node.Level {
|
||||
case 1:
|
||||
if !r.firstHeader {
|
||||
out(w, titleHeader)
|
||||
r.firstHeader = true
|
||||
break
|
||||
}
|
||||
out(w, topLevelHeader)
|
||||
case 2:
|
||||
out(w, secondLevelHdr)
|
||||
default:
|
||||
out(w, otherHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
openTag := listTag
|
||||
closeTag := listCloseTag
|
||||
if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
|
||||
// tags for definition lists handled within Item node
|
||||
openTag = ""
|
||||
closeTag = ""
|
||||
}
|
||||
if entering {
|
||||
r.listDepth++
|
||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||
r.listCounters = append(r.listCounters, 1)
|
||||
}
|
||||
out(w, openTag)
|
||||
} else {
|
||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||
r.listCounters = r.listCounters[:len(r.listCounters)-1]
|
||||
}
|
||||
out(w, closeTag)
|
||||
r.listDepth--
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
if entering {
|
||||
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
|
||||
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
|
||||
r.listCounters[len(r.listCounters)-1]++
|
||||
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
|
||||
// state machine for handling terms and following definitions
|
||||
// since blackfriday does not distinguish them properly, nor
|
||||
// does it seperate them into separate lists as it should
|
||||
if !r.defineTerm {
|
||||
out(w, arglistTag)
|
||||
r.defineTerm = true
|
||||
} else {
|
||||
r.defineTerm = false
|
||||
}
|
||||
} else {
|
||||
out(w, ".IP \\(bu 2\n")
|
||||
}
|
||||
} else {
|
||||
out(w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
if entering {
|
||||
out(w, tableStart)
|
||||
//call walker to count cells (and rows?) so format section can be produced
|
||||
columns := countColumns(node)
|
||||
out(w, strings.Repeat("l ", columns)+"\n")
|
||||
out(w, strings.Repeat("l ", columns)+".\n")
|
||||
} else {
|
||||
out(w, tableEnd)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
|
||||
var (
|
||||
start, end string
|
||||
)
|
||||
if node.IsHeader {
|
||||
start = codespanTag
|
||||
end = codespanCloseTag
|
||||
}
|
||||
if entering {
|
||||
if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
|
||||
out(w, "\t"+start)
|
||||
} else {
|
||||
out(w, start)
|
||||
}
|
||||
} else {
|
||||
// need to carriage return if we are at the end of the header row
|
||||
if node.IsHeader && node.Next == nil {
|
||||
end = end + crTag
|
||||
}
|
||||
out(w, end)
|
||||
}
|
||||
}
|
||||
|
||||
// because roff format requires knowing the column count before outputting any table
|
||||
// data we need to walk a table tree and count the columns
|
||||
func countColumns(node *blackfriday.Node) int {
|
||||
var columns int
|
||||
|
||||
node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
|
||||
switch node.Type {
|
||||
case blackfriday.TableRow:
|
||||
if !entering {
|
||||
return blackfriday.Terminate
|
||||
}
|
||||
case blackfriday.TableCell:
|
||||
if entering {
|
||||
columns++
|
||||
}
|
||||
default:
|
||||
}
|
||||
return blackfriday.GoToNext
|
||||
})
|
||||
return columns
|
||||
}
|
||||
|
||||
func out(w io.Writer, output string) {
|
||||
io.WriteString(w, output) // nolint: errcheck
|
||||
}
|
||||
|
||||
func needsBackslash(c byte) bool {
|
||||
for _, r := range []byte("-_&\\~") {
|
||||
if c == r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func escapeSpecialChars(w io.Writer, text []byte) {
|
||||
for i := 0; i < len(text); i++ {
|
||||
// escape initial apostrophe or period
|
||||
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
|
||||
out(w, "\\&")
|
||||
}
|
||||
|
||||
// directly copy normal characters
|
||||
org := i
|
||||
|
||||
for i < len(text) && !needsBackslash(text[i]) {
|
||||
i++
|
||||
}
|
||||
if i > org {
|
||||
w.Write(text[org:i]) // nolint: errcheck
|
||||
}
|
||||
|
||||
// escape a character
|
||||
if i >= len(text) {
|
||||
break
|
||||
}
|
||||
|
||||
w.Write([]byte{'\\', text[i]}) // nolint: errcheck
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
*.log
|
||||
*.swp
|
||||
.idea
|
||||
*.patch
|
||||
### Go template
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
.DS_Store
|
||||
app
|
||||
demo
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 inhere
|
||||
|
||||
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.
|
|
@ -0,0 +1,468 @@
|
|||
# CLI Color
|
||||
|
||||
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/color?style=flat-square)
|
||||
[![Actions Status](https://github.com/gookit/color/workflows/action-tests/badge.svg)](https://github.com/gookit/color/actions)
|
||||
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/51b28c5f7ffe4cc2b0f12ecf25ed247f)](https://app.codacy.com/app/inhere/color)
|
||||
[![GoDoc](https://godoc.org/github.com/gookit/color?status.svg)](https://pkg.go.dev/github.com/gookit/color?tab=overview)
|
||||
[![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/color)](https://github.com/gookit/color)
|
||||
[![Build Status](https://travis-ci.org/gookit/color.svg?branch=master)](https://travis-ci.org/gookit/color)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/gookit/color/badge.svg?branch=master)](https://coveralls.io/github/gookit/color?branch=master)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/gookit/color)](https://goreportcard.com/report/github.com/gookit/color)
|
||||
|
||||
A command-line color library with true color support, universal API methods and Windows support.
|
||||
|
||||
> **[中文说明](README.zh-CN.md)**
|
||||
|
||||
Basic color preview:
|
||||
|
||||
![basic-color](_examples/images/basic-color2.png)
|
||||
|
||||
Now, 256 colors and RGB colors have also been supported to work in Windows CMD and PowerShell:
|
||||
|
||||
![color-on-cmd-pwsh](_examples/images/color-on-cmd-pwsh.jpg)
|
||||
|
||||
## Features
|
||||
|
||||
- Simple to use, zero dependencies
|
||||
- Supports rich color output: 16-color (4-bit), 256-color (8-bit), true color (24-bit, RGB)
|
||||
- 16-color output is the most commonly used and most widely supported, working on any Windows version
|
||||
- Since `v1.2.4` **the 256-color (8-bit), true color (24-bit) support windows CMD and PowerShell**
|
||||
- See [this gist](https://gist.github.com/XVilka/8346728) for information on true color support
|
||||
- Generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf`
|
||||
- Supports HTML tag-style color rendering, such as `<green>message</>`.
|
||||
- In addition to using built-in tags, it also supports custom color attributes
|
||||
- Custom color attributes support the use of 16 color names, 256 color values, rgb color values and hex color values
|
||||
- Support working on Windows `cmd` and `powerShell` terminal
|
||||
- Basic colors: `Bold`, `Black`, `White`, `Gray`, `Red`, `Green`, `Yellow`, `Blue`, `Magenta`, `Cyan`
|
||||
- Additional styles: `Info`, `Note`, `Light`, `Error`, `Danger`, `Notice`, `Success`, `Comment`, `Primary`, `Warning`, `Question`, `Secondary`
|
||||
- Support by set `NO_COLOR` for disable color or use `FORCE_COLOR` for force open color render.
|
||||
- Support Rgb, 256, 16 color conversion
|
||||
|
||||
## GoDoc
|
||||
|
||||
- [godoc for gopkg](https://pkg.go.dev/gopkg.in/gookit/color.v1)
|
||||
- [godoc for github](https://pkg.go.dev/github.com/gookit/color)
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/color
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// quick use package func
|
||||
color.Redp("Simple to use color")
|
||||
color.Redln("Simple to use color")
|
||||
color.Greenp("Simple to use color\n")
|
||||
color.Cyanln("Simple to use color")
|
||||
color.Yellowln("Simple to use color")
|
||||
|
||||
// quick use like fmt.Print*
|
||||
color.Red.Println("Simple to use color")
|
||||
color.Green.Print("Simple to use color\n")
|
||||
color.Cyan.Printf("Simple to use %s\n", "color")
|
||||
color.Yellow.Printf("Simple to use %s\n", "color")
|
||||
|
||||
// use like func
|
||||
red := color.FgRed.Render
|
||||
green := color.FgGreen.Render
|
||||
fmt.Printf("%s line %s library\n", red("Command"), green("color"))
|
||||
|
||||
// custom color
|
||||
color.New(color.FgWhite, color.BgBlack).Println("custom color style")
|
||||
|
||||
// can also:
|
||||
color.Style{color.FgCyan, color.OpBold}.Println("custom color style")
|
||||
|
||||
// internal theme/style:
|
||||
color.Info.Tips("message")
|
||||
color.Info.Prompt("message")
|
||||
color.Info.Println("message")
|
||||
color.Warn.Println("message")
|
||||
color.Error.Println("message")
|
||||
|
||||
// use style tag
|
||||
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>\n")
|
||||
// Custom label attr: Supports the use of 16 color names, 256 color values, rgb color values and hex color values
|
||||
color.Println("<fg=11aa23>he</><bg=120,35,156>llo</>, <fg=167;bg=232>wel</><fg=red>come</>")
|
||||
|
||||
// apply a style tag
|
||||
color.Tag("info").Println("info style text")
|
||||
|
||||
// prompt message
|
||||
color.Info.Prompt("prompt style message")
|
||||
color.Warn.Prompt("prompt style message")
|
||||
|
||||
// tips message
|
||||
color.Info.Tips("tips style message")
|
||||
color.Warn.Tips("tips style message")
|
||||
}
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/demo.go`
|
||||
|
||||
![colored-out](_examples/images/color-demo.jpg)
|
||||
|
||||
## Basic/16 color
|
||||
|
||||
Supported on any Windows version. Provide generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf`
|
||||
|
||||
```go
|
||||
color.Bold.Println("bold message")
|
||||
color.Black.Println("bold message")
|
||||
color.White.Println("bold message")
|
||||
color.Gray.Println("bold message")
|
||||
color.Red.Println("yellow message")
|
||||
color.Blue.Println("yellow message")
|
||||
color.Cyan.Println("yellow message")
|
||||
color.Yellow.Println("yellow message")
|
||||
color.Magenta.Println("yellow message")
|
||||
|
||||
// Only use foreground color
|
||||
color.FgCyan.Printf("Simple to use %s\n", "color")
|
||||
// Only use background color
|
||||
color.BgRed.Printf("Simple to use %s\n", "color")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/color_16.go`
|
||||
|
||||
![basic-color](_examples/images/basic-color.png)
|
||||
|
||||
### Custom build color
|
||||
|
||||
```go
|
||||
// Full custom: foreground, background, option
|
||||
myStyle := color.New(color.FgWhite, color.BgBlack, color.OpBold)
|
||||
myStyle.Println("custom color style")
|
||||
|
||||
// can also:
|
||||
color.Style{color.FgCyan, color.OpBold}.Println("custom color style")
|
||||
```
|
||||
|
||||
custom set console settings:
|
||||
|
||||
```go
|
||||
// set console color
|
||||
color.Set(color.FgCyan)
|
||||
|
||||
// print message
|
||||
fmt.Print("message")
|
||||
|
||||
// reset console settings
|
||||
color.Reset()
|
||||
```
|
||||
|
||||
### Additional styles
|
||||
|
||||
provide generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf`
|
||||
|
||||
print message use defined style:
|
||||
|
||||
```go
|
||||
color.Info.Println("Info message")
|
||||
color.Note.Println("Note message")
|
||||
color.Notice.Println("Notice message")
|
||||
color.Error.Println("Error message")
|
||||
color.Danger.Println("Danger message")
|
||||
color.Warn.Println("Warn message")
|
||||
color.Debug.Println("Debug message")
|
||||
color.Primary.Println("Primary message")
|
||||
color.Question.Println("Question message")
|
||||
color.Secondary.Println("Secondary message")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/theme_basic.go`
|
||||
|
||||
![theme-basic](_examples/images/theme-basic.png)
|
||||
|
||||
**Tips style**
|
||||
|
||||
```go
|
||||
color.Info.Tips("Info tips message")
|
||||
color.Note.Tips("Note tips message")
|
||||
color.Notice.Tips("Notice tips message")
|
||||
color.Error.Tips("Error tips message")
|
||||
color.Danger.Tips("Danger tips message")
|
||||
color.Warn.Tips("Warn tips message")
|
||||
color.Debug.Tips("Debug tips message")
|
||||
color.Primary.Tips("Primary tips message")
|
||||
color.Question.Tips("Question tips message")
|
||||
color.Secondary.Tips("Secondary tips message")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/theme_tips.go`
|
||||
|
||||
![theme-tips](_examples/images/theme-tips.png)
|
||||
|
||||
**Prompt Style**
|
||||
|
||||
```go
|
||||
color.Info.Prompt("Info prompt message")
|
||||
color.Note.Prompt("Note prompt message")
|
||||
color.Notice.Prompt("Notice prompt message")
|
||||
color.Error.Prompt("Error prompt message")
|
||||
color.Danger.Prompt("Danger prompt message")
|
||||
color.Warn.Prompt("Warn prompt message")
|
||||
color.Debug.Prompt("Debug prompt message")
|
||||
color.Primary.Prompt("Primary prompt message")
|
||||
color.Question.Prompt("Question prompt message")
|
||||
color.Secondary.Prompt("Secondary prompt message")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/theme_prompt.go`
|
||||
|
||||
![theme-prompt](_examples/images/theme-prompt.png)
|
||||
|
||||
**Block Style**
|
||||
|
||||
```go
|
||||
color.Info.Block("Info block message")
|
||||
color.Note.Block("Note block message")
|
||||
color.Notice.Block("Notice block message")
|
||||
color.Error.Block("Error block message")
|
||||
color.Danger.Block("Danger block message")
|
||||
color.Warn.Block("Warn block message")
|
||||
color.Debug.Block("Debug block message")
|
||||
color.Primary.Block("Primary block message")
|
||||
color.Question.Block("Question block message")
|
||||
color.Secondary.Block("Secondary block message")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/theme_block.go`
|
||||
|
||||
![theme-block](_examples/images/theme-block.png)
|
||||
|
||||
## 256-color usage
|
||||
|
||||
> 256 colors support Windows CMD, PowerShell environment after `v1.2.4`
|
||||
|
||||
### Set the foreground or background color
|
||||
|
||||
- `color.C256(val uint8, isBg ...bool) Color256`
|
||||
|
||||
```go
|
||||
c := color.C256(132) // fg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
|
||||
c := color.C256(132, true) // bg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
### 256-color style
|
||||
|
||||
Can be used to set foreground and background colors at the same time.
|
||||
|
||||
- `S256(fgAndBg ...uint8) *Style256`
|
||||
|
||||
```go
|
||||
s := color.S256(32, 203)
|
||||
s.Println("message")
|
||||
s.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
with options:
|
||||
|
||||
```go
|
||||
s := color.S256(32, 203)
|
||||
s.SetOpts(color.Opts{color.OpBold})
|
||||
|
||||
s.Println("style with options")
|
||||
s.Printf("style with %s\n", "options")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/color_256.go`
|
||||
|
||||
![color-tags](_examples/images/color-256.png)
|
||||
|
||||
## RGB/True color
|
||||
|
||||
> RGB colors support Windows `CMD`, `PowerShell` environment after `v1.2.4`
|
||||
|
||||
**Preview:**
|
||||
|
||||
> Run demo: `Run demo: go run ./_examples/color_rgb.go`
|
||||
|
||||
![color-rgb](_examples/images/color-rgb.png)
|
||||
|
||||
example:
|
||||
|
||||
```go
|
||||
color.RGB(30, 144, 255).Println("message. use RGB number")
|
||||
|
||||
color.HEX("#1976D2").Println("blue-darken")
|
||||
color.HEX("#D50000", true).Println("red-accent. use HEX style")
|
||||
|
||||
color.RGBStyleFromString("213,0,0").Println("red-accent. use RGB number")
|
||||
color.HEXStyle("eee", "D50000").Println("deep-purple color")
|
||||
```
|
||||
|
||||
### Set the foreground or background color
|
||||
|
||||
- `color.RGB(r, g, b uint8, isBg ...bool) RGBColor`
|
||||
|
||||
```go
|
||||
c := color.RGB(30,144,255) // fg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
|
||||
c := color.RGB(30,144,255, true) // bg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
Create a style from an hexadecimal color string:
|
||||
|
||||
- `color.HEX(hex string, isBg ...bool) RGBColor`
|
||||
|
||||
```go
|
||||
c := color.HEX("ccc") // can also: "cccccc" "#cccccc"
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
|
||||
c = color.HEX("aabbcc", true) // as bg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
### RGB color style
|
||||
|
||||
Can be used to set the foreground and background colors at the same time.
|
||||
|
||||
- `color.NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle`
|
||||
|
||||
```go
|
||||
s := color.NewRGBStyle(RGB(20, 144, 234), RGB(234, 78, 23))
|
||||
s.Println("message")
|
||||
s.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
Create a style from an hexadecimal color string:
|
||||
|
||||
- `color.HEXStyle(fg string, bg ...string) *RGBStyle`
|
||||
|
||||
```go
|
||||
s := color.HEXStyle("11aa23", "eee")
|
||||
s.Println("message")
|
||||
s.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
with options:
|
||||
|
||||
```go
|
||||
s := color.HEXStyle("11aa23", "eee")
|
||||
s.SetOpts(color.Opts{color.OpBold})
|
||||
|
||||
s.Println("style with options")
|
||||
s.Printf("style with %s\n", "options")
|
||||
```
|
||||
|
||||
## HTML-like tag usage
|
||||
|
||||
**Supported** on Windows `cmd.exe` `PowerShell` .
|
||||
|
||||
```go
|
||||
// use style tag
|
||||
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>")
|
||||
color.Println("<suc>hello</>")
|
||||
color.Println("<error>hello</>")
|
||||
color.Println("<warning>hello</>")
|
||||
|
||||
// custom color attributes
|
||||
color.Print("<fg=yellow;bg=black;op=underscore;>hello, welcome</>\n")
|
||||
|
||||
// Custom label attr: Supports the use of 16 color names, 256 color values, rgb color values and hex color values
|
||||
color.Println("<fg=11aa23>he</><bg=120,35,156>llo</>, <fg=167;bg=232>wel</><fg=red>come</>")
|
||||
```
|
||||
|
||||
- `color.Tag`
|
||||
|
||||
```go
|
||||
// set a style tag
|
||||
color.Tag("info").Print("info style text")
|
||||
color.Tag("info").Printf("%s style text", "info")
|
||||
color.Tag("info").Println("info style text")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/color_tag.go`
|
||||
|
||||
![color-tags](_examples/images/color-tags.png)
|
||||
|
||||
## Color convert
|
||||
|
||||
Supports conversion between Rgb, 256, 16 colors, `Rgb <=> 256 <=> 16`
|
||||
|
||||
```go
|
||||
basic := color.Red
|
||||
basic.Println("basic color")
|
||||
|
||||
c256 := color.Red.C256()
|
||||
c256.Println("256 color")
|
||||
c256.C16().Println("basic color")
|
||||
|
||||
rgb := color.Red.RGB()
|
||||
rgb.Println("rgb color")
|
||||
rgb.C256().Println("256 color")
|
||||
```
|
||||
|
||||
## Func refer
|
||||
|
||||
There are some useful functions reference
|
||||
|
||||
- `Disable()` disable color render
|
||||
- `SetOutput(io.Writer)` custom set the colored text output writer
|
||||
- `ForceOpenColor()` force open color render
|
||||
- `Colors2code(colors ...Color) string` Convert colors to code. return like "32;45;3"
|
||||
- `ClearCode(str string) string` Use for clear color codes
|
||||
- `ClearTag(s string) string` clear all color html-tag for a string
|
||||
- `IsConsole(w io.Writer)` Determine whether w is one of stderr, stdout, stdin
|
||||
- `HexToRgb(hex string) (rgb []int)` Convert hex color string to RGB numbers
|
||||
- `RgbToHex(rgb []int) string` Convert RGB to hex code
|
||||
- More useful func please see https://pkg.go.dev/github.com/gookit/color
|
||||
|
||||
## Project use
|
||||
|
||||
Check out these projects, which use https://github.com/gookit/color :
|
||||
|
||||
- https://github.com/Delta456/box-cli-maker Make Highly Customized Boxes for your CLI
|
||||
|
||||
## Gookit packages
|
||||
|
||||
- [gookit/ini](https://github.com/gookit/ini) Go config management, use INI files
|
||||
- [gookit/rux](https://github.com/gookit/rux) Simple and fast request router for golang HTTP
|
||||
- [gookit/gcli](https://github.com/gookit/gcli) build CLI application, tool library, running CLI commands
|
||||
- [gookit/slog](https://github.com/gookit/slog) Concise and extensible go log library
|
||||
- [gookit/event](https://github.com/gookit/event) Lightweight event manager and dispatcher implements by Go
|
||||
- [gookit/cache](https://github.com/gookit/cache) Generic cache use and cache manager for golang. support File, Memory, Redis, Memcached.
|
||||
- [gookit/config](https://github.com/gookit/config) Go config management. support JSON, YAML, TOML, INI, HCL, ENV and Flags
|
||||
- [gookit/color](https://github.com/gookit/color) A command-line color library with true color support, universal API methods and Windows support
|
||||
- [gookit/filter](https://github.com/gookit/filter) Provide filtering, sanitizing, and conversion of golang data
|
||||
- [gookit/validate](https://github.com/gookit/validate) Use for data validation and filtering. support Map, Struct, Form data
|
||||
- [gookit/goutil](https://github.com/gookit/goutil) Some utils for the Go: string, array/slice, map, format, cli, env, filesystem, test and more
|
||||
- More, please see https://github.com/gookit
|
||||
|
||||
## See also
|
||||
|
||||
- [inhere/console](https://github.com/inhere/php-console)
|
||||
- [xo/terminfo](https://github.com/xo/terminfo)
|
||||
- [beego/bee](https://github.com/beego/bee)
|
||||
- [issue9/term](https://github.com/issue9/term)
|
||||
- [ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code)
|
||||
- [Standard ANSI color map](https://conemu.github.io/en/AnsiEscapeCodes.html#Standard_ANSI_color_map)
|
||||
- [Terminal Colors](https://gist.github.com/XVilka/8346728)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](/LICENSE)
|
|
@ -0,0 +1,472 @@
|
|||
# CLI Color
|
||||
|
||||
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/color?style=flat-square)
|
||||
[![Actions Status](https://github.com/gookit/color/workflows/action-tests/badge.svg)](https://github.com/gookit/color/actions)
|
||||
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/51b28c5f7ffe4cc2b0f12ecf25ed247f)](https://app.codacy.com/app/inhere/color)
|
||||
[![GoDoc](https://godoc.org/github.com/gookit/color?status.svg)](https://pkg.go.dev/github.com/gookit/color?tab=overview)
|
||||
[![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/color)](https://github.com/gookit/color)
|
||||
[![Build Status](https://travis-ci.org/gookit/color.svg?branch=master)](https://travis-ci.org/gookit/color)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/gookit/color/badge.svg?branch=master)](https://coveralls.io/github/gookit/color?branch=master)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/gookit/color)](https://goreportcard.com/report/github.com/gookit/color)
|
||||
|
||||
Golang下的命令行色彩使用库, 拥有丰富的色彩渲染输出,通用的API方法,兼容Windows系统
|
||||
|
||||
> **[EN README](README.md)**
|
||||
|
||||
基本颜色预览:
|
||||
|
||||
![basic-color](_examples/images/basic-color2.png)
|
||||
|
||||
现在,256色和RGB色彩也已经支持windows CMD和PowerShell中工作:
|
||||
|
||||
![color-on-cmd-pwsh](_examples/images/color-on-cmd-pwsh.jpg)
|
||||
|
||||
## 功能特色
|
||||
|
||||
- 使用简单方便
|
||||
- 支持丰富的颜色输出, 16色(4bit),256色(8bit),RGB色彩(24bit, RGB)
|
||||
- 16色(4bit)是最常用和支持最广的,支持Windows `cmd.exe`
|
||||
- 自 `v1.2.4` 起 **256色(8bit),RGB色彩(24bit)均支持Windows CMD和PowerShell终端**
|
||||
- 请查看 [this gist](https://gist.github.com/XVilka/8346728) 了解支持RGB色彩的终端
|
||||
- 提供通用的API方法:`Print` `Printf` `Println` `Sprint` `Sprintf`
|
||||
- 同时支持html标签式的颜色渲染,除了使用内置标签,同时支持自定义颜色属性
|
||||
- 例如: `this an <green>message</>` 标签内部的文本将会渲染为绿色字体
|
||||
- 自定义颜色属性: 支持使用16色彩名称,256色彩值,rgb色彩值以及hex色彩值
|
||||
- 基础色彩: `Bold` `Black` `White` `Gray` `Red` `Green` `Yellow` `Blue` `Magenta` `Cyan`
|
||||
- 扩展风格: `Info` `Note` `Light` `Error` `Danger` `Notice` `Success` `Comment` `Primary` `Warning` `Question` `Secondary`
|
||||
- 支持通过设置环境变量 `NO_COLOR` 来禁用色彩,或者使用 `FORCE_COLOR` 来强制使用色彩渲染.
|
||||
- 支持 Rgb, 256, 16 色彩之间的互相转换
|
||||
- 支持Linux、Mac,同时兼容Windows系统环境
|
||||
|
||||
## GoDoc
|
||||
|
||||
- [godoc for gopkg](https://pkg.go.dev/gopkg.in/gookit/color.v1)
|
||||
- [godoc for github](https://pkg.go.dev/github.com/gookit/color)
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/color
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
如下,引入当前包就可以快速的使用
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 简单快速的使用,跟 fmt.Print* 类似
|
||||
color.Redp("Simple to use color")
|
||||
color.Redln("Simple to use color")
|
||||
color.Greenp("Simple to use color\n")
|
||||
color.Cyanln("Simple to use color")
|
||||
color.Yellowln("Simple to use color")
|
||||
|
||||
// 简单快速的使用,跟 fmt.Print* 类似
|
||||
color.Red.Println("Simple to use color")
|
||||
color.Green.Print("Simple to use color\n")
|
||||
color.Cyan.Printf("Simple to use %s\n", "color")
|
||||
color.Yellow.Printf("Simple to use %s\n", "color")
|
||||
|
||||
// use like func
|
||||
red := color.FgRed.Render
|
||||
green := color.FgGreen.Render
|
||||
fmt.Printf("%s line %s library\n", red("Command"), green("color"))
|
||||
|
||||
// 自定义颜色
|
||||
color.New(color.FgWhite, color.BgBlack).Println("custom color style")
|
||||
|
||||
// 也可以:
|
||||
color.Style{color.FgCyan, color.OpBold}.Println("custom color style")
|
||||
|
||||
// internal style:
|
||||
color.Info.Println("message")
|
||||
color.Warn.Println("message")
|
||||
color.Error.Println("message")
|
||||
|
||||
// 使用内置颜色标签
|
||||
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>\n")
|
||||
// 自定义标签: 支持使用16色彩名称,256色彩值,rgb色彩值以及hex色彩值
|
||||
color.Println("<fg=11aa23>he</><bg=120,35,156>llo</>, <fg=167;bg=232>wel</><fg=red>come</>")
|
||||
|
||||
// apply a style tag
|
||||
color.Tag("info").Println("info style text")
|
||||
|
||||
// prompt message
|
||||
color.Info.Prompt("prompt style message")
|
||||
color.Warn.Prompt("prompt style message")
|
||||
|
||||
// tips message
|
||||
color.Info.Tips("tips style message")
|
||||
color.Warn.Tips("tips style message")
|
||||
}
|
||||
```
|
||||
|
||||
> 运行 demo: `go run ./_examples/demo.go`
|
||||
|
||||
![colored-out](_examples/images/color-demo.jpg)
|
||||
|
||||
## 基础颜色(16-color)
|
||||
|
||||
提供通用的API方法:`Print` `Printf` `Println` `Sprint` `Sprintf`
|
||||
|
||||
> 支持在windows `cmd.exe` `powerShell` 等终端使用
|
||||
|
||||
```go
|
||||
color.Bold.Println("bold message")
|
||||
color.Black.Println("bold message")
|
||||
color.White.Println("bold message")
|
||||
color.Gray.Println("bold message")
|
||||
color.Red.Println("yellow message")
|
||||
color.Blue.Println("yellow message")
|
||||
color.Cyan.Println("yellow message")
|
||||
color.Yellow.Println("yellow message")
|
||||
color.Magenta.Println("yellow message")
|
||||
|
||||
// Only use foreground color
|
||||
color.FgCyan.Printf("Simple to use %s\n", "color")
|
||||
// Only use background color
|
||||
color.BgRed.Printf("Simple to use %s\n", "color")
|
||||
```
|
||||
|
||||
> 运行demo: `go run ./_examples/color_16.go`
|
||||
|
||||
![basic-color](_examples/images/basic-color.png)
|
||||
|
||||
### 构建风格
|
||||
|
||||
```go
|
||||
// 仅设置前景色
|
||||
color.FgCyan.Printf("Simple to use %s\n", "color")
|
||||
// 仅设置背景色
|
||||
color.BgRed.Printf("Simple to use %s\n", "color")
|
||||
|
||||
// 完全自定义: 前景色 背景色 选项
|
||||
style := color.New(color.FgWhite, color.BgBlack, color.OpBold)
|
||||
style.Println("custom color style")
|
||||
|
||||
// 也可以:
|
||||
color.Style{color.FgCyan, color.OpBold}.Println("custom color style")
|
||||
```
|
||||
|
||||
直接设置控制台属性:
|
||||
|
||||
```go
|
||||
// 设置console颜色
|
||||
color.Set(color.FgCyan)
|
||||
|
||||
// 输出信息
|
||||
fmt.Print("message")
|
||||
|
||||
// 重置console颜色
|
||||
color.Reset()
|
||||
```
|
||||
|
||||
> 当然,color已经内置丰富的色彩风格支持
|
||||
|
||||
### 扩展风格方法
|
||||
|
||||
提供通用的API方法:`Print` `Printf` `Println` `Sprint` `Sprintf`
|
||||
|
||||
> 支持在windows `cmd.exe` `powerShell` 等终端使用
|
||||
|
||||
基础使用:
|
||||
|
||||
```go
|
||||
// print message
|
||||
color.Info.Println("Info message")
|
||||
color.Note.Println("Note message")
|
||||
color.Notice.Println("Notice message")
|
||||
color.Error.Println("Error message")
|
||||
color.Danger.Println("Danger message")
|
||||
color.Warn.Println("Warn message")
|
||||
color.Debug.Println("Debug message")
|
||||
color.Primary.Println("Primary message")
|
||||
color.Question.Println("Question message")
|
||||
color.Secondary.Println("Secondary message")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/theme_basic.go`
|
||||
|
||||
![theme-basic](_examples/images/theme-basic.png)
|
||||
|
||||
**简约提示风格**
|
||||
|
||||
```go
|
||||
color.Info.Tips("Info tips message")
|
||||
color.Note.Tips("Note tips message")
|
||||
color.Notice.Tips("Notice tips message")
|
||||
color.Error.Tips("Error tips message")
|
||||
color.Danger.Tips("Danger tips message")
|
||||
color.Warn.Tips("Warn tips message")
|
||||
color.Debug.Tips("Debug tips message")
|
||||
color.Primary.Tips("Primary tips message")
|
||||
color.Question.Tips("Question tips message")
|
||||
color.Secondary.Tips("Secondary tips message")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/theme_tips.go`
|
||||
|
||||
![theme-tips](_examples/images/theme-tips.png)
|
||||
|
||||
**着重提示风格**
|
||||
|
||||
```go
|
||||
color.Info.Prompt("Info prompt message")
|
||||
color.Note.Prompt("Note prompt message")
|
||||
color.Notice.Prompt("Notice prompt message")
|
||||
color.Error.Prompt("Error prompt message")
|
||||
color.Danger.Prompt("Danger prompt message")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/theme_prompt.go`
|
||||
|
||||
![theme-prompt](_examples/images/theme-prompt.png)
|
||||
|
||||
**强调提示风格**
|
||||
|
||||
```go
|
||||
color.Warn.Block("Warn block message")
|
||||
color.Debug.Block("Debug block message")
|
||||
color.Primary.Block("Primary block message")
|
||||
color.Question.Block("Question block message")
|
||||
color.Secondary.Block("Secondary block message")
|
||||
```
|
||||
|
||||
Run demo: `go run ./_examples/theme_block.go`
|
||||
|
||||
![theme-block](_examples/images/theme-block.png)
|
||||
|
||||
## 256 色彩使用
|
||||
|
||||
> 256色彩在 `v1.2.4` 后支持Windows CMD,PowerShell 环境
|
||||
|
||||
### 使用前景或后景色
|
||||
|
||||
- `color.C256(val uint8, isBg ...bool) Color256`
|
||||
|
||||
```go
|
||||
c := color.C256(132) // fg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
|
||||
c := color.C256(132, true) // bg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
### 使用256 色彩风格
|
||||
|
||||
> 可同时设置前景和背景色
|
||||
|
||||
- `color.S256(fgAndBg ...uint8) *Style256`
|
||||
|
||||
```go
|
||||
s := color.S256(32, 203)
|
||||
s.Println("message")
|
||||
s.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
可以同时添加选项设置:
|
||||
|
||||
```go
|
||||
s := color.S256(32, 203)
|
||||
s.SetOpts(color.Opts{color.OpBold})
|
||||
|
||||
s.Println("style with options")
|
||||
s.Printf("style with %s\n", "options")
|
||||
```
|
||||
|
||||
> 运行 demo: `go run ./_examples/color_256.go`
|
||||
|
||||
![color-tags](_examples/images/color-256.png)
|
||||
|
||||
## RGB/True色彩使用
|
||||
|
||||
> RGB色彩在 `v1.2.4` 后支持 Windows `CMD`, `PowerShell` 环境
|
||||
|
||||
**效果预览:**
|
||||
|
||||
> 运行 demo: `Run demo: go run ./_examples/color_rgb.go`
|
||||
|
||||
![color-rgb](_examples/images/color-rgb.png)
|
||||
|
||||
代码示例:
|
||||
|
||||
```go
|
||||
color.RGB(30, 144, 255).Println("message. use RGB number")
|
||||
|
||||
color.HEX("#1976D2").Println("blue-darken")
|
||||
color.HEX("#D50000", true).Println("red-accent. use HEX style")
|
||||
|
||||
color.RGBStyleFromString("213,0,0").Println("red-accent. use RGB number")
|
||||
color.HEXStyle("eee", "D50000").Println("deep-purple color")
|
||||
```
|
||||
|
||||
### 使用前景或后景色
|
||||
|
||||
- `color.RGB(r, g, b uint8, isBg ...bool) RGBColor`
|
||||
|
||||
```go
|
||||
c := color.RGB(30,144,255) // fg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
|
||||
c := color.RGB(30,144,255, true) // bg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
- `color.HEX(hex string, isBg ...bool) RGBColor` 从16进制颜色创建
|
||||
|
||||
```go
|
||||
c := color.HEX("ccc") // 也可以写为: "cccccc" "#cccccc"
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
|
||||
c = color.HEX("aabbcc", true) // as bg color
|
||||
c.Println("message")
|
||||
c.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
### 使用RGB风格
|
||||
|
||||
> 可同时设置前景和背景色
|
||||
|
||||
- `color.NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle`
|
||||
|
||||
```go
|
||||
s := color.NewRGBStyle(RGB(20, 144, 234), RGB(234, 78, 23))
|
||||
s.Println("message")
|
||||
s.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
- `color.HEXStyle(fg string, bg ...string) *RGBStyle` 从16进制颜色创建
|
||||
|
||||
```go
|
||||
s := color.HEXStyle("11aa23", "eee")
|
||||
s.Println("message")
|
||||
s.Printf("format %s", "message")
|
||||
```
|
||||
|
||||
- 可以同时添加选项设置:
|
||||
|
||||
```go
|
||||
s := color.HEXStyle("11aa23", "eee")
|
||||
s.SetOpts(color.Opts{color.OpBold})
|
||||
|
||||
s.Println("style with options")
|
||||
s.Printf("style with %s\n", "options")
|
||||
```
|
||||
|
||||
## 使用颜色标签
|
||||
|
||||
> **支持** 在windows `cmd.exe` `PowerShell` 使用
|
||||
|
||||
使用内置的颜色标签,可以非常方便简单的构建自己需要的任何格式
|
||||
|
||||
> 同时支持自定义颜色属性: 支持使用16色彩名称,256色彩值,rgb色彩值以及hex色彩值
|
||||
|
||||
```go
|
||||
// 使用内置的 color tag
|
||||
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>")
|
||||
color.Println("<suc>hello</>")
|
||||
color.Println("<error>hello</>")
|
||||
color.Println("<warning>hello</>")
|
||||
|
||||
// 自定义颜色属性
|
||||
color.Print("<fg=yellow;bg=black;op=underscore;>hello, welcome</>\n")
|
||||
|
||||
// 自定义颜色属性: 支持使用16色彩名称,256色彩值,rgb色彩值以及hex色彩值
|
||||
color.Println("<fg=11aa23>he</><bg=120,35,156>llo</>, <fg=167;bg=232>wel</><fg=red>come</>")
|
||||
```
|
||||
|
||||
- 使用 `color.Tag`
|
||||
|
||||
给后面输出的文本信息加上给定的颜色风格标签
|
||||
|
||||
```go
|
||||
// set a style tag
|
||||
color.Tag("info").Print("info style text")
|
||||
color.Tag("info").Printf("%s style text", "info")
|
||||
color.Tag("info").Println("info style text")
|
||||
```
|
||||
|
||||
> 运行 demo: `go run ./_examples/color_tag.go`
|
||||
|
||||
![color-tags](_examples/images/color-tags.png)
|
||||
|
||||
## 颜色转换
|
||||
|
||||
支持 Rgb, 256, 16 色彩之间的互相转换 `Rgb <=> 256 <=> 16`
|
||||
|
||||
```go
|
||||
basic := color.Red
|
||||
basic.Println("basic color")
|
||||
|
||||
c256 := color.Red.C256()
|
||||
c256.Println("256 color")
|
||||
c256.C16().Println("basic color")
|
||||
|
||||
rgb := color.Red.RGB()
|
||||
rgb.Println("rgb color")
|
||||
rgb.C256().Println("256 color")
|
||||
```
|
||||
|
||||
## 方法参考
|
||||
|
||||
一些有用的工具方法参考
|
||||
|
||||
- `Disable()` disable color render
|
||||
- `SetOutput(io.Writer)` custom set the colored text output writer
|
||||
- `ForceOpenColor()` force open color render
|
||||
- `ClearCode(str string) string` Use for clear color codes
|
||||
- `Colors2code(colors ...Color) string` Convert colors to code. return like "32;45;3"
|
||||
- `ClearTag(s string) string` clear all color html-tag for a string
|
||||
- `IsConsole(w io.Writer)` Determine whether w is one of stderr, stdout, stdin
|
||||
- `HexToRgb(hex string) (rgb []int)` Convert hex color string to RGB numbers
|
||||
- `RgbToHex(rgb []int) string` Convert RGB to hex code
|
||||
- 更多请查看文档 https://pkg.go.dev/github.com/gookit/color
|
||||
|
||||
## 使用color的项目
|
||||
|
||||
看看这些使用了 https://github.com/gookit/color 的项目:
|
||||
|
||||
- https://github.com/Delta456/box-cli-maker Make Highly Customized Boxes for your CLI
|
||||
|
||||
## Gookit 工具包
|
||||
|
||||
- [gookit/ini](https://github.com/gookit/ini) INI配置读取管理,支持多文件加载,数据覆盖合并, 解析ENV变量, 解析变量引用
|
||||
- [gookit/rux](https://github.com/gookit/rux) Simple and fast request router for golang HTTP
|
||||
- [gookit/gcli](https://github.com/gookit/gcli) Go的命令行应用,工具库,运行CLI命令,支持命令行色彩,用户交互,进度显示,数据格式化显示
|
||||
- [gookit/slog](https://github.com/gookit/slog) 简洁易扩展的go日志库
|
||||
- [gookit/event](https://github.com/gookit/event) Go实现的轻量级的事件管理、调度程序库, 支持设置监听器的优先级, 支持对一组事件进行监听
|
||||
- [gookit/cache](https://github.com/gookit/cache) 通用的缓存使用包装库,通过包装各种常用的驱动,来提供统一的使用API
|
||||
- [gookit/config](https://github.com/gookit/config) Go应用配置管理,支持多种格式(JSON, YAML, TOML, INI, HCL, ENV, Flags),多文件加载,远程文件加载,数据合并
|
||||
- [gookit/color](https://github.com/gookit/color) CLI 控制台颜色渲染工具库, 拥有简洁的使用API,支持16色,256色,RGB色彩渲染输出
|
||||
- [gookit/filter](https://github.com/gookit/filter) 提供对Golang数据的过滤,净化,转换
|
||||
- [gookit/validate](https://github.com/gookit/validate) Go通用的数据验证与过滤库,使用简单,内置大部分常用验证、过滤器
|
||||
- [gookit/goutil](https://github.com/gookit/goutil) Go 的一些工具函数,格式化,特殊处理,常用信息获取等
|
||||
- 更多请查看 https://github.com/gookit
|
||||
|
||||
## 参考项目
|
||||
|
||||
- [inhere/console](https://github.com/inhere/php-console)
|
||||
- [xo/terminfo](https://github.com/xo/terminfo)
|
||||
- [beego/bee](https://github.com/beego/bee)
|
||||
- [issue9/term](https://github.com/issue9/term)
|
||||
- [ANSI转义序列](https://zh.wikipedia.org/wiki/ANSI转义序列)
|
||||
- [Standard ANSI color map](https://conemu.github.io/en/AnsiEscapeCodes.html#Standard_ANSI_color_map)
|
||||
- [Terminal Colors](https://gist.github.com/XVilka/8346728)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
Package color is Command line color library.
|
||||
Support rich color rendering output, universal API method, compatible with Windows system
|
||||
|
||||
Source code and other details for the project are available at GitHub:
|
||||
|
||||
https://github.com/gookit/color
|
||||
|
||||
More usage please see README and tests.
|
||||
*/
|
||||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/xo/terminfo"
|
||||
)
|
||||
|
||||
// terminal color available level alias of the terminfo.ColorLevel*
|
||||
const (
|
||||
LevelNo = terminfo.ColorLevelNone // not support color.
|
||||
Level16 = terminfo.ColorLevelBasic // 3/4 bit color supported
|
||||
Level256 = terminfo.ColorLevelHundreds // 8 bit color supported
|
||||
LevelRgb = terminfo.ColorLevelMillions // (24 bit)true color supported
|
||||
)
|
||||
|
||||
// color render templates
|
||||
// ESC 操作的表示:
|
||||
// "\033"(Octal 8进制) = "\x1b"(Hexadecimal 16进制) = 27 (10进制)
|
||||
const (
|
||||
SettingTpl = "\x1b[%sm"
|
||||
FullColorTpl = "\x1b[%sm%s\x1b[0m"
|
||||
)
|
||||
|
||||
// ResetSet Close all properties.
|
||||
const ResetSet = "\x1b[0m"
|
||||
|
||||
// CodeExpr regex to clear color codes eg "\033[1;36mText\x1b[0m"
|
||||
const CodeExpr = `\033\[[\d;?]+m`
|
||||
|
||||
var (
|
||||
// Enable switch color render and display
|
||||
//
|
||||
// NOTICE:
|
||||
// if ENV: NO_COLOR is not empty, will disable color render.
|
||||
Enable = os.Getenv("NO_COLOR") == ""
|
||||
// RenderTag render HTML tag on call color.Xprint, color.PrintX
|
||||
RenderTag = true
|
||||
// debug mode for development.
|
||||
//
|
||||
// set env:
|
||||
// COLOR_DEBUG_MODE=on
|
||||
// or:
|
||||
// COLOR_DEBUG_MODE=on go run ./_examples/envcheck.go
|
||||
debugMode = os.Getenv("COLOR_DEBUG_MODE") == "on"
|
||||
// inner errors record on detect color level
|
||||
innerErrs []error
|
||||
// output the default io.Writer message print
|
||||
output io.Writer = os.Stdout
|
||||
// mark current env, It's like in `cmd.exe`
|
||||
// if not in windows, it's always is False.
|
||||
isLikeInCmd bool
|
||||
// the color support level for current terminal
|
||||
// needVTP - need enable VTP, only for windows OS
|
||||
colorLevel, needVTP = detectTermColorLevel()
|
||||
// match color codes
|
||||
codeRegex = regexp.MustCompile(CodeExpr)
|
||||
// mark current env is support color.
|
||||
// Always: isLikeInCmd != supportColor
|
||||
// supportColor = IsSupportColor()
|
||||
)
|
||||
|
||||
// TermColorLevel value on current ENV
|
||||
func TermColorLevel() terminfo.ColorLevel {
|
||||
return colorLevel
|
||||
}
|
||||
|
||||
// SupportColor on the current ENV
|
||||
func SupportColor() bool {
|
||||
return colorLevel > terminfo.ColorLevelNone
|
||||
}
|
||||
|
||||
// Support16Color on the current ENV
|
||||
// func Support16Color() bool {
|
||||
// return colorLevel > terminfo.ColorLevelNone
|
||||
// }
|
||||
|
||||
// Support256Color on the current ENV
|
||||
func Support256Color() bool {
|
||||
return colorLevel > terminfo.ColorLevelBasic
|
||||
}
|
||||
|
||||
// SupportTrueColor on the current ENV
|
||||
func SupportTrueColor() bool {
|
||||
return colorLevel > terminfo.ColorLevelHundreds
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* global settings
|
||||
*************************************************************/
|
||||
|
||||
// Set set console color attributes
|
||||
func Set(colors ...Color) (int, error) {
|
||||
code := Colors2code(colors...)
|
||||
err := SetTerminal(code)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Reset reset console color attributes
|
||||
func Reset() (int, error) {
|
||||
err := ResetTerminal()
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Disable disable color output
|
||||
func Disable() bool {
|
||||
oldVal := Enable
|
||||
Enable = false
|
||||
return oldVal
|
||||
}
|
||||
|
||||
// NotRenderTag on call color.Xprint, color.PrintX
|
||||
func NotRenderTag() {
|
||||
RenderTag = false
|
||||
}
|
||||
|
||||
// SetOutput set default colored text output
|
||||
func SetOutput(w io.Writer) {
|
||||
output = w
|
||||
}
|
||||
|
||||
// ResetOutput reset output
|
||||
func ResetOutput() {
|
||||
output = os.Stdout
|
||||
}
|
||||
|
||||
// ResetOptions reset all package option setting
|
||||
func ResetOptions() {
|
||||
RenderTag = true
|
||||
Enable = true
|
||||
output = os.Stdout
|
||||
}
|
||||
|
||||
// ForceColor force open color render
|
||||
func ForceSetColorLevel(level terminfo.ColorLevel) terminfo.ColorLevel {
|
||||
oldLevelVal := colorLevel
|
||||
colorLevel = level
|
||||
return oldLevelVal
|
||||
}
|
||||
|
||||
// ForceColor force open color render
|
||||
func ForceColor() terminfo.ColorLevel {
|
||||
return ForceOpenColor()
|
||||
}
|
||||
|
||||
// ForceOpenColor force open color render
|
||||
func ForceOpenColor() terminfo.ColorLevel {
|
||||
// TODO should set level to ?
|
||||
return ForceSetColorLevel(terminfo.ColorLevelMillions)
|
||||
}
|
||||
|
||||
// IsLikeInCmd check result
|
||||
// Deprecated
|
||||
func IsLikeInCmd() bool {
|
||||
return isLikeInCmd
|
||||
}
|
||||
|
||||
// InnerErrs info
|
||||
func InnerErrs() []error {
|
||||
return innerErrs
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* render color code
|
||||
*************************************************************/
|
||||
|
||||
// RenderCode render message by color code.
|
||||
// Usage:
|
||||
// msg := RenderCode("3;32;45", "some", "message")
|
||||
func RenderCode(code string, args ...interface{}) string {
|
||||
var message string
|
||||
if ln := len(args); ln == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
message = fmt.Sprint(args...)
|
||||
if len(code) == 0 {
|
||||
return message
|
||||
}
|
||||
|
||||
// disabled OR not support color
|
||||
if !Enable || !SupportColor() {
|
||||
return ClearCode(message)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(FullColorTpl, code, message)
|
||||
}
|
||||
|
||||
// RenderWithSpaces Render code with spaces.
|
||||
// If the number of args is > 1, a space will be added between the args
|
||||
func RenderWithSpaces(code string, args ...interface{}) string {
|
||||
message := formatArgsForPrintln(args)
|
||||
if len(code) == 0 {
|
||||
return message
|
||||
}
|
||||
|
||||
// disabled OR not support color
|
||||
if !Enable || !SupportColor() {
|
||||
return ClearCode(message)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(FullColorTpl, code, message)
|
||||
}
|
||||
|
||||
// RenderString render a string with color code.
|
||||
// Usage:
|
||||
// msg := RenderString("3;32;45", "a message")
|
||||
func RenderString(code string, str string) string {
|
||||
if len(code) == 0 || str == "" {
|
||||
return str
|
||||
}
|
||||
|
||||
// disabled OR not support color
|
||||
if !Enable || !SupportColor() {
|
||||
return ClearCode(str)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(FullColorTpl, code, str)
|
||||
}
|
||||
|
||||
// ClearCode clear color codes.
|
||||
// eg: "\033[36;1mText\x1b[0m" -> "Text"
|
||||
func ClearCode(str string) string {
|
||||
return codeRegex.ReplaceAllString(str, "")
|
||||
}
|
|
@ -0,0 +1,440 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Color Color16, 16 color value type
|
||||
// 3(2^3=8) OR 4(2^4=16) bite color.
|
||||
type Color uint8
|
||||
type Basic = Color // alias of Color
|
||||
|
||||
// Opts basic color options. code: 0 - 9
|
||||
type Opts []Color
|
||||
|
||||
// Add option value
|
||||
func (o *Opts) Add(ops ...Color) {
|
||||
for _, op := range ops {
|
||||
if uint8(op) < 10 {
|
||||
*o = append(*o, op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid options
|
||||
func (o Opts) IsValid() bool {
|
||||
return len(o) > 0
|
||||
}
|
||||
|
||||
// IsEmpty options
|
||||
func (o Opts) IsEmpty() bool {
|
||||
return len(o) == 0
|
||||
}
|
||||
|
||||
// String options to string. eg: "1;3"
|
||||
func (o Opts) String() string {
|
||||
return Colors2code(o...)
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Basic 16 color definition
|
||||
*************************************************************/
|
||||
|
||||
// Base value for foreground/background color
|
||||
const (
|
||||
FgBase uint8 = 30
|
||||
BgBase uint8 = 40
|
||||
// hi color base code
|
||||
HiFgBase uint8 = 90
|
||||
HiBgBase uint8 = 100
|
||||
)
|
||||
|
||||
// Foreground colors. basic foreground colors 30 - 37
|
||||
const (
|
||||
FgBlack Color = iota + 30
|
||||
FgRed
|
||||
FgGreen
|
||||
FgYellow
|
||||
FgBlue
|
||||
FgMagenta // 品红
|
||||
FgCyan // 青色
|
||||
FgWhite
|
||||
// FgDefault revert default FG
|
||||
FgDefault Color = 39
|
||||
)
|
||||
|
||||
// Extra foreground color 90 - 97(非标准)
|
||||
const (
|
||||
FgDarkGray Color = iota + 90 // 亮黑(灰)
|
||||
FgLightRed
|
||||
FgLightGreen
|
||||
FgLightYellow
|
||||
FgLightBlue
|
||||
FgLightMagenta
|
||||
FgLightCyan
|
||||
FgLightWhite
|
||||
// FgGray is alias of FgDarkGray
|
||||
FgGray Color = 90 // 亮黑(灰)
|
||||
)
|
||||
|
||||
// Background colors. basic background colors 40 - 47
|
||||
const (
|
||||
BgBlack Color = iota + 40
|
||||
BgRed
|
||||
BgGreen
|
||||
BgYellow // BgBrown like yellow
|
||||
BgBlue
|
||||
BgMagenta
|
||||
BgCyan
|
||||
BgWhite
|
||||
// BgDefault revert default BG
|
||||
BgDefault Color = 49
|
||||
)
|
||||
|
||||
// Extra background color 100 - 107(非标准)
|
||||
const (
|
||||
BgDarkGray Color = iota + 100
|
||||
BgLightRed
|
||||
BgLightGreen
|
||||
BgLightYellow
|
||||
BgLightBlue
|
||||
BgLightMagenta
|
||||
BgLightCyan
|
||||
BgLightWhite
|
||||
// BgGray is alias of BgDarkGray
|
||||
BgGray Color = 100
|
||||
)
|
||||
|
||||
// Option settings
|
||||
const (
|
||||
OpReset Color = iota // 0 重置所有设置
|
||||
OpBold // 1 加粗
|
||||
OpFuzzy // 2 模糊(不是所有的终端仿真器都支持)
|
||||
OpItalic // 3 斜体(不是所有的终端仿真器都支持)
|
||||
OpUnderscore // 4 下划线
|
||||
OpBlink // 5 闪烁
|
||||
OpFastBlink // 5 快速闪烁(未广泛支持)
|
||||
OpReverse // 7 颠倒的 交换背景色与前景色
|
||||
OpConcealed // 8 隐匿的
|
||||
OpStrikethrough // 9 删除的,删除线(未广泛支持)
|
||||
)
|
||||
|
||||
// There are basic and light foreground color aliases
|
||||
const (
|
||||
Red = FgRed
|
||||
Cyan = FgCyan
|
||||
Gray = FgDarkGray // is light Black
|
||||
Blue = FgBlue
|
||||
Black = FgBlack
|
||||
Green = FgGreen
|
||||
White = FgWhite
|
||||
Yellow = FgYellow
|
||||
Magenta = FgMagenta
|
||||
|
||||
// special
|
||||
|
||||
Bold = OpBold
|
||||
Normal = FgDefault
|
||||
|
||||
// extra light
|
||||
|
||||
LightRed = FgLightRed
|
||||
LightCyan = FgLightCyan
|
||||
LightBlue = FgLightBlue
|
||||
LightGreen = FgLightGreen
|
||||
LightWhite = FgLightWhite
|
||||
LightYellow = FgLightYellow
|
||||
LightMagenta = FgLightMagenta
|
||||
|
||||
HiRed = FgLightRed
|
||||
HiCyan = FgLightCyan
|
||||
HiBlue = FgLightBlue
|
||||
HiGreen = FgLightGreen
|
||||
HiWhite = FgLightWhite
|
||||
HiYellow = FgLightYellow
|
||||
HiMagenta = FgLightMagenta
|
||||
|
||||
BgHiRed = BgLightRed
|
||||
BgHiCyan = BgLightCyan
|
||||
BgHiBlue = BgLightBlue
|
||||
BgHiGreen = BgLightGreen
|
||||
BgHiWhite = BgLightWhite
|
||||
BgHiYellow = BgLightYellow
|
||||
BgHiMagenta = BgLightMagenta
|
||||
)
|
||||
|
||||
// Bit4 an method for create Color
|
||||
func Bit4(code uint8) Color {
|
||||
return Color(code)
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Color render methods
|
||||
*************************************************************/
|
||||
|
||||
// Name get color code name.
|
||||
func (c Color) Name() string {
|
||||
name, ok := basic2nameMap[uint8(c)]
|
||||
if ok {
|
||||
return name
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// Text render a text message
|
||||
func (c Color) Text(message string) string {
|
||||
return RenderString(c.String(), message)
|
||||
}
|
||||
|
||||
// Render messages by color setting
|
||||
// Usage:
|
||||
// green := color.FgGreen.Render
|
||||
// fmt.Println(green("message"))
|
||||
func (c Color) Render(a ...interface{}) string {
|
||||
return RenderCode(c.String(), a...)
|
||||
}
|
||||
|
||||
// Renderln messages by color setting.
|
||||
// like Println, will add spaces for each argument
|
||||
// Usage:
|
||||
// green := color.FgGreen.Renderln
|
||||
// fmt.Println(green("message"))
|
||||
func (c Color) Renderln(a ...interface{}) string {
|
||||
return RenderWithSpaces(c.String(), a...)
|
||||
}
|
||||
|
||||
// Sprint render messages by color setting. is alias of the Render()
|
||||
func (c Color) Sprint(a ...interface{}) string {
|
||||
return RenderCode(c.String(), a...)
|
||||
}
|
||||
|
||||
// Sprintf format and render message.
|
||||
// Usage:
|
||||
// green := color.Green.Sprintf
|
||||
// colored := green("message")
|
||||
func (c Color) Sprintf(format string, args ...interface{}) string {
|
||||
return RenderString(c.String(), fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Print messages.
|
||||
// Usage:
|
||||
// color.Green.Print("message")
|
||||
// OR:
|
||||
// green := color.FgGreen.Print
|
||||
// green("message")
|
||||
func (c Color) Print(args ...interface{}) {
|
||||
doPrintV2(c.Code(), fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Printf format and print messages.
|
||||
// Usage:
|
||||
// color.Cyan.Printf("string %s", "arg0")
|
||||
func (c Color) Printf(format string, a ...interface{}) {
|
||||
doPrintV2(c.Code(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Println messages with new line
|
||||
func (c Color) Println(a ...interface{}) {
|
||||
doPrintlnV2(c.String(), a)
|
||||
}
|
||||
|
||||
// Light current color. eg: 36(FgCyan) -> 96(FgLightCyan).
|
||||
// Usage:
|
||||
// lightCyan := Cyan.Light()
|
||||
// lightCyan.Print("message")
|
||||
func (c Color) Light() Color {
|
||||
val := int(c)
|
||||
if val >= 30 && val <= 47 {
|
||||
return Color(uint8(c) + 60)
|
||||
}
|
||||
|
||||
// don't change
|
||||
return c
|
||||
}
|
||||
|
||||
// Darken current color. eg. 96(FgLightCyan) -> 36(FgCyan)
|
||||
// Usage:
|
||||
// cyan := LightCyan.Darken()
|
||||
// cyan.Print("message")
|
||||
func (c Color) Darken() Color {
|
||||
val := int(c)
|
||||
if val >= 90 && val <= 107 {
|
||||
return Color(uint8(c) - 60)
|
||||
}
|
||||
|
||||
// don't change
|
||||
return c
|
||||
}
|
||||
|
||||
// C256 convert 16 color to 256-color code.
|
||||
func (c Color) C256() Color256 {
|
||||
val := uint8(c)
|
||||
if val < 10 { // is option code
|
||||
return emptyC256 // empty
|
||||
}
|
||||
|
||||
var isBg uint8
|
||||
if val >= BgBase && val <= 47 { // is bg
|
||||
isBg = AsBg
|
||||
val = val - 10 // to fg code
|
||||
} else if val >= HiBgBase && val <= 107 { // is hi bg
|
||||
isBg = AsBg
|
||||
val = val - 10 // to fg code
|
||||
}
|
||||
|
||||
if c256, ok := basicTo256Map[val]; ok {
|
||||
return Color256{c256, isBg}
|
||||
}
|
||||
|
||||
// use raw value direct convert
|
||||
return Color256{val}
|
||||
}
|
||||
|
||||
// RGB convert 16 color to 256-color code.
|
||||
func (c Color) RGB() RGBColor {
|
||||
val := uint8(c)
|
||||
if val < 10 { // is option code
|
||||
return emptyRGBColor
|
||||
}
|
||||
|
||||
return HEX(Basic2hex(val))
|
||||
}
|
||||
|
||||
// Code convert to code string. eg "35"
|
||||
func (c Color) Code() string {
|
||||
// return fmt.Sprintf("%d", c)
|
||||
return strconv.Itoa(int(c))
|
||||
}
|
||||
|
||||
// String convert to code string. eg "35"
|
||||
func (c Color) String() string {
|
||||
// return fmt.Sprintf("%d", c)
|
||||
return strconv.Itoa(int(c))
|
||||
}
|
||||
|
||||
// IsValid color value
|
||||
func (c Color) IsValid() bool {
|
||||
return c < 107
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* basic color maps
|
||||
*************************************************************/
|
||||
|
||||
// FgColors foreground colors map
|
||||
var FgColors = map[string]Color{
|
||||
"black": FgBlack,
|
||||
"red": FgRed,
|
||||
"green": FgGreen,
|
||||
"yellow": FgYellow,
|
||||
"blue": FgBlue,
|
||||
"magenta": FgMagenta,
|
||||
"cyan": FgCyan,
|
||||
"white": FgWhite,
|
||||
"default": FgDefault,
|
||||
}
|
||||
|
||||
// BgColors background colors map
|
||||
var BgColors = map[string]Color{
|
||||
"black": BgBlack,
|
||||
"red": BgRed,
|
||||
"green": BgGreen,
|
||||
"yellow": BgYellow,
|
||||
"blue": BgBlue,
|
||||
"magenta": BgMagenta,
|
||||
"cyan": BgCyan,
|
||||
"white": BgWhite,
|
||||
"default": BgDefault,
|
||||
}
|
||||
|
||||
// ExFgColors extra foreground colors map
|
||||
var ExFgColors = map[string]Color{
|
||||
"darkGray": FgDarkGray,
|
||||
"lightRed": FgLightRed,
|
||||
"lightGreen": FgLightGreen,
|
||||
"lightYellow": FgLightYellow,
|
||||
"lightBlue": FgLightBlue,
|
||||
"lightMagenta": FgLightMagenta,
|
||||
"lightCyan": FgLightCyan,
|
||||
"lightWhite": FgLightWhite,
|
||||
}
|
||||
|
||||
// ExBgColors extra background colors map
|
||||
var ExBgColors = map[string]Color{
|
||||
"darkGray": BgDarkGray,
|
||||
"lightRed": BgLightRed,
|
||||
"lightGreen": BgLightGreen,
|
||||
"lightYellow": BgLightYellow,
|
||||
"lightBlue": BgLightBlue,
|
||||
"lightMagenta": BgLightMagenta,
|
||||
"lightCyan": BgLightCyan,
|
||||
"lightWhite": BgLightWhite,
|
||||
}
|
||||
|
||||
// Options color options map
|
||||
// Deprecated
|
||||
// NOTICE: please use AllOptions instead.
|
||||
var Options = AllOptions
|
||||
|
||||
// AllOptions color options map
|
||||
var AllOptions = map[string]Color{
|
||||
"reset": OpReset,
|
||||
"bold": OpBold,
|
||||
"fuzzy": OpFuzzy,
|
||||
"italic": OpItalic,
|
||||
"underscore": OpUnderscore,
|
||||
"blink": OpBlink,
|
||||
"reverse": OpReverse,
|
||||
"concealed": OpConcealed,
|
||||
}
|
||||
|
||||
var (
|
||||
// TODO basic name alias
|
||||
// basicNameAlias = map[string]string{}
|
||||
|
||||
// basic color name to code
|
||||
name2basicMap = initName2basicMap()
|
||||
// basic2nameMap basic color code to name
|
||||
basic2nameMap = map[uint8]string{
|
||||
30: "black",
|
||||
31: "red",
|
||||
32: "green",
|
||||
33: "yellow",
|
||||
34: "blue",
|
||||
35: "magenta",
|
||||
36: "cyan",
|
||||
37: "white",
|
||||
// hi color code
|
||||
90: "lightBlack",
|
||||
91: "lightRed",
|
||||
92: "lightGreen",
|
||||
93: "lightYellow",
|
||||
94: "lightBlue",
|
||||
95: "lightMagenta",
|
||||
96: "lightCyan",
|
||||
97: "lightWhite",
|
||||
// options
|
||||
0: "reset",
|
||||
1: "bold",
|
||||
2: "fuzzy",
|
||||
3: "italic",
|
||||
4: "underscore",
|
||||
5: "blink",
|
||||
7: "reverse",
|
||||
8: "concealed",
|
||||
}
|
||||
)
|
||||
|
||||
// Basic2nameMap data
|
||||
func Basic2nameMap() map[uint8]string {
|
||||
return basic2nameMap
|
||||
}
|
||||
|
||||
func initName2basicMap() map[string]uint8 {
|
||||
n2b := make(map[string]uint8, len(basic2nameMap))
|
||||
for u, s := range basic2nameMap {
|
||||
n2b[s] = u
|
||||
}
|
||||
return n2b
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
from wikipedia, 256 color:
|
||||
ESC[ … 38;5;<n> … m选择前景色
|
||||
ESC[ … 48;5;<n> … m选择背景色
|
||||
0- 7:标准颜色(同 ESC[30–37m)
|
||||
8- 15:高强度颜色(同 ESC[90–97m)
|
||||
16-231:6 × 6 × 6 立方(216色): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
|
||||
232-255:从黑到白的24阶灰度色
|
||||
*/
|
||||
|
||||
// tpl for 8 bit 256 color(`2^8`)
|
||||
//
|
||||
// format:
|
||||
// ESC[ … 38;5;<n> … m // 选择前景色
|
||||
// ESC[ … 48;5;<n> … m // 选择背景色
|
||||
//
|
||||
// example:
|
||||
// fg "\x1b[38;5;242m"
|
||||
// bg "\x1b[48;5;208m"
|
||||
// both "\x1b[38;5;242;48;5;208m"
|
||||
//
|
||||
// links:
|
||||
// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#8位
|
||||
const (
|
||||
TplFg256 = "38;5;%d"
|
||||
TplBg256 = "48;5;%d"
|
||||
Fg256Pfx = "38;5;"
|
||||
Bg256Pfx = "48;5;"
|
||||
)
|
||||
|
||||
/*************************************************************
|
||||
* 8bit(256) Color: Bit8Color Color256
|
||||
*************************************************************/
|
||||
|
||||
// Color256 256 color (8 bit), uint8 range at 0 - 255
|
||||
//
|
||||
// 颜色值使用10进制和16进制都可 0x98 = 152
|
||||
//
|
||||
// The color consists of two uint8:
|
||||
// 0: color value
|
||||
// 1: color type; Fg=0, Bg=1, >1: unset value
|
||||
//
|
||||
// example:
|
||||
// fg color: [152, 0]
|
||||
// bg color: [152, 1]
|
||||
//
|
||||
// NOTICE: now support 256 color on windows CMD, PowerShell
|
||||
// lint warn - Name starts with package name
|
||||
type Color256 [2]uint8
|
||||
type Bit8Color = Color256 // alias
|
||||
|
||||
var emptyC256 = Color256{1: 99}
|
||||
|
||||
// Bit8 create a color256
|
||||
func Bit8(val uint8, isBg ...bool) Color256 {
|
||||
return C256(val, isBg...)
|
||||
}
|
||||
|
||||
// C256 create a color256
|
||||
func C256(val uint8, isBg ...bool) Color256 {
|
||||
bc := Color256{val}
|
||||
|
||||
// mark is bg color
|
||||
if len(isBg) > 0 && isBg[0] {
|
||||
bc[1] = AsBg
|
||||
}
|
||||
|
||||
return bc
|
||||
}
|
||||
|
||||
// Set terminal by 256 color code
|
||||
func (c Color256) Set() error {
|
||||
return SetTerminal(c.String())
|
||||
}
|
||||
|
||||
// Reset terminal. alias of the ResetTerminal()
|
||||
func (c Color256) Reset() error {
|
||||
return ResetTerminal()
|
||||
}
|
||||
|
||||
// Print print message
|
||||
func (c Color256) Print(a ...interface{}) {
|
||||
doPrintV2(c.String(), fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Printf format and print message
|
||||
func (c Color256) Printf(format string, a ...interface{}) {
|
||||
doPrintV2(c.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Println print message with newline
|
||||
func (c Color256) Println(a ...interface{}) {
|
||||
doPrintlnV2(c.String(), a)
|
||||
}
|
||||
|
||||
// Sprint returns rendered message
|
||||
func (c Color256) Sprint(a ...interface{}) string {
|
||||
return RenderCode(c.String(), a...)
|
||||
}
|
||||
|
||||
// Sprintf returns format and rendered message
|
||||
func (c Color256) Sprintf(format string, a ...interface{}) string {
|
||||
return RenderString(c.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// C16 convert color-256 to 16 color.
|
||||
func (c Color256) C16() Color {
|
||||
return c.Basic()
|
||||
}
|
||||
|
||||
// Basic convert color-256 to basic 16 color.
|
||||
func (c Color256) Basic() Color {
|
||||
return Color(c[0]) // TODO
|
||||
}
|
||||
|
||||
// RGB convert color-256 to RGB color.
|
||||
func (c Color256) RGB() RGBColor {
|
||||
return RGBFromSlice(C256ToRgb(c[0]), c[1] == AsBg)
|
||||
}
|
||||
|
||||
// RGBColor convert color-256 to RGB color.
|
||||
func (c Color256) RGBColor() RGBColor {
|
||||
return c.RGB()
|
||||
}
|
||||
|
||||
// Value return color value
|
||||
func (c Color256) Value() uint8 {
|
||||
return c[0]
|
||||
}
|
||||
|
||||
// Code convert to color code string. eg: "12"
|
||||
func (c Color256) Code() string {
|
||||
return strconv.Itoa(int(c[0]))
|
||||
}
|
||||
|
||||
// FullCode convert to color code string with prefix. eg: "38;5;12"
|
||||
func (c Color256) FullCode() string {
|
||||
return c.String()
|
||||
}
|
||||
|
||||
// String convert to color code string with prefix. eg: "38;5;12"
|
||||
func (c Color256) String() string {
|
||||
if c[1] == AsFg { // 0 is Fg
|
||||
// return fmt.Sprintf(TplFg256, c[0])
|
||||
return Fg256Pfx + strconv.Itoa(int(c[0]))
|
||||
}
|
||||
|
||||
if c[1] == AsBg { // 1 is Bg
|
||||
// return fmt.Sprintf(TplBg256, c[0])
|
||||
return Bg256Pfx + strconv.Itoa(int(c[0]))
|
||||
}
|
||||
|
||||
return "" // empty
|
||||
}
|
||||
|
||||
// IsFg color
|
||||
func (c Color256) IsFg() bool {
|
||||
return c[1] == AsFg
|
||||
}
|
||||
|
||||
// ToFg 256 color
|
||||
func (c Color256) ToFg() Color256 {
|
||||
c[1] = AsFg
|
||||
return c
|
||||
}
|
||||
|
||||
// IsBg color
|
||||
func (c Color256) IsBg() bool {
|
||||
return c[1] == AsBg
|
||||
}
|
||||
|
||||
// ToBg 256 color
|
||||
func (c Color256) ToBg() Color256 {
|
||||
c[1] = AsBg
|
||||
return c
|
||||
}
|
||||
|
||||
// IsEmpty value
|
||||
func (c Color256) IsEmpty() bool {
|
||||
return c[1] > 1
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* 8bit(256) Style
|
||||
*************************************************************/
|
||||
|
||||
// Style256 definition
|
||||
//
|
||||
// 前/背景色
|
||||
// 都是由两位uint8组成, 第一位是色彩值;
|
||||
// 第二位与 Bit8Color 不一样的是,在这里表示是否设置了值 0 未设置 !=0 已设置
|
||||
type Style256 struct {
|
||||
// p Printer
|
||||
|
||||
// Name of the style
|
||||
Name string
|
||||
// color options of the style
|
||||
opts Opts
|
||||
// fg and bg color
|
||||
fg, bg Color256
|
||||
}
|
||||
|
||||
// S256 create a color256 style
|
||||
// Usage:
|
||||
// s := color.S256()
|
||||
// s := color.S256(132) // fg
|
||||
// s := color.S256(132, 203) // fg and bg
|
||||
func S256(fgAndBg ...uint8) *Style256 {
|
||||
s := &Style256{}
|
||||
vl := len(fgAndBg)
|
||||
if vl > 0 { // with fg
|
||||
s.fg = Color256{fgAndBg[0], 1}
|
||||
|
||||
if vl > 1 { // and with bg
|
||||
s.bg = Color256{fgAndBg[1], 1}
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Set fg and bg color value, can also with color options
|
||||
func (s *Style256) Set(fgVal, bgVal uint8, opts ...Color) *Style256 {
|
||||
s.fg = Color256{fgVal, 1}
|
||||
s.bg = Color256{bgVal, 1}
|
||||
s.opts.Add(opts...)
|
||||
return s
|
||||
}
|
||||
|
||||
// SetBg set bg color value
|
||||
func (s *Style256) SetBg(bgVal uint8) *Style256 {
|
||||
s.bg = Color256{bgVal, 1}
|
||||
return s
|
||||
}
|
||||
|
||||
// SetFg set fg color value
|
||||
func (s *Style256) SetFg(fgVal uint8) *Style256 {
|
||||
s.fg = Color256{fgVal, 1}
|
||||
return s
|
||||
}
|
||||
|
||||
// SetOpts set options
|
||||
func (s *Style256) SetOpts(opts Opts) *Style256 {
|
||||
s.opts = opts
|
||||
return s
|
||||
}
|
||||
|
||||
// AddOpts add options
|
||||
func (s *Style256) AddOpts(opts ...Color) *Style256 {
|
||||
s.opts.Add(opts...)
|
||||
return s
|
||||
}
|
||||
|
||||
// Print message
|
||||
func (s *Style256) Print(a ...interface{}) {
|
||||
doPrintV2(s.String(), fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Printf format and print message
|
||||
func (s *Style256) Printf(format string, a ...interface{}) {
|
||||
doPrintV2(s.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Println print message with newline
|
||||
func (s *Style256) Println(a ...interface{}) {
|
||||
doPrintlnV2(s.String(), a)
|
||||
}
|
||||
|
||||
// Sprint returns rendered message
|
||||
func (s *Style256) Sprint(a ...interface{}) string {
|
||||
return RenderCode(s.Code(), a...)
|
||||
}
|
||||
|
||||
// Sprintf returns format and rendered message
|
||||
func (s *Style256) Sprintf(format string, a ...interface{}) string {
|
||||
return RenderString(s.Code(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Code convert to color code string
|
||||
func (s *Style256) Code() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// String convert to color code string
|
||||
func (s *Style256) String() string {
|
||||
var ss []string
|
||||
if s.fg[1] > 0 {
|
||||
ss = append(ss, fmt.Sprintf(TplFg256, s.fg[0]))
|
||||
}
|
||||
|
||||
if s.bg[1] > 0 {
|
||||
ss = append(ss, fmt.Sprintf(TplBg256, s.bg[0]))
|
||||
}
|
||||
|
||||
if s.opts.IsValid() {
|
||||
ss = append(ss, s.opts.String())
|
||||
}
|
||||
|
||||
return strings.Join(ss, ";")
|
||||
}
|
|
@ -0,0 +1,391 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 24 bit RGB color
|
||||
// RGB:
|
||||
// R 0-255 G 0-255 B 0-255
|
||||
// R 00-FF G 00-FF B 00-FF (16进制)
|
||||
//
|
||||
// Format:
|
||||
// ESC[ … 38;2;<r>;<g>;<b> … m // Select RGB foreground color
|
||||
// ESC[ … 48;2;<r>;<g>;<b> … m // Choose RGB background color
|
||||
//
|
||||
// links:
|
||||
// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#24位
|
||||
//
|
||||
// example:
|
||||
// fg: \x1b[38;2;30;144;255mMESSAGE\x1b[0m
|
||||
// bg: \x1b[48;2;30;144;255mMESSAGE\x1b[0m
|
||||
// both: \x1b[38;2;233;90;203;48;2;30;144;255mMESSAGE\x1b[0m
|
||||
const (
|
||||
TplFgRGB = "38;2;%d;%d;%d"
|
||||
TplBgRGB = "48;2;%d;%d;%d"
|
||||
FgRGBPfx = "38;2;"
|
||||
BgRGBPfx = "48;2;"
|
||||
)
|
||||
|
||||
// mark color is fg or bg.
|
||||
const (
|
||||
AsFg uint8 = iota
|
||||
AsBg
|
||||
)
|
||||
|
||||
// values from https://github.com/go-terminfo/terminfo
|
||||
// var (
|
||||
// RgbaBlack = image_color.RGBA{0, 0, 0, 255}
|
||||
// Red = color.RGBA{205, 0, 0, 255}
|
||||
// Green = color.RGBA{0, 205, 0, 255}
|
||||
// Orange = color.RGBA{205, 205, 0, 255}
|
||||
// Blue = color.RGBA{0, 0, 238, 255}
|
||||
// Magenta = color.RGBA{205, 0, 205, 255}
|
||||
// Cyan = color.RGBA{0, 205, 205, 255}
|
||||
// LightGrey = color.RGBA{229, 229, 229, 255}
|
||||
//
|
||||
// DarkGrey = color.RGBA{127, 127, 127, 255}
|
||||
// LightRed = color.RGBA{255, 0, 0, 255}
|
||||
// LightGreen = color.RGBA{0, 255, 0, 255}
|
||||
// Yellow = color.RGBA{255, 255, 0, 255}
|
||||
// LightBlue = color.RGBA{92, 92, 255, 255}
|
||||
// LightMagenta = color.RGBA{255, 0, 255, 255}
|
||||
// LightCyan = color.RGBA{0, 255, 255, 255}
|
||||
// White = color.RGBA{255, 255, 255, 255}
|
||||
// )
|
||||
|
||||
/*************************************************************
|
||||
* RGB Color(Bit24Color, TrueColor)
|
||||
*************************************************************/
|
||||
|
||||
// RGBColor definition.
|
||||
//
|
||||
// The first to third digits represent the color value.
|
||||
// The last digit represents the foreground(0), background(1), >1 is unset value
|
||||
//
|
||||
// Usage:
|
||||
// // 0, 1, 2 is R,G,B.
|
||||
// // 3rd: Fg=0, Bg=1, >1: unset value
|
||||
// RGBColor{30,144,255, 0}
|
||||
// RGBColor{30,144,255, 1}
|
||||
//
|
||||
// NOTICE: now support RGB color on windows CMD, PowerShell
|
||||
type RGBColor [4]uint8
|
||||
|
||||
// create a empty RGBColor
|
||||
var emptyRGBColor = RGBColor{3: 99}
|
||||
|
||||
// RGB color create.
|
||||
// Usage:
|
||||
// c := RGB(30,144,255)
|
||||
// c := RGB(30,144,255, true)
|
||||
// c.Print("message")
|
||||
func RGB(r, g, b uint8, isBg ...bool) RGBColor {
|
||||
rgb := RGBColor{r, g, b}
|
||||
if len(isBg) > 0 && isBg[0] {
|
||||
rgb[3] = AsBg
|
||||
}
|
||||
|
||||
return rgb
|
||||
}
|
||||
|
||||
// Rgb alias of the RGB()
|
||||
func Rgb(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) }
|
||||
|
||||
// Bit24 alias of the RGB()
|
||||
func Bit24(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) }
|
||||
|
||||
// RGBFromSlice quick RGBColor from slice
|
||||
func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor {
|
||||
return RGB(rgb[0], rgb[1], rgb[2], isBg...)
|
||||
}
|
||||
|
||||
// HEX create RGB color from a HEX color string.
|
||||
// Usage:
|
||||
// c := HEX("ccc") // rgb: [204 204 204]
|
||||
// c := HEX("aabbcc") // rgb: [170 187 204]
|
||||
// c := HEX("#aabbcc")
|
||||
// c := HEX("0xaabbcc")
|
||||
// c.Print("message")
|
||||
func HEX(hex string, isBg ...bool) RGBColor {
|
||||
if rgb := HexToRgb(hex); len(rgb) > 0 {
|
||||
return RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]), isBg...)
|
||||
}
|
||||
|
||||
// mark is empty
|
||||
return emptyRGBColor
|
||||
}
|
||||
|
||||
// Hex alias of the HEX()
|
||||
func Hex(hex string, isBg ...bool) RGBColor { return HEX(hex, isBg...) }
|
||||
|
||||
// RGBFromString create RGB color from a string.
|
||||
// Usage:
|
||||
// c := RGBFromString("170,187,204")
|
||||
// c.Print("message")
|
||||
func RGBFromString(rgb string, isBg ...bool) RGBColor {
|
||||
ss := stringToArr(rgb, ",")
|
||||
if len(ss) != 3 {
|
||||
return emptyRGBColor
|
||||
}
|
||||
|
||||
var ar [3]int
|
||||
for i, val := range ss {
|
||||
iv, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return emptyRGBColor
|
||||
}
|
||||
|
||||
ar[i] = iv
|
||||
}
|
||||
|
||||
return RGB(uint8(ar[0]), uint8(ar[1]), uint8(ar[2]), isBg...)
|
||||
}
|
||||
|
||||
// Set terminal by rgb/true color code
|
||||
func (c RGBColor) Set() error {
|
||||
return SetTerminal(c.String())
|
||||
}
|
||||
|
||||
// Reset terminal. alias of the ResetTerminal()
|
||||
func (c RGBColor) Reset() error {
|
||||
return ResetTerminal()
|
||||
}
|
||||
|
||||
// Print print message
|
||||
func (c RGBColor) Print(a ...interface{}) {
|
||||
doPrintV2(c.String(), fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Printf format and print message
|
||||
func (c RGBColor) Printf(format string, a ...interface{}) {
|
||||
doPrintV2(c.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Println print message with newline
|
||||
func (c RGBColor) Println(a ...interface{}) {
|
||||
doPrintlnV2(c.String(), a)
|
||||
}
|
||||
|
||||
// Sprint returns rendered message
|
||||
func (c RGBColor) Sprint(a ...interface{}) string {
|
||||
return RenderCode(c.String(), a...)
|
||||
}
|
||||
|
||||
// Sprintf returns format and rendered message
|
||||
func (c RGBColor) Sprintf(format string, a ...interface{}) string {
|
||||
return RenderString(c.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Values to RGB values
|
||||
func (c RGBColor) Values() []int {
|
||||
return []int{int(c[0]), int(c[1]), int(c[2])}
|
||||
}
|
||||
|
||||
// Code to color code string without prefix. eg: "204;123;56"
|
||||
func (c RGBColor) Code() string {
|
||||
return fmt.Sprintf("%d;%d;%d", c[0], c[1], c[2])
|
||||
}
|
||||
|
||||
// Hex color rgb to hex string. as in "ff0080".
|
||||
func (c RGBColor) Hex() string {
|
||||
return fmt.Sprintf("%02x%02x%02x", c[0], c[1], c[2])
|
||||
}
|
||||
|
||||
// FullCode to color code string with prefix
|
||||
func (c RGBColor) FullCode() string {
|
||||
return c.String()
|
||||
}
|
||||
|
||||
// String to color code string with prefix. eg: "38;2;204;123;56"
|
||||
func (c RGBColor) String() string {
|
||||
if c[3] == AsFg {
|
||||
return fmt.Sprintf(TplFgRGB, c[0], c[1], c[2])
|
||||
}
|
||||
|
||||
if c[3] == AsBg {
|
||||
return fmt.Sprintf(TplBgRGB, c[0], c[1], c[2])
|
||||
}
|
||||
|
||||
// c[3] > 1 is empty
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsEmpty value
|
||||
func (c RGBColor) IsEmpty() bool {
|
||||
return c[3] > AsBg
|
||||
}
|
||||
|
||||
// IsValid value
|
||||
// func (c RGBColor) IsValid() bool {
|
||||
// return c[3] <= AsBg
|
||||
// }
|
||||
|
||||
// C256 returns the closest approximate 256 (8 bit) color
|
||||
func (c RGBColor) C256() Color256 {
|
||||
return C256(RgbTo256(c[0], c[1], c[2]), c[3] == AsBg)
|
||||
}
|
||||
|
||||
// Basic returns the closest approximate 16 (4 bit) color
|
||||
func (c RGBColor) Basic() Color {
|
||||
// return Color(RgbToAnsi(c[0], c[1], c[2], c[3] == AsBg))
|
||||
return Color(Rgb2basic(c[0], c[1], c[2], c[3] == AsBg))
|
||||
}
|
||||
|
||||
// Color returns the closest approximate 16 (4 bit) color
|
||||
func (c RGBColor) Color() Color { return c.Basic() }
|
||||
|
||||
// C16 returns the closest approximate 16 (4 bit) color
|
||||
func (c RGBColor) C16() Color { return c.Basic() }
|
||||
|
||||
/*************************************************************
|
||||
* RGB Style
|
||||
*************************************************************/
|
||||
|
||||
// RGBStyle definition.
|
||||
//
|
||||
// Foreground/Background color
|
||||
// All are composed of 4 digits uint8, the first three digits are the color value;
|
||||
// The last bit is different from RGBColor, here it indicates whether the value is set.
|
||||
// - 1 Has been set
|
||||
// - ^1 Not set
|
||||
type RGBStyle struct {
|
||||
// Name of the style
|
||||
Name string
|
||||
// color options of the style
|
||||
opts Opts
|
||||
// fg and bg color
|
||||
fg, bg RGBColor
|
||||
}
|
||||
|
||||
// NewRGBStyle create a RGBStyle.
|
||||
func NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle {
|
||||
s := &RGBStyle{}
|
||||
if len(bg) > 0 {
|
||||
s.SetBg(bg[0])
|
||||
}
|
||||
|
||||
return s.SetFg(fg)
|
||||
}
|
||||
|
||||
// HEXStyle create a RGBStyle from HEX color string.
|
||||
// Usage:
|
||||
// s := HEXStyle("aabbcc", "eee")
|
||||
// s.Print("message")
|
||||
func HEXStyle(fg string, bg ...string) *RGBStyle {
|
||||
s := &RGBStyle{}
|
||||
if len(bg) > 0 {
|
||||
s.SetBg(HEX(bg[0]))
|
||||
}
|
||||
|
||||
if len(fg) > 0 {
|
||||
s.SetFg(HEX(fg))
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// RGBStyleFromString create a RGBStyle from color value string.
|
||||
// Usage:
|
||||
// s := RGBStyleFromString("170,187,204", "70,87,4")
|
||||
// s.Print("message")
|
||||
func RGBStyleFromString(fg string, bg ...string) *RGBStyle {
|
||||
s := &RGBStyle{}
|
||||
if len(bg) > 0 {
|
||||
s.SetBg(RGBFromString(bg[0]))
|
||||
}
|
||||
|
||||
return s.SetFg(RGBFromString(fg))
|
||||
}
|
||||
|
||||
// Set fg and bg color, can also with color options
|
||||
func (s *RGBStyle) Set(fg, bg RGBColor, opts ...Color) *RGBStyle {
|
||||
return s.SetFg(fg).SetBg(bg).SetOpts(opts)
|
||||
}
|
||||
|
||||
// SetFg set fg color
|
||||
func (s *RGBStyle) SetFg(fg RGBColor) *RGBStyle {
|
||||
fg[3] = 1 // add fixed value, mark is valid
|
||||
s.fg = fg
|
||||
return s
|
||||
}
|
||||
|
||||
// SetBg set bg color
|
||||
func (s *RGBStyle) SetBg(bg RGBColor) *RGBStyle {
|
||||
bg[3] = 1 // add fixed value, mark is valid
|
||||
s.bg = bg
|
||||
return s
|
||||
}
|
||||
|
||||
// SetOpts set color options
|
||||
func (s *RGBStyle) SetOpts(opts Opts) *RGBStyle {
|
||||
s.opts = opts
|
||||
return s
|
||||
}
|
||||
|
||||
// AddOpts add options
|
||||
func (s *RGBStyle) AddOpts(opts ...Color) *RGBStyle {
|
||||
s.opts.Add(opts...)
|
||||
return s
|
||||
}
|
||||
|
||||
// Print print message
|
||||
func (s *RGBStyle) Print(a ...interface{}) {
|
||||
doPrintV2(s.String(), fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Printf format and print message
|
||||
func (s *RGBStyle) Printf(format string, a ...interface{}) {
|
||||
doPrintV2(s.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Println print message with newline
|
||||
func (s *RGBStyle) Println(a ...interface{}) {
|
||||
doPrintlnV2(s.String(), a)
|
||||
}
|
||||
|
||||
// Sprint returns rendered message
|
||||
func (s *RGBStyle) Sprint(a ...interface{}) string {
|
||||
return RenderCode(s.String(), a...)
|
||||
}
|
||||
|
||||
// Sprintf returns format and rendered message
|
||||
func (s *RGBStyle) Sprintf(format string, a ...interface{}) string {
|
||||
return RenderString(s.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Code convert to color code string
|
||||
func (s *RGBStyle) Code() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// FullCode convert to color code string
|
||||
func (s *RGBStyle) FullCode() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// String convert to color code string
|
||||
func (s *RGBStyle) String() string {
|
||||
var ss []string
|
||||
// last value ensure is enable.
|
||||
if s.fg[3] == 1 {
|
||||
ss = append(ss, fmt.Sprintf(TplFgRGB, s.fg[0], s.fg[1], s.fg[2]))
|
||||
}
|
||||
|
||||
if s.bg[3] == 1 {
|
||||
ss = append(ss, fmt.Sprintf(TplBgRGB, s.bg[0], s.bg[1], s.bg[2]))
|
||||
}
|
||||
|
||||
if s.opts.IsValid() {
|
||||
ss = append(ss, s.opts.String())
|
||||
}
|
||||
|
||||
return strings.Join(ss, ";")
|
||||
}
|
||||
|
||||
// IsEmpty style
|
||||
func (s *RGBStyle) IsEmpty() bool {
|
||||
return s.fg[3] != 1 && s.bg[3] != 1
|
||||
}
|
|
@ -0,0 +1,427 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// output colored text like use html tag. (not support windows cmd)
|
||||
const (
|
||||
// MatchExpr regex to match color tags
|
||||
//
|
||||
// Notice: golang 不支持反向引用. 即不支持使用 \1 引用第一个匹配 ([a-z=;]+)
|
||||
// MatchExpr = `<([a-z=;]+)>(.*?)<\/\1>`
|
||||
// 所以调整一下 统一使用 `</>` 来结束标签,例如 "<info>some text</>"
|
||||
//
|
||||
// allow custom attrs, eg: "<fg=white;bg=blue;op=bold>content</>"
|
||||
// (?s:...) s - 让 "." 匹配换行
|
||||
MatchExpr = `<([0-9a-zA-Z_=,;]+)>(?s:(.*?))<\/>`
|
||||
|
||||
// AttrExpr regex to match custom color attributes
|
||||
// eg: "<fg=white;bg=blue;op=bold>content</>"
|
||||
AttrExpr = `(fg|bg|op)[\s]*=[\s]*([0-9a-zA-Z,]+);?`
|
||||
|
||||
// StripExpr regex used for removing color tags
|
||||
// StripExpr = `<[\/]?[a-zA-Z=;]+>`
|
||||
// 随着上面的做一些调整
|
||||
StripExpr = `<[\/]?[0-9a-zA-Z_=,;]*>`
|
||||
)
|
||||
|
||||
var (
|
||||
attrRegex = regexp.MustCompile(AttrExpr)
|
||||
matchRegex = regexp.MustCompile(MatchExpr)
|
||||
stripRegex = regexp.MustCompile(StripExpr)
|
||||
)
|
||||
|
||||
/*************************************************************
|
||||
* internal defined color tags
|
||||
*************************************************************/
|
||||
|
||||
// There are internal defined color tags
|
||||
// Usage: <tag>content text</>
|
||||
// @notice 加 0 在前面是为了防止之前的影响到现在的设置
|
||||
var colorTags = map[string]string{
|
||||
// basic tags
|
||||
"red": "0;31",
|
||||
"red1": "1;31", // with bold
|
||||
"redB": "1;31",
|
||||
"red_b": "1;31",
|
||||
"blue": "0;34",
|
||||
"blue1": "1;34", // with bold
|
||||
"blueB": "1;34",
|
||||
"blue_b": "1;34",
|
||||
"cyan": "0;36",
|
||||
"cyan1": "1;36", // with bold
|
||||
"cyanB": "1;36",
|
||||
"cyan_b": "1;36",
|
||||
"green": "0;32",
|
||||
"green1": "1;32", // with bold
|
||||
"greenB": "1;32",
|
||||
"green_b": "1;32",
|
||||
"black": "0;30",
|
||||
"white": "1;37",
|
||||
"default": "0;39", // no color
|
||||
"normal": "0;39", // no color
|
||||
"brown": "0;33", // #A52A2A
|
||||
"yellow": "0;33",
|
||||
"ylw0": "0;33",
|
||||
"yellowB": "1;33", // with bold
|
||||
"ylw1": "1;33",
|
||||
"ylwB": "1;33",
|
||||
"magenta": "0;35",
|
||||
"mga": "0;35", // short name
|
||||
"magentaB": "1;35", // with bold
|
||||
"mgb": "1;35",
|
||||
"mgaB": "1;35",
|
||||
|
||||
// light/hi tags
|
||||
|
||||
"gray": "0;90",
|
||||
"darkGray": "0;90",
|
||||
"dark_gray": "0;90",
|
||||
"lightYellow": "0;93",
|
||||
"light_yellow": "0;93",
|
||||
"hiYellow": "0;93",
|
||||
"hi_yellow": "0;93",
|
||||
"hiYellowB": "1;93", // with bold
|
||||
"hi_yellow_b": "1;93",
|
||||
"lightMagenta": "0;95",
|
||||
"light_magenta": "0;95",
|
||||
"hiMagenta": "0;95",
|
||||
"hi_magenta": "0;95",
|
||||
"lightMagentaB": "1;95", // with bold
|
||||
"hiMagentaB": "1;95", // with bold
|
||||
"hi_magenta_b": "1;95",
|
||||
"lightRed": "0;91",
|
||||
"light_red": "0;91",
|
||||
"hiRed": "0;91",
|
||||
"hi_red": "0;91",
|
||||
"lightRedB": "1;91", // with bold
|
||||
"light_red_b": "1;91",
|
||||
"hi_red_b": "1;91",
|
||||
"lightGreen": "0;92",
|
||||
"light_green": "0;92",
|
||||
"hiGreen": "0;92",
|
||||
"hi_green": "0;92",
|
||||
"lightGreenB": "1;92",
|
||||
"light_green_b": "1;92",
|
||||
"hi_green_b": "1;92",
|
||||
"lightBlue": "0;94",
|
||||
"light_blue": "0;94",
|
||||
"hiBlue": "0;94",
|
||||
"hi_blue": "0;94",
|
||||
"lightBlueB": "1;94",
|
||||
"light_blue_b": "1;94",
|
||||
"hi_blue_b": "1;94",
|
||||
"lightCyan": "0;96",
|
||||
"light_cyan": "0;96",
|
||||
"hiCyan": "0;96",
|
||||
"hi_cyan": "0;96",
|
||||
"lightCyanB": "1;96",
|
||||
"light_cyan_b": "1;96",
|
||||
"hi_cyan_b": "1;96",
|
||||
"lightWhite": "0;97;40",
|
||||
"light_white": "0;97;40",
|
||||
|
||||
// option
|
||||
"bold": "1",
|
||||
"b": "1",
|
||||
"underscore": "4",
|
||||
"us": "4", // short name for 'underscore'
|
||||
"reverse": "7",
|
||||
|
||||
// alert tags, like bootstrap's alert
|
||||
"suc": "1;32", // same "green" and "bold"
|
||||
"success": "1;32",
|
||||
"info": "0;32", // same "green",
|
||||
"comment": "0;33", // same "brown"
|
||||
"note": "36;1",
|
||||
"notice": "36;4",
|
||||
"warn": "0;1;33",
|
||||
"warning": "0;30;43",
|
||||
"primary": "0;34",
|
||||
"danger": "1;31", // same "red" but add bold
|
||||
"err": "97;41",
|
||||
"error": "97;41", // fg light white; bg red
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* parse color tags
|
||||
*************************************************************/
|
||||
|
||||
var (
|
||||
tagParser = TagParser{}
|
||||
rxNumStr = regexp.MustCompile("^[0-9]{1,3}$")
|
||||
rxHexCode = regexp.MustCompile("^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
|
||||
)
|
||||
|
||||
// TagParser struct
|
||||
type TagParser struct {
|
||||
disable bool
|
||||
}
|
||||
|
||||
// NewTagParser create
|
||||
func NewTagParser() *TagParser {
|
||||
return &TagParser{}
|
||||
}
|
||||
|
||||
// func (tp *TagParser) Disable() *TagParser {
|
||||
// tp.disable = true
|
||||
// return tp
|
||||
// }
|
||||
|
||||
// ParseByEnv parse given string. will check package setting.
|
||||
func (tp *TagParser) ParseByEnv(str string) string {
|
||||
// disable handler TAG
|
||||
if !RenderTag {
|
||||
return str
|
||||
}
|
||||
|
||||
// disable OR not support color
|
||||
if !Enable || !SupportColor() {
|
||||
return ClearTag(str)
|
||||
}
|
||||
|
||||
return tp.Parse(str)
|
||||
}
|
||||
|
||||
// Parse parse given string, replace color tag and return rendered string
|
||||
func (tp *TagParser) Parse(str string) string {
|
||||
// not contains color tag
|
||||
if !strings.Contains(str, "</>") {
|
||||
return str
|
||||
}
|
||||
|
||||
// find color tags by regex. str eg: "<fg=white;bg=blue;op=bold>content</>"
|
||||
matched := matchRegex.FindAllStringSubmatch(str, -1)
|
||||
|
||||
// item: 0 full text 1 tag name 2 tag content
|
||||
for _, item := range matched {
|
||||
full, tag, content := item[0], item[1], item[2]
|
||||
|
||||
// use defined tag name: "<info>content</>" -> tag: "info"
|
||||
if !strings.ContainsRune(tag, '=') {
|
||||
code := colorTags[tag]
|
||||
if len(code) > 0 {
|
||||
now := RenderString(code, content)
|
||||
// old := WrapTag(content, tag) is equals to var 'full'
|
||||
str = strings.Replace(str, full, now, 1)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// custom color in tag
|
||||
// - basic: "fg=white;bg=blue;op=bold"
|
||||
if code := ParseCodeFromAttr(tag); len(code) > 0 {
|
||||
now := RenderString(code, content)
|
||||
str = strings.Replace(str, full, now, 1)
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// func (tp *TagParser) ParseAttr(attr string) (code string) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// ReplaceTag parse string, replace color tag and return rendered string
|
||||
func ReplaceTag(str string) string {
|
||||
return tagParser.ParseByEnv(str)
|
||||
}
|
||||
|
||||
// ParseCodeFromAttr parse color attributes.
|
||||
//
|
||||
// attr format:
|
||||
// // VALUE please see var: FgColors, BgColors, AllOptions
|
||||
// "fg=VALUE;bg=VALUE;op=VALUE"
|
||||
// 16 color:
|
||||
// "fg=yellow"
|
||||
// "bg=red"
|
||||
// "op=bold,underscore" option is allow multi value
|
||||
// "fg=white;bg=blue;op=bold"
|
||||
// "fg=white;op=bold,underscore"
|
||||
// 256 color:
|
||||
// "fg=167"
|
||||
// "fg=167;bg=23"
|
||||
// "fg=167;bg=23;op=bold"
|
||||
// true color:
|
||||
// // hex
|
||||
// "fg=fc1cac"
|
||||
// "fg=fc1cac;bg=c2c3c4"
|
||||
// // r,g,b
|
||||
// "fg=23,45,214"
|
||||
// "fg=23,45,214;bg=109,99,88"
|
||||
func ParseCodeFromAttr(attr string) (code string) {
|
||||
if !strings.ContainsRune(attr, '=') {
|
||||
return
|
||||
}
|
||||
|
||||
attr = strings.Trim(attr, ";=,")
|
||||
if len(attr) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var codes []string
|
||||
matched := attrRegex.FindAllStringSubmatch(attr, -1)
|
||||
|
||||
for _, item := range matched {
|
||||
pos, val := item[1], item[2]
|
||||
switch pos {
|
||||
case "fg":
|
||||
if c, ok := FgColors[val]; ok { // basic
|
||||
codes = append(codes, c.String())
|
||||
} else if c, ok := ExFgColors[val]; ok { // extra
|
||||
codes = append(codes, c.String())
|
||||
} else if code := rgbHex256toCode(val, false); code != "" {
|
||||
codes = append(codes, code)
|
||||
}
|
||||
case "bg":
|
||||
if c, ok := BgColors[val]; ok { // basic bg
|
||||
codes = append(codes, c.String())
|
||||
} else if c, ok := ExBgColors[val]; ok { // extra bg
|
||||
codes = append(codes, c.String())
|
||||
} else if code := rgbHex256toCode(val, true); code != "" {
|
||||
codes = append(codes, code)
|
||||
}
|
||||
case "op": // options allow multi value
|
||||
if strings.Contains(val, ",") {
|
||||
ns := strings.Split(val, ",")
|
||||
for _, n := range ns {
|
||||
if c, ok := AllOptions[n]; ok {
|
||||
codes = append(codes, c.String())
|
||||
}
|
||||
}
|
||||
} else if c, ok := AllOptions[val]; ok {
|
||||
codes = append(codes, c.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(codes, ";")
|
||||
}
|
||||
|
||||
func rgbHex256toCode(val string, isBg bool) (code string) {
|
||||
if len(val) == 6 && rxHexCode.MatchString(val) { // hex: "fc1cac"
|
||||
code = HEX(val, isBg).String()
|
||||
} else if strings.ContainsRune(val, ',') { // rgb: "231,178,161"
|
||||
code = strings.Replace(val, ",", ";", -1)
|
||||
if isBg {
|
||||
code = BgRGBPfx + code
|
||||
} else {
|
||||
code = FgRGBPfx + code
|
||||
}
|
||||
} else if len(val) < 4 && rxNumStr.MatchString(val) { // 256 code
|
||||
if isBg {
|
||||
code = Bg256Pfx + val
|
||||
} else {
|
||||
code = Fg256Pfx + val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ClearTag clear all tag for a string
|
||||
func ClearTag(s string) string {
|
||||
if !strings.Contains(s, "</>") {
|
||||
return s
|
||||
}
|
||||
|
||||
return stripRegex.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* helper methods
|
||||
*************************************************************/
|
||||
|
||||
// GetTagCode get color code by tag name
|
||||
func GetTagCode(name string) string {
|
||||
return colorTags[name]
|
||||
}
|
||||
|
||||
// ApplyTag for messages
|
||||
func ApplyTag(tag string, a ...interface{}) string {
|
||||
return RenderCode(GetTagCode(tag), a...)
|
||||
}
|
||||
|
||||
// WrapTag wrap a tag for a string "<tag>content</>"
|
||||
func WrapTag(s string, tag string) string {
|
||||
if s == "" || tag == "" {
|
||||
return s
|
||||
}
|
||||
|
||||
return fmt.Sprintf("<%s>%s</>", tag, s)
|
||||
}
|
||||
|
||||
// GetColorTags get all internal color tags
|
||||
func GetColorTags() map[string]string {
|
||||
return colorTags
|
||||
}
|
||||
|
||||
// IsDefinedTag is defined tag name
|
||||
func IsDefinedTag(name string) bool {
|
||||
_, ok := colorTags[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Tag extra
|
||||
*************************************************************/
|
||||
|
||||
// Tag value is a defined style name
|
||||
// Usage:
|
||||
// Tag("info").Println("message")
|
||||
type Tag string
|
||||
|
||||
// Print messages
|
||||
func (tg Tag) Print(a ...interface{}) {
|
||||
name := string(tg)
|
||||
str := fmt.Sprint(a...)
|
||||
|
||||
if stl := GetStyle(name); !stl.IsEmpty() {
|
||||
stl.Print(str)
|
||||
} else {
|
||||
doPrintV2(GetTagCode(name), str)
|
||||
}
|
||||
}
|
||||
|
||||
// Printf format and print messages
|
||||
func (tg Tag) Printf(format string, a ...interface{}) {
|
||||
name := string(tg)
|
||||
str := fmt.Sprintf(format, a...)
|
||||
|
||||
if stl := GetStyle(name); !stl.IsEmpty() {
|
||||
stl.Print(str)
|
||||
} else {
|
||||
doPrintV2(GetTagCode(name), str)
|
||||
}
|
||||
}
|
||||
|
||||
// Println messages line
|
||||
func (tg Tag) Println(a ...interface{}) {
|
||||
name := string(tg)
|
||||
if stl := GetStyle(name); !stl.IsEmpty() {
|
||||
stl.Println(a...)
|
||||
} else {
|
||||
doPrintlnV2(GetTagCode(name), a)
|
||||
}
|
||||
}
|
||||
|
||||
// Sprint render messages
|
||||
func (tg Tag) Sprint(a ...interface{}) string {
|
||||
name := string(tg)
|
||||
// if stl := GetStyle(name); !stl.IsEmpty() {
|
||||
// return stl.Render(args...)
|
||||
// }
|
||||
|
||||
return RenderCode(GetTagCode(name), a...)
|
||||
}
|
||||
|
||||
// Sprintf format and render messages
|
||||
func (tg Tag) Sprintf(format string, a ...interface{}) string {
|
||||
tag := string(tg)
|
||||
str := fmt.Sprintf(format, a...)
|
||||
|
||||
return RenderString(GetTagCode(tag), str)
|
||||
}
|
|
@ -0,0 +1,593 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ---------- basic(16) <=> 256 color convert ----------
|
||||
basicTo256Map = map[uint8]uint8{
|
||||
30: 0, // black 000000
|
||||
31: 160, // red c51e14
|
||||
32: 34, // green 1dc121
|
||||
33: 184, // yellow c7c329
|
||||
34: 20, // blue 0a2fc4
|
||||
35: 170, // magenta c839c5
|
||||
36: 44, // cyan 20c5c6
|
||||
37: 188, // white c7c7c7
|
||||
90: 59, // lightBlack 686868
|
||||
91: 203, // lightRed fd6f6b
|
||||
92: 83, // lightGreen 67f86f
|
||||
93: 227, // lightYellow fffa72
|
||||
94: 69, // lightBlue 6a76fb
|
||||
95: 213, // lightMagenta fd7cfc
|
||||
96: 87, // lightCyan 68fdfe
|
||||
97: 15, // lightWhite ffffff
|
||||
}
|
||||
|
||||
// ---------- basic(16) <=> RGB color convert ----------
|
||||
// refer from Hyper app
|
||||
basic2hexMap = map[uint8]string{
|
||||
30: "000000", // black
|
||||
31: "c51e14", // red
|
||||
32: "1dc121", // green
|
||||
33: "c7c329", // yellow
|
||||
34: "0a2fc4", // blue
|
||||
35: "c839c5", // magenta
|
||||
36: "20c5c6", // cyan
|
||||
37: "c7c7c7", // white
|
||||
90: "686868", // lightBlack/darkGray
|
||||
91: "fd6f6b", // lightRed
|
||||
92: "67f86f", // lightGreen
|
||||
93: "fffa72", // lightYellow
|
||||
94: "6a76fb", // lightBlue
|
||||
95: "fd7cfc", // lightMagenta
|
||||
96: "68fdfe", // lightCyan
|
||||
97: "ffffff", // lightWhite
|
||||
}
|
||||
// will convert data from basic2hexMap
|
||||
hex2basicMap = initHex2basicMap()
|
||||
|
||||
// ---------- 256 <=> RGB color convert ----------
|
||||
// adapted from https://gist.github.com/MicahElliott/719710
|
||||
|
||||
c256ToHexMap = init256ToHexMap()
|
||||
|
||||
// rgb to 256 color look-up table
|
||||
// RGB hex => 256 code
|
||||
hexTo256Table = map[string]uint8{
|
||||
// Primary 3-bit (8 colors). Unique representation!
|
||||
"000000": 0,
|
||||
"800000": 1,
|
||||
"008000": 2,
|
||||
"808000": 3,
|
||||
"000080": 4,
|
||||
"800080": 5,
|
||||
"008080": 6,
|
||||
"c0c0c0": 7,
|
||||
|
||||
// Equivalent "bright" versions of original 8 colors.
|
||||
"808080": 8,
|
||||
"ff0000": 9,
|
||||
"00ff00": 10,
|
||||
"ffff00": 11,
|
||||
"0000ff": 12,
|
||||
"ff00ff": 13,
|
||||
"00ffff": 14,
|
||||
"ffffff": 15,
|
||||
|
||||
// values commented out below are duplicates from the prior sections
|
||||
|
||||
// Strictly ascending.
|
||||
// "000000": 16,
|
||||
"000001": 16, // up: avoid key conflicts, value + 1
|
||||
"00005f": 17,
|
||||
"000087": 18,
|
||||
"0000af": 19,
|
||||
"0000d7": 20,
|
||||
// "0000ff": 21,
|
||||
"0000fe": 21, // up: avoid key conflicts, value - 1
|
||||
"005f00": 22,
|
||||
"005f5f": 23,
|
||||
"005f87": 24,
|
||||
"005faf": 25,
|
||||
"005fd7": 26,
|
||||
"005fff": 27,
|
||||
"008700": 28,
|
||||
"00875f": 29,
|
||||
"008787": 30,
|
||||
"0087af": 31,
|
||||
"0087d7": 32,
|
||||
"0087ff": 33,
|
||||
"00af00": 34,
|
||||
"00af5f": 35,
|
||||
"00af87": 36,
|
||||
"00afaf": 37,
|
||||
"00afd7": 38,
|
||||
"00afff": 39,
|
||||
"00d700": 40,
|
||||
"00d75f": 41,
|
||||
"00d787": 42,
|
||||
"00d7af": 43,
|
||||
"00d7d7": 44,
|
||||
"00d7ff": 45,
|
||||
// "00ff00": 46,
|
||||
"00ff01": 46, // up: avoid key conflicts, value + 1
|
||||
"00ff5f": 47,
|
||||
"00ff87": 48,
|
||||
"00ffaf": 49,
|
||||
"00ffd7": 50,
|
||||
// "00ffff": 51,
|
||||
"00fffe": 51, // up: avoid key conflicts, value - 1
|
||||
"5f0000": 52,
|
||||
"5f005f": 53,
|
||||
"5f0087": 54,
|
||||
"5f00af": 55,
|
||||
"5f00d7": 56,
|
||||
"5f00ff": 57,
|
||||
"5f5f00": 58,
|
||||
"5f5f5f": 59,
|
||||
"5f5f87": 60,
|
||||
"5f5faf": 61,
|
||||
"5f5fd7": 62,
|
||||
"5f5fff": 63,
|
||||
"5f8700": 64,
|
||||
"5f875f": 65,
|
||||
"5f8787": 66,
|
||||
"5f87af": 67,
|
||||
"5f87d7": 68,
|
||||
"5f87ff": 69,
|
||||
"5faf00": 70,
|
||||
"5faf5f": 71,
|
||||
"5faf87": 72,
|
||||
"5fafaf": 73,
|
||||
"5fafd7": 74,
|
||||
"5fafff": 75,
|
||||
"5fd700": 76,
|
||||
"5fd75f": 77,
|
||||
"5fd787": 78,
|
||||
"5fd7af": 79,
|
||||
"5fd7d7": 80,
|
||||
"5fd7ff": 81,
|
||||
"5fff00": 82,
|
||||
"5fff5f": 83,
|
||||
"5fff87": 84,
|
||||
"5fffaf": 85,
|
||||
"5fffd7": 86,
|
||||
"5fffff": 87,
|
||||
"870000": 88,
|
||||
"87005f": 89,
|
||||
"870087": 90,
|
||||
"8700af": 91,
|
||||
"8700d7": 92,
|
||||
"8700ff": 93,
|
||||
"875f00": 94,
|
||||
"875f5f": 95,
|
||||
"875f87": 96,
|
||||
"875faf": 97,
|
||||
"875fd7": 98,
|
||||
"875fff": 99,
|
||||
"878700": 100,
|
||||
"87875f": 101,
|
||||
"878787": 102,
|
||||
"8787af": 103,
|
||||
"8787d7": 104,
|
||||
"8787ff": 105,
|
||||
"87af00": 106,
|
||||
"87af5f": 107,
|
||||
"87af87": 108,
|
||||
"87afaf": 109,
|
||||
"87afd7": 110,
|
||||
"87afff": 111,
|
||||
"87d700": 112,
|
||||
"87d75f": 113,
|
||||
"87d787": 114,
|
||||
"87d7af": 115,
|
||||
"87d7d7": 116,
|
||||
"87d7ff": 117,
|
||||
"87ff00": 118,
|
||||
"87ff5f": 119,
|
||||
"87ff87": 120,
|
||||
"87ffaf": 121,
|
||||
"87ffd7": 122,
|
||||
"87ffff": 123,
|
||||
"af0000": 124,
|
||||
"af005f": 125,
|
||||
"af0087": 126,
|
||||
"af00af": 127,
|
||||
"af00d7": 128,
|
||||
"af00ff": 129,
|
||||
"af5f00": 130,
|
||||
"af5f5f": 131,
|
||||
"af5f87": 132,
|
||||
"af5faf": 133,
|
||||
"af5fd7": 134,
|
||||
"af5fff": 135,
|
||||
"af8700": 136,
|
||||
"af875f": 137,
|
||||
"af8787": 138,
|
||||
"af87af": 139,
|
||||
"af87d7": 140,
|
||||
"af87ff": 141,
|
||||
"afaf00": 142,
|
||||
"afaf5f": 143,
|
||||
"afaf87": 144,
|
||||
"afafaf": 145,
|
||||
"afafd7": 146,
|
||||
"afafff": 147,
|
||||
"afd700": 148,
|
||||
"afd75f": 149,
|
||||
"afd787": 150,
|
||||
"afd7af": 151,
|
||||
"afd7d7": 152,
|
||||
"afd7ff": 153,
|
||||
"afff00": 154,
|
||||
"afff5f": 155,
|
||||
"afff87": 156,
|
||||
"afffaf": 157,
|
||||
"afffd7": 158,
|
||||
"afffff": 159,
|
||||
"d70000": 160,
|
||||
"d7005f": 161,
|
||||
"d70087": 162,
|
||||
"d700af": 163,
|
||||
"d700d7": 164,
|
||||
"d700ff": 165,
|
||||
"d75f00": 166,
|
||||
"d75f5f": 167,
|
||||
"d75f87": 168,
|
||||
"d75faf": 169,
|
||||
"d75fd7": 170,
|
||||
"d75fff": 171,
|
||||
"d78700": 172,
|
||||
"d7875f": 173,
|
||||
"d78787": 174,
|
||||
"d787af": 175,
|
||||
"d787d7": 176,
|
||||
"d787ff": 177,
|
||||
"d7af00": 178,
|
||||
"d7af5f": 179,
|
||||
"d7af87": 180,
|
||||
"d7afaf": 181,
|
||||
"d7afd7": 182,
|
||||
"d7afff": 183,
|
||||
"d7d700": 184,
|
||||
"d7d75f": 185,
|
||||
"d7d787": 186,
|
||||
"d7d7af": 187,
|
||||
"d7d7d7": 188,
|
||||
"d7d7ff": 189,
|
||||
"d7ff00": 190,
|
||||
"d7ff5f": 191,
|
||||
"d7ff87": 192,
|
||||
"d7ffaf": 193,
|
||||
"d7ffd7": 194,
|
||||
"d7ffff": 195,
|
||||
// "ff0000": 196,
|
||||
"ff0001": 196, // up: avoid key conflicts, value + 1
|
||||
"ff005f": 197,
|
||||
"ff0087": 198,
|
||||
"ff00af": 199,
|
||||
"ff00d7": 200,
|
||||
// "ff00ff": 201,
|
||||
"ff00fe": 201, // up: avoid key conflicts, value - 1
|
||||
"ff5f00": 202,
|
||||
"ff5f5f": 203,
|
||||
"ff5f87": 204,
|
||||
"ff5faf": 205,
|
||||
"ff5fd7": 206,
|
||||
"ff5fff": 207,
|
||||
"ff8700": 208,
|
||||
"ff875f": 209,
|
||||
"ff8787": 210,
|
||||
"ff87af": 211,
|
||||
"ff87d7": 212,
|
||||
"ff87ff": 213,
|
||||
"ffaf00": 214,
|
||||
"ffaf5f": 215,
|
||||
"ffaf87": 216,
|
||||
"ffafaf": 217,
|
||||
"ffafd7": 218,
|
||||
"ffafff": 219,
|
||||
"ffd700": 220,
|
||||
"ffd75f": 221,
|
||||
"ffd787": 222,
|
||||
"ffd7af": 223,
|
||||
"ffd7d7": 224,
|
||||
"ffd7ff": 225,
|
||||
// "ffff00": 226,
|
||||
"ffff01": 226, // up: avoid key conflicts, value + 1
|
||||
"ffff5f": 227,
|
||||
"ffff87": 228,
|
||||
"ffffaf": 229,
|
||||
"ffffd7": 230,
|
||||
// "ffffff": 231,
|
||||
"fffffe": 231, // up: avoid key conflicts, value - 1
|
||||
|
||||
// Gray-scale range.
|
||||
"080808": 232,
|
||||
"121212": 233,
|
||||
"1c1c1c": 234,
|
||||
"262626": 235,
|
||||
"303030": 236,
|
||||
"3a3a3a": 237,
|
||||
"444444": 238,
|
||||
"4e4e4e": 239,
|
||||
"585858": 240,
|
||||
"626262": 241,
|
||||
"6c6c6c": 242,
|
||||
"767676": 243,
|
||||
// "808080": 244,
|
||||
"808081": 244, // up: avoid key conflicts, value + 1
|
||||
"8a8a8a": 245,
|
||||
"949494": 246,
|
||||
"9e9e9e": 247,
|
||||
"a8a8a8": 248,
|
||||
"b2b2b2": 249,
|
||||
"bcbcbc": 250,
|
||||
"c6c6c6": 251,
|
||||
"d0d0d0": 252,
|
||||
"dadada": 253,
|
||||
"e4e4e4": 254,
|
||||
"eeeeee": 255,
|
||||
}
|
||||
|
||||
incs = []uint8{0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff}
|
||||
)
|
||||
|
||||
func initHex2basicMap() map[string]uint8 {
|
||||
h2b := make(map[string]uint8, len(basic2hexMap))
|
||||
// ini data map
|
||||
for u, s := range basic2hexMap {
|
||||
h2b[s] = u
|
||||
}
|
||||
return h2b
|
||||
}
|
||||
|
||||
func init256ToHexMap() map[uint8]string {
|
||||
c256toh := make(map[uint8]string, len(hexTo256Table))
|
||||
// ini data map
|
||||
for hex, c256 := range hexTo256Table {
|
||||
c256toh[c256] = hex
|
||||
}
|
||||
return c256toh
|
||||
}
|
||||
|
||||
// RgbTo256Table mapping data
|
||||
func RgbTo256Table() map[string]uint8 {
|
||||
return hexTo256Table
|
||||
}
|
||||
|
||||
// Colors2code convert colors to code. return like "32;45;3"
|
||||
func Colors2code(colors ...Color) string {
|
||||
if len(colors) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var codes []string
|
||||
for _, color := range colors {
|
||||
codes = append(codes, color.String())
|
||||
}
|
||||
|
||||
return strings.Join(codes, ";")
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* HEX code <=> RGB/True color code
|
||||
*************************************************************/
|
||||
|
||||
// Hex2rgb alias of the HexToRgb()
|
||||
func Hex2rgb(hex string) []int { return HexToRgb(hex) }
|
||||
|
||||
// HexToRGB alias of the HexToRgb()
|
||||
func HexToRGB(hex string) []int { return HexToRgb(hex) }
|
||||
|
||||
// HexToRgb convert hex color string to RGB numbers
|
||||
//
|
||||
// Usage:
|
||||
// rgb := HexToRgb("ccc") // rgb: [204 204 204]
|
||||
// rgb := HexToRgb("aabbcc") // rgb: [170 187 204]
|
||||
// rgb := HexToRgb("#aabbcc") // rgb: [170 187 204]
|
||||
// rgb := HexToRgb("0xad99c0") // rgb: [170 187 204]
|
||||
func HexToRgb(hex string) (rgb []int) {
|
||||
hex = strings.TrimSpace(hex)
|
||||
if hex == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// like from css. eg "#ccc" "#ad99c0"
|
||||
if hex[0] == '#' {
|
||||
hex = hex[1:]
|
||||
}
|
||||
|
||||
hex = strings.ToLower(hex)
|
||||
switch len(hex) {
|
||||
case 3: // "ccc"
|
||||
hex = string([]byte{hex[0], hex[0], hex[1], hex[1], hex[2], hex[2]})
|
||||
case 8: // "0xad99c0"
|
||||
hex = strings.TrimPrefix(hex, "0x")
|
||||
}
|
||||
|
||||
// recheck
|
||||
if len(hex) != 6 {
|
||||
return
|
||||
}
|
||||
|
||||
// convert string to int64
|
||||
if i64, err := strconv.ParseInt(hex, 16, 32); err == nil {
|
||||
color := int(i64)
|
||||
// parse int
|
||||
rgb = make([]int, 3)
|
||||
rgb[0] = color >> 16
|
||||
rgb[1] = (color & 0x00FF00) >> 8
|
||||
rgb[2] = color & 0x0000FF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Rgb2hex alias of the RgbToHex()
|
||||
func Rgb2hex(rgb []int) string { return RgbToHex(rgb) }
|
||||
|
||||
// RgbToHex convert RGB-code to hex-code
|
||||
//
|
||||
// Usage:
|
||||
// hex := RgbToHex([]int{170, 187, 204}) // hex: "aabbcc"
|
||||
func RgbToHex(rgb []int) string {
|
||||
hexNodes := make([]string, len(rgb))
|
||||
|
||||
for _, v := range rgb {
|
||||
hexNodes = append(hexNodes, strconv.FormatInt(int64(v), 16))
|
||||
}
|
||||
return strings.Join(hexNodes, "")
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* 4bit(16) color <=> RGB/True color
|
||||
*************************************************************/
|
||||
|
||||
// Basic2hex convert basic color to hex string.
|
||||
func Basic2hex(val uint8) string {
|
||||
return basic2hexMap[val]
|
||||
}
|
||||
|
||||
// Hex2basic convert hex string to basic color code.
|
||||
func Hex2basic(hex string) uint8 {
|
||||
return hex2basicMap[hex]
|
||||
}
|
||||
|
||||
// Rgb2basic alias of the RgbToAnsi()
|
||||
func Rgb2basic(r, g, b uint8, isBg bool) uint8 {
|
||||
// is basic color, direct use static map data.
|
||||
hex := RgbToHex([]int{int(r), int(g), int(b)})
|
||||
if val, ok := hex2basicMap[hex]; ok {
|
||||
if isBg {
|
||||
return val + 10
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return RgbToAnsi(r, g, b, isBg)
|
||||
}
|
||||
|
||||
// Rgb2ansi alias of the RgbToAnsi()
|
||||
func Rgb2ansi(r, g, b uint8, isBg bool) uint8 {
|
||||
return RgbToAnsi(r, g, b, isBg)
|
||||
}
|
||||
|
||||
// RgbToAnsi convert RGB-code to 16-code
|
||||
// refer https://github.com/radareorg/radare2/blob/master/libr/cons/rgb.c#L249-L271
|
||||
func RgbToAnsi(r, g, b uint8, isBg bool) uint8 {
|
||||
var bright, c, k uint8
|
||||
base := compareVal(isBg, BgBase, FgBase)
|
||||
|
||||
// eco bright-specific
|
||||
if r == 0x80 && g == 0x80 && b == 0x80 { // 0x80=128
|
||||
bright = 53
|
||||
} else if r == 0xff || g == 0xff || b == 0xff { // 0xff=255
|
||||
bright = 60
|
||||
} // else bright = 0
|
||||
|
||||
if r == g && g == b {
|
||||
// 0x7f=127
|
||||
// r = (r > 0x7f) ? 1 : 0;
|
||||
r = compareVal(r > 0x7f, 1, 0)
|
||||
g = compareVal(g > 0x7f, 1, 0)
|
||||
b = compareVal(b > 0x7f, 1, 0)
|
||||
} else {
|
||||
k = (r + g + b) / 3
|
||||
|
||||
// r = (r >= k) ? 1 : 0;
|
||||
r = compareVal(r >= k, 1, 0)
|
||||
g = compareVal(g >= k, 1, 0)
|
||||
b = compareVal(b >= k, 1, 0)
|
||||
}
|
||||
|
||||
// c = (r ? 1 : 0) + (g ? (b ? 6 : 2) : (b ? 4 : 0))
|
||||
c = compareVal(r > 0, 1, 0)
|
||||
|
||||
if g > 0 {
|
||||
c += compareVal(b > 0, 6, 2)
|
||||
} else {
|
||||
c += compareVal(b > 0, 4, 0)
|
||||
}
|
||||
return base + bright + c
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* 8bit(256) color <=> RGB/True color
|
||||
*************************************************************/
|
||||
|
||||
// Rgb2short convert RGB-code to 256-code
|
||||
func Rgb2short(r, g, b uint8) uint8 {
|
||||
return RgbTo256(r, g, b)
|
||||
}
|
||||
|
||||
// RgbTo256 convert RGB-code to 256-code
|
||||
func RgbTo256(r, g, b uint8) uint8 {
|
||||
res := make([]uint8, 3)
|
||||
for partI, part := range [3]uint8{r, g, b} {
|
||||
i := 0
|
||||
for i < len(incs)-1 {
|
||||
s, b := incs[i], incs[i+1] // smaller, bigger
|
||||
if s <= part && part <= b {
|
||||
s1 := math.Abs(float64(s) - float64(part))
|
||||
b1 := math.Abs(float64(b) - float64(part))
|
||||
var closest uint8
|
||||
if s1 < b1 {
|
||||
closest = s
|
||||
} else {
|
||||
closest = b
|
||||
}
|
||||
res[partI] = closest
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
hex := fmt.Sprintf("%02x%02x%02x", res[0], res[1], res[2])
|
||||
equiv := hexTo256Table[hex]
|
||||
return equiv
|
||||
}
|
||||
|
||||
// C256ToRgb convert an 256 color code to RGB numbers
|
||||
func C256ToRgb(val uint8) (rgb []uint8) {
|
||||
hex := c256ToHexMap[val]
|
||||
// convert to rgb code
|
||||
rgbInts := Hex2rgb(hex)
|
||||
|
||||
return []uint8{
|
||||
uint8(rgbInts[0]),
|
||||
uint8(rgbInts[1]),
|
||||
uint8(rgbInts[2]),
|
||||
}
|
||||
}
|
||||
|
||||
// C256ToRgbV1 convert an 256 color code to RGB numbers
|
||||
// refer https://github.com/torvalds/linux/commit/cec5b2a97a11ade56a701e83044d0a2a984c67b4
|
||||
func C256ToRgbV1(val uint8) (rgb []uint8) {
|
||||
var r, g, b uint8
|
||||
if val < 8 { // Standard colours.
|
||||
// r = val&1 ? 0xaa : 0x00;
|
||||
r = compareVal(val&1 == 1, 0xaa, 0x00)
|
||||
g = compareVal(val&2 == 2, 0xaa, 0x00)
|
||||
b = compareVal(val&4 == 4, 0xaa, 0x00)
|
||||
} else if val < 16 {
|
||||
// r = val & 1 ? 0xff : 0x55;
|
||||
r = compareVal(val&1 == 1, 0xff, 0x55)
|
||||
g = compareVal(val&2 == 2, 0xff, 0x55)
|
||||
b = compareVal(val&4 == 4, 0xff, 0x55)
|
||||
} else if val < 232 { /* 6x6x6 colour cube. */
|
||||
r = (val - 16) / 36 * 85 / 2
|
||||
g = (val - 16) / 6 % 6 * 85 / 2
|
||||
b = (val - 16) % 6 * 85 / 2
|
||||
} else { /* Grayscale ramp. */
|
||||
nv := uint8(int(val)*10 - 2312)
|
||||
// set value
|
||||
r, g, b = nv, nv, nv
|
||||
}
|
||||
|
||||
return []uint8{r, g, b}
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/xo/terminfo"
|
||||
)
|
||||
|
||||
/*************************************************************
|
||||
* helper methods for detect color supports
|
||||
*************************************************************/
|
||||
|
||||
// DetectColorLevel for current env
|
||||
//
|
||||
// NOTICE: The method will detect terminal info each times,
|
||||
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
|
||||
func DetectColorLevel() terminfo.ColorLevel {
|
||||
level, _ := detectTermColorLevel()
|
||||
return level
|
||||
}
|
||||
|
||||
// detect terminal color support level
|
||||
//
|
||||
// refer https://github.com/Delta456/box-cli-maker
|
||||
func detectTermColorLevel() (level terminfo.ColorLevel, needVTP bool) {
|
||||
// on windows WSL:
|
||||
// - runtime.GOOS == "Linux"
|
||||
// - support true-color
|
||||
// env:
|
||||
// WSL_DISTRO_NAME=Debian
|
||||
if val := os.Getenv("WSL_DISTRO_NAME"); val != "" {
|
||||
// detect WSL as it has True Color support
|
||||
if detectWSL() {
|
||||
debugf("True Color support on WSL environment")
|
||||
return terminfo.ColorLevelMillions, false
|
||||
}
|
||||
}
|
||||
|
||||
isWin := runtime.GOOS == "windows"
|
||||
termVal := os.Getenv("TERM")
|
||||
|
||||
// on TERM=screen: not support true-color
|
||||
if termVal != "screen" {
|
||||
// On JetBrains Terminal
|
||||
// - support true-color
|
||||
// env:
|
||||
// TERMINAL_EMULATOR=JetBrains-JediTerm
|
||||
val := os.Getenv("TERMINAL_EMULATOR")
|
||||
if val == "JetBrains-JediTerm" {
|
||||
debugf("True Color support on JetBrains-JediTerm, is win: %v", isWin)
|
||||
return terminfo.ColorLevelMillions, isWin
|
||||
}
|
||||
}
|
||||
|
||||
// level, err = terminfo.ColorLevelFromEnv()
|
||||
level = detectColorLevelFromEnv(termVal, isWin)
|
||||
debugf("color level by detectColorLevelFromEnv: %s", level.String())
|
||||
|
||||
// fallback: simple detect by TERM value string.
|
||||
if level == terminfo.ColorLevelNone {
|
||||
debugf("level none - fallback check special term color support")
|
||||
// on Windows: enable VTP as it has True Color support
|
||||
level, needVTP = detectSpecialTermColor(termVal)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// detectColorFromEnv returns the color level COLORTERM, FORCE_COLOR,
|
||||
// TERM_PROGRAM, or determined from the TERM environment variable.
|
||||
//
|
||||
// refer the terminfo.ColorLevelFromEnv()
|
||||
// https://en.wikipedia.org/wiki/Terminfo
|
||||
func detectColorLevelFromEnv(termVal string, isWin bool) terminfo.ColorLevel {
|
||||
// check for overriding environment variables
|
||||
colorTerm, termProg, forceColor := os.Getenv("COLORTERM"), os.Getenv("TERM_PROGRAM"), os.Getenv("FORCE_COLOR")
|
||||
switch {
|
||||
case strings.Contains(colorTerm, "truecolor") || strings.Contains(colorTerm, "24bit"):
|
||||
if termVal == "screen" { // on TERM=screen: not support true-color
|
||||
return terminfo.ColorLevelHundreds
|
||||
}
|
||||
return terminfo.ColorLevelMillions
|
||||
case colorTerm != "" || forceColor != "":
|
||||
return terminfo.ColorLevelBasic
|
||||
case termProg == "Apple_Terminal":
|
||||
return terminfo.ColorLevelHundreds
|
||||
case termProg == "Terminus" || termProg == "Hyper":
|
||||
if termVal == "screen" { // on TERM=screen: not support true-color
|
||||
return terminfo.ColorLevelHundreds
|
||||
}
|
||||
return terminfo.ColorLevelMillions
|
||||
case termProg == "iTerm.app":
|
||||
if termVal == "screen" { // on TERM=screen: not support true-color
|
||||
return terminfo.ColorLevelHundreds
|
||||
}
|
||||
|
||||
// check iTerm version
|
||||
ver := os.Getenv("TERM_PROGRAM_VERSION")
|
||||
if ver != "" {
|
||||
i, err := strconv.Atoi(strings.Split(ver, ".")[0])
|
||||
if err != nil {
|
||||
saveInternalError(terminfo.ErrInvalidTermProgramVersion)
|
||||
// return terminfo.ColorLevelNone
|
||||
return terminfo.ColorLevelHundreds
|
||||
}
|
||||
if i == 3 {
|
||||
return terminfo.ColorLevelMillions
|
||||
}
|
||||
}
|
||||
return terminfo.ColorLevelHundreds
|
||||
}
|
||||
|
||||
// otherwise determine from TERM's max_colors capability
|
||||
if !isWin && termVal != "" {
|
||||
debugf("TERM=%s - check color level by load terminfo file", termVal)
|
||||
ti, err := terminfo.Load(termVal)
|
||||
if err != nil {
|
||||
saveInternalError(err)
|
||||
return terminfo.ColorLevelNone
|
||||
}
|
||||
|
||||
debugf("the loaded term info file is: %s", ti.File)
|
||||
v, ok := ti.Nums[terminfo.MaxColors]
|
||||
switch {
|
||||
case !ok || v <= 16:
|
||||
return terminfo.ColorLevelNone
|
||||
case ok && v >= 256:
|
||||
return terminfo.ColorLevelHundreds
|
||||
}
|
||||
return terminfo.ColorLevelBasic
|
||||
}
|
||||
|
||||
// no TERM env value. default return none level
|
||||
return terminfo.ColorLevelNone
|
||||
// return terminfo.ColorLevelBasic
|
||||
}
|
||||
|
||||
var detectedWSL bool
|
||||
var wslContents string
|
||||
|
||||
// https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
|
||||
func detectWSL() bool {
|
||||
if !detectedWSL {
|
||||
b := make([]byte, 1024)
|
||||
// `cat /proc/version`
|
||||
// on mac:
|
||||
// !not the file!
|
||||
// on linux(debian,ubuntu,alpine):
|
||||
// Linux version 4.19.121-linuxkit (root@18b3f92ade35) (gcc version 9.2.0 (Alpine 9.2.0)) #1 SMP Thu Jan 21 15:36:34 UTC 2021
|
||||
// on win git bash, conEmu:
|
||||
// MINGW64_NT-10.0-19042 version 3.1.7-340.x86_64 (@WIN-N0G619FD3UK) (gcc version 9.3.0 (GCC) ) 2020-10-23 13:08 UTC
|
||||
// on WSL:
|
||||
// Linux version 4.4.0-19041-Microsoft (Microsoft@Microsoft.com) (gcc version 5.4.0 (GCC) ) #488-Microsoft Mon Sep 01 13:43:00 PST 2020
|
||||
f, err := os.Open("/proc/version")
|
||||
if err == nil {
|
||||
_, _ = f.Read(b) // ignore error
|
||||
if err = f.Close(); err != nil {
|
||||
saveInternalError(err)
|
||||
}
|
||||
|
||||
wslContents = string(b)
|
||||
}
|
||||
detectedWSL = true
|
||||
}
|
||||
return strings.Contains(wslContents, "Microsoft")
|
||||
}
|
||||
|
||||
// refer
|
||||
// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/detect_unix.go#L27-L45
|
||||
// detect WSL as it has True Color support
|
||||
func isWSL() bool {
|
||||
// on windows WSL:
|
||||
// - runtime.GOOS == "Linux"
|
||||
// - support true-color
|
||||
// WSL_DISTRO_NAME=Debian
|
||||
if val := os.Getenv("WSL_DISTRO_NAME"); val == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// `cat /proc/sys/kernel/osrelease`
|
||||
// on mac:
|
||||
// !not the file!
|
||||
// on linux:
|
||||
// 4.19.121-linuxkit
|
||||
// on WSL Output:
|
||||
// 4.4.0-19041-Microsoft
|
||||
wsl, err := ioutil.ReadFile("/proc/sys/kernel/osrelease")
|
||||
if err != nil {
|
||||
saveInternalError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
// it gives "Microsoft" for WSL and "microsoft" for WSL 2
|
||||
// it support True-color
|
||||
content := strings.ToLower(string(wsl))
|
||||
return strings.Contains(content, "microsoft")
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* helper methods for check env
|
||||
*************************************************************/
|
||||
|
||||
// IsWindows OS env
|
||||
func IsWindows() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
|
||||
// IsConsole Determine whether w is one of stderr, stdout, stdin
|
||||
func IsConsole(w io.Writer) bool {
|
||||
o, ok := w.(*os.File)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
fd := o.Fd()
|
||||
|
||||
// fix: cannot use 'o == os.Stdout' to compare
|
||||
return fd == uintptr(syscall.Stdout) || fd == uintptr(syscall.Stdin) || fd == uintptr(syscall.Stderr)
|
||||
}
|
||||
|
||||
// IsMSys msys(MINGW64) environment, does not necessarily support color
|
||||
func IsMSys() bool {
|
||||
// like "MSYSTEM=MINGW64"
|
||||
if len(os.Getenv("MSYSTEM")) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsSupportColor check current console is support color.
|
||||
//
|
||||
// NOTICE: The method will detect terminal info each times,
|
||||
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
|
||||
func IsSupportColor() bool {
|
||||
return IsSupport16Color()
|
||||
}
|
||||
|
||||
// IsSupportColor check current console is support color.
|
||||
//
|
||||
// NOTICE: The method will detect terminal info each times,
|
||||
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
|
||||
func IsSupport16Color() bool {
|
||||
level, _ := detectTermColorLevel()
|
||||
return level > terminfo.ColorLevelNone
|
||||
}
|
||||
|
||||
// IsSupport256Color render check
|
||||
//
|
||||
// NOTICE: The method will detect terminal info each times,
|
||||
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
|
||||
func IsSupport256Color() bool {
|
||||
level, _ := detectTermColorLevel()
|
||||
return level > terminfo.ColorLevelBasic
|
||||
}
|
||||
|
||||
// IsSupportRGBColor check. alias of the IsSupportTrueColor()
|
||||
//
|
||||
// NOTICE: The method will detect terminal info each times,
|
||||
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
|
||||
func IsSupportRGBColor() bool {
|
||||
return IsSupportTrueColor()
|
||||
}
|
||||
|
||||
// IsSupportTrueColor render check.
|
||||
//
|
||||
// NOTICE: The method will detect terminal info each times,
|
||||
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
|
||||
//
|
||||
// ENV:
|
||||
// "COLORTERM=truecolor"
|
||||
// "COLORTERM=24bit"
|
||||
func IsSupportTrueColor() bool {
|
||||
level, _ := detectTermColorLevel()
|
||||
return level > terminfo.ColorLevelHundreds
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// +build !windows
|
||||
|
||||
// The method in the file has no effect
|
||||
// Only for compatibility with non-Windows systems
|
||||
|
||||
package color
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/xo/terminfo"
|
||||
)
|
||||
|
||||
// detect special term color support
|
||||
func detectSpecialTermColor(termVal string) (terminfo.ColorLevel, bool) {
|
||||
if termVal == "" {
|
||||
return terminfo.ColorLevelNone, false
|
||||
}
|
||||
|
||||
debugf("terminfo check fail - fallback detect color by check TERM value")
|
||||
|
||||
// on TERM=screen:
|
||||
// - support 256, not support true-color. test on macOS
|
||||
if termVal == "screen" {
|
||||
return terminfo.ColorLevelHundreds, false
|
||||
}
|
||||
|
||||
if strings.Contains(termVal, "256color") {
|
||||
return terminfo.ColorLevelHundreds, false
|
||||
}
|
||||
|
||||
if strings.Contains(termVal, "xterm") {
|
||||
return terminfo.ColorLevelHundreds, false
|
||||
// return terminfo.ColorLevelBasic, false
|
||||
}
|
||||
|
||||
// return terminfo.ColorLevelNone, nil
|
||||
return terminfo.ColorLevelBasic, false
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
//
|
||||
// Usage:
|
||||
// IsTerminal(os.Stdout.Fd())
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
return fd == uintptr(syscall.Stdout) || fd == uintptr(syscall.Stdin) || fd == uintptr(syscall.Stderr)
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
// +build windows
|
||||
|
||||
// Display color on windows
|
||||
// refer:
|
||||
// golang.org/x/sys/windows
|
||||
// golang.org/x/crypto/ssh/terminal
|
||||
// https://docs.microsoft.com/en-us/windows/console
|
||||
package color
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/xo/terminfo"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// related docs
|
||||
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences
|
||||
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
|
||||
var (
|
||||
// isMSys bool
|
||||
kernel32 *syscall.LazyDLL
|
||||
|
||||
procGetConsoleMode *syscall.LazyProc
|
||||
procSetConsoleMode *syscall.LazyProc
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !SupportColor() {
|
||||
isLikeInCmd = true
|
||||
return
|
||||
}
|
||||
|
||||
// if disabled.
|
||||
if !Enable {
|
||||
return
|
||||
}
|
||||
|
||||
// if at windows's ConEmu, Cmder, putty ... terminals not need VTP
|
||||
|
||||
// -------- try force enable colors on windows terminal -------
|
||||
tryEnableVTP(needVTP)
|
||||
|
||||
// fetch console screen buffer info
|
||||
// err := getConsoleScreenBufferInfo(uintptr(syscall.Stdout), &defScreenInfo)
|
||||
}
|
||||
|
||||
// try force enable colors on windows terminal
|
||||
func tryEnableVTP(enable bool) bool {
|
||||
if !enable {
|
||||
return false
|
||||
}
|
||||
|
||||
debugf("True-Color by enable VirtualTerminalProcessing on windows")
|
||||
|
||||
initKernel32Proc()
|
||||
|
||||
// enable colors on windows terminal
|
||||
if tryEnableOnCONOUT() {
|
||||
return true
|
||||
}
|
||||
|
||||
return tryEnableOnStdout()
|
||||
}
|
||||
|
||||
func initKernel32Proc() {
|
||||
if kernel32 != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// load related windows dll
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||
}
|
||||
|
||||
func tryEnableOnCONOUT() bool {
|
||||
outHandle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
saveInternalError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
err = EnableVirtualTerminalProcessing(outHandle, true)
|
||||
if err != nil {
|
||||
saveInternalError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func tryEnableOnStdout() bool {
|
||||
// try direct open syscall.Stdout
|
||||
err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
|
||||
if err != nil {
|
||||
saveInternalError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the Windows Version and Build Number
|
||||
var (
|
||||
winVersion, _, buildNumber = windows.RtlGetNtVersionNumbers()
|
||||
)
|
||||
|
||||
// refer
|
||||
// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/detect_windows.go#L30-L57
|
||||
// https://github.com/gookit/color/issues/25#issuecomment-738727917
|
||||
// detects the Color Level Supported on windows: cmd, powerShell
|
||||
func detectSpecialTermColor(termVal string) (tl terminfo.ColorLevel, needVTP bool) {
|
||||
if os.Getenv("ConEmuANSI") == "ON" {
|
||||
debugf("support True Color by ConEmuANSI=ON")
|
||||
// ConEmuANSI is "ON" for generic ANSI support
|
||||
// but True Color option is enabled by default
|
||||
// I am just assuming that people wouldn't have disabled it
|
||||
// Even if it is not enabled then ConEmu will auto round off
|
||||
// accordingly
|
||||
return terminfo.ColorLevelMillions, false
|
||||
}
|
||||
|
||||
// Before Windows 10 Build Number 10586, console never supported ANSI Colors
|
||||
if buildNumber < 10586 || winVersion < 10 {
|
||||
// Detect if using ANSICON on older systems
|
||||
if os.Getenv("ANSICON") != "" {
|
||||
conVersion := os.Getenv("ANSICON_VER")
|
||||
// 8 bit Colors were only supported after v1.81 release
|
||||
if conVersion >= "181" {
|
||||
return terminfo.ColorLevelHundreds, false
|
||||
}
|
||||
return terminfo.ColorLevelBasic, false
|
||||
}
|
||||
|
||||
return terminfo.ColorLevelNone, false
|
||||
}
|
||||
|
||||
// True Color is not available before build 14931 so fallback to 8 bit color.
|
||||
if buildNumber < 14931 {
|
||||
return terminfo.ColorLevelHundreds, true
|
||||
}
|
||||
|
||||
// Windows 10 build 14931 is the first release that supports 16m/TrueColor
|
||||
debugf("support True Color on windows version is >= build 14931")
|
||||
return terminfo.ColorLevelMillions, true
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* render full color code on windows(8,16,24bit color)
|
||||
*************************************************************/
|
||||
|
||||
// docs https://docs.microsoft.com/zh-cn/windows/console/getconsolemode#parameters
|
||||
const (
|
||||
// equals to docs page's ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
|
||||
EnableVirtualTerminalProcessingMode uint32 = 0x4
|
||||
)
|
||||
|
||||
// EnableVirtualTerminalProcessing Enable virtual terminal processing
|
||||
//
|
||||
// ref from github.com/konsorten/go-windows-terminal-sequences
|
||||
// doc https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
|
||||
//
|
||||
// Usage:
|
||||
// err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
|
||||
// // support print color text
|
||||
// err = EnableVirtualTerminalProcessing(syscall.Stdout, false)
|
||||
func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error {
|
||||
var mode uint32
|
||||
// Check if it is currently in the terminal
|
||||
// err := syscall.GetConsoleMode(syscall.Stdout, &mode)
|
||||
err := syscall.GetConsoleMode(stream, &mode)
|
||||
if err != nil {
|
||||
// fmt.Println("EnableVirtualTerminalProcessing", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if enable {
|
||||
mode |= EnableVirtualTerminalProcessingMode
|
||||
} else {
|
||||
mode &^= EnableVirtualTerminalProcessingMode
|
||||
}
|
||||
|
||||
ret, _, err := procSetConsoleMode.Call(uintptr(stream), uintptr(mode))
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderColorCodeOnCmd enable cmd color render.
|
||||
// func renderColorCodeOnCmd(fn func()) {
|
||||
// err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
|
||||
// // if is not in terminal, will clear color tag.
|
||||
// if err != nil {
|
||||
// // panic(err)
|
||||
// fn()
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // force open color render
|
||||
// old := ForceOpenColor()
|
||||
// fn()
|
||||
// // revert color setting
|
||||
// supportColor = old
|
||||
//
|
||||
// err = EnableVirtualTerminalProcessing(syscall.Stdout, false)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// }
|
||||
|
||||
/*************************************************************
|
||||
* render simple color code on windows
|
||||
*************************************************************/
|
||||
|
||||
// IsTty returns true if the given file descriptor is a terminal.
|
||||
func IsTty(fd uintptr) bool {
|
||||
initKernel32Proc()
|
||||
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
//
|
||||
// Usage:
|
||||
// fd := os.Stdout.Fd()
|
||||
// fd := uintptr(syscall.Stdout) // for windows
|
||||
// IsTerminal(fd)
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
initKernel32Proc()
|
||||
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/gookit/color
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,122 @@
|
|||
package color
|
||||
|
||||
import "fmt"
|
||||
|
||||
/*************************************************************
|
||||
* colored message Printer
|
||||
*************************************************************/
|
||||
|
||||
// PrinterFace interface
|
||||
type PrinterFace interface {
|
||||
fmt.Stringer
|
||||
Sprint(a ...interface{}) string
|
||||
Sprintf(format string, a ...interface{}) string
|
||||
Print(a ...interface{})
|
||||
Printf(format string, a ...interface{})
|
||||
Println(a ...interface{})
|
||||
}
|
||||
|
||||
// Printer a generic color message printer.
|
||||
//
|
||||
// Usage:
|
||||
// p := &Printer{Code: "32;45;3"}
|
||||
// p.Print("message")
|
||||
type Printer struct {
|
||||
// NoColor disable color.
|
||||
NoColor bool
|
||||
// Code color code string. eg "32;45;3"
|
||||
Code string
|
||||
}
|
||||
|
||||
// NewPrinter instance
|
||||
func NewPrinter(colorCode string) *Printer {
|
||||
return &Printer{Code: colorCode}
|
||||
}
|
||||
|
||||
// String returns color code string. eg: "32;45;3"
|
||||
func (p *Printer) String() string {
|
||||
// panic("implement me")
|
||||
return p.Code
|
||||
}
|
||||
|
||||
// Sprint returns rendering colored messages
|
||||
func (p *Printer) Sprint(a ...interface{}) string {
|
||||
return RenderCode(p.String(), a...)
|
||||
}
|
||||
|
||||
// Sprintf returns format and rendering colored messages
|
||||
func (p *Printer) Sprintf(format string, a ...interface{}) string {
|
||||
return RenderString(p.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Print rendering colored messages
|
||||
func (p *Printer) Print(a ...interface{}) {
|
||||
doPrintV2(p.String(), fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Printf format and rendering colored messages
|
||||
func (p *Printer) Printf(format string, a ...interface{}) {
|
||||
doPrintV2(p.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Println rendering colored messages with newline
|
||||
func (p *Printer) Println(a ...interface{}) {
|
||||
doPrintlnV2(p.Code, a)
|
||||
}
|
||||
|
||||
// IsEmpty color code
|
||||
func (p *Printer) IsEmpty() bool {
|
||||
return p.Code == ""
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* SimplePrinter struct
|
||||
*************************************************************/
|
||||
|
||||
// SimplePrinter use for quick use color print on inject to struct
|
||||
type SimplePrinter struct{}
|
||||
|
||||
// Print message
|
||||
func (s *SimplePrinter) Print(v ...interface{}) {
|
||||
Print(v...)
|
||||
}
|
||||
|
||||
// Printf message
|
||||
func (s *SimplePrinter) Printf(format string, v ...interface{}) {
|
||||
Printf(format, v...)
|
||||
}
|
||||
|
||||
// Println message
|
||||
func (s *SimplePrinter) Println(v ...interface{}) {
|
||||
Println(v...)
|
||||
}
|
||||
|
||||
// Infof message
|
||||
func (s *SimplePrinter) Infof(format string, a ...interface{}) {
|
||||
Info.Printf(format, a...)
|
||||
}
|
||||
|
||||
// Infoln message
|
||||
func (s *SimplePrinter) Infoln(a ...interface{}) {
|
||||
Info.Println(a...)
|
||||
}
|
||||
|
||||
// Warnf message
|
||||
func (s *SimplePrinter) Warnf(format string, a ...interface{}) {
|
||||
Warn.Printf(format, a...)
|
||||
}
|
||||
|
||||
// Warnln message
|
||||
func (s *SimplePrinter) Warnln(a ...interface{}) {
|
||||
Warn.Println(a...)
|
||||
}
|
||||
|
||||
// Errorf message
|
||||
func (s *SimplePrinter) Errorf(format string, a ...interface{}) {
|
||||
Error.Printf(format, a...)
|
||||
}
|
||||
|
||||
// Errorln message
|
||||
func (s *SimplePrinter) Errorln(a ...interface{}) {
|
||||
Error.Println(a...)
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package color
|
||||
|
||||
/*************************************************************
|
||||
* quick use color print message
|
||||
*************************************************************/
|
||||
|
||||
// Redp print message with Red color
|
||||
func Redp(a ...interface{}) {
|
||||
Red.Print(a...)
|
||||
}
|
||||
|
||||
// Redln print message line with Red color
|
||||
func Redln(a ...interface{}) {
|
||||
Red.Println(a...)
|
||||
}
|
||||
|
||||
// Bluep print message with Blue color
|
||||
func Bluep(a ...interface{}) {
|
||||
Blue.Print(a...)
|
||||
}
|
||||
|
||||
// Blueln print message line with Blue color
|
||||
func Blueln(a ...interface{}) {
|
||||
Blue.Println(a...)
|
||||
}
|
||||
|
||||
// Cyanp print message with Cyan color
|
||||
func Cyanp(a ...interface{}) {
|
||||
Cyan.Print(a...)
|
||||
}
|
||||
|
||||
// Cyanln print message line with Cyan color
|
||||
func Cyanln(a ...interface{}) {
|
||||
Cyan.Println(a...)
|
||||
}
|
||||
|
||||
// Grayp print message with Gray color
|
||||
func Grayp(a ...interface{}) {
|
||||
Gray.Print(a...)
|
||||
}
|
||||
|
||||
// Grayln print message line with Gray color
|
||||
func Grayln(a ...interface{}) {
|
||||
Gray.Println(a...)
|
||||
}
|
||||
|
||||
// Greenp print message with Green color
|
||||
func Greenp(a ...interface{}) {
|
||||
Green.Print(a...)
|
||||
}
|
||||
|
||||
// Greenln print message line with Green color
|
||||
func Greenln(a ...interface{}) {
|
||||
Green.Println(a...)
|
||||
}
|
||||
|
||||
// Yellowp print message with Yellow color
|
||||
func Yellowp(a ...interface{}) {
|
||||
Yellow.Print(a...)
|
||||
}
|
||||
|
||||
// Yellowln print message line with Yellow color
|
||||
func Yellowln(a ...interface{}) {
|
||||
Yellow.Println(a...)
|
||||
}
|
||||
|
||||
// Magentap print message with Magenta color
|
||||
func Magentap(a ...interface{}) {
|
||||
Magenta.Print(a...)
|
||||
}
|
||||
|
||||
// Magentaln print message line with Magenta color
|
||||
func Magentaln(a ...interface{}) {
|
||||
Magenta.Println(a...)
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* quick use style print message
|
||||
*************************************************************/
|
||||
|
||||
// Infof print message with Info style
|
||||
func Infof(format string, a ...interface{}) {
|
||||
Info.Printf(format, a...)
|
||||
}
|
||||
|
||||
// Infoln print message with Info style
|
||||
func Infoln(a ...interface{}) {
|
||||
Info.Println(a...)
|
||||
}
|
||||
|
||||
// Errorf print message with Error style
|
||||
func Errorf(format string, a ...interface{}) {
|
||||
Error.Printf(format, a...)
|
||||
}
|
||||
|
||||
// Errorln print message with Error style
|
||||
func Errorln(a ...interface{}) {
|
||||
Error.Println(a...)
|
||||
}
|
||||
|
||||
// Warnf print message with Warn style
|
||||
func Warnf(format string, a ...interface{}) {
|
||||
Warn.Printf(format, a...)
|
||||
}
|
||||
|
||||
// Warnln print message with Warn style
|
||||
func Warnln(a ...interface{}) {
|
||||
Warn.Println(a...)
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*************************************************************
|
||||
* 16 color Style
|
||||
*************************************************************/
|
||||
|
||||
// Style a 16 color style. can add: fg color, bg color, color options
|
||||
//
|
||||
// Example:
|
||||
// color.Style{color.FgGreen}.Print("message")
|
||||
type Style []Color
|
||||
|
||||
// New create a custom style
|
||||
//
|
||||
// Usage:
|
||||
// color.New(color.FgGreen).Print("message")
|
||||
// equals to:
|
||||
// color.Style{color.FgGreen}.Print("message")
|
||||
func New(colors ...Color) Style {
|
||||
return colors
|
||||
}
|
||||
|
||||
// Save to global styles map
|
||||
func (s Style) Save(name string) {
|
||||
AddStyle(name, s)
|
||||
}
|
||||
|
||||
// Add to global styles map
|
||||
func (s *Style) Add(cs ...Color) {
|
||||
*s = append(*s, cs...)
|
||||
}
|
||||
|
||||
// Render render text
|
||||
// Usage:
|
||||
// color.New(color.FgGreen).Render("text")
|
||||
// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text")
|
||||
func (s Style) Render(a ...interface{}) string {
|
||||
return RenderCode(s.String(), a...)
|
||||
}
|
||||
|
||||
// Renderln render text line.
|
||||
// like Println, will add spaces for each argument
|
||||
// Usage:
|
||||
// color.New(color.FgGreen).Renderln("text", "more")
|
||||
// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text", "more")
|
||||
func (s Style) Renderln(a ...interface{}) string {
|
||||
return RenderWithSpaces(s.String(), a...)
|
||||
}
|
||||
|
||||
// Sprint is alias of the 'Render'
|
||||
func (s Style) Sprint(a ...interface{}) string {
|
||||
return RenderCode(s.String(), a...)
|
||||
}
|
||||
|
||||
// Sprintf format and render message.
|
||||
func (s Style) Sprintf(format string, a ...interface{}) string {
|
||||
return RenderString(s.String(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Print render and Print text
|
||||
func (s Style) Print(a ...interface{}) {
|
||||
doPrintV2(s.String(), fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Printf render and print text
|
||||
func (s Style) Printf(format string, a ...interface{}) {
|
||||
doPrintV2(s.Code(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Println render and print text line
|
||||
func (s Style) Println(a ...interface{}) {
|
||||
doPrintlnV2(s.String(), a)
|
||||
}
|
||||
|
||||
// Code convert to code string. returns like "32;45;3"
|
||||
func (s Style) Code() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// String convert to code string. returns like "32;45;3"
|
||||
func (s Style) String() string {
|
||||
return Colors2code(s...)
|
||||
}
|
||||
|
||||
// IsEmpty style
|
||||
func (s Style) IsEmpty() bool {
|
||||
return len(s) == 0
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Theme(extended Style)
|
||||
*************************************************************/
|
||||
|
||||
// Theme definition. extends from Style
|
||||
type Theme struct {
|
||||
// Name theme name
|
||||
Name string
|
||||
// Style for the theme
|
||||
Style
|
||||
}
|
||||
|
||||
// NewTheme instance
|
||||
func NewTheme(name string, style Style) *Theme {
|
||||
return &Theme{name, style}
|
||||
}
|
||||
|
||||
// Save to themes map
|
||||
func (t *Theme) Save() {
|
||||
AddTheme(t.Name, t.Style)
|
||||
}
|
||||
|
||||
// Tips use name as title, only apply style for name
|
||||
func (t *Theme) Tips(format string, a ...interface{}) {
|
||||
// only apply style for name
|
||||
t.Print(strings.ToUpper(t.Name) + ": ")
|
||||
Printf(format+"\n", a...)
|
||||
}
|
||||
|
||||
// Prompt use name as title, and apply style for message
|
||||
func (t *Theme) Prompt(format string, a ...interface{}) {
|
||||
title := strings.ToUpper(t.Name) + ":"
|
||||
t.Println(title, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Block like Prompt, but will wrap a empty line
|
||||
func (t *Theme) Block(format string, a ...interface{}) {
|
||||
title := strings.ToUpper(t.Name) + ":\n"
|
||||
|
||||
t.Println(title, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Theme: internal themes
|
||||
*************************************************************/
|
||||
|
||||
// internal themes(like bootstrap style)
|
||||
// Usage:
|
||||
// color.Info.Print("message")
|
||||
// color.Info.Printf("a %s message", "test")
|
||||
// color.Warn.Println("message")
|
||||
// color.Error.Println("message")
|
||||
var (
|
||||
// Info color style
|
||||
Info = &Theme{"info", Style{OpReset, FgGreen}}
|
||||
// Note color style
|
||||
Note = &Theme{"note", Style{OpBold, FgLightCyan}}
|
||||
// Warn color style
|
||||
Warn = &Theme{"warning", Style{OpBold, FgYellow}}
|
||||
// Light color style
|
||||
Light = &Theme{"light", Style{FgLightWhite, BgBlack}}
|
||||
// Error color style
|
||||
Error = &Theme{"error", Style{FgLightWhite, BgRed}}
|
||||
// Danger color style
|
||||
Danger = &Theme{"danger", Style{OpBold, FgRed}}
|
||||
// Debug color style
|
||||
Debug = &Theme{"debug", Style{OpReset, FgCyan}}
|
||||
// Notice color style
|
||||
Notice = &Theme{"notice", Style{OpBold, FgCyan}}
|
||||
// Comment color style
|
||||
Comment = &Theme{"comment", Style{OpReset, FgYellow}}
|
||||
// Success color style
|
||||
Success = &Theme{"success", Style{OpBold, FgGreen}}
|
||||
// Primary color style
|
||||
Primary = &Theme{"primary", Style{OpReset, FgBlue}}
|
||||
// Question color style
|
||||
Question = &Theme{"question", Style{OpReset, FgMagenta}}
|
||||
// Secondary color style
|
||||
Secondary = &Theme{"secondary", Style{FgDarkGray}}
|
||||
)
|
||||
|
||||
// Themes internal defined themes.
|
||||
// Usage:
|
||||
// color.Themes["info"].Println("message")
|
||||
var Themes = map[string]*Theme{
|
||||
"info": Info,
|
||||
"note": Note,
|
||||
"light": Light,
|
||||
"error": Error,
|
||||
|
||||
"debug": Debug,
|
||||
"danger": Danger,
|
||||
"notice": Notice,
|
||||
"success": Success,
|
||||
"comment": Comment,
|
||||
"primary": Primary,
|
||||
"warning": Warn,
|
||||
|
||||
"question": Question,
|
||||
"secondary": Secondary,
|
||||
}
|
||||
|
||||
// AddTheme add a theme and style
|
||||
func AddTheme(name string, style Style) {
|
||||
Themes[name] = NewTheme(name, style)
|
||||
Styles[name] = style
|
||||
}
|
||||
|
||||
// GetTheme get defined theme by name
|
||||
func GetTheme(name string) *Theme {
|
||||
return Themes[name]
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* internal styles
|
||||
*************************************************************/
|
||||
|
||||
// Styles internal defined styles, like bootstrap styles.
|
||||
// Usage:
|
||||
// color.Styles["info"].Println("message")
|
||||
var Styles = map[string]Style{
|
||||
"info": {OpReset, FgGreen},
|
||||
"note": {OpBold, FgLightCyan},
|
||||
"light": {FgLightWhite, BgRed},
|
||||
"error": {FgLightWhite, BgRed},
|
||||
|
||||
"danger": {OpBold, FgRed},
|
||||
"notice": {OpBold, FgCyan},
|
||||
"success": {OpBold, FgGreen},
|
||||
"comment": {OpReset, FgMagenta},
|
||||
"primary": {OpReset, FgBlue},
|
||||
"warning": {OpBold, FgYellow},
|
||||
|
||||
"question": {OpReset, FgMagenta},
|
||||
"secondary": {FgDarkGray},
|
||||
}
|
||||
|
||||
// some style name alias
|
||||
var styleAliases = map[string]string{
|
||||
"err": "error",
|
||||
"suc": "success",
|
||||
"warn": "warning",
|
||||
}
|
||||
|
||||
// AddStyle add a style
|
||||
func AddStyle(name string, s Style) {
|
||||
Styles[name] = s
|
||||
}
|
||||
|
||||
// GetStyle get defined style by name
|
||||
func GetStyle(name string) Style {
|
||||
if s, ok := Styles[name]; ok {
|
||||
return s
|
||||
}
|
||||
|
||||
if realName, ok := styleAliases[name]; ok {
|
||||
return Styles[realName]
|
||||
}
|
||||
|
||||
// empty style
|
||||
return New()
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* color scheme
|
||||
*************************************************************/
|
||||
|
||||
// Scheme struct
|
||||
type Scheme struct {
|
||||
Name string
|
||||
Styles map[string]Style
|
||||
}
|
||||
|
||||
// NewScheme create new Scheme
|
||||
func NewScheme(name string, styles map[string]Style) *Scheme {
|
||||
return &Scheme{Name: name, Styles: styles}
|
||||
}
|
||||
|
||||
// NewDefaultScheme create an defuault color Scheme
|
||||
func NewDefaultScheme(name string) *Scheme {
|
||||
return NewScheme(name, map[string]Style{
|
||||
"info": {OpReset, FgGreen},
|
||||
"warn": {OpBold, FgYellow},
|
||||
"error": {FgLightWhite, BgRed},
|
||||
})
|
||||
}
|
||||
|
||||
// Style get by name
|
||||
func (s *Scheme) Style(name string) Style {
|
||||
return s.Styles[name]
|
||||
}
|
||||
|
||||
// Infof message print
|
||||
func (s *Scheme) Infof(format string, a ...interface{}) {
|
||||
s.Styles["info"].Printf(format, a...)
|
||||
}
|
||||
|
||||
// Infoln message print
|
||||
func (s *Scheme) Infoln(v ...interface{}) {
|
||||
s.Styles["info"].Println(v...)
|
||||
}
|
||||
|
||||
// Warnf message print
|
||||
func (s *Scheme) Warnf(format string, a ...interface{}) {
|
||||
s.Styles["warn"].Printf(format, a...)
|
||||
}
|
||||
|
||||
// Warnln message print
|
||||
func (s *Scheme) Warnln(v ...interface{}) {
|
||||
s.Styles["warn"].Println(v...)
|
||||
}
|
||||
|
||||
// Errorf message print
|
||||
func (s *Scheme) Errorf(format string, a ...interface{}) {
|
||||
s.Styles["error"].Printf(format, a...)
|
||||
}
|
||||
|
||||
// Errorln message print
|
||||
func (s *Scheme) Errorln(v ...interface{}) {
|
||||
s.Styles["error"].Println(v...)
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SetTerminal by given code.
|
||||
func SetTerminal(code string) error {
|
||||
if !Enable || !SupportColor() {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := fmt.Fprintf(output, SettingTpl, code)
|
||||
return err
|
||||
}
|
||||
|
||||
// ResetTerminal terminal setting.
|
||||
func ResetTerminal() error {
|
||||
if !Enable || !SupportColor() {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := fmt.Fprint(output, ResetSet)
|
||||
return err
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* print methods(will auto parse color tags)
|
||||
*************************************************************/
|
||||
|
||||
// Print render color tag and print messages
|
||||
func Print(a ...interface{}) {
|
||||
Fprint(output, a...)
|
||||
}
|
||||
|
||||
// Printf format and print messages
|
||||
func Printf(format string, a ...interface{}) {
|
||||
Fprintf(output, format, a...)
|
||||
}
|
||||
|
||||
// Println messages with new line
|
||||
func Println(a ...interface{}) {
|
||||
Fprintln(output, a...)
|
||||
}
|
||||
|
||||
// Fprint print rendered messages to writer
|
||||
// Notice: will ignore print error
|
||||
func Fprint(w io.Writer, a ...interface{}) {
|
||||
_, err := fmt.Fprint(w, Render(a...))
|
||||
saveInternalError(err)
|
||||
|
||||
// if isLikeInCmd {
|
||||
// renderColorCodeOnCmd(func() {
|
||||
// _, _ = fmt.Fprint(w, Render(a...))
|
||||
// })
|
||||
// } else {
|
||||
// _, _ = fmt.Fprint(w, Render(a...))
|
||||
// }
|
||||
}
|
||||
|
||||
// Fprintf print format and rendered messages to writer.
|
||||
// Notice: will ignore print error
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) {
|
||||
str := fmt.Sprintf(format, a...)
|
||||
_, err := fmt.Fprint(w, ReplaceTag(str))
|
||||
saveInternalError(err)
|
||||
}
|
||||
|
||||
// Fprintln print rendered messages line to writer
|
||||
// Notice: will ignore print error
|
||||
func Fprintln(w io.Writer, a ...interface{}) {
|
||||
str := formatArgsForPrintln(a)
|
||||
_, err := fmt.Fprintln(w, ReplaceTag(str))
|
||||
saveInternalError(err)
|
||||
}
|
||||
|
||||
// Lprint passes colored messages to a log.Logger for printing.
|
||||
// Notice: should be goroutine safe
|
||||
func Lprint(l *log.Logger, a ...interface{}) {
|
||||
l.Print(Render(a...))
|
||||
}
|
||||
|
||||
// Render parse color tags, return rendered string.
|
||||
// Usage:
|
||||
// text := Render("<info>hello</> <cyan>world</>!")
|
||||
// fmt.Println(text)
|
||||
func Render(a ...interface{}) string {
|
||||
if len(a) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ReplaceTag(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprint parse color tags, return rendered string
|
||||
func Sprint(a ...interface{}) string {
|
||||
if len(a) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ReplaceTag(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintf format and return rendered string
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return ReplaceTag(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// String alias of the ReplaceTag
|
||||
func String(s string) string {
|
||||
return ReplaceTag(s)
|
||||
}
|
||||
|
||||
// Text alias of the ReplaceTag
|
||||
func Text(s string) string {
|
||||
return ReplaceTag(s)
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* helper methods for print
|
||||
*************************************************************/
|
||||
|
||||
// new implementation, support render full color code on pwsh.exe, cmd.exe
|
||||
func doPrintV2(code, str string) {
|
||||
_, err := fmt.Fprint(output, RenderString(code, str))
|
||||
saveInternalError(err)
|
||||
|
||||
// if isLikeInCmd {
|
||||
// renderColorCodeOnCmd(func() {
|
||||
// _, _ = fmt.Fprint(output, RenderString(code, str))
|
||||
// })
|
||||
// } else {
|
||||
// _, _ = fmt.Fprint(output, RenderString(code, str))
|
||||
// }
|
||||
}
|
||||
|
||||
// new implementation, support render full color code on pwsh.exe, cmd.exe
|
||||
func doPrintlnV2(code string, args []interface{}) {
|
||||
str := formatArgsForPrintln(args)
|
||||
_, err := fmt.Fprintln(output, RenderString(code, str))
|
||||
saveInternalError(err)
|
||||
}
|
||||
|
||||
// if use Println, will add spaces for each arg
|
||||
func formatArgsForPrintln(args []interface{}) (message string) {
|
||||
if ln := len(args); ln == 0 {
|
||||
message = ""
|
||||
} else if ln == 1 {
|
||||
message = fmt.Sprint(args[0])
|
||||
} else {
|
||||
message = fmt.Sprintln(args...)
|
||||
// clear last "\n"
|
||||
message = message[:len(message)-1]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* helper methods
|
||||
*************************************************************/
|
||||
|
||||
// is on debug mode
|
||||
// func isDebugMode() bool {
|
||||
// return debugMode == "on"
|
||||
// }
|
||||
|
||||
func debugf(f string, v ...interface{}) {
|
||||
if debugMode {
|
||||
fmt.Print("COLOR_DEBUG: ")
|
||||
fmt.Printf(f, v...)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// equals: return ok ? val1 : val2
|
||||
func compareVal(ok bool, val1, val2 uint8) uint8 {
|
||||
if ok {
|
||||
return val1
|
||||
}
|
||||
return val2
|
||||
}
|
||||
|
||||
func saveInternalError(err error) {
|
||||
if err != nil {
|
||||
debugf("inner error: %s", err.Error())
|
||||
innerErrs = append(innerErrs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func stringToArr(str, sep string) (arr []string) {
|
||||
str = strings.TrimSpace(str)
|
||||
if str == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ss := strings.Split(str, sep)
|
||||
for _, val := range ss {
|
||||
if val = strings.TrimSpace(val); val != "" {
|
||||
arr = append(arr, val)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go generate
|
||||
- git diff --cached --exit-code
|
||||
- ./go.test.sh
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,27 @@
|
|||
go-runewidth
|
||||
============
|
||||
|
||||
[![Build Status](https://travis-ci.org/mattn/go-runewidth.png?branch=master)](https://travis-ci.org/mattn/go-runewidth)
|
||||
[![Codecov](https://codecov.io/gh/mattn/go-runewidth/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-runewidth)
|
||||
[![GoDoc](https://godoc.org/github.com/mattn/go-runewidth?status.svg)](http://godoc.org/github.com/mattn/go-runewidth)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-runewidth)](https://goreportcard.com/report/github.com/mattn/go-runewidth)
|
||||
|
||||
Provides functions to get fixed width of the character or string.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```go
|
||||
runewidth.StringWidth("つのだ☆HIRO") == 12
|
||||
```
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
Yasuhiro Matsumoto
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
under the MIT License: http://mattn.mit-license.org/2013
|
|
@ -0,0 +1,5 @@
|
|||
module github.com/mattn/go-runewidth
|
||||
|
||||
go 1.9
|
||||
|
||||
require github.com/rivo/uniseg v0.2.0
|
|
@ -0,0 +1,2 @@
|
|||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
|
@ -0,0 +1,273 @@
|
|||
package runewidth
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
//go:generate go run script/generate.go
|
||||
|
||||
var (
|
||||
// EastAsianWidth will be set true if the current locale is CJK
|
||||
EastAsianWidth bool
|
||||
|
||||
// StrictEmojiNeutral should be set false if handle broken fonts
|
||||
StrictEmojiNeutral bool = true
|
||||
|
||||
// DefaultCondition is a condition in current locale
|
||||
DefaultCondition = &Condition{
|
||||
EastAsianWidth: false,
|
||||
StrictEmojiNeutral: true,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
handleEnv()
|
||||
}
|
||||
|
||||
func handleEnv() {
|
||||
env := os.Getenv("RUNEWIDTH_EASTASIAN")
|
||||
if env == "" {
|
||||
EastAsianWidth = IsEastAsian()
|
||||
} else {
|
||||
EastAsianWidth = env == "1"
|
||||
}
|
||||
// update DefaultCondition
|
||||
DefaultCondition.EastAsianWidth = EastAsianWidth
|
||||
}
|
||||
|
||||
type interval struct {
|
||||
first rune
|
||||
last rune
|
||||
}
|
||||
|
||||
type table []interval
|
||||
|
||||
func inTables(r rune, ts ...table) bool {
|
||||
for _, t := range ts {
|
||||
if inTable(r, t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func inTable(r rune, t table) bool {
|
||||
if r < t[0].first {
|
||||
return false
|
||||
}
|
||||
|
||||
bot := 0
|
||||
top := len(t) - 1
|
||||
for top >= bot {
|
||||
mid := (bot + top) >> 1
|
||||
|
||||
switch {
|
||||
case t[mid].last < r:
|
||||
bot = mid + 1
|
||||
case t[mid].first > r:
|
||||
top = mid - 1
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var private = table{
|
||||
{0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD},
|
||||
}
|
||||
|
||||
var nonprint = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
|
||||
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
|
||||
{0x2028, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
|
||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
|
||||
}
|
||||
|
||||
// Condition have flag EastAsianWidth whether the current locale is CJK or not.
|
||||
type Condition struct {
|
||||
EastAsianWidth bool
|
||||
StrictEmojiNeutral bool
|
||||
}
|
||||
|
||||
// NewCondition return new instance of Condition which is current locale.
|
||||
func NewCondition() *Condition {
|
||||
return &Condition{
|
||||
EastAsianWidth: EastAsianWidth,
|
||||
StrictEmojiNeutral: StrictEmojiNeutral,
|
||||
}
|
||||
}
|
||||
|
||||
// RuneWidth returns the number of cells in r.
|
||||
// See http://www.unicode.org/reports/tr11/
|
||||
func (c *Condition) RuneWidth(r rune) int {
|
||||
// optimized version, verified by TestRuneWidthChecksums()
|
||||
if !c.EastAsianWidth {
|
||||
switch {
|
||||
case r < 0x20 || r > 0x10FFFF:
|
||||
return 0
|
||||
case (r >= 0x7F && r <= 0x9F) || r == 0xAD: // nonprint
|
||||
return 0
|
||||
case r < 0x300:
|
||||
return 1
|
||||
case inTable(r, narrow):
|
||||
return 1
|
||||
case inTables(r, nonprint, combining):
|
||||
return 0
|
||||
case inTable(r, doublewidth):
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining):
|
||||
return 0
|
||||
case inTable(r, narrow):
|
||||
return 1
|
||||
case inTables(r, ambiguous, doublewidth):
|
||||
return 2
|
||||
case !c.StrictEmojiNeutral && inTables(r, ambiguous, emoji, narrow):
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StringWidth return width as you can see
|
||||
func (c *Condition) StringWidth(s string) (width int) {
|
||||
g := uniseg.NewGraphemes(s)
|
||||
for g.Next() {
|
||||
var chWidth int
|
||||
for _, r := range g.Runes() {
|
||||
chWidth = c.RuneWidth(r)
|
||||
if chWidth > 0 {
|
||||
break // Our best guess at this point is to use the width of the first non-zero-width rune.
|
||||
}
|
||||
}
|
||||
width += chWidth
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Truncate return string truncated with w cells
|
||||
func (c *Condition) Truncate(s string, w int, tail string) string {
|
||||
if c.StringWidth(s) <= w {
|
||||
return s
|
||||
}
|
||||
w -= c.StringWidth(tail)
|
||||
var width int
|
||||
pos := len(s)
|
||||
g := uniseg.NewGraphemes(s)
|
||||
for g.Next() {
|
||||
var chWidth int
|
||||
for _, r := range g.Runes() {
|
||||
chWidth = c.RuneWidth(r)
|
||||
if chWidth > 0 {
|
||||
break // See StringWidth() for details.
|
||||
}
|
||||
}
|
||||
if width+chWidth > w {
|
||||
pos, _ = g.Positions()
|
||||
break
|
||||
}
|
||||
width += chWidth
|
||||
}
|
||||
return s[:pos] + tail
|
||||
}
|
||||
|
||||
// Wrap return string wrapped with w cells
|
||||
func (c *Condition) Wrap(s string, w int) string {
|
||||
width := 0
|
||||
out := ""
|
||||
for _, r := range []rune(s) {
|
||||
cw := c.RuneWidth(r)
|
||||
if r == '\n' {
|
||||
out += string(r)
|
||||
width = 0
|
||||
continue
|
||||
} else if width+cw > w {
|
||||
out += "\n"
|
||||
width = 0
|
||||
out += string(r)
|
||||
width += cw
|
||||
continue
|
||||
}
|
||||
out += string(r)
|
||||
width += cw
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// FillLeft return string filled in left by spaces in w cells
|
||||
func (c *Condition) FillLeft(s string, w int) string {
|
||||
width := c.StringWidth(s)
|
||||
count := w - width
|
||||
if count > 0 {
|
||||
b := make([]byte, count)
|
||||
for i := range b {
|
||||
b[i] = ' '
|
||||
}
|
||||
return string(b) + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FillRight return string filled in left by spaces in w cells
|
||||
func (c *Condition) FillRight(s string, w int) string {
|
||||
width := c.StringWidth(s)
|
||||
count := w - width
|
||||
if count > 0 {
|
||||
b := make([]byte, count)
|
||||
for i := range b {
|
||||
b[i] = ' '
|
||||
}
|
||||
return s + string(b)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// RuneWidth returns the number of cells in r.
|
||||
// See http://www.unicode.org/reports/tr11/
|
||||
func RuneWidth(r rune) int {
|
||||
return DefaultCondition.RuneWidth(r)
|
||||
}
|
||||
|
||||
// IsAmbiguousWidth returns whether is ambiguous width or not.
|
||||
func IsAmbiguousWidth(r rune) bool {
|
||||
return inTables(r, private, ambiguous)
|
||||
}
|
||||
|
||||
// IsNeutralWidth returns whether is neutral width or not.
|
||||
func IsNeutralWidth(r rune) bool {
|
||||
return inTable(r, neutral)
|
||||
}
|
||||
|
||||
// StringWidth return width as you can see
|
||||
func StringWidth(s string) (width int) {
|
||||
return DefaultCondition.StringWidth(s)
|
||||
}
|
||||
|
||||
// Truncate return string truncated with w cells
|
||||
func Truncate(s string, w int, tail string) string {
|
||||
return DefaultCondition.Truncate(s, w, tail)
|
||||
}
|
||||
|
||||
// Wrap return string wrapped with w cells
|
||||
func Wrap(s string, w int) string {
|
||||
return DefaultCondition.Wrap(s, w)
|
||||
}
|
||||
|
||||
// FillLeft return string filled in left by spaces in w cells
|
||||
func FillLeft(s string, w int) string {
|
||||
return DefaultCondition.FillLeft(s, w)
|
||||
}
|
||||
|
||||
// FillRight return string filled in left by spaces in w cells
|
||||
func FillRight(s string, w int) string {
|
||||
return DefaultCondition.FillRight(s, w)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// +build appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// +build js
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
func IsEastAsian() bool {
|
||||
// TODO: Implement this for the web. Detect east asian in a compatible way, and return true.
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// +build !windows
|
||||
// +build !js
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`)
|
||||
|
||||
var mblenTable = map[string]int{
|
||||
"utf-8": 6,
|
||||
"utf8": 6,
|
||||
"jis": 8,
|
||||
"eucjp": 3,
|
||||
"euckr": 2,
|
||||
"euccn": 2,
|
||||
"sjis": 2,
|
||||
"cp932": 2,
|
||||
"cp51932": 2,
|
||||
"cp936": 2,
|
||||
"cp949": 2,
|
||||
"cp950": 2,
|
||||
"big5": 2,
|
||||
"gbk": 2,
|
||||
"gb2312": 2,
|
||||
}
|
||||
|
||||
func isEastAsian(locale string) bool {
|
||||
charset := strings.ToLower(locale)
|
||||
r := reLoc.FindStringSubmatch(locale)
|
||||
if len(r) == 2 {
|
||||
charset = strings.ToLower(r[1])
|
||||
}
|
||||
|
||||
if strings.HasSuffix(charset, "@cjk_narrow") {
|
||||
return false
|
||||
}
|
||||
|
||||
for pos, b := range []byte(charset) {
|
||||
if b == '@' {
|
||||
charset = charset[:pos]
|
||||
break
|
||||
}
|
||||
}
|
||||
max := 1
|
||||
if m, ok := mblenTable[charset]; ok {
|
||||
max = m
|
||||
}
|
||||
if max > 1 && (charset[0] != 'u' ||
|
||||
strings.HasPrefix(locale, "ja") ||
|
||||
strings.HasPrefix(locale, "ko") ||
|
||||
strings.HasPrefix(locale, "zh")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
locale := os.Getenv("LC_ALL")
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LC_CTYPE")
|
||||
}
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LANG")
|
||||
}
|
||||
|
||||
// ignore C locale
|
||||
if locale == "POSIX" || locale == "C" {
|
||||
return false
|
||||
}
|
||||
if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') {
|
||||
return false
|
||||
}
|
||||
|
||||
return isEastAsian(locale)
|
||||
}
|
|
@ -0,0 +1,439 @@
|
|||
// Code generated by script/generate.go. DO NOT EDIT.
|
||||
|
||||
package runewidth
|
||||
|
||||
var combining = table{
|
||||
{0x0300, 0x036F}, {0x0483, 0x0489}, {0x07EB, 0x07F3},
|
||||
{0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0D00, 0x0D01},
|
||||
{0x135D, 0x135F}, {0x1A7F, 0x1A7F}, {0x1AB0, 0x1AC0},
|
||||
{0x1B6B, 0x1B73}, {0x1DC0, 0x1DF9}, {0x1DFB, 0x1DFF},
|
||||
{0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2DE0, 0x2DFF},
|
||||
{0x3099, 0x309A}, {0xA66F, 0xA672}, {0xA674, 0xA67D},
|
||||
{0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA8E0, 0xA8F1},
|
||||
{0xFE20, 0xFE2F}, {0x101FD, 0x101FD}, {0x10376, 0x1037A},
|
||||
{0x10EAB, 0x10EAC}, {0x10F46, 0x10F50}, {0x11300, 0x11301},
|
||||
{0x1133B, 0x1133C}, {0x11366, 0x1136C}, {0x11370, 0x11374},
|
||||
{0x16AF0, 0x16AF4}, {0x1D165, 0x1D169}, {0x1D16D, 0x1D172},
|
||||
{0x1D17B, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD},
|
||||
{0x1D242, 0x1D244}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018},
|
||||
{0x1E01B, 0x1E021}, {0x1E023, 0x1E024}, {0x1E026, 0x1E02A},
|
||||
{0x1E8D0, 0x1E8D6},
|
||||
}
|
||||
|
||||
var doublewidth = table{
|
||||
{0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A},
|
||||
{0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3},
|
||||
{0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653},
|
||||
{0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1},
|
||||
{0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5},
|
||||
{0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
|
||||
{0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA},
|
||||
{0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B},
|
||||
{0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E},
|
||||
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
|
||||
{0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C},
|
||||
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x2E99},
|
||||
{0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB},
|
||||
{0x3000, 0x303E}, {0x3041, 0x3096}, {0x3099, 0x30FF},
|
||||
{0x3105, 0x312F}, {0x3131, 0x318E}, {0x3190, 0x31E3},
|
||||
{0x31F0, 0x321E}, {0x3220, 0x3247}, {0x3250, 0x4DBF},
|
||||
{0x4E00, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C},
|
||||
{0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19},
|
||||
{0xFE30, 0xFE52}, {0xFE54, 0xFE66}, {0xFE68, 0xFE6B},
|
||||
{0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4},
|
||||
{0x16FF0, 0x16FF1}, {0x17000, 0x187F7}, {0x18800, 0x18CD5},
|
||||
{0x18D00, 0x18D08}, {0x1B000, 0x1B11E}, {0x1B150, 0x1B152},
|
||||
{0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, {0x1F004, 0x1F004},
|
||||
{0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A},
|
||||
{0x1F200, 0x1F202}, {0x1F210, 0x1F23B}, {0x1F240, 0x1F248},
|
||||
{0x1F250, 0x1F251}, {0x1F260, 0x1F265}, {0x1F300, 0x1F320},
|
||||
{0x1F32D, 0x1F335}, {0x1F337, 0x1F37C}, {0x1F37E, 0x1F393},
|
||||
{0x1F3A0, 0x1F3CA}, {0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0},
|
||||
{0x1F3F4, 0x1F3F4}, {0x1F3F8, 0x1F43E}, {0x1F440, 0x1F440},
|
||||
{0x1F442, 0x1F4FC}, {0x1F4FF, 0x1F53D}, {0x1F54B, 0x1F54E},
|
||||
{0x1F550, 0x1F567}, {0x1F57A, 0x1F57A}, {0x1F595, 0x1F596},
|
||||
{0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F64F}, {0x1F680, 0x1F6C5},
|
||||
{0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2}, {0x1F6D5, 0x1F6D7},
|
||||
{0x1F6EB, 0x1F6EC}, {0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB},
|
||||
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F978},
|
||||
{0x1F97A, 0x1F9CB}, {0x1F9CD, 0x1F9FF}, {0x1FA70, 0x1FA74},
|
||||
{0x1FA78, 0x1FA7A}, {0x1FA80, 0x1FA86}, {0x1FA90, 0x1FAA8},
|
||||
{0x1FAB0, 0x1FAB6}, {0x1FAC0, 0x1FAC2}, {0x1FAD0, 0x1FAD6},
|
||||
{0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
|
||||
}
|
||||
|
||||
var ambiguous = table{
|
||||
{0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8},
|
||||
{0x00AA, 0x00AA}, {0x00AD, 0x00AE}, {0x00B0, 0x00B4},
|
||||
{0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6},
|
||||
{0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1},
|
||||
{0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED},
|
||||
{0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA},
|
||||
{0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101},
|
||||
{0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B},
|
||||
{0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133},
|
||||
{0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144},
|
||||
{0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153},
|
||||
{0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE},
|
||||
{0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4},
|
||||
{0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA},
|
||||
{0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261},
|
||||
{0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB},
|
||||
{0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB},
|
||||
{0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0300, 0x036F},
|
||||
{0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1},
|
||||
{0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F},
|
||||
{0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016},
|
||||
{0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022},
|
||||
{0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033},
|
||||
{0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E},
|
||||
{0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084},
|
||||
{0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105},
|
||||
{0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116},
|
||||
{0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B},
|
||||
{0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B},
|
||||
{0x2170, 0x2179}, {0x2189, 0x2189}, {0x2190, 0x2199},
|
||||
{0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4},
|
||||
{0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203},
|
||||
{0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F},
|
||||
{0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A},
|
||||
{0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225},
|
||||
{0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237},
|
||||
{0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C},
|
||||
{0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267},
|
||||
{0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283},
|
||||
{0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299},
|
||||
{0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312},
|
||||
{0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573},
|
||||
{0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1},
|
||||
{0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7},
|
||||
{0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8},
|
||||
{0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5},
|
||||
{0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609},
|
||||
{0x260E, 0x260F}, {0x261C, 0x261C}, {0x261E, 0x261E},
|
||||
{0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661},
|
||||
{0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D},
|
||||
{0x266F, 0x266F}, {0x269E, 0x269F}, {0x26BF, 0x26BF},
|
||||
{0x26C6, 0x26CD}, {0x26CF, 0x26D3}, {0x26D5, 0x26E1},
|
||||
{0x26E3, 0x26E3}, {0x26E8, 0x26E9}, {0x26EB, 0x26F1},
|
||||
{0x26F4, 0x26F4}, {0x26F6, 0x26F9}, {0x26FB, 0x26FC},
|
||||
{0x26FE, 0x26FF}, {0x273D, 0x273D}, {0x2776, 0x277F},
|
||||
{0x2B56, 0x2B59}, {0x3248, 0x324F}, {0xE000, 0xF8FF},
|
||||
{0xFE00, 0xFE0F}, {0xFFFD, 0xFFFD}, {0x1F100, 0x1F10A},
|
||||
{0x1F110, 0x1F12D}, {0x1F130, 0x1F169}, {0x1F170, 0x1F18D},
|
||||
{0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xE0100, 0xE01EF},
|
||||
{0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD},
|
||||
}
|
||||
var narrow = table{
|
||||
{0x0020, 0x007E}, {0x00A2, 0x00A3}, {0x00A5, 0x00A6},
|
||||
{0x00AC, 0x00AC}, {0x00AF, 0x00AF}, {0x27E6, 0x27ED},
|
||||
{0x2985, 0x2986},
|
||||
}
|
||||
|
||||
var neutral = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x00A0}, {0x00A9, 0x00A9},
|
||||
{0x00AB, 0x00AB}, {0x00B5, 0x00B5}, {0x00BB, 0x00BB},
|
||||
{0x00C0, 0x00C5}, {0x00C7, 0x00CF}, {0x00D1, 0x00D6},
|
||||
{0x00D9, 0x00DD}, {0x00E2, 0x00E5}, {0x00E7, 0x00E7},
|
||||
{0x00EB, 0x00EB}, {0x00EE, 0x00EF}, {0x00F1, 0x00F1},
|
||||
{0x00F4, 0x00F6}, {0x00FB, 0x00FB}, {0x00FD, 0x00FD},
|
||||
{0x00FF, 0x0100}, {0x0102, 0x0110}, {0x0112, 0x0112},
|
||||
{0x0114, 0x011A}, {0x011C, 0x0125}, {0x0128, 0x012A},
|
||||
{0x012C, 0x0130}, {0x0134, 0x0137}, {0x0139, 0x013E},
|
||||
{0x0143, 0x0143}, {0x0145, 0x0147}, {0x014C, 0x014C},
|
||||
{0x014E, 0x0151}, {0x0154, 0x0165}, {0x0168, 0x016A},
|
||||
{0x016C, 0x01CD}, {0x01CF, 0x01CF}, {0x01D1, 0x01D1},
|
||||
{0x01D3, 0x01D3}, {0x01D5, 0x01D5}, {0x01D7, 0x01D7},
|
||||
{0x01D9, 0x01D9}, {0x01DB, 0x01DB}, {0x01DD, 0x0250},
|
||||
{0x0252, 0x0260}, {0x0262, 0x02C3}, {0x02C5, 0x02C6},
|
||||
{0x02C8, 0x02C8}, {0x02CC, 0x02CC}, {0x02CE, 0x02CF},
|
||||
{0x02D1, 0x02D7}, {0x02DC, 0x02DC}, {0x02DE, 0x02DE},
|
||||
{0x02E0, 0x02FF}, {0x0370, 0x0377}, {0x037A, 0x037F},
|
||||
{0x0384, 0x038A}, {0x038C, 0x038C}, {0x038E, 0x0390},
|
||||
{0x03AA, 0x03B0}, {0x03C2, 0x03C2}, {0x03CA, 0x0400},
|
||||
{0x0402, 0x040F}, {0x0450, 0x0450}, {0x0452, 0x052F},
|
||||
{0x0531, 0x0556}, {0x0559, 0x058A}, {0x058D, 0x058F},
|
||||
{0x0591, 0x05C7}, {0x05D0, 0x05EA}, {0x05EF, 0x05F4},
|
||||
{0x0600, 0x061C}, {0x061E, 0x070D}, {0x070F, 0x074A},
|
||||
{0x074D, 0x07B1}, {0x07C0, 0x07FA}, {0x07FD, 0x082D},
|
||||
{0x0830, 0x083E}, {0x0840, 0x085B}, {0x085E, 0x085E},
|
||||
{0x0860, 0x086A}, {0x08A0, 0x08B4}, {0x08B6, 0x08C7},
|
||||
{0x08D3, 0x0983}, {0x0985, 0x098C}, {0x098F, 0x0990},
|
||||
{0x0993, 0x09A8}, {0x09AA, 0x09B0}, {0x09B2, 0x09B2},
|
||||
{0x09B6, 0x09B9}, {0x09BC, 0x09C4}, {0x09C7, 0x09C8},
|
||||
{0x09CB, 0x09CE}, {0x09D7, 0x09D7}, {0x09DC, 0x09DD},
|
||||
{0x09DF, 0x09E3}, {0x09E6, 0x09FE}, {0x0A01, 0x0A03},
|
||||
{0x0A05, 0x0A0A}, {0x0A0F, 0x0A10}, {0x0A13, 0x0A28},
|
||||
{0x0A2A, 0x0A30}, {0x0A32, 0x0A33}, {0x0A35, 0x0A36},
|
||||
{0x0A38, 0x0A39}, {0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42},
|
||||
{0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A51, 0x0A51},
|
||||
{0x0A59, 0x0A5C}, {0x0A5E, 0x0A5E}, {0x0A66, 0x0A76},
|
||||
{0x0A81, 0x0A83}, {0x0A85, 0x0A8D}, {0x0A8F, 0x0A91},
|
||||
{0x0A93, 0x0AA8}, {0x0AAA, 0x0AB0}, {0x0AB2, 0x0AB3},
|
||||
{0x0AB5, 0x0AB9}, {0x0ABC, 0x0AC5}, {0x0AC7, 0x0AC9},
|
||||
{0x0ACB, 0x0ACD}, {0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE3},
|
||||
{0x0AE6, 0x0AF1}, {0x0AF9, 0x0AFF}, {0x0B01, 0x0B03},
|
||||
{0x0B05, 0x0B0C}, {0x0B0F, 0x0B10}, {0x0B13, 0x0B28},
|
||||
{0x0B2A, 0x0B30}, {0x0B32, 0x0B33}, {0x0B35, 0x0B39},
|
||||
{0x0B3C, 0x0B44}, {0x0B47, 0x0B48}, {0x0B4B, 0x0B4D},
|
||||
{0x0B55, 0x0B57}, {0x0B5C, 0x0B5D}, {0x0B5F, 0x0B63},
|
||||
{0x0B66, 0x0B77}, {0x0B82, 0x0B83}, {0x0B85, 0x0B8A},
|
||||
{0x0B8E, 0x0B90}, {0x0B92, 0x0B95}, {0x0B99, 0x0B9A},
|
||||
{0x0B9C, 0x0B9C}, {0x0B9E, 0x0B9F}, {0x0BA3, 0x0BA4},
|
||||
{0x0BA8, 0x0BAA}, {0x0BAE, 0x0BB9}, {0x0BBE, 0x0BC2},
|
||||
{0x0BC6, 0x0BC8}, {0x0BCA, 0x0BCD}, {0x0BD0, 0x0BD0},
|
||||
{0x0BD7, 0x0BD7}, {0x0BE6, 0x0BFA}, {0x0C00, 0x0C0C},
|
||||
{0x0C0E, 0x0C10}, {0x0C12, 0x0C28}, {0x0C2A, 0x0C39},
|
||||
{0x0C3D, 0x0C44}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D},
|
||||
{0x0C55, 0x0C56}, {0x0C58, 0x0C5A}, {0x0C60, 0x0C63},
|
||||
{0x0C66, 0x0C6F}, {0x0C77, 0x0C8C}, {0x0C8E, 0x0C90},
|
||||
{0x0C92, 0x0CA8}, {0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9},
|
||||
{0x0CBC, 0x0CC4}, {0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD},
|
||||
{0x0CD5, 0x0CD6}, {0x0CDE, 0x0CDE}, {0x0CE0, 0x0CE3},
|
||||
{0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF2}, {0x0D00, 0x0D0C},
|
||||
{0x0D0E, 0x0D10}, {0x0D12, 0x0D44}, {0x0D46, 0x0D48},
|
||||
{0x0D4A, 0x0D4F}, {0x0D54, 0x0D63}, {0x0D66, 0x0D7F},
|
||||
{0x0D81, 0x0D83}, {0x0D85, 0x0D96}, {0x0D9A, 0x0DB1},
|
||||
{0x0DB3, 0x0DBB}, {0x0DBD, 0x0DBD}, {0x0DC0, 0x0DC6},
|
||||
{0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD4}, {0x0DD6, 0x0DD6},
|
||||
{0x0DD8, 0x0DDF}, {0x0DE6, 0x0DEF}, {0x0DF2, 0x0DF4},
|
||||
{0x0E01, 0x0E3A}, {0x0E3F, 0x0E5B}, {0x0E81, 0x0E82},
|
||||
{0x0E84, 0x0E84}, {0x0E86, 0x0E8A}, {0x0E8C, 0x0EA3},
|
||||
{0x0EA5, 0x0EA5}, {0x0EA7, 0x0EBD}, {0x0EC0, 0x0EC4},
|
||||
{0x0EC6, 0x0EC6}, {0x0EC8, 0x0ECD}, {0x0ED0, 0x0ED9},
|
||||
{0x0EDC, 0x0EDF}, {0x0F00, 0x0F47}, {0x0F49, 0x0F6C},
|
||||
{0x0F71, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FBE, 0x0FCC},
|
||||
{0x0FCE, 0x0FDA}, {0x1000, 0x10C5}, {0x10C7, 0x10C7},
|
||||
{0x10CD, 0x10CD}, {0x10D0, 0x10FF}, {0x1160, 0x1248},
|
||||
{0x124A, 0x124D}, {0x1250, 0x1256}, {0x1258, 0x1258},
|
||||
{0x125A, 0x125D}, {0x1260, 0x1288}, {0x128A, 0x128D},
|
||||
{0x1290, 0x12B0}, {0x12B2, 0x12B5}, {0x12B8, 0x12BE},
|
||||
{0x12C0, 0x12C0}, {0x12C2, 0x12C5}, {0x12C8, 0x12D6},
|
||||
{0x12D8, 0x1310}, {0x1312, 0x1315}, {0x1318, 0x135A},
|
||||
{0x135D, 0x137C}, {0x1380, 0x1399}, {0x13A0, 0x13F5},
|
||||
{0x13F8, 0x13FD}, {0x1400, 0x169C}, {0x16A0, 0x16F8},
|
||||
{0x1700, 0x170C}, {0x170E, 0x1714}, {0x1720, 0x1736},
|
||||
{0x1740, 0x1753}, {0x1760, 0x176C}, {0x176E, 0x1770},
|
||||
{0x1772, 0x1773}, {0x1780, 0x17DD}, {0x17E0, 0x17E9},
|
||||
{0x17F0, 0x17F9}, {0x1800, 0x180E}, {0x1810, 0x1819},
|
||||
{0x1820, 0x1878}, {0x1880, 0x18AA}, {0x18B0, 0x18F5},
|
||||
{0x1900, 0x191E}, {0x1920, 0x192B}, {0x1930, 0x193B},
|
||||
{0x1940, 0x1940}, {0x1944, 0x196D}, {0x1970, 0x1974},
|
||||
{0x1980, 0x19AB}, {0x19B0, 0x19C9}, {0x19D0, 0x19DA},
|
||||
{0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E}, {0x1A60, 0x1A7C},
|
||||
{0x1A7F, 0x1A89}, {0x1A90, 0x1A99}, {0x1AA0, 0x1AAD},
|
||||
{0x1AB0, 0x1AC0}, {0x1B00, 0x1B4B}, {0x1B50, 0x1B7C},
|
||||
{0x1B80, 0x1BF3}, {0x1BFC, 0x1C37}, {0x1C3B, 0x1C49},
|
||||
{0x1C4D, 0x1C88}, {0x1C90, 0x1CBA}, {0x1CBD, 0x1CC7},
|
||||
{0x1CD0, 0x1CFA}, {0x1D00, 0x1DF9}, {0x1DFB, 0x1F15},
|
||||
{0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D},
|
||||
{0x1F50, 0x1F57}, {0x1F59, 0x1F59}, {0x1F5B, 0x1F5B},
|
||||
{0x1F5D, 0x1F5D}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4},
|
||||
{0x1FB6, 0x1FC4}, {0x1FC6, 0x1FD3}, {0x1FD6, 0x1FDB},
|
||||
{0x1FDD, 0x1FEF}, {0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFE},
|
||||
{0x2000, 0x200F}, {0x2011, 0x2012}, {0x2017, 0x2017},
|
||||
{0x201A, 0x201B}, {0x201E, 0x201F}, {0x2023, 0x2023},
|
||||
{0x2028, 0x202F}, {0x2031, 0x2031}, {0x2034, 0x2034},
|
||||
{0x2036, 0x203A}, {0x203C, 0x203D}, {0x203F, 0x2064},
|
||||
{0x2066, 0x2071}, {0x2075, 0x207E}, {0x2080, 0x2080},
|
||||
{0x2085, 0x208E}, {0x2090, 0x209C}, {0x20A0, 0x20A8},
|
||||
{0x20AA, 0x20AB}, {0x20AD, 0x20BF}, {0x20D0, 0x20F0},
|
||||
{0x2100, 0x2102}, {0x2104, 0x2104}, {0x2106, 0x2108},
|
||||
{0x210A, 0x2112}, {0x2114, 0x2115}, {0x2117, 0x2120},
|
||||
{0x2123, 0x2125}, {0x2127, 0x212A}, {0x212C, 0x2152},
|
||||
{0x2155, 0x215A}, {0x215F, 0x215F}, {0x216C, 0x216F},
|
||||
{0x217A, 0x2188}, {0x218A, 0x218B}, {0x219A, 0x21B7},
|
||||
{0x21BA, 0x21D1}, {0x21D3, 0x21D3}, {0x21D5, 0x21E6},
|
||||
{0x21E8, 0x21FF}, {0x2201, 0x2201}, {0x2204, 0x2206},
|
||||
{0x2209, 0x220A}, {0x220C, 0x220E}, {0x2210, 0x2210},
|
||||
{0x2212, 0x2214}, {0x2216, 0x2219}, {0x221B, 0x221C},
|
||||
{0x2221, 0x2222}, {0x2224, 0x2224}, {0x2226, 0x2226},
|
||||
{0x222D, 0x222D}, {0x222F, 0x2233}, {0x2238, 0x223B},
|
||||
{0x223E, 0x2247}, {0x2249, 0x224B}, {0x224D, 0x2251},
|
||||
{0x2253, 0x225F}, {0x2262, 0x2263}, {0x2268, 0x2269},
|
||||
{0x226C, 0x226D}, {0x2270, 0x2281}, {0x2284, 0x2285},
|
||||
{0x2288, 0x2294}, {0x2296, 0x2298}, {0x229A, 0x22A4},
|
||||
{0x22A6, 0x22BE}, {0x22C0, 0x2311}, {0x2313, 0x2319},
|
||||
{0x231C, 0x2328}, {0x232B, 0x23E8}, {0x23ED, 0x23EF},
|
||||
{0x23F1, 0x23F2}, {0x23F4, 0x2426}, {0x2440, 0x244A},
|
||||
{0x24EA, 0x24EA}, {0x254C, 0x254F}, {0x2574, 0x257F},
|
||||
{0x2590, 0x2591}, {0x2596, 0x259F}, {0x25A2, 0x25A2},
|
||||
{0x25AA, 0x25B1}, {0x25B4, 0x25B5}, {0x25B8, 0x25BB},
|
||||
{0x25BE, 0x25BF}, {0x25C2, 0x25C5}, {0x25C9, 0x25CA},
|
||||
{0x25CC, 0x25CD}, {0x25D2, 0x25E1}, {0x25E6, 0x25EE},
|
||||
{0x25F0, 0x25FC}, {0x25FF, 0x2604}, {0x2607, 0x2608},
|
||||
{0x260A, 0x260D}, {0x2610, 0x2613}, {0x2616, 0x261B},
|
||||
{0x261D, 0x261D}, {0x261F, 0x263F}, {0x2641, 0x2641},
|
||||
{0x2643, 0x2647}, {0x2654, 0x265F}, {0x2662, 0x2662},
|
||||
{0x2666, 0x2666}, {0x266B, 0x266B}, {0x266E, 0x266E},
|
||||
{0x2670, 0x267E}, {0x2680, 0x2692}, {0x2694, 0x269D},
|
||||
{0x26A0, 0x26A0}, {0x26A2, 0x26A9}, {0x26AC, 0x26BC},
|
||||
{0x26C0, 0x26C3}, {0x26E2, 0x26E2}, {0x26E4, 0x26E7},
|
||||
{0x2700, 0x2704}, {0x2706, 0x2709}, {0x270C, 0x2727},
|
||||
{0x2729, 0x273C}, {0x273E, 0x274B}, {0x274D, 0x274D},
|
||||
{0x274F, 0x2752}, {0x2756, 0x2756}, {0x2758, 0x2775},
|
||||
{0x2780, 0x2794}, {0x2798, 0x27AF}, {0x27B1, 0x27BE},
|
||||
{0x27C0, 0x27E5}, {0x27EE, 0x2984}, {0x2987, 0x2B1A},
|
||||
{0x2B1D, 0x2B4F}, {0x2B51, 0x2B54}, {0x2B5A, 0x2B73},
|
||||
{0x2B76, 0x2B95}, {0x2B97, 0x2C2E}, {0x2C30, 0x2C5E},
|
||||
{0x2C60, 0x2CF3}, {0x2CF9, 0x2D25}, {0x2D27, 0x2D27},
|
||||
{0x2D2D, 0x2D2D}, {0x2D30, 0x2D67}, {0x2D6F, 0x2D70},
|
||||
{0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6}, {0x2DA8, 0x2DAE},
|
||||
{0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE}, {0x2DC0, 0x2DC6},
|
||||
{0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6}, {0x2DD8, 0x2DDE},
|
||||
{0x2DE0, 0x2E52}, {0x303F, 0x303F}, {0x4DC0, 0x4DFF},
|
||||
{0xA4D0, 0xA62B}, {0xA640, 0xA6F7}, {0xA700, 0xA7BF},
|
||||
{0xA7C2, 0xA7CA}, {0xA7F5, 0xA82C}, {0xA830, 0xA839},
|
||||
{0xA840, 0xA877}, {0xA880, 0xA8C5}, {0xA8CE, 0xA8D9},
|
||||
{0xA8E0, 0xA953}, {0xA95F, 0xA95F}, {0xA980, 0xA9CD},
|
||||
{0xA9CF, 0xA9D9}, {0xA9DE, 0xA9FE}, {0xAA00, 0xAA36},
|
||||
{0xAA40, 0xAA4D}, {0xAA50, 0xAA59}, {0xAA5C, 0xAAC2},
|
||||
{0xAADB, 0xAAF6}, {0xAB01, 0xAB06}, {0xAB09, 0xAB0E},
|
||||
{0xAB11, 0xAB16}, {0xAB20, 0xAB26}, {0xAB28, 0xAB2E},
|
||||
{0xAB30, 0xAB6B}, {0xAB70, 0xABED}, {0xABF0, 0xABF9},
|
||||
{0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xD800, 0xDFFF},
|
||||
{0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFB1D, 0xFB36},
|
||||
{0xFB38, 0xFB3C}, {0xFB3E, 0xFB3E}, {0xFB40, 0xFB41},
|
||||
{0xFB43, 0xFB44}, {0xFB46, 0xFBC1}, {0xFBD3, 0xFD3F},
|
||||
{0xFD50, 0xFD8F}, {0xFD92, 0xFDC7}, {0xFDF0, 0xFDFD},
|
||||
{0xFE20, 0xFE2F}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC},
|
||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFC}, {0x10000, 0x1000B},
|
||||
{0x1000D, 0x10026}, {0x10028, 0x1003A}, {0x1003C, 0x1003D},
|
||||
{0x1003F, 0x1004D}, {0x10050, 0x1005D}, {0x10080, 0x100FA},
|
||||
{0x10100, 0x10102}, {0x10107, 0x10133}, {0x10137, 0x1018E},
|
||||
{0x10190, 0x1019C}, {0x101A0, 0x101A0}, {0x101D0, 0x101FD},
|
||||
{0x10280, 0x1029C}, {0x102A0, 0x102D0}, {0x102E0, 0x102FB},
|
||||
{0x10300, 0x10323}, {0x1032D, 0x1034A}, {0x10350, 0x1037A},
|
||||
{0x10380, 0x1039D}, {0x1039F, 0x103C3}, {0x103C8, 0x103D5},
|
||||
{0x10400, 0x1049D}, {0x104A0, 0x104A9}, {0x104B0, 0x104D3},
|
||||
{0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563},
|
||||
{0x1056F, 0x1056F}, {0x10600, 0x10736}, {0x10740, 0x10755},
|
||||
{0x10760, 0x10767}, {0x10800, 0x10805}, {0x10808, 0x10808},
|
||||
{0x1080A, 0x10835}, {0x10837, 0x10838}, {0x1083C, 0x1083C},
|
||||
{0x1083F, 0x10855}, {0x10857, 0x1089E}, {0x108A7, 0x108AF},
|
||||
{0x108E0, 0x108F2}, {0x108F4, 0x108F5}, {0x108FB, 0x1091B},
|
||||
{0x1091F, 0x10939}, {0x1093F, 0x1093F}, {0x10980, 0x109B7},
|
||||
{0x109BC, 0x109CF}, {0x109D2, 0x10A03}, {0x10A05, 0x10A06},
|
||||
{0x10A0C, 0x10A13}, {0x10A15, 0x10A17}, {0x10A19, 0x10A35},
|
||||
{0x10A38, 0x10A3A}, {0x10A3F, 0x10A48}, {0x10A50, 0x10A58},
|
||||
{0x10A60, 0x10A9F}, {0x10AC0, 0x10AE6}, {0x10AEB, 0x10AF6},
|
||||
{0x10B00, 0x10B35}, {0x10B39, 0x10B55}, {0x10B58, 0x10B72},
|
||||
{0x10B78, 0x10B91}, {0x10B99, 0x10B9C}, {0x10BA9, 0x10BAF},
|
||||
{0x10C00, 0x10C48}, {0x10C80, 0x10CB2}, {0x10CC0, 0x10CF2},
|
||||
{0x10CFA, 0x10D27}, {0x10D30, 0x10D39}, {0x10E60, 0x10E7E},
|
||||
{0x10E80, 0x10EA9}, {0x10EAB, 0x10EAD}, {0x10EB0, 0x10EB1},
|
||||
{0x10F00, 0x10F27}, {0x10F30, 0x10F59}, {0x10FB0, 0x10FCB},
|
||||
{0x10FE0, 0x10FF6}, {0x11000, 0x1104D}, {0x11052, 0x1106F},
|
||||
{0x1107F, 0x110C1}, {0x110CD, 0x110CD}, {0x110D0, 0x110E8},
|
||||
{0x110F0, 0x110F9}, {0x11100, 0x11134}, {0x11136, 0x11147},
|
||||
{0x11150, 0x11176}, {0x11180, 0x111DF}, {0x111E1, 0x111F4},
|
||||
{0x11200, 0x11211}, {0x11213, 0x1123E}, {0x11280, 0x11286},
|
||||
{0x11288, 0x11288}, {0x1128A, 0x1128D}, {0x1128F, 0x1129D},
|
||||
{0x1129F, 0x112A9}, {0x112B0, 0x112EA}, {0x112F0, 0x112F9},
|
||||
{0x11300, 0x11303}, {0x11305, 0x1130C}, {0x1130F, 0x11310},
|
||||
{0x11313, 0x11328}, {0x1132A, 0x11330}, {0x11332, 0x11333},
|
||||
{0x11335, 0x11339}, {0x1133B, 0x11344}, {0x11347, 0x11348},
|
||||
{0x1134B, 0x1134D}, {0x11350, 0x11350}, {0x11357, 0x11357},
|
||||
{0x1135D, 0x11363}, {0x11366, 0x1136C}, {0x11370, 0x11374},
|
||||
{0x11400, 0x1145B}, {0x1145D, 0x11461}, {0x11480, 0x114C7},
|
||||
{0x114D0, 0x114D9}, {0x11580, 0x115B5}, {0x115B8, 0x115DD},
|
||||
{0x11600, 0x11644}, {0x11650, 0x11659}, {0x11660, 0x1166C},
|
||||
{0x11680, 0x116B8}, {0x116C0, 0x116C9}, {0x11700, 0x1171A},
|
||||
{0x1171D, 0x1172B}, {0x11730, 0x1173F}, {0x11800, 0x1183B},
|
||||
{0x118A0, 0x118F2}, {0x118FF, 0x11906}, {0x11909, 0x11909},
|
||||
{0x1190C, 0x11913}, {0x11915, 0x11916}, {0x11918, 0x11935},
|
||||
{0x11937, 0x11938}, {0x1193B, 0x11946}, {0x11950, 0x11959},
|
||||
{0x119A0, 0x119A7}, {0x119AA, 0x119D7}, {0x119DA, 0x119E4},
|
||||
{0x11A00, 0x11A47}, {0x11A50, 0x11AA2}, {0x11AC0, 0x11AF8},
|
||||
{0x11C00, 0x11C08}, {0x11C0A, 0x11C36}, {0x11C38, 0x11C45},
|
||||
{0x11C50, 0x11C6C}, {0x11C70, 0x11C8F}, {0x11C92, 0x11CA7},
|
||||
{0x11CA9, 0x11CB6}, {0x11D00, 0x11D06}, {0x11D08, 0x11D09},
|
||||
{0x11D0B, 0x11D36}, {0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D},
|
||||
{0x11D3F, 0x11D47}, {0x11D50, 0x11D59}, {0x11D60, 0x11D65},
|
||||
{0x11D67, 0x11D68}, {0x11D6A, 0x11D8E}, {0x11D90, 0x11D91},
|
||||
{0x11D93, 0x11D98}, {0x11DA0, 0x11DA9}, {0x11EE0, 0x11EF8},
|
||||
{0x11FB0, 0x11FB0}, {0x11FC0, 0x11FF1}, {0x11FFF, 0x12399},
|
||||
{0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543},
|
||||
{0x13000, 0x1342E}, {0x13430, 0x13438}, {0x14400, 0x14646},
|
||||
{0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A60, 0x16A69},
|
||||
{0x16A6E, 0x16A6F}, {0x16AD0, 0x16AED}, {0x16AF0, 0x16AF5},
|
||||
{0x16B00, 0x16B45}, {0x16B50, 0x16B59}, {0x16B5B, 0x16B61},
|
||||
{0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, {0x16E40, 0x16E9A},
|
||||
{0x16F00, 0x16F4A}, {0x16F4F, 0x16F87}, {0x16F8F, 0x16F9F},
|
||||
{0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C}, {0x1BC80, 0x1BC88},
|
||||
{0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BCA3}, {0x1D000, 0x1D0F5},
|
||||
{0x1D100, 0x1D126}, {0x1D129, 0x1D1E8}, {0x1D200, 0x1D245},
|
||||
{0x1D2E0, 0x1D2F3}, {0x1D300, 0x1D356}, {0x1D360, 0x1D378},
|
||||
{0x1D400, 0x1D454}, {0x1D456, 0x1D49C}, {0x1D49E, 0x1D49F},
|
||||
{0x1D4A2, 0x1D4A2}, {0x1D4A5, 0x1D4A6}, {0x1D4A9, 0x1D4AC},
|
||||
{0x1D4AE, 0x1D4B9}, {0x1D4BB, 0x1D4BB}, {0x1D4BD, 0x1D4C3},
|
||||
{0x1D4C5, 0x1D505}, {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514},
|
||||
{0x1D516, 0x1D51C}, {0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E},
|
||||
{0x1D540, 0x1D544}, {0x1D546, 0x1D546}, {0x1D54A, 0x1D550},
|
||||
{0x1D552, 0x1D6A5}, {0x1D6A8, 0x1D7CB}, {0x1D7CE, 0x1DA8B},
|
||||
{0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006},
|
||||
{0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024},
|
||||
{0x1E026, 0x1E02A}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D},
|
||||
{0x1E140, 0x1E149}, {0x1E14E, 0x1E14F}, {0x1E2C0, 0x1E2F9},
|
||||
{0x1E2FF, 0x1E2FF}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6},
|
||||
{0x1E900, 0x1E94B}, {0x1E950, 0x1E959}, {0x1E95E, 0x1E95F},
|
||||
{0x1EC71, 0x1ECB4}, {0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03},
|
||||
{0x1EE05, 0x1EE1F}, {0x1EE21, 0x1EE22}, {0x1EE24, 0x1EE24},
|
||||
{0x1EE27, 0x1EE27}, {0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37},
|
||||
{0x1EE39, 0x1EE39}, {0x1EE3B, 0x1EE3B}, {0x1EE42, 0x1EE42},
|
||||
{0x1EE47, 0x1EE47}, {0x1EE49, 0x1EE49}, {0x1EE4B, 0x1EE4B},
|
||||
{0x1EE4D, 0x1EE4F}, {0x1EE51, 0x1EE52}, {0x1EE54, 0x1EE54},
|
||||
{0x1EE57, 0x1EE57}, {0x1EE59, 0x1EE59}, {0x1EE5B, 0x1EE5B},
|
||||
{0x1EE5D, 0x1EE5D}, {0x1EE5F, 0x1EE5F}, {0x1EE61, 0x1EE62},
|
||||
{0x1EE64, 0x1EE64}, {0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72},
|
||||
{0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C}, {0x1EE7E, 0x1EE7E},
|
||||
{0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3},
|
||||
{0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB}, {0x1EEF0, 0x1EEF1},
|
||||
{0x1F000, 0x1F003}, {0x1F005, 0x1F02B}, {0x1F030, 0x1F093},
|
||||
{0x1F0A0, 0x1F0AE}, {0x1F0B1, 0x1F0BF}, {0x1F0C1, 0x1F0CE},
|
||||
{0x1F0D1, 0x1F0F5}, {0x1F10B, 0x1F10F}, {0x1F12E, 0x1F12F},
|
||||
{0x1F16A, 0x1F16F}, {0x1F1AD, 0x1F1AD}, {0x1F1E6, 0x1F1FF},
|
||||
{0x1F321, 0x1F32C}, {0x1F336, 0x1F336}, {0x1F37D, 0x1F37D},
|
||||
{0x1F394, 0x1F39F}, {0x1F3CB, 0x1F3CE}, {0x1F3D4, 0x1F3DF},
|
||||
{0x1F3F1, 0x1F3F3}, {0x1F3F5, 0x1F3F7}, {0x1F43F, 0x1F43F},
|
||||
{0x1F441, 0x1F441}, {0x1F4FD, 0x1F4FE}, {0x1F53E, 0x1F54A},
|
||||
{0x1F54F, 0x1F54F}, {0x1F568, 0x1F579}, {0x1F57B, 0x1F594},
|
||||
{0x1F597, 0x1F5A3}, {0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F},
|
||||
{0x1F6C6, 0x1F6CB}, {0x1F6CD, 0x1F6CF}, {0x1F6D3, 0x1F6D4},
|
||||
{0x1F6E0, 0x1F6EA}, {0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F773},
|
||||
{0x1F780, 0x1F7D8}, {0x1F800, 0x1F80B}, {0x1F810, 0x1F847},
|
||||
{0x1F850, 0x1F859}, {0x1F860, 0x1F887}, {0x1F890, 0x1F8AD},
|
||||
{0x1F8B0, 0x1F8B1}, {0x1F900, 0x1F90B}, {0x1F93B, 0x1F93B},
|
||||
{0x1F946, 0x1F946}, {0x1FA00, 0x1FA53}, {0x1FA60, 0x1FA6D},
|
||||
{0x1FB00, 0x1FB92}, {0x1FB94, 0x1FBCA}, {0x1FBF0, 0x1FBF9},
|
||||
{0xE0001, 0xE0001}, {0xE0020, 0xE007F},
|
||||
}
|
||||
|
||||
var emoji = table{
|
||||
{0x203C, 0x203C}, {0x2049, 0x2049}, {0x2122, 0x2122},
|
||||
{0x2139, 0x2139}, {0x2194, 0x2199}, {0x21A9, 0x21AA},
|
||||
{0x231A, 0x231B}, {0x2328, 0x2328}, {0x2388, 0x2388},
|
||||
{0x23CF, 0x23CF}, {0x23E9, 0x23F3}, {0x23F8, 0x23FA},
|
||||
{0x24C2, 0x24C2}, {0x25AA, 0x25AB}, {0x25B6, 0x25B6},
|
||||
{0x25C0, 0x25C0}, {0x25FB, 0x25FE}, {0x2600, 0x2605},
|
||||
{0x2607, 0x2612}, {0x2614, 0x2685}, {0x2690, 0x2705},
|
||||
{0x2708, 0x2712}, {0x2714, 0x2714}, {0x2716, 0x2716},
|
||||
{0x271D, 0x271D}, {0x2721, 0x2721}, {0x2728, 0x2728},
|
||||
{0x2733, 0x2734}, {0x2744, 0x2744}, {0x2747, 0x2747},
|
||||
{0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755},
|
||||
{0x2757, 0x2757}, {0x2763, 0x2767}, {0x2795, 0x2797},
|
||||
{0x27A1, 0x27A1}, {0x27B0, 0x27B0}, {0x27BF, 0x27BF},
|
||||
{0x2934, 0x2935}, {0x2B05, 0x2B07}, {0x2B1B, 0x2B1C},
|
||||
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x3030, 0x3030},
|
||||
{0x303D, 0x303D}, {0x3297, 0x3297}, {0x3299, 0x3299},
|
||||
{0x1F000, 0x1F0FF}, {0x1F10D, 0x1F10F}, {0x1F12F, 0x1F12F},
|
||||
{0x1F16C, 0x1F171}, {0x1F17E, 0x1F17F}, {0x1F18E, 0x1F18E},
|
||||
{0x1F191, 0x1F19A}, {0x1F1AD, 0x1F1E5}, {0x1F201, 0x1F20F},
|
||||
{0x1F21A, 0x1F21A}, {0x1F22F, 0x1F22F}, {0x1F232, 0x1F23A},
|
||||
{0x1F23C, 0x1F23F}, {0x1F249, 0x1F3FA}, {0x1F400, 0x1F53D},
|
||||
{0x1F546, 0x1F64F}, {0x1F680, 0x1F6FF}, {0x1F774, 0x1F77F},
|
||||
{0x1F7D5, 0x1F7FF}, {0x1F80C, 0x1F80F}, {0x1F848, 0x1F84F},
|
||||
{0x1F85A, 0x1F85F}, {0x1F888, 0x1F88F}, {0x1F8AE, 0x1F8FF},
|
||||
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1FAFF},
|
||||
{0x1FC00, 0x1FFFD},
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// +build windows
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP")
|
||||
)
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
r1, _, _ := procGetConsoleOutputCP.Call()
|
||||
if r1 == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch int(r1) {
|
||||
case 932, 51932, 936, 949, 950:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
vendor/
|
||||
|
||||
# This is where we test stuff
|
||||
/experimenting/
|
||||
|
||||
/.history
|
||||
/.vscode
|
|
@ -0,0 +1,92 @@
|
|||
linters-settings:
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
disabled-checks:
|
||||
- dupImport
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
- whyNoLint
|
||||
- wrapperFunc
|
||||
- exitAfterDefer
|
||||
- hugeParam
|
||||
- ptrToRefParam
|
||||
- paramTypeCombine
|
||||
- unnamedResult
|
||||
# maligned:
|
||||
# suggest-new: true
|
||||
misspell:
|
||||
locale: US
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- gocritic
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- interfacer
|
||||
- unconvert
|
||||
- gosimple
|
||||
- godox
|
||||
- whitespace
|
||||
- staticcheck
|
||||
# - bodyclose
|
||||
# - maligned
|
||||
# - godot
|
||||
# - deadcode
|
||||
# - depguard
|
||||
# - dogsled
|
||||
# - dupl
|
||||
# - errcheck
|
||||
# - exhaustive
|
||||
# - funlen
|
||||
# - gochecknoinits
|
||||
# - goconst
|
||||
# - gocyclo
|
||||
# - gofmt
|
||||
# - goimports
|
||||
# - golint
|
||||
# - gomnd
|
||||
# - goprintffuncname
|
||||
# - lll
|
||||
# - misspell
|
||||
# - nakedret
|
||||
# - noctx
|
||||
# - nolintlint
|
||||
# - rowserrcheck
|
||||
# - scopelint
|
||||
# - structcheck
|
||||
# - stylecheck
|
||||
# - typecheck
|
||||
# - unparam
|
||||
# - unused
|
||||
# - varcheck
|
||||
# - whitespace
|
||||
# - asciicheck
|
||||
# - gochecknoglobals
|
||||
# - gocognit
|
||||
# - goerr113
|
||||
# - nestif
|
||||
# - prealloc
|
||||
# - testpackage
|
||||
# - wsl
|
||||
issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
- gocritic
|
||||
# https://github.com/go-critic/go-critic/issues/926
|
||||
- linters:
|
||||
- gocritic
|
||||
text: "unnecessaryDefer:"
|
||||
service:
|
||||
golangci-lint-version: 1.31.x # use the fixed version to not introduce new linters unexpectedly
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,76 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at pterm@marvinjwendt.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
|
@ -0,0 +1,217 @@
|
|||
# Contributing to PTerm
|
||||
|
||||
> This document explains how to participate in the development of PTerm.\
|
||||
If your goal is to report a bug instead of programming PTerm, you can do so [here](https://github.com/pterm/pterm/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||
|
||||
## Creating a new printer
|
||||
|
||||
> In this chapter we will show you how to create a new printer.
|
||||
|
||||
### `TextPrinter` Template
|
||||
```go
|
||||
package pterm
|
||||
|
||||
type TemplatePrinter struct{
|
||||
// TODO: Add printer settings here
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func (p TemplatePrinter) Sprint(a ...interface{}) string {
|
||||
panic("write printer code here")
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p TemplatePrinter) Sprintln(a ...interface{}) string {
|
||||
return Sprintln(p.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func (p TemplatePrinter) Sprintf(format string, a ...interface{}) string {
|
||||
return p.Sprint(Sprintf(format, 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.
|
||||
func (p TemplatePrinter) Print(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprint(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p TemplatePrinter) Println(a ...interface{}) *TextPrinter {
|
||||
Println(p.Sprint(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func (p TemplatePrinter) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintf(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
```
|
||||
|
||||
### `RenderablePrinter` Template
|
||||
|
||||
```go
|
||||
package pterm
|
||||
|
||||
type TemplatePrinter struct{
|
||||
// TODO: Add printer settings here
|
||||
}
|
||||
|
||||
// Srender renders the Template as a string.
|
||||
func (p TemplatePrinter) Srender() (string, error) {
|
||||
var ret string
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Render prints the Template to the terminal.
|
||||
func (p TemplatePrinter) Render() error {
|
||||
s, err := p.Srender()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Println(s)
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### `LivePrinter` Template
|
||||
|
||||
```go
|
||||
// Start the TemplatePrinter.
|
||||
package pterm
|
||||
import "github.com/pterm/pterm"
|
||||
|
||||
type TemplatePrinter struct{
|
||||
|
||||
}
|
||||
|
||||
|
||||
func (s TemplatePrinter) Start(text...interface{}) (*TemplatePrinter, error) { // TODO: Replace Template with actual printer.
|
||||
// TODO: start logic
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// Stop terminates the TemplatePrinter immediately.
|
||||
// The TemplatePrinter will not resolve into anything.
|
||||
func (s *TemplatePrinter) Stop() error {
|
||||
// TODO: stop logic
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenericStart runs Start, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Start instead of this in your program.
|
||||
func (s *TemplatePrinter) GenericStart() (*LivePrinter, error) {
|
||||
_, err := s.Start()
|
||||
lp := LivePrinter(s)
|
||||
return &lp, err
|
||||
}
|
||||
|
||||
// GenericStop runs Stop, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Stop instead of this in your program.
|
||||
func (s *TemplatePrinter) GenericStop() (*LivePrinter, error) {
|
||||
err := s.Stop()
|
||||
lp := LivePrinter(s)
|
||||
return &lp, err
|
||||
}
|
||||
```
|
||||
|
||||
## Writing Tests
|
||||
|
||||
> Each method of PTerm must be tested.
|
||||
|
||||
### Required tests for every printer
|
||||
|
||||
#### Nil Check
|
||||
|
||||
> This ensures that a printer without set values will not produce errors.
|
||||
|
||||
```go
|
||||
func TestTemplatePrinterNilPrint(t *testing.T) { // TODO: Replace "Template" with actual printer name.
|
||||
p := TemplatePrinter{} // TODO: Replace "Template" with actual printer name.
|
||||
p.Println("Hello, World!")
|
||||
}
|
||||
```
|
||||
|
||||
#### `WithXxx()` Methods
|
||||
|
||||
> Each method, which starts with `With` can be tested by checking if it actually creates a new printer and sets the value.
|
||||
|
||||
Example from `SectionPrinter`:
|
||||
|
||||
```go
|
||||
func TestSectionPrinter_WithStyle(t *testing.T) {
|
||||
p := SectionPrinter{}
|
||||
s := NewStyle(FgRed, BgRed, Bold)
|
||||
p2 := p.WithStyle(s)
|
||||
|
||||
assert.Equal(t, s, p2.Style)
|
||||
assert.Empty(t, p.Style)
|
||||
}
|
||||
|
||||
func TestSectionPrinter_WithTopPadding(t *testing.T) {
|
||||
p := SectionPrinter{}
|
||||
p2 := p.WithTopPadding(1337)
|
||||
|
||||
assert.Equal(t, 1337, p2.TopPadding)
|
||||
assert.Empty(t, p.TopPadding)
|
||||
}
|
||||
```
|
||||
|
||||
### `TextPrinter` Tests Template
|
||||
|
||||
```go
|
||||
func TestTemplatePrinterPrintMethods(t *testing.T) { // TODO: Replace "Template" with actual printer name.
|
||||
p := DefaultTemplate // TODO: Replace "Template" with actual printer name.
|
||||
|
||||
t.Run("Print", func(t *testing.T) {
|
||||
testPrintContains(t, func(w io.Writer, a interface{}) {
|
||||
p.Print(a)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Printf", func(t *testing.T) {
|
||||
testPrintfContains(t, func(w io.Writer, format string, a interface{}) {
|
||||
p.Printf(format, a)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Println", func(t *testing.T) {
|
||||
testPrintlnContains(t, func(w io.Writer, a interface{}) {
|
||||
p.Println(a)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Sprint", func(t *testing.T) {
|
||||
testSprintContains(t, func(a interface{}) string {
|
||||
return p.Sprint(a)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Sprintf", func(t *testing.T) {
|
||||
testSprintfContains(t, func(format string, a interface{}) string {
|
||||
return p.Sprintf(format, a)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Sprintln", func(t *testing.T) {
|
||||
testSprintlnContains(t, func(a interface{}) string {
|
||||
return p.Sprintln(a)
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 pterm
|
||||
|
||||
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.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,130 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/atomicgo/cursor"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// DefaultArea is the default area printer.
|
||||
var DefaultArea = AreaPrinter{}
|
||||
|
||||
// AreaPrinter prints an area which can be updated easily.
|
||||
// use this printer for live output like charts, algorithm visualizations, simulations and even games.
|
||||
type AreaPrinter struct {
|
||||
RemoveWhenDone bool
|
||||
Fullscreen bool
|
||||
Center bool
|
||||
|
||||
content string
|
||||
isActive bool
|
||||
|
||||
area *cursor.Area
|
||||
}
|
||||
|
||||
// GetContent returns the current area content.
|
||||
func (p *AreaPrinter) GetContent() string {
|
||||
return p.content
|
||||
}
|
||||
|
||||
// WithRemoveWhenDone removes the AreaPrinter content after it is stopped.
|
||||
func (p AreaPrinter) WithRemoveWhenDone(b ...bool) *AreaPrinter {
|
||||
p.RemoveWhenDone = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithFullscreen sets the AreaPrinter height the same height as the terminal, making it fullscreen.
|
||||
func (p AreaPrinter) WithFullscreen(b ...bool) *AreaPrinter {
|
||||
p.Fullscreen = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithCenter centers the AreaPrinter content to the terminal.
|
||||
func (p AreaPrinter) WithCenter(b ...bool) *AreaPrinter {
|
||||
p.Center = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// Update overwrites the content of the AreaPrinter.
|
||||
// Can be used live.
|
||||
func (p *AreaPrinter) Update(text ...interface{}) {
|
||||
if p.area == nil {
|
||||
newArea := cursor.NewArea()
|
||||
p.area = &newArea
|
||||
}
|
||||
str := Sprint(text...)
|
||||
p.content = str
|
||||
|
||||
if p.Center {
|
||||
str = DefaultCenter.Sprint(str)
|
||||
}
|
||||
|
||||
if p.Fullscreen {
|
||||
str = strings.TrimRight(str, "\n")
|
||||
height := GetTerminalHeight()
|
||||
contentHeight := strings.Count(str, "\n")
|
||||
|
||||
topPadding := 0
|
||||
bottomPadding := height - contentHeight - 2
|
||||
|
||||
if p.Center {
|
||||
topPadding = (bottomPadding / 2) + 1
|
||||
bottomPadding /= 2
|
||||
}
|
||||
|
||||
if height > contentHeight {
|
||||
str = strings.Repeat("\n", topPadding) + str
|
||||
str += strings.Repeat("\n", bottomPadding)
|
||||
}
|
||||
}
|
||||
p.area.Update(str)
|
||||
}
|
||||
|
||||
// Start the AreaPrinter.
|
||||
func (p *AreaPrinter) Start(text ...interface{}) (*AreaPrinter, error) {
|
||||
p.isActive = true
|
||||
str := Sprint(text...)
|
||||
newArea := cursor.NewArea()
|
||||
p.area = &newArea
|
||||
|
||||
p.Update(str)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Stop terminates the AreaPrinter immediately.
|
||||
// The AreaPrinter will not resolve into anything.
|
||||
func (p *AreaPrinter) Stop() error {
|
||||
p.isActive = false
|
||||
if p.RemoveWhenDone {
|
||||
p.Clear()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenericStart runs Start, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Start instead of this in your program.
|
||||
func (p *AreaPrinter) GenericStart() (*LivePrinter, error) {
|
||||
_, _ = p.Start()
|
||||
lp := LivePrinter(p)
|
||||
return &lp, nil
|
||||
}
|
||||
|
||||
// GenericStop runs Stop, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Stop instead of this in your program.
|
||||
func (p *AreaPrinter) GenericStop() (*LivePrinter, error) {
|
||||
_ = p.Stop()
|
||||
lp := LivePrinter(p)
|
||||
return &lp, nil
|
||||
}
|
||||
|
||||
// Wrapper function that clears the content of the Area.
|
||||
// Moves the cursor to the bottom of the terminal, clears n lines upwards from
|
||||
// the current position and moves the cursor again.
|
||||
func (p *AreaPrinter) Clear() {
|
||||
p.area.Clear()
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package pterm
|
||||
|
||||
// Bars is used to display multiple Bar.
|
||||
type Bars []Bar
|
||||
|
||||
// Bar is used in bar charts.
|
||||
type Bar struct {
|
||||
Label string
|
||||
Value int
|
||||
Style *Style
|
||||
LabelStyle *Style
|
||||
}
|
||||
|
||||
// WithLabel returns a new Bar with a specific option.
|
||||
func (p Bar) WithLabel(s string) *Bar {
|
||||
p.Label = s
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithLabelStyle returns a new Bar with a specific option.
|
||||
func (p Bar) WithLabelStyle(style *Style) *Bar {
|
||||
p.LabelStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithValue returns a new Bar with a specific option.
|
||||
func (p Bar) WithValue(value int) *Bar {
|
||||
p.Value = value
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithStyle returns a new Bar with a specific option.
|
||||
func (p Bar) WithStyle(style *Style) *Bar {
|
||||
p.Style = style
|
||||
return &p
|
||||
}
|
|
@ -0,0 +1,416 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// BarChartPrinter is used to print bar charts.
|
||||
type BarChartPrinter struct {
|
||||
Bars Bars
|
||||
Horizontal bool
|
||||
ShowValue bool
|
||||
// Height sets the maximum height of a vertical bar chart.
|
||||
// The default is calculated to fit into the terminal.
|
||||
// Ignored if Horizontal is set to true.
|
||||
Height int
|
||||
// Width sets the maximum width of a horizontal bar chart.
|
||||
// The default is calculated to fit into the terminal.
|
||||
// Ignored if Horizontal is set to false.
|
||||
Width int
|
||||
VerticalBarCharacter string
|
||||
HorizontalBarCharacter string
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultBarChart is the default BarChartPrinter.
|
||||
DefaultBarChart = BarChartPrinter{
|
||||
Horizontal: false,
|
||||
VerticalBarCharacter: "██",
|
||||
HorizontalBarCharacter: "█",
|
||||
// keep in sync with RecalculateTerminalSize()
|
||||
Height: GetTerminalHeight() * 2 / 3,
|
||||
Width: GetTerminalWidth() * 2 / 3,
|
||||
}
|
||||
)
|
||||
|
||||
// WithBars returns a new BarChartPrinter with a specific option.
|
||||
func (p BarChartPrinter) WithBars(bars Bars) *BarChartPrinter {
|
||||
p.Bars = bars
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithVerticalBarCharacter returns a new BarChartPrinter with a specific option.
|
||||
func (p BarChartPrinter) WithVerticalBarCharacter(char string) *BarChartPrinter {
|
||||
p.VerticalBarCharacter = char
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithHorizontalBarCharacter returns a new BarChartPrinter with a specific option.
|
||||
func (p BarChartPrinter) WithHorizontalBarCharacter(char string) *BarChartPrinter {
|
||||
p.HorizontalBarCharacter = char
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithHorizontal returns a new BarChartPrinter with a specific option.
|
||||
func (p BarChartPrinter) WithHorizontal(b ...bool) *BarChartPrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.Horizontal = b2
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithHeight returns a new BarChartPrinter with a specific option.
|
||||
func (p BarChartPrinter) WithHeight(value int) *BarChartPrinter {
|
||||
p.Height = value
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithWidth returns a new BarChartPrinter with a specific option.
|
||||
func (p BarChartPrinter) WithWidth(value int) *BarChartPrinter {
|
||||
p.Width = value
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithShowValue returns a new BarChartPrinter with a specific option.
|
||||
func (p BarChartPrinter) WithShowValue(b ...bool) *BarChartPrinter {
|
||||
p.ShowValue = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p BarChartPrinter) getRawOutput() string {
|
||||
var ret string
|
||||
|
||||
for _, bar := range p.Bars {
|
||||
ret += Sprintfln("%s: %d", bar.Label, bar.Value)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Srender renders the BarChart as a string.
|
||||
func (p BarChartPrinter) Srender() (string, error) {
|
||||
maxAbsValue := func(value1 int, value2 int) int {
|
||||
min := value1
|
||||
max := value2
|
||||
|
||||
if value1 > value2 {
|
||||
min = value2
|
||||
max = value1
|
||||
}
|
||||
|
||||
maxAbs := max
|
||||
|
||||
if min < 0 && -min > max { // This is to avoid something like "int(math.Abs(float64(minBarValue)))"
|
||||
maxAbs = -min // (--) == (+)
|
||||
}
|
||||
|
||||
return maxAbs
|
||||
}
|
||||
|
||||
abs := func(value int) int {
|
||||
if value < 0 {
|
||||
return -value
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
// =================================== VERTICAL BARS RENDERER ======================================================
|
||||
|
||||
type renderParams struct {
|
||||
repeatCount int
|
||||
bar Bar
|
||||
positiveChartPartHeight int
|
||||
negativeChartPartHeight int
|
||||
positiveChartPartWidth int
|
||||
negativeChartPartWidth int
|
||||
indent string
|
||||
showValue bool
|
||||
moveUp bool
|
||||
moveRight bool
|
||||
}
|
||||
|
||||
renderPositiveVerticalBar := func(renderedBarRef *string, rParams renderParams) {
|
||||
if rParams.showValue {
|
||||
*renderedBarRef += Sprint(rParams.indent + strconv.Itoa(rParams.bar.Value) + rParams.indent + "\n")
|
||||
}
|
||||
|
||||
for i := rParams.positiveChartPartHeight; i > 0; i-- {
|
||||
if i > rParams.repeatCount {
|
||||
*renderedBarRef += rParams.indent + " " + rParams.indent + " \n"
|
||||
} else {
|
||||
*renderedBarRef += rParams.indent + rParams.bar.Style.Sprint(p.VerticalBarCharacter) + rParams.indent + " \n"
|
||||
}
|
||||
}
|
||||
|
||||
// Used when we draw diagram with both POSITIVE and NEGATIVE values.
|
||||
// In such case we separately draw top and bottom half of chart.
|
||||
// And we need MOVE UP positive part to top part of chart,
|
||||
// technically by adding empty pillars with height == height of chart's bottom part.
|
||||
if rParams.moveUp {
|
||||
for i := 0; i <= rParams.negativeChartPartHeight; i++ {
|
||||
*renderedBarRef += rParams.indent + " " + rParams.indent + " \n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderNegativeVerticalBar := func(renderedBarRef *string, rParams renderParams) {
|
||||
for i := 0; i > -rParams.negativeChartPartHeight; i-- {
|
||||
if i > rParams.repeatCount {
|
||||
*renderedBarRef += rParams.indent + rParams.bar.Style.Sprint(p.VerticalBarCharacter) + rParams.indent + " \n"
|
||||
} else {
|
||||
*renderedBarRef += rParams.indent + " " + rParams.indent + " \n"
|
||||
}
|
||||
}
|
||||
|
||||
if rParams.showValue {
|
||||
*renderedBarRef += Sprint(rParams.indent + strconv.Itoa(rParams.bar.Value) + rParams.indent + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
// =================================== HORIZONTAL BARS RENDERER ====================================================
|
||||
renderPositiveHorizontalBar := func(renderedBarRef *string, rParams renderParams) {
|
||||
if rParams.moveRight {
|
||||
for i := 0; i < rParams.negativeChartPartWidth; i++ {
|
||||
*renderedBarRef += " "
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < rParams.positiveChartPartWidth; i++ {
|
||||
if i < rParams.repeatCount {
|
||||
*renderedBarRef += rParams.bar.Style.Sprint(p.HorizontalBarCharacter)
|
||||
} else {
|
||||
*renderedBarRef += " "
|
||||
}
|
||||
}
|
||||
|
||||
if rParams.showValue {
|
||||
// For positive horizontal bars we add one more space before adding value,
|
||||
// so they will be well aligned with negative values, which have "-" sign before them
|
||||
*renderedBarRef += " "
|
||||
|
||||
*renderedBarRef += " " + strconv.Itoa(rParams.bar.Value)
|
||||
}
|
||||
}
|
||||
|
||||
renderNegativeHorizontalBar := func(renderedBarRef *string, rParams renderParams) {
|
||||
for i := -rParams.negativeChartPartWidth; i < 0; i++ {
|
||||
if i < rParams.repeatCount {
|
||||
*renderedBarRef += " "
|
||||
} else {
|
||||
*renderedBarRef += rParams.bar.Style.Sprint(p.HorizontalBarCharacter)
|
||||
}
|
||||
}
|
||||
|
||||
// In order to print values well-aligned (in case when we have both - positive and negative part of chart),
|
||||
// we should insert an indent with width == width of positive chart part
|
||||
if rParams.positiveChartPartWidth > 0 {
|
||||
for i := 0; i < rParams.positiveChartPartWidth; i++ {
|
||||
*renderedBarRef += " "
|
||||
}
|
||||
}
|
||||
|
||||
if rParams.showValue {
|
||||
/*
|
||||
This is in order to achieve this effect:
|
||||
0
|
||||
-15
|
||||
0
|
||||
-19
|
||||
|
||||
INSTEAD OF THIS:
|
||||
|
||||
0
|
||||
-15
|
||||
0
|
||||
-19
|
||||
*/
|
||||
if rParams.repeatCount == 0 {
|
||||
*renderedBarRef += " "
|
||||
}
|
||||
|
||||
*renderedBarRef += " " + strconv.Itoa(rParams.bar.Value)
|
||||
}
|
||||
}
|
||||
// =================================================================================================================
|
||||
|
||||
if RawOutput {
|
||||
return p.getRawOutput(), nil
|
||||
}
|
||||
for i, bar := range p.Bars {
|
||||
if bar.Style == nil {
|
||||
p.Bars[i].Style = &ThemeDefault.BarStyle
|
||||
}
|
||||
|
||||
if bar.LabelStyle == nil {
|
||||
p.Bars[i].LabelStyle = &ThemeDefault.BarLabelStyle
|
||||
}
|
||||
|
||||
p.Bars[i].Label = p.Bars[i].LabelStyle.Sprint(bar.Label)
|
||||
}
|
||||
|
||||
var ret string
|
||||
|
||||
var maxLabelHeight int
|
||||
var maxBarValue int
|
||||
var minBarValue int
|
||||
var maxAbsBarValue int
|
||||
var rParams renderParams
|
||||
|
||||
for _, bar := range p.Bars {
|
||||
if bar.Value > maxBarValue {
|
||||
maxBarValue = bar.Value
|
||||
}
|
||||
if bar.Value < minBarValue {
|
||||
minBarValue = bar.Value
|
||||
}
|
||||
labelHeight := len(strings.Split(bar.Label, "\n"))
|
||||
if labelHeight > maxLabelHeight {
|
||||
maxLabelHeight = labelHeight
|
||||
}
|
||||
}
|
||||
|
||||
maxAbsBarValue = maxAbsValue(maxBarValue, minBarValue)
|
||||
|
||||
if p.Horizontal {
|
||||
panels := Panels{[]Panel{{}, {}}}
|
||||
|
||||
rParams.showValue = p.ShowValue
|
||||
rParams.positiveChartPartWidth = p.Width
|
||||
rParams.negativeChartPartWidth = p.Width
|
||||
|
||||
// If chart will consist of two parts - positive and negative - we should recalculate max bars WIDTH in LEFT and RIGHT parts
|
||||
if minBarValue < 0 && maxBarValue > 0 {
|
||||
rParams.positiveChartPartWidth = abs(internal.MapRangeToRange(-float32(maxAbsBarValue), float32(maxAbsBarValue), -float32(p.Width)/2, float32(p.Width)/2, float32(maxBarValue)))
|
||||
rParams.negativeChartPartWidth = abs(internal.MapRangeToRange(-float32(maxAbsBarValue), float32(maxAbsBarValue), -float32(p.Width)/2, float32(p.Width)/2, float32(minBarValue)))
|
||||
}
|
||||
|
||||
for _, bar := range p.Bars {
|
||||
rParams.bar = bar
|
||||
panels[0][0].Data += "\n" + bar.Label
|
||||
panels[0][1].Data += "\n"
|
||||
|
||||
if minBarValue >= 0 {
|
||||
// As we don't have negative values, draw only positive (right) part of the chart:
|
||||
rParams.repeatCount = internal.MapRangeToRange(0, float32(maxAbsBarValue), 0, float32(p.Width), float32(bar.Value))
|
||||
rParams.moveRight = false
|
||||
|
||||
renderPositiveHorizontalBar(&panels[0][1].Data, rParams)
|
||||
} else if maxBarValue <= 0 {
|
||||
// As we have only negative values, draw only negative (left) part of the chart:
|
||||
rParams.repeatCount = internal.MapRangeToRange(-float32(maxAbsBarValue), 0, -float32(p.Width), 0, float32(bar.Value))
|
||||
rParams.positiveChartPartWidth = 0
|
||||
|
||||
renderNegativeHorizontalBar(&panels[0][1].Data, rParams)
|
||||
} else {
|
||||
// We have positive and negative values, so draw both (left+right) parts of the chart:
|
||||
rParams.repeatCount = internal.MapRangeToRange(-float32(maxAbsBarValue), float32(maxAbsBarValue), -float32(p.Width)/2, float32(p.Width)/2, float32(bar.Value))
|
||||
|
||||
if bar.Value >= 0 {
|
||||
rParams.moveRight = true
|
||||
|
||||
renderPositiveHorizontalBar(&panels[0][1].Data, rParams)
|
||||
}
|
||||
|
||||
if bar.Value < 0 {
|
||||
renderNegativeHorizontalBar(&panels[0][1].Data, rParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
ret, _ = DefaultPanel.WithPanels(panels).Srender()
|
||||
return ret, nil
|
||||
} else {
|
||||
renderedBars := make([]string, len(p.Bars))
|
||||
|
||||
rParams.showValue = p.ShowValue
|
||||
rParams.positiveChartPartHeight = p.Height
|
||||
rParams.negativeChartPartHeight = p.Height
|
||||
|
||||
// If chart will consist of two parts - positive and negative - we should recalculate max bars height in top and bottom parts
|
||||
if minBarValue < 0 && maxBarValue > 0 {
|
||||
rParams.positiveChartPartHeight = abs(internal.MapRangeToRange(-float32(maxAbsBarValue), float32(maxAbsBarValue), -float32(p.Height)/2, float32(p.Height)/2, float32(maxBarValue)))
|
||||
rParams.negativeChartPartHeight = abs(internal.MapRangeToRange(-float32(maxAbsBarValue), float32(maxAbsBarValue), -float32(p.Height)/2, float32(p.Height)/2, float32(minBarValue)))
|
||||
}
|
||||
|
||||
for i, bar := range p.Bars {
|
||||
var renderedBar string
|
||||
rParams.bar = bar
|
||||
rParams.indent = strings.Repeat(" ", internal.GetStringMaxWidth(RemoveColorFromString(bar.Label))/2)
|
||||
|
||||
if minBarValue >= 0 {
|
||||
// As we don't have negative values, draw only positive (top) part of the chart:
|
||||
rParams.repeatCount = internal.MapRangeToRange(0, float32(maxAbsBarValue), 0, float32(p.Height), float32(bar.Value))
|
||||
rParams.moveUp = false // Don't MOVE UP as we have ONLY positive part of chart.
|
||||
|
||||
renderPositiveVerticalBar(&renderedBar, rParams)
|
||||
} else if maxBarValue <= 0 {
|
||||
// As we have only negative values, draw only negative (bottom) part of the chart:
|
||||
rParams.repeatCount = internal.MapRangeToRange(-float32(maxAbsBarValue), 0, -float32(p.Height), 0, float32(bar.Value))
|
||||
|
||||
renderNegativeVerticalBar(&renderedBar, rParams)
|
||||
} else {
|
||||
// We have positive and negative values, so draw both (top+bottom) parts of the chart:
|
||||
rParams.repeatCount = internal.MapRangeToRange(-float32(maxAbsBarValue), float32(maxAbsBarValue), -float32(p.Height)/2, float32(p.Height)/2, float32(bar.Value))
|
||||
|
||||
if bar.Value >= 0 {
|
||||
rParams.moveUp = true // MOVE UP positive part, because we have both positive and negative parts of chart.
|
||||
|
||||
renderPositiveVerticalBar(&renderedBar, rParams)
|
||||
}
|
||||
|
||||
if bar.Value < 0 {
|
||||
renderNegativeVerticalBar(&renderedBar, rParams)
|
||||
}
|
||||
}
|
||||
|
||||
labelHeight := len(strings.Split(bar.Label, "\n"))
|
||||
renderedBars[i] = renderedBar + bar.Label + strings.Repeat("\n", maxLabelHeight-labelHeight) + " "
|
||||
}
|
||||
|
||||
var maxBarHeight int
|
||||
|
||||
for _, bar := range renderedBars {
|
||||
totalBarHeight := len(strings.Split(bar, "\n"))
|
||||
if totalBarHeight > maxBarHeight {
|
||||
maxBarHeight = totalBarHeight
|
||||
}
|
||||
}
|
||||
|
||||
for i, bar := range renderedBars {
|
||||
totalBarHeight := len(strings.Split(bar, "\n"))
|
||||
if totalBarHeight < maxBarHeight {
|
||||
renderedBars[i] = strings.Repeat("\n", maxBarHeight-totalBarHeight) + renderedBars[i]
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i <= maxBarHeight; i++ {
|
||||
for _, barString := range renderedBars {
|
||||
var barLine string
|
||||
letterLines := strings.Split(barString, "\n")
|
||||
maxBarWidth := internal.GetStringMaxWidth(RemoveColorFromString(barString))
|
||||
if len(letterLines) > i {
|
||||
barLine = letterLines[i]
|
||||
}
|
||||
letterLineLength := runewidth.StringWidth(RemoveColorFromString(barLine))
|
||||
if letterLineLength < maxBarWidth {
|
||||
barLine += strings.Repeat(" ", maxBarWidth-letterLineLength)
|
||||
}
|
||||
ret += barLine
|
||||
}
|
||||
ret += "\n"
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Render prints the Template to the terminal.
|
||||
func (p BarChartPrinter) Render() error {
|
||||
s, _ := p.Srender()
|
||||
Println(s)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package pterm
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
// DefaultBasicText returns a default BasicTextPrinter, which can be used to print text as is.
|
||||
// No default style is present for BasicTextPrinter.
|
||||
DefaultBasicText = BasicTextPrinter{}
|
||||
)
|
||||
|
||||
// BasicTextPrinter is the printer used to print the input as-is or as specified by user formatting.
|
||||
type BasicTextPrinter struct {
|
||||
Style *Style
|
||||
}
|
||||
|
||||
// WithStyle adds a style to the printer.
|
||||
func (p BasicTextPrinter) WithStyle(style *Style) *BasicTextPrinter {
|
||||
p.Style = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func (p BasicTextPrinter) Sprint(a ...interface{}) string {
|
||||
if p.Style == nil {
|
||||
p.Style = NewStyle()
|
||||
}
|
||||
return p.Style.Sprint(a...)
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p BasicTextPrinter) Sprintln(a ...interface{}) string {
|
||||
str := fmt.Sprintln(a...)
|
||||
return Sprintln(p.Sprint(str))
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func (p BasicTextPrinter) Sprintf(format string, a ...interface{}) string {
|
||||
return p.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p BasicTextPrinter) Sprintfln(format string, a ...interface{}) string {
|
||||
return p.Sprintf(format, a...) + "\n"
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *BasicTextPrinter) Print(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprint(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *BasicTextPrinter) Println(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintln(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func (p *BasicTextPrinter) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintf(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
func (p *BasicTextPrinter) Printfln(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintfln(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p *BasicTextPrinter) PrintOnError(a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p *BasicTextPrinter) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
|
@ -0,0 +1,549 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// Letters is a slice of Letter.
|
||||
type Letters []Letter
|
||||
|
||||
// NewLettersFromString creates a Letters object from a string, which is prefilled with the LetterStyle from ThemeDefault.
|
||||
// You can override the ThemeDefault LetterStyle if you want to.
|
||||
func NewLettersFromString(text string) Letters {
|
||||
return NewLettersFromStringWithStyle(text, &ThemeDefault.LetterStyle)
|
||||
}
|
||||
|
||||
// NewLettersFromStringWithStyle creates a Letters object from a string and applies a Style to it.
|
||||
func NewLettersFromStringWithStyle(text string, style *Style) Letters {
|
||||
s := strings.Split(text, "")
|
||||
l := Letters{}
|
||||
|
||||
for _, s2 := range s {
|
||||
l = append(l, Letter{
|
||||
String: s2,
|
||||
Style: style,
|
||||
})
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// Letter is an object, which holds a string and a specific Style for it.
|
||||
type Letter struct {
|
||||
String string
|
||||
Style *Style
|
||||
}
|
||||
|
||||
// WithStyle returns a new Letter with a specific Style.
|
||||
func (l Letter) WithStyle(style *Style) *Letter {
|
||||
l.Style = style
|
||||
return &l
|
||||
}
|
||||
|
||||
// WithString returns a new Letter with a specific String.
|
||||
func (l Letter) WithString(s string) *Letter {
|
||||
l.String = s
|
||||
return &l
|
||||
}
|
||||
|
||||
// BigTextPrinter renders big text.
|
||||
// You can use this as title screen for your application.
|
||||
type BigTextPrinter struct {
|
||||
// BigCharacters holds the map from a normal character to it's big version.
|
||||
BigCharacters map[string]string
|
||||
Letters Letters
|
||||
}
|
||||
|
||||
// WithBigCharacters returns a new BigTextPrinter with specific BigCharacters.
|
||||
func (p BigTextPrinter) WithBigCharacters(chars map[string]string) *BigTextPrinter {
|
||||
p.BigCharacters = chars
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithLetters returns a new BigTextPrinter with specific Letters
|
||||
func (p BigTextPrinter) WithLetters(letters ...Letters) *BigTextPrinter {
|
||||
l := Letters{}
|
||||
for _, letter := range letters {
|
||||
l = append(l, letter...)
|
||||
}
|
||||
p.Letters = l
|
||||
return &p
|
||||
}
|
||||
|
||||
// Srender renders the BigText as a string.
|
||||
func (p BigTextPrinter) Srender() (string, error) {
|
||||
var ret string
|
||||
|
||||
if RawOutput {
|
||||
for _, letter := range p.Letters {
|
||||
ret += letter.String
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
var bigLetters Letters
|
||||
for _, l := range p.Letters {
|
||||
if val, ok := p.BigCharacters[l.String]; ok {
|
||||
bigLetters = append(bigLetters, Letter{
|
||||
String: val,
|
||||
Style: l.Style,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var maxHeight int
|
||||
|
||||
for _, l := range bigLetters {
|
||||
h := strings.Count(l.String, "\n")
|
||||
if h > maxHeight {
|
||||
maxHeight = h
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i <= maxHeight; i++ {
|
||||
for _, letter := range bigLetters {
|
||||
var letterLine string
|
||||
letterLines := strings.Split(letter.String, "\n")
|
||||
maxLetterWidth := internal.GetStringMaxWidth(letter.String)
|
||||
if len(letterLines) > i {
|
||||
letterLine = letterLines[i]
|
||||
}
|
||||
letterLineLength := runewidth.StringWidth(letterLine)
|
||||
if letterLineLength < maxLetterWidth {
|
||||
letterLine += strings.Repeat(" ", maxLetterWidth-letterLineLength)
|
||||
}
|
||||
ret += letter.Style.Sprint(letterLine)
|
||||
}
|
||||
ret += "\n"
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Render prints the BigText to the terminal.
|
||||
func (p BigTextPrinter) Render() error {
|
||||
s, _ := p.Srender()
|
||||
Println(s)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultBigText contains default values for BigTextPrinter.
|
||||
var DefaultBigText = BigTextPrinter{
|
||||
BigCharacters: map[string]string{
|
||||
"a": ` █████
|
||||
██ ██
|
||||
███████
|
||||
██ ██
|
||||
██ ██ `,
|
||||
"A": ` █████
|
||||
██ ██
|
||||
███████
|
||||
██ ██
|
||||
██ ██ `,
|
||||
"b": `██████
|
||||
██ ██
|
||||
██████
|
||||
██ ██
|
||||
██████`,
|
||||
"B": `██████
|
||||
██ ██
|
||||
██████
|
||||
██ ██
|
||||
██████`,
|
||||
"c": ` ██████
|
||||
██
|
||||
██
|
||||
██
|
||||
██████`,
|
||||
"C": ` ██████
|
||||
██
|
||||
██
|
||||
██
|
||||
██████`,
|
||||
"d": `██████
|
||||
██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██████ `,
|
||||
"D": `██████
|
||||
██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██████ `,
|
||||
"e": `███████
|
||||
██
|
||||
█████
|
||||
██
|
||||
███████`,
|
||||
"E": `███████
|
||||
██
|
||||
█████
|
||||
██
|
||||
███████`,
|
||||
"f": `███████
|
||||
██
|
||||
█████
|
||||
██
|
||||
██ `,
|
||||
"F": `███████
|
||||
██
|
||||
█████
|
||||
██
|
||||
██ `,
|
||||
"g": ` ██████
|
||||
██
|
||||
██ ███
|
||||
██ ██
|
||||
██████ `,
|
||||
"G": ` ██████
|
||||
██
|
||||
██ ███
|
||||
██ ██
|
||||
██████ `,
|
||||
"h": `██ ██
|
||||
██ ██
|
||||
███████
|
||||
██ ██
|
||||
██ ██ `,
|
||||
"H": `██ ██
|
||||
██ ██
|
||||
███████
|
||||
██ ██
|
||||
██ ██ `,
|
||||
"i": `██
|
||||
██
|
||||
██
|
||||
██
|
||||
██`,
|
||||
"I": `██
|
||||
██
|
||||
██
|
||||
██
|
||||
██`,
|
||||
"j": ` ██
|
||||
██
|
||||
██
|
||||
██ ██
|
||||
█████ `,
|
||||
"J": ` ██
|
||||
██
|
||||
██
|
||||
██ ██
|
||||
█████ `,
|
||||
"k": `██ ██
|
||||
██ ██
|
||||
█████
|
||||
██ ██
|
||||
██ ██`,
|
||||
"K": `██ ██
|
||||
██ ██
|
||||
█████
|
||||
██ ██
|
||||
██ ██`,
|
||||
"l": `██
|
||||
██
|
||||
██
|
||||
██
|
||||
███████ `,
|
||||
"L": `██
|
||||
██
|
||||
██
|
||||
██
|
||||
███████ `,
|
||||
"m": `███ ███
|
||||
████ ████
|
||||
██ ████ ██
|
||||
██ ██ ██
|
||||
██ ██`,
|
||||
"M": `███ ███
|
||||
████ ████
|
||||
██ ████ ██
|
||||
██ ██ ██
|
||||
██ ██`,
|
||||
"n": `███ ██
|
||||
████ ██
|
||||
██ ██ ██
|
||||
██ ██ ██
|
||||
██ ████`,
|
||||
"N": `███ ██
|
||||
████ ██
|
||||
██ ██ ██
|
||||
██ ██ ██
|
||||
██ ████`,
|
||||
"o": ` ██████
|
||||
██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██████ `,
|
||||
"O": ` ██████
|
||||
██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██████ `,
|
||||
"p": `██████
|
||||
██ ██
|
||||
██████
|
||||
██
|
||||
██ `,
|
||||
"P": `██████
|
||||
██ ██
|
||||
██████
|
||||
██
|
||||
██ `,
|
||||
"q": ` ██████
|
||||
██ ██
|
||||
██ ██
|
||||
██ ▄▄ ██
|
||||
██████
|
||||
▀▀ `,
|
||||
"Q": ` ██████
|
||||
██ ██
|
||||
██ ██
|
||||
██ ▄▄ ██
|
||||
██████
|
||||
▀▀ `,
|
||||
"r": `██████
|
||||
██ ██
|
||||
██████
|
||||
██ ██
|
||||
██ ██`,
|
||||
"R": `██████
|
||||
██ ██
|
||||
██████
|
||||
██ ██
|
||||
██ ██`,
|
||||
"s": `███████
|
||||
██
|
||||
███████
|
||||
██
|
||||
███████`,
|
||||
"S": `███████
|
||||
██
|
||||
███████
|
||||
██
|
||||
███████`,
|
||||
"t": `████████
|
||||
██
|
||||
██
|
||||
██
|
||||
██ `,
|
||||
"T": `████████
|
||||
██
|
||||
██
|
||||
██
|
||||
██ `,
|
||||
"u": `██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██████ `,
|
||||
"U": `██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██████ `,
|
||||
"v": `██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
████ `,
|
||||
"V": `██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██ ██
|
||||
████ `,
|
||||
"w": `██ ██
|
||||
██ ██
|
||||
██ █ ██
|
||||
██ ███ ██
|
||||
███ ███ `,
|
||||
"W": `██ ██
|
||||
██ ██
|
||||
██ █ ██
|
||||
██ ███ ██
|
||||
███ ███ `,
|
||||
"x": `██ ██
|
||||
██ ██
|
||||
███
|
||||
██ ██
|
||||
██ ██ `,
|
||||
"X": `██ ██
|
||||
██ ██
|
||||
███
|
||||
██ ██
|
||||
██ ██ `,
|
||||
"y": `██ ██
|
||||
██ ██
|
||||
████
|
||||
██
|
||||
██ `,
|
||||
"Y": `██ ██
|
||||
██ ██
|
||||
████
|
||||
██
|
||||
██ `,
|
||||
"z": `███████
|
||||
███
|
||||
███
|
||||
███
|
||||
███████`,
|
||||
"Z": `███████
|
||||
███
|
||||
███
|
||||
███
|
||||
███████`,
|
||||
"0": ` ██████
|
||||
██ ████
|
||||
██ ██ ██
|
||||
████ ██
|
||||
██████ `,
|
||||
"1": ` ██
|
||||
███
|
||||
██
|
||||
██
|
||||
██ `,
|
||||
"2": `██████
|
||||
██
|
||||
█████
|
||||
██
|
||||
███████ `,
|
||||
"3": `██████
|
||||
██
|
||||
█████
|
||||
██
|
||||
██████ `,
|
||||
"4": `██ ██
|
||||
██ ██
|
||||
███████
|
||||
██
|
||||
██ `,
|
||||
"5": `███████
|
||||
██
|
||||
███████
|
||||
██
|
||||
███████`,
|
||||
"6": ` ██████
|
||||
██
|
||||
███████
|
||||
██ ██
|
||||
██████ `,
|
||||
"7": `███████
|
||||
██
|
||||
██
|
||||
██
|
||||
██`,
|
||||
"8": ` █████
|
||||
██ ██
|
||||
█████
|
||||
██ ██
|
||||
█████ `,
|
||||
"9": ` █████
|
||||
██ ██
|
||||
██████
|
||||
██
|
||||
█████ `,
|
||||
" ": " ",
|
||||
"!": `██
|
||||
██
|
||||
██
|
||||
|
||||
██ `,
|
||||
"$": `▄▄███▄▄·
|
||||
██
|
||||
███████
|
||||
██
|
||||
███████
|
||||
▀▀▀ `,
|
||||
"%": `██ ██
|
||||
██
|
||||
██
|
||||
██
|
||||
██ ██`,
|
||||
"/": ` ██
|
||||
██
|
||||
██
|
||||
██
|
||||
██ `,
|
||||
"(": ` ██
|
||||
██
|
||||
██
|
||||
██
|
||||
██ `,
|
||||
")": `██
|
||||
██
|
||||
██
|
||||
██
|
||||
██ `,
|
||||
"?": `██████
|
||||
██
|
||||
▄███
|
||||
▀▀
|
||||
██ `,
|
||||
"[": `███
|
||||
██
|
||||
██
|
||||
██
|
||||
███`,
|
||||
"]": `███
|
||||
██
|
||||
██
|
||||
██
|
||||
███ `,
|
||||
".": `
|
||||
|
||||
|
||||
|
||||
██`,
|
||||
",": `
|
||||
|
||||
|
||||
|
||||
▄█`,
|
||||
"-": `
|
||||
|
||||
█████
|
||||
|
||||
|
||||
`,
|
||||
"<": ` ██
|
||||
██
|
||||
██
|
||||
██
|
||||
██ `,
|
||||
">": `██
|
||||
██
|
||||
██
|
||||
██
|
||||
██ `,
|
||||
"*": `
|
||||
▄ ██ ▄
|
||||
████
|
||||
▀ ██ ▀
|
||||
`,
|
||||
"#": ` ██ ██
|
||||
████████
|
||||
██ ██
|
||||
████████
|
||||
██ ██ `,
|
||||
"_": `
|
||||
|
||||
|
||||
|
||||
███████ `,
|
||||
":": `
|
||||
██
|
||||
|
||||
|
||||
██ `,
|
||||
"°": ` ████
|
||||
██ ██
|
||||
████
|
||||
|
||||
`,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,363 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// BoxPrinter is able to render a box around printables.
|
||||
type BoxPrinter struct {
|
||||
Title string
|
||||
TitleTopLeft bool
|
||||
TitleTopRight bool
|
||||
TitleTopCenter bool
|
||||
TitleBottomLeft bool
|
||||
TitleBottomRight bool
|
||||
TitleBottomCenter bool
|
||||
TextStyle *Style
|
||||
VerticalString string
|
||||
BoxStyle *Style
|
||||
HorizontalString string
|
||||
TopRightCornerString string
|
||||
TopLeftCornerString string
|
||||
BottomLeftCornerString string
|
||||
BottomRightCornerString string
|
||||
TopPadding int
|
||||
BottomPadding int
|
||||
RightPadding int
|
||||
LeftPadding int
|
||||
}
|
||||
|
||||
// DefaultBox is the default BoxPrinter.
|
||||
var DefaultBox = BoxPrinter{
|
||||
VerticalString: "|",
|
||||
TopRightCornerString: "└",
|
||||
TopLeftCornerString: "┘",
|
||||
BottomLeftCornerString: "┐",
|
||||
BottomRightCornerString: "┌",
|
||||
HorizontalString: "─",
|
||||
BoxStyle: &ThemeDefault.BoxStyle,
|
||||
TextStyle: &ThemeDefault.BoxTextStyle,
|
||||
RightPadding: 1,
|
||||
LeftPadding: 1,
|
||||
TopPadding: 0,
|
||||
BottomPadding: 0,
|
||||
TitleTopLeft: true,
|
||||
}
|
||||
|
||||
// WithTitle returns a new box with a specific Title.
|
||||
func (p BoxPrinter) WithTitle(str string) *BoxPrinter {
|
||||
p.Title = str
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTitleTopLeft returns a new box with a specific Title alignment.
|
||||
func (p BoxPrinter) WithTitleTopLeft(b ...bool) *BoxPrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.TitleTopLeft = b2
|
||||
p.TitleTopRight = false
|
||||
p.TitleTopCenter = false
|
||||
p.TitleBottomLeft = false
|
||||
p.TitleBottomRight = false
|
||||
p.TitleBottomCenter = false
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTitleTopRight returns a new box with a specific Title alignment.
|
||||
func (p BoxPrinter) WithTitleTopRight(b ...bool) *BoxPrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.TitleTopLeft = false
|
||||
p.TitleTopRight = b2
|
||||
p.TitleTopCenter = false
|
||||
p.TitleBottomLeft = false
|
||||
p.TitleBottomRight = false
|
||||
p.TitleBottomCenter = false
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTitleTopCenter returns a new box with a specific Title alignment.
|
||||
func (p BoxPrinter) WithTitleTopCenter(b ...bool) *BoxPrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.TitleTopLeft = false
|
||||
p.TitleTopRight = false
|
||||
p.TitleTopCenter = b2
|
||||
p.TitleBottomLeft = false
|
||||
p.TitleBottomRight = false
|
||||
p.TitleBottomCenter = false
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTitleBottomLeft returns a new box with a specific Title alignment.
|
||||
func (p BoxPrinter) WithTitleBottomLeft(b ...bool) *BoxPrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.TitleTopLeft = false
|
||||
p.TitleTopRight = false
|
||||
p.TitleTopCenter = false
|
||||
p.TitleBottomLeft = b2
|
||||
p.TitleBottomRight = false
|
||||
p.TitleBottomCenter = false
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTitleBottomRight returns a new box with a specific Title alignment.
|
||||
func (p BoxPrinter) WithTitleBottomRight(b ...bool) *BoxPrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.TitleTopLeft = false
|
||||
p.TitleTopRight = false
|
||||
p.TitleTopCenter = false
|
||||
p.TitleBottomLeft = false
|
||||
p.TitleBottomRight = b2
|
||||
p.TitleBottomCenter = false
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTitleBottomCenter returns a new box with a specific Title alignment.
|
||||
func (p BoxPrinter) WithTitleBottomCenter(b ...bool) *BoxPrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.TitleTopLeft = false
|
||||
p.TitleTopRight = false
|
||||
p.TitleTopCenter = false
|
||||
p.TitleBottomLeft = false
|
||||
p.TitleBottomRight = false
|
||||
p.TitleBottomCenter = b2
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBoxStyle returns a new box with a specific box Style.
|
||||
func (p BoxPrinter) WithBoxStyle(style *Style) *BoxPrinter {
|
||||
p.BoxStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTextStyle returns a new box with a specific text Style.
|
||||
func (p BoxPrinter) WithTextStyle(style *Style) *BoxPrinter {
|
||||
p.TextStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTopRightCornerString returns a new box with a specific TopRightCornerString.
|
||||
func (p BoxPrinter) WithTopRightCornerString(str string) *BoxPrinter {
|
||||
p.TopRightCornerString = str
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTopLeftCornerString returns a new box with a specific TopLeftCornerString.
|
||||
func (p BoxPrinter) WithTopLeftCornerString(str string) *BoxPrinter {
|
||||
p.TopLeftCornerString = str
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBottomRightCornerString returns a new box with a specific BottomRightCornerString.
|
||||
func (p BoxPrinter) WithBottomRightCornerString(str string) *BoxPrinter {
|
||||
p.BottomRightCornerString = str
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBottomLeftCornerString returns a new box with a specific BottomLeftCornerString.
|
||||
func (p BoxPrinter) WithBottomLeftCornerString(str string) *BoxPrinter {
|
||||
p.BottomLeftCornerString = str
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithVerticalString returns a new box with a specific VerticalString.
|
||||
func (p BoxPrinter) WithVerticalString(str string) *BoxPrinter {
|
||||
p.VerticalString = str
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithHorizontalString returns a new box with a specific HorizontalString.
|
||||
func (p BoxPrinter) WithHorizontalString(str string) *BoxPrinter {
|
||||
p.HorizontalString = str
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTopPadding returns a new box with a specific TopPadding.
|
||||
func (p BoxPrinter) WithTopPadding(padding int) *BoxPrinter {
|
||||
if padding < 0 {
|
||||
padding = 0
|
||||
}
|
||||
p.TopPadding = padding
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBottomPadding returns a new box with a specific BottomPadding.
|
||||
func (p BoxPrinter) WithBottomPadding(padding int) *BoxPrinter {
|
||||
if padding < 0 {
|
||||
padding = 0
|
||||
}
|
||||
p.BottomPadding = padding
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithRightPadding returns a new box with a specific RightPadding.
|
||||
func (p BoxPrinter) WithRightPadding(padding int) *BoxPrinter {
|
||||
if padding < 0 {
|
||||
padding = 0
|
||||
}
|
||||
p.RightPadding = padding
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithLeftPadding returns a new box with a specific LeftPadding.
|
||||
func (p BoxPrinter) WithLeftPadding(padding int) *BoxPrinter {
|
||||
if padding < 0 {
|
||||
padding = 0
|
||||
}
|
||||
p.LeftPadding = padding
|
||||
return &p
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func (p BoxPrinter) Sprint(a ...interface{}) string {
|
||||
if p.BoxStyle == nil {
|
||||
p.BoxStyle = &ThemeDefault.BoxStyle
|
||||
}
|
||||
if p.TextStyle == nil {
|
||||
p.TextStyle = &ThemeDefault.BoxTextStyle
|
||||
}
|
||||
maxWidth := internal.GetStringMaxWidth(Sprint(a...))
|
||||
|
||||
var topLine string
|
||||
var bottomLine string
|
||||
|
||||
if p.Title == "" {
|
||||
topLine = p.BoxStyle.Sprint(p.BottomRightCornerString) + strings.Repeat(p.BoxStyle.Sprint(p.HorizontalString),
|
||||
maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.BottomLeftCornerString)
|
||||
bottomLine = p.BoxStyle.Sprint(p.TopRightCornerString) + strings.Repeat(p.BoxStyle.Sprint(p.HorizontalString),
|
||||
maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.TopLeftCornerString)
|
||||
} else {
|
||||
p.Title = strings.ReplaceAll(p.Title, "\n", " ")
|
||||
if (maxWidth + p.RightPadding + p.LeftPadding - 4) < len(RemoveColorFromString(p.Title)) {
|
||||
p.RightPadding = len(RemoveColorFromString(p.Title)) - (maxWidth + p.RightPadding + p.LeftPadding - 5)
|
||||
}
|
||||
if p.TitleTopLeft {
|
||||
topLine = p.BoxStyle.Sprint(p.BottomRightCornerString) + internal.AddTitleToLine(p.Title, p.BoxStyle.Sprint(p.HorizontalString), maxWidth+p.LeftPadding+p.RightPadding, true) + p.BoxStyle.Sprint(p.BottomLeftCornerString)
|
||||
bottomLine = p.BoxStyle.Sprint(p.TopRightCornerString) + strings.Repeat(p.BoxStyle.Sprint(p.HorizontalString),
|
||||
maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.TopLeftCornerString)
|
||||
} else if p.TitleTopRight {
|
||||
topLine = p.BoxStyle.Sprint(p.BottomRightCornerString) + internal.AddTitleToLine(p.Title, p.BoxStyle.Sprint(p.HorizontalString), maxWidth+p.LeftPadding+p.RightPadding, false) + p.BoxStyle.Sprint(p.BottomLeftCornerString)
|
||||
bottomLine = p.BoxStyle.Sprint(p.TopRightCornerString) + strings.Repeat(p.BoxStyle.Sprint(p.HorizontalString),
|
||||
maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.TopLeftCornerString)
|
||||
} else if p.TitleTopCenter {
|
||||
topLine = p.BoxStyle.Sprint(p.BottomRightCornerString) + internal.AddTitleToLineCenter(p.Title, p.BoxStyle.Sprint(p.HorizontalString), maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.BottomLeftCornerString)
|
||||
bottomLine = p.BoxStyle.Sprint(p.TopRightCornerString) + strings.Repeat(p.BoxStyle.Sprint(p.HorizontalString),
|
||||
maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.TopLeftCornerString)
|
||||
} else if p.TitleBottomLeft {
|
||||
topLine = p.BoxStyle.Sprint(p.BottomRightCornerString) + strings.Repeat(p.BoxStyle.Sprint(p.HorizontalString),
|
||||
maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.BottomLeftCornerString)
|
||||
bottomLine = p.BoxStyle.Sprint(p.TopRightCornerString) + internal.AddTitleToLine(p.Title, p.BoxStyle.Sprint(p.HorizontalString), maxWidth+p.LeftPadding+p.RightPadding, true) + p.BoxStyle.Sprint(p.TopLeftCornerString)
|
||||
} else if p.TitleBottomRight {
|
||||
topLine = p.BoxStyle.Sprint(p.BottomRightCornerString) + strings.Repeat(p.BoxStyle.Sprint(p.HorizontalString),
|
||||
maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.BottomLeftCornerString)
|
||||
bottomLine = p.BoxStyle.Sprint(p.TopRightCornerString) + internal.AddTitleToLine(p.Title, p.BoxStyle.Sprint(p.HorizontalString), maxWidth+p.LeftPadding+p.RightPadding, false) + p.BoxStyle.Sprint(p.TopLeftCornerString)
|
||||
} else if p.TitleBottomCenter {
|
||||
topLine = p.BoxStyle.Sprint(p.BottomRightCornerString) + strings.Repeat(p.BoxStyle.Sprint(p.HorizontalString),
|
||||
maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.BottomLeftCornerString)
|
||||
bottomLine = p.BoxStyle.Sprint(p.TopRightCornerString) + internal.AddTitleToLineCenter(p.Title, p.BoxStyle.Sprint(p.HorizontalString), maxWidth+p.LeftPadding+p.RightPadding) + p.BoxStyle.Sprint(p.TopLeftCornerString)
|
||||
}
|
||||
}
|
||||
|
||||
boxString := strings.Repeat("\n", p.TopPadding) + Sprint(a...) + strings.Repeat("\n", p.BottomPadding)
|
||||
|
||||
ss := strings.Split(boxString, "\n")
|
||||
for i, s2 := range ss {
|
||||
if runewidth.StringWidth(RemoveColorFromString(s2)) < maxWidth {
|
||||
ss[i] = p.BoxStyle.Sprint(p.VerticalString) + strings.Repeat(" ", p.LeftPadding) + p.TextStyle.Sprint(s2) +
|
||||
strings.Repeat(" ", maxWidth-runewidth.StringWidth(RemoveColorFromString(s2))+p.RightPadding) +
|
||||
p.BoxStyle.Sprint(p.VerticalString)
|
||||
} else {
|
||||
ss[i] = p.BoxStyle.Sprint(p.VerticalString) + strings.Repeat(" ", p.LeftPadding) + p.TextStyle.Sprint(s2) +
|
||||
strings.Repeat(" ", p.RightPadding) + p.BoxStyle.Sprint(p.VerticalString)
|
||||
}
|
||||
}
|
||||
return topLine + "\n" + strings.Join(ss, "\n") + "\n" + bottomLine
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p BoxPrinter) Sprintln(a ...interface{}) string {
|
||||
return p.Sprint(strings.TrimSuffix(Sprintln(a...), "\n")) + "\n"
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func (p BoxPrinter) Sprintf(format string, a ...interface{}) string {
|
||||
return p.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p BoxPrinter) Sprintfln(format string, a ...interface{}) string {
|
||||
return p.Sprintf(format, a...) + "\n"
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p BoxPrinter) Print(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprint(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p BoxPrinter) Println(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintln(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func (p BoxPrinter) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintf(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
func (p BoxPrinter) Printfln(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintfln(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p BoxPrinter) PrintOnError(a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p BoxPrinter) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// NewBulletListFromStrings returns a BulletListPrinter with Text using the NewTreeListItemFromString method.
|
||||
func NewBulletListFromStrings(s []string, padding string) BulletListPrinter {
|
||||
var lis []BulletListItem
|
||||
for _, line := range s {
|
||||
lis = append(lis, NewBulletListItemFromString(line, padding))
|
||||
}
|
||||
return *DefaultBulletList.WithItems(lis)
|
||||
}
|
||||
|
||||
// NewBulletListItemFromString returns a BulletListItem with a Text. The padding is counted in the Text to define the Level of the ListItem.
|
||||
func NewBulletListItemFromString(text string, padding string) BulletListItem {
|
||||
s, l := internal.RemoveAndCountPrefix(text, padding)
|
||||
return BulletListItem{
|
||||
Level: l,
|
||||
Text: s,
|
||||
}
|
||||
}
|
||||
|
||||
// BulletListItem is able to render a ListItem.
|
||||
type BulletListItem struct {
|
||||
Level int
|
||||
Text string
|
||||
TextStyle *Style
|
||||
Bullet string
|
||||
BulletStyle *Style
|
||||
}
|
||||
|
||||
// WithLevel returns a new BulletListItem with a specific Level.
|
||||
func (p BulletListItem) WithLevel(level int) *BulletListItem {
|
||||
p.Level = level
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithText returns a new BulletListItem with a specific Text.
|
||||
func (p BulletListItem) WithText(text string) *BulletListItem {
|
||||
p.Text = text
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTextStyle returns a new BulletListItem with a specific TextStyle.
|
||||
func (p BulletListItem) WithTextStyle(style *Style) *BulletListItem {
|
||||
p.TextStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBullet returns a new BulletListItem with a specific Prefix.
|
||||
func (p BulletListItem) WithBullet(bullet string) *BulletListItem {
|
||||
p.Bullet = bullet
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBulletStyle returns a new BulletListItem with a specific BulletStyle.
|
||||
func (p BulletListItem) WithBulletStyle(style *Style) *BulletListItem {
|
||||
p.BulletStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// NewBulletListFromString returns a BulletListPrinter with Text using the NewTreeListItemFromString method, splitting after return (\n).
|
||||
func NewBulletListFromString(s string, padding string) BulletListPrinter {
|
||||
return NewBulletListFromStrings(strings.Split(s, "\n"), padding)
|
||||
}
|
||||
|
||||
// DefaultBulletList contains standards, which can be used to print a BulletListPrinter.
|
||||
var DefaultBulletList = BulletListPrinter{
|
||||
Bullet: "•",
|
||||
TextStyle: &ThemeDefault.BulletListTextStyle,
|
||||
BulletStyle: &ThemeDefault.BulletListBulletStyle,
|
||||
}
|
||||
|
||||
// BulletListPrinter is able to render a list.
|
||||
type BulletListPrinter struct {
|
||||
Items []BulletListItem
|
||||
TextStyle *Style
|
||||
Bullet string
|
||||
BulletStyle *Style
|
||||
}
|
||||
|
||||
// WithItems returns a new list with specific Items.
|
||||
func (l BulletListPrinter) WithItems(items []BulletListItem) *BulletListPrinter {
|
||||
l.Items = append(l.Items, items...)
|
||||
return &l
|
||||
}
|
||||
|
||||
// WithTextStyle returns a new list with a specific text style.
|
||||
func (l BulletListPrinter) WithTextStyle(style *Style) *BulletListPrinter {
|
||||
l.TextStyle = style
|
||||
return &l
|
||||
}
|
||||
|
||||
// WithBullet returns a new list with a specific bullet.
|
||||
func (l BulletListPrinter) WithBullet(bullet string) *BulletListPrinter {
|
||||
l.Bullet = bullet
|
||||
return &l
|
||||
}
|
||||
|
||||
// WithBulletStyle returns a new list with a specific bullet style.
|
||||
func (l BulletListPrinter) WithBulletStyle(style *Style) *BulletListPrinter {
|
||||
l.BulletStyle = style
|
||||
return &l
|
||||
}
|
||||
|
||||
// Render prints the list to the terminal.
|
||||
func (l BulletListPrinter) Render() error {
|
||||
s, _ := l.Srender()
|
||||
Println(s)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Srender renders the list as a string.
|
||||
func (l BulletListPrinter) Srender() (string, error) {
|
||||
var ret string
|
||||
for _, item := range l.Items {
|
||||
if item.TextStyle == nil {
|
||||
if l.TextStyle == nil {
|
||||
item.TextStyle = &ThemeDefault.BulletListTextStyle
|
||||
} else {
|
||||
item.TextStyle = l.TextStyle
|
||||
}
|
||||
}
|
||||
if item.BulletStyle == nil {
|
||||
if l.BulletStyle == nil {
|
||||
item.BulletStyle = &ThemeDefault.BulletListBulletStyle
|
||||
} else {
|
||||
item.BulletStyle = l.BulletStyle
|
||||
}
|
||||
}
|
||||
if item.Bullet == "" {
|
||||
ret += strings.Repeat(" ", item.Level) + item.BulletStyle.Sprint(l.Bullet) + " " + item.TextStyle.Sprint(item.Text) + "\n"
|
||||
} else {
|
||||
ret += strings.Repeat(" ", item.Level) + item.BulletStyle.Sprint(item.Bullet) + " " + item.TextStyle.Sprint(item.Text) + "\n"
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// DefaultCenter is the default CenterPrinter.
|
||||
var DefaultCenter = CenterPrinter{
|
||||
CenterEachLineSeparately: false,
|
||||
}
|
||||
|
||||
// CenterPrinter prints centered text.
|
||||
type CenterPrinter struct {
|
||||
CenterEachLineSeparately bool
|
||||
}
|
||||
|
||||
// WithCenterEachLineSeparately centers each line separately.
|
||||
func (p CenterPrinter) WithCenterEachLineSeparately(b ...bool) *CenterPrinter {
|
||||
bt := internal.WithBoolean(b)
|
||||
p.CenterEachLineSeparately = bt
|
||||
return &p
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func (p CenterPrinter) Sprint(a ...interface{}) string {
|
||||
if RawOutput {
|
||||
return Sprint(a...)
|
||||
}
|
||||
|
||||
lines := strings.Split(Sprint(a...), "\n")
|
||||
|
||||
var ret string
|
||||
|
||||
if p.CenterEachLineSeparately {
|
||||
for _, line := range lines {
|
||||
margin := (GetTerminalWidth() - runewidth.StringWidth(RemoveColorFromString(line))) / 2
|
||||
if margin < 1 {
|
||||
ret += line + "\n"
|
||||
} else {
|
||||
ret += strings.Repeat(" ", margin) + line + "\n"
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
var maxLineWidth int
|
||||
|
||||
for _, line := range lines {
|
||||
lineLength := runewidth.StringWidth(RemoveColorFromString(line))
|
||||
if maxLineWidth < lineLength {
|
||||
maxLineWidth = lineLength
|
||||
}
|
||||
}
|
||||
|
||||
indent := GetTerminalWidth() - maxLineWidth
|
||||
|
||||
if indent/2 < 1 {
|
||||
for _, line := range lines {
|
||||
ret += line + "\n"
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
ret += strings.Repeat(" ", indent/2) + line + "\n"
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p CenterPrinter) Sprintln(a ...interface{}) string {
|
||||
return p.Sprint(Sprintln(a...))
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func (p CenterPrinter) Sprintf(format string, a ...interface{}) string {
|
||||
return p.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p CenterPrinter) Sprintfln(format string, a ...interface{}) string {
|
||||
return p.Sprintf(format, a...) + "\n"
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p CenterPrinter) Print(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprint(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p CenterPrinter) Println(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintln(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func (p CenterPrinter) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintf(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
func (p CenterPrinter) Printfln(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintfln(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p CenterPrinter) PrintOnError(a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p CenterPrinter) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
// PrintColor is false if PTerm should not print colored output.
|
||||
var PrintColor = true
|
||||
|
||||
// EnableColor enables colors.
|
||||
func EnableColor() {
|
||||
color.Enable = true
|
||||
PrintColor = true
|
||||
}
|
||||
|
||||
// DisableColor disables colors.
|
||||
func DisableColor() {
|
||||
color.Enable = false
|
||||
PrintColor = false
|
||||
}
|
||||
|
||||
// Foreground colors. basic foreground colors 30 - 37.
|
||||
const (
|
||||
FgBlack Color = iota + 30
|
||||
FgRed
|
||||
FgGreen
|
||||
FgYellow
|
||||
FgBlue
|
||||
FgMagenta
|
||||
FgCyan
|
||||
FgWhite
|
||||
// FgDefault revert default FG.
|
||||
FgDefault Color = 39
|
||||
)
|
||||
|
||||
// Extra foreground color 90 - 97.
|
||||
const (
|
||||
FgDarkGray Color = iota + 90
|
||||
FgLightRed
|
||||
FgLightGreen
|
||||
FgLightYellow
|
||||
FgLightBlue
|
||||
FgLightMagenta
|
||||
FgLightCyan
|
||||
FgLightWhite
|
||||
// FgGray is an alias of FgDarkGray.
|
||||
FgGray Color = 90
|
||||
)
|
||||
|
||||
// Background colors. basic background colors 40 - 47.
|
||||
const (
|
||||
BgBlack Color = iota + 40
|
||||
BgRed
|
||||
BgGreen
|
||||
BgYellow // BgBrown like yellow
|
||||
BgBlue
|
||||
BgMagenta
|
||||
BgCyan
|
||||
BgWhite
|
||||
// BgDefault reverts to the default background.
|
||||
BgDefault Color = 49
|
||||
)
|
||||
|
||||
// Extra background color 100 - 107.
|
||||
const (
|
||||
BgDarkGray Color = iota + 100
|
||||
BgLightRed
|
||||
BgLightGreen
|
||||
BgLightYellow
|
||||
BgLightBlue
|
||||
BgLightMagenta
|
||||
BgLightCyan
|
||||
BgLightWhite
|
||||
// BgGray is an alias of BgDarkGray.
|
||||
BgGray Color = 100
|
||||
)
|
||||
|
||||
// Option settings.
|
||||
const (
|
||||
Reset Color = iota
|
||||
Bold
|
||||
Fuzzy
|
||||
Italic
|
||||
Underscore
|
||||
Blink
|
||||
FastBlink
|
||||
Reverse
|
||||
Concealed
|
||||
Strikethrough
|
||||
)
|
||||
|
||||
var (
|
||||
// Red is an alias for FgRed.Sprint.
|
||||
Red = FgRed.Sprint
|
||||
// Cyan is an alias for FgCyan.Sprint.
|
||||
Cyan = FgCyan.Sprint
|
||||
// Gray is an alias for FgGray.Sprint.
|
||||
Gray = FgGray.Sprint
|
||||
// Blue is an alias for FgBlue.Sprint.
|
||||
Blue = FgBlue.Sprint
|
||||
// Black is an alias for FgBlack.Sprint.
|
||||
Black = FgBlack.Sprint
|
||||
// Green is an alias for FgGreen.Sprint.
|
||||
Green = FgGreen.Sprint
|
||||
// White is an alias for FgWhite.Sprint.
|
||||
White = FgWhite.Sprint
|
||||
// Yellow is an alias for FgYellow.Sprint.
|
||||
Yellow = FgYellow.Sprint
|
||||
// Magenta is an alias for FgMagenta.Sprint.
|
||||
Magenta = FgMagenta.Sprint
|
||||
|
||||
// Normal is an alias for FgDefault.Sprint.
|
||||
Normal = FgDefault.Sprint
|
||||
|
||||
// extra light.
|
||||
|
||||
// LightRed is a shortcut for FgLightRed.Sprint.
|
||||
LightRed = FgLightRed.Sprint
|
||||
// LightCyan is a shortcut for FgLightCyan.Sprint.
|
||||
LightCyan = FgLightCyan.Sprint
|
||||
// LightBlue is a shortcut for FgLightBlue.Sprint.
|
||||
LightBlue = FgLightBlue.Sprint
|
||||
// LightGreen is a shortcut for FgLightGreen.Sprint.
|
||||
LightGreen = FgLightGreen.Sprint
|
||||
// LightWhite is a shortcut for FgLightWhite.Sprint.
|
||||
LightWhite = FgLightWhite.Sprint
|
||||
// LightYellow is a shortcut for FgLightYellow.Sprint.
|
||||
LightYellow = FgLightYellow.Sprint
|
||||
// LightMagenta is a shortcut for FgLightMagenta.Sprint.
|
||||
LightMagenta = FgLightMagenta.Sprint
|
||||
)
|
||||
|
||||
// Color is a number which will be used to color strings in the terminal.
|
||||
type Color uint8
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// Input will be colored with the parent Color.
|
||||
func (c Color) Sprintln(a ...interface{}) string {
|
||||
str := fmt.Sprintln(a...)
|
||||
return c.Sprint(str)
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
// Input will be colored with the parent Color.
|
||||
func (c Color) Sprint(a ...interface{}) string {
|
||||
message := Sprint(a...)
|
||||
messageLines := strings.Split(message, "\n")
|
||||
for i, line := range messageLines {
|
||||
messageLines[i] = color.RenderCode(c.String(), strings.ReplaceAll(line, color.ResetSet, Sprintf("\x1b[0m\u001B[%sm", c.String())))
|
||||
}
|
||||
message = strings.Join(messageLines, "\n")
|
||||
return message
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
// Input will be colored with the parent Color.
|
||||
func (c Color) Sprintf(format string, a ...interface{}) string {
|
||||
return c.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// Input will be colored with the parent Color.
|
||||
func (c Color) Sprintfln(format string, a ...interface{}) string {
|
||||
return c.Sprint(Sprintf(format, a...) + "\n")
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Input will be colored with the parent Color.
|
||||
func (c Color) Println(a ...interface{}) *TextPrinter {
|
||||
Print(c.Sprintln(a...))
|
||||
tc := TextPrinter(c)
|
||||
return &tc
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Input will be colored with the parent Color.
|
||||
func (c Color) Print(a ...interface{}) *TextPrinter {
|
||||
Print(c.Sprint(a...))
|
||||
tc := TextPrinter(c)
|
||||
return &tc
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// Input will be colored with the parent Color.
|
||||
func (c Color) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
Print(c.Sprintf(format, a...))
|
||||
tc := TextPrinter(c)
|
||||
return &tc
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
// Input will be colored with the parent Color.
|
||||
func (c Color) Printfln(format string, a ...interface{}) *TextPrinter {
|
||||
Print(c.Sprintfln(format, a...))
|
||||
tp := TextPrinter(c)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p Color) PrintOnError(a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p Color) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// String converts the color to a string. eg "35".
|
||||
func (c Color) String() string {
|
||||
return fmt.Sprintf("%d", c)
|
||||
}
|
||||
|
||||
// Style is a collection of colors.
|
||||
// Can include foreground, background and styling (eg. Bold, Underscore, etc.) colors.
|
||||
type Style []Color
|
||||
|
||||
// NewStyle returns a new Style.
|
||||
// Accepts multiple colors.
|
||||
func NewStyle(colors ...Color) *Style {
|
||||
ret := Style{}
|
||||
for _, c := range colors {
|
||||
ret = append(ret, c)
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// Add styles to the current Style.
|
||||
func (s Style) Add(styles ...Style) Style {
|
||||
ret := s
|
||||
|
||||
for _, st := range styles {
|
||||
ret = append(ret, st...)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
// Input will be colored with the parent Style.
|
||||
func (s Style) Sprint(a ...interface{}) string {
|
||||
message := Sprint(a...)
|
||||
messageLines := strings.Split(message, "\n")
|
||||
for i, line := range messageLines {
|
||||
messageLines[i] = color.RenderCode(s.String(), strings.ReplaceAll(line, color.ResetSet, Sprintf("\x1b[0m\u001B[%sm", s.String())))
|
||||
}
|
||||
message = strings.Join(messageLines, "\n")
|
||||
return color.RenderCode(s.String(), message)
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// Input will be colored with the parent Style.
|
||||
func (s Style) Sprintln(a ...interface{}) string {
|
||||
return s.Sprint(a...) + "\n"
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
// Input will be colored with the parent Style.
|
||||
func (s Style) Sprintf(format string, a ...interface{}) string {
|
||||
return s.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// Input will be colored with the parent Style.
|
||||
func (s Style) Sprintfln(format string, a ...interface{}) string {
|
||||
return s.Sprint(Sprintf(format, a...) + "\n")
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Input will be colored with the parent Style.
|
||||
func (s Style) Print(a ...interface{}) {
|
||||
Print(s.Sprint(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.
|
||||
// Input will be colored with the parent Style.
|
||||
func (s Style) Println(a ...interface{}) {
|
||||
Println(s.Sprint(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.
|
||||
// Input will be colored with the parent Style.
|
||||
func (s Style) Printf(format string, a ...interface{}) {
|
||||
Print(s.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
// Input will be colored with the parent Style.
|
||||
func (s Style) Printfln(format string, a ...interface{}) {
|
||||
Print(s.Sprintfln(format, a...))
|
||||
}
|
||||
|
||||
// Code convert to code string. returns like "32;45;3".
|
||||
func (s Style) Code() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// String convert to code string. returns like "32;45;3".
|
||||
func (s Style) String() string {
|
||||
return colors2code(s...)
|
||||
}
|
||||
|
||||
// Converts colors to code.
|
||||
// Return format: "32;45;3".
|
||||
func colors2code(colors ...Color) string {
|
||||
if len(colors) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var codes []string
|
||||
for _, c := range colors {
|
||||
codes = append(codes, c.String())
|
||||
}
|
||||
|
||||
return strings.Join(codes, ";")
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"types": {
|
||||
"refactor": {
|
||||
"description": "Changes which neither fix a bug nor add a feature",
|
||||
},
|
||||
"fix": {
|
||||
"description": "Changes which patch a bug"
|
||||
},
|
||||
"feat": {
|
||||
"description": "Changes which introduce a new feature"
|
||||
},
|
||||
"build": {
|
||||
"description": "Changes which affect the build system or external dependencies (example scopes: gulp, broccoli, npm)"
|
||||
},
|
||||
"chore": {
|
||||
"description": "Changes which aren’t user-facing"
|
||||
},
|
||||
"style": {
|
||||
"description": "Changes which don't affect code logic.\nWhite-spaces, formatting, missing semi-colons, etc"
|
||||
},
|
||||
"test": {
|
||||
"description": "Changes which add missing tests or correct existing tests"
|
||||
},
|
||||
"docs": {
|
||||
"description": "Changes which affect documentation",
|
||||
"scopes": {
|
||||
"pterm-sh": {},
|
||||
"examples": {},
|
||||
"readme": {},
|
||||
"contributing": {}
|
||||
}
|
||||
},
|
||||
"perf": {
|
||||
"description": "Changes which improve performance"
|
||||
},
|
||||
"ci": {
|
||||
"description": "Changes which affect CI configuration files and scripts (example scopes: travis, circle, browser-stack, sauce-labs)"
|
||||
},
|
||||
"revert": {
|
||||
"description": "Changes which revert a previous commit"
|
||||
}
|
||||
},
|
||||
"footerTypes": [
|
||||
{
|
||||
"name": "BREAKING CHANGE",
|
||||
"description": "The commit introduces breaking API changes"
|
||||
},
|
||||
{
|
||||
"name": "Closes",
|
||||
"description": "The commit closes issues or pull requests"
|
||||
},
|
||||
{
|
||||
"name": "Implements",
|
||||
"description": "The commit implements features"
|
||||
},
|
||||
{
|
||||
"name": "Co-authored-by",
|
||||
"description": "The commit is co-authored by another person (for multiple people use one line each)"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package pterm
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrTerminalSizeNotDetectable - the terminal size can not be detected and the fallback values are used.
|
||||
ErrTerminalSizeNotDetectable = errors.New("terminal size could not be detected - using fallback value")
|
||||
|
||||
// ErrHexCodeIsInvalid - the given HEX code is invalid.
|
||||
ErrHexCodeIsInvalid = errors.New("hex code is not valid")
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
module github.com/pterm/pterm
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/MarvinJWendt/testza v0.2.12
|
||||
github.com/atomicgo/cursor v0.0.1
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/mattn/go-runewidth v0.0.13
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
)
|
|
@ -0,0 +1,46 @@
|
|||
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
|
||||
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
|
||||
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
|
||||
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
|
||||
github.com/MarvinJWendt/testza v0.2.12 h1:/PRp/BF+27t2ZxynTiqj0nyND5PbOtfJS0SuTuxmgeg=
|
||||
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
|
||||
github.com/atomicgo/cursor v0.0.1 h1:xdogsqa6YYlLfM+GyClC/Lchf7aiMerFiZQn7soTOoU=
|
||||
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk=
|
||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
|
||||
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
|
||||
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
|
||||
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
|
||||
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8=
|
||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,228 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultHeader returns the printer for a default header text.
|
||||
// Defaults to LightWhite, Bold Text and a Gray DefaultHeader background.
|
||||
DefaultHeader = HeaderPrinter{
|
||||
TextStyle: &ThemeDefault.HeaderTextStyle,
|
||||
BackgroundStyle: &ThemeDefault.HeaderBackgroundStyle,
|
||||
Margin: 5,
|
||||
}
|
||||
)
|
||||
|
||||
// HeaderPrinter contains the data used to craft a header.
|
||||
// A header is printed as a big box with text in it.
|
||||
// Can be used as title screens or section separator.
|
||||
type HeaderPrinter struct {
|
||||
TextStyle *Style
|
||||
BackgroundStyle *Style
|
||||
Margin int
|
||||
FullWidth bool
|
||||
}
|
||||
|
||||
// WithTextStyle returns a new HeaderPrinter with changed
|
||||
func (p HeaderPrinter) WithTextStyle(style *Style) *HeaderPrinter {
|
||||
p.TextStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBackgroundStyle changes the background styling of the header.
|
||||
func (p HeaderPrinter) WithBackgroundStyle(style *Style) *HeaderPrinter {
|
||||
p.BackgroundStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithMargin changes the background styling of the header.
|
||||
func (p HeaderPrinter) WithMargin(margin int) *HeaderPrinter {
|
||||
p.Margin = margin
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithFullWidth enables full width on a HeaderPrinter.
|
||||
func (p HeaderPrinter) WithFullWidth(b ...bool) *HeaderPrinter {
|
||||
p.FullWidth = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func (p HeaderPrinter) Sprint(a ...interface{}) string {
|
||||
if RawOutput {
|
||||
return Sprint(a...)
|
||||
}
|
||||
|
||||
if p.TextStyle == nil {
|
||||
p.TextStyle = NewStyle()
|
||||
}
|
||||
if p.BackgroundStyle == nil {
|
||||
p.BackgroundStyle = NewStyle()
|
||||
}
|
||||
|
||||
text := Sprint(a...)
|
||||
|
||||
var blankLine string
|
||||
|
||||
longestLine := internal.ReturnLongestLine(text, "\n")
|
||||
longestLineLen := runewidth.StringWidth(RemoveColorFromString(longestLine)) + p.Margin*2
|
||||
|
||||
if p.FullWidth {
|
||||
text = splitText(text, GetTerminalWidth()-p.Margin*2)
|
||||
blankLine = strings.Repeat(" ", GetTerminalWidth())
|
||||
} else {
|
||||
if longestLineLen > GetTerminalWidth() {
|
||||
text = splitText(text, GetTerminalWidth()-p.Margin*2)
|
||||
blankLine = strings.Repeat(" ", GetTerminalWidth())
|
||||
} else {
|
||||
text = splitText(text, longestLineLen-p.Margin*2)
|
||||
blankLine = strings.Repeat(" ", longestLineLen)
|
||||
}
|
||||
}
|
||||
|
||||
var marginString string
|
||||
var ret string
|
||||
|
||||
if p.FullWidth {
|
||||
longestLineLen = runewidth.StringWidth(RemoveColorFromString(internal.ReturnLongestLine(text, "\n")))
|
||||
marginString = strings.Repeat(" ", (GetTerminalWidth()-longestLineLen)/2)
|
||||
} else {
|
||||
marginString = strings.Repeat(" ", p.Margin)
|
||||
}
|
||||
|
||||
ret += p.BackgroundStyle.Sprint(blankLine) + "\n"
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
line = strings.ReplaceAll(line, "\n", "")
|
||||
line = marginString + line + marginString
|
||||
if runewidth.StringWidth(line) < runewidth.StringWidth(blankLine) {
|
||||
line += strings.Repeat(" ", runewidth.StringWidth(blankLine)-runewidth.StringWidth(line))
|
||||
}
|
||||
ret += p.BackgroundStyle.Sprint(p.TextStyle.Sprint(line)) + "\n"
|
||||
}
|
||||
ret += p.BackgroundStyle.Sprint(blankLine) + "\n"
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func splitText(text string, width int) string {
|
||||
var lines []string
|
||||
linesTmp := strings.Split(text, "\n")
|
||||
for _, line := range linesTmp {
|
||||
if runewidth.StringWidth(RemoveColorFromString(line)) > width {
|
||||
extraLines := []string{""}
|
||||
extraLinesCounter := 0
|
||||
for i, letter := range line {
|
||||
if i%width == 0 && i != 0 {
|
||||
extraLinesCounter++
|
||||
extraLines = append(extraLines, "")
|
||||
}
|
||||
extraLines[extraLinesCounter] += string(letter)
|
||||
}
|
||||
for _, extraLine := range extraLines {
|
||||
extraLine += "\n"
|
||||
lines = append(lines, extraLine)
|
||||
}
|
||||
} else {
|
||||
line += "\n"
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
|
||||
var line string
|
||||
for _, s := range lines {
|
||||
line += s
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(line, "\n")
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p HeaderPrinter) Sprintln(a ...interface{}) string {
|
||||
return p.Sprint(strings.TrimSuffix(Sprintln(a...), "\n"))
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func (p HeaderPrinter) Sprintf(format string, a ...interface{}) string {
|
||||
return p.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p HeaderPrinter) Sprintfln(format string, a ...interface{}) string {
|
||||
return p.Sprintf(format, a...) + "\n"
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *HeaderPrinter) Print(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprint(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *HeaderPrinter) Println(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintln(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func (p *HeaderPrinter) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintf(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
func (p *HeaderPrinter) Printfln(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintfln(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p *HeaderPrinter) PrintOnError(a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p *HeaderPrinter) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package pterm
|
||||
|
||||
// LivePrinter is a printer which can update it's output live.
|
||||
type LivePrinter interface {
|
||||
// GenericStart runs Start, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Start instead of this in your program.
|
||||
GenericStart() (*LivePrinter, error)
|
||||
|
||||
// GenericStop runs Stop, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Stop instead of this in your program.
|
||||
GenericStop() (*LivePrinter, error)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package pterm
|
||||
|
||||
// RenderPrinter is used to display renderable content.
|
||||
// Example for renderable content is a Table.
|
||||
type RenderPrinter interface {
|
||||
// Render the XXX to the terminal.
|
||||
Render() error
|
||||
|
||||
// Srender returns the rendered string of XXX.
|
||||
Srender() (string, error)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package pterm
|
||||
|
||||
// TextPrinter contains methods to print formatted text to the console or return it as a string.
|
||||
type TextPrinter interface {
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
Sprint(a ...interface{}) string
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
Sprintln(a ...interface{}) string
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
Sprintf(format string, a ...interface{}) string
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
Sprintfln(format string, a ...interface{}) string
|
||||
|
||||
// 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.
|
||||
Print(a ...interface{}) *TextPrinter
|
||||
|
||||
// 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.
|
||||
Println(a ...interface{}) *TextPrinter
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
Printf(format string, a ...interface{}) *TextPrinter
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
Printfln(format string, a ...interface{}) *TextPrinter
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
PrintOnError(a ...interface{}) *TextPrinter
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
PrintOnErrorf(format string, a ...interface{}) *TextPrinter
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
// CenterText returns a centered string with a padding left and right
|
||||
func CenterText(text string, width int) string {
|
||||
var lines []string
|
||||
linesTmp := strings.Split(text, "\n")
|
||||
for _, line := range linesTmp {
|
||||
if len(color.ClearCode(line)) > width {
|
||||
extraLines := []string{""}
|
||||
extraLinesCounter := 0
|
||||
for i, letter := range line {
|
||||
if i%width == 0 && i != 0 {
|
||||
extraLinesCounter++
|
||||
extraLines = append(extraLines, "")
|
||||
}
|
||||
extraLines[extraLinesCounter] += string(letter)
|
||||
}
|
||||
for _, extraLine := range extraLines {
|
||||
padding := width - len(color.ClearCode(extraLine))
|
||||
extraLine = strings.Repeat(" ", padding/2) + extraLine + strings.Repeat(" ", padding/2) + "\n"
|
||||
lines = append(lines, extraLine)
|
||||
}
|
||||
} else {
|
||||
padding := width - len(color.ClearCode(line))
|
||||
line = strings.Repeat(" ", padding/2) + line + strings.Repeat(" ", padding/2) + "\n"
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
|
||||
var line string
|
||||
for _, s := range lines {
|
||||
line += s
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(line, "\n")
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package internal
|
||||
|
||||
// RandomStrings contains a list of random strings to use while testing.
|
||||
var RandomStrings = []string{
|
||||
"hello world", "²³14234!`§=)$-.€@_&", "This is a sentence.", "This\nstring\nhas\nmultiple\nlines",
|
||||
"windows\r\nline\r\nendings", "\rtext",
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// ReturnLongestLine returns the longest line with a given separator
|
||||
func ReturnLongestLine(text, sep string) string {
|
||||
lines := strings.Split(text, sep)
|
||||
var longest string
|
||||
for _, line := range lines {
|
||||
if runewidth.StringWidth(color.ClearCode(line)) > runewidth.StringWidth(color.ClearCode(longest)) {
|
||||
longest = line
|
||||
}
|
||||
}
|
||||
|
||||
return longest
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package internal
|
||||
|
||||
func MapRangeToRange(fromMin, fromMax, toMin, toMax, current float32) int {
|
||||
if fromMax-fromMin == 0 {
|
||||
return 0
|
||||
}
|
||||
return int(toMin + ((toMax-toMin)/(fromMax-fromMin))*(current-fromMin))
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// GetStringMaxWidth returns the maximum width of a string with multiple lines.
|
||||
func GetStringMaxWidth(s string) int {
|
||||
var max int
|
||||
ss := strings.Split(s, "\n")
|
||||
for _, s2 := range ss {
|
||||
if runewidth.StringWidth(color.ClearCode(s2)) > max {
|
||||
max = runewidth.StringWidth(color.ClearCode(s2))
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package internal
|
||||
|
||||
import "math"
|
||||
|
||||
// Percentage calculates percentage.
|
||||
func Percentage(total, current float64) float64 {
|
||||
return (current / total) * 100
|
||||
}
|
||||
|
||||
// PercentageRound returns a rounded Percentage.
|
||||
func PercentageRound(total, current float64) float64 {
|
||||
return math.Round(Percentage(total, current))
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RemoveAndCountPrefix(input, subString string) (string, int) {
|
||||
inputLength := len(input)
|
||||
input = strings.TrimLeft(input, subString)
|
||||
return input, inputLength - len(input)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
// AddTitleToLine adds a title to a site of a line ex: "─ This is the title ──────"
|
||||
func AddTitleToLine(title, line string, length int, left bool) string {
|
||||
var ret string
|
||||
if left {
|
||||
ret += line + " " + title + " " + line + strings.Repeat(line, length-(4+len(color.ClearCode(title))))
|
||||
} else {
|
||||
ret += strings.Repeat(line, length-(4+len(color.ClearCode(title)))) + line + " " + title + " " + line
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// AddTitleToLineCenter adds a title to the center of a line ex: "─ This is the title ──────"
|
||||
func AddTitleToLineCenter(title, line string, length int) string {
|
||||
var ret string
|
||||
repeatString := length - (4 + len(color.ClearCode(title)))
|
||||
unevenRepeatString := repeatString % 2
|
||||
|
||||
ret += strings.Repeat(line, repeatString/2) + line + " " + title + " " + line + strings.Repeat(line, repeatString/2+unevenRepeatString)
|
||||
|
||||
return ret
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package internal
|
||||
|
||||
// WithBoolean helps an option setter (WithXXX(b ...bool) to return true, if no boolean is set, but false if it's explicitly set to false.
|
||||
func WithBoolean(b []bool) bool {
|
||||
if len(b) == 0 {
|
||||
b = append(b, true)
|
||||
}
|
||||
return b[0]
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// Panel contains the data, which should be printed inside a PanelPrinter.
|
||||
type Panel struct {
|
||||
Data string
|
||||
}
|
||||
|
||||
// Panels is a two dimensional coordinate system for Panel.
|
||||
type Panels [][]Panel
|
||||
|
||||
// DefaultPanel is the default PanelPrinter.
|
||||
var DefaultPanel = PanelPrinter{
|
||||
Padding: 1,
|
||||
}
|
||||
|
||||
// PanelPrinter prints content in boxes.
|
||||
type PanelPrinter struct {
|
||||
Panels Panels
|
||||
Padding int
|
||||
BottomPadding int
|
||||
SameColumnWidth bool
|
||||
BoxPrinter BoxPrinter
|
||||
}
|
||||
|
||||
// WithPanels returns a new PanelPrinter with specific options.
|
||||
func (p PanelPrinter) WithPanels(panels Panels) *PanelPrinter {
|
||||
p.Panels = panels
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithPadding returns a new PanelPrinter with specific options.
|
||||
func (p PanelPrinter) WithPadding(padding int) *PanelPrinter {
|
||||
if padding < 0 {
|
||||
padding = 0
|
||||
}
|
||||
p.Padding = padding
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBottomPadding returns a new PanelPrinter with specific options.
|
||||
func (p PanelPrinter) WithBottomPadding(bottomPadding int) *PanelPrinter {
|
||||
if bottomPadding < 0 {
|
||||
bottomPadding = 0
|
||||
}
|
||||
p.BottomPadding = bottomPadding
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithSameColumnWidth returns a new PanelPrinter with specific options.
|
||||
func (p PanelPrinter) WithSameColumnWidth(b ...bool) *PanelPrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.SameColumnWidth = b2
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBoxPrinter returns a new PanelPrinter with specific options.
|
||||
func (p PanelPrinter) WithBoxPrinter(boxPrinter BoxPrinter) *PanelPrinter {
|
||||
p.BoxPrinter = boxPrinter
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p PanelPrinter) getRawOutput() string {
|
||||
var ret string
|
||||
for _, panel := range p.Panels {
|
||||
for _, panel2 := range panel {
|
||||
ret += panel2.Data + "\n\n"
|
||||
}
|
||||
ret += "\n"
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Srender renders the Template as a string.
|
||||
func (p PanelPrinter) Srender() (string, error) {
|
||||
var ret string
|
||||
|
||||
if RawOutput {
|
||||
return p.getRawOutput(), nil
|
||||
}
|
||||
|
||||
for i := range p.Panels {
|
||||
for i2 := range p.Panels[i] {
|
||||
p.Panels[i][i2].Data = strings.TrimSuffix(p.Panels[i][i2].Data, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
if p.BoxPrinter != (BoxPrinter{}) {
|
||||
for i := range p.Panels {
|
||||
for i2 := range p.Panels[i] {
|
||||
p.Panels[i][i2].Data = p.BoxPrinter.Sprint(p.Panels[i][i2].Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range p.Panels {
|
||||
if len(p.Panels)-1 != i {
|
||||
for i2 := range p.Panels[i] {
|
||||
p.Panels[i][i2].Data += strings.Repeat("\n", p.BottomPadding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
columnMaxHeightMap := make(map[int]int)
|
||||
|
||||
if p.SameColumnWidth {
|
||||
for _, panel := range p.Panels {
|
||||
for i, p2 := range panel {
|
||||
if columnMaxHeightMap[i] < internal.GetStringMaxWidth(p2.Data) {
|
||||
columnMaxHeightMap[i] = internal.GetStringMaxWidth(p2.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, boxLine := range p.Panels {
|
||||
var maxHeight int
|
||||
|
||||
var renderedPanels []string
|
||||
|
||||
for _, box := range boxLine {
|
||||
renderedPanels = append(renderedPanels, box.Data)
|
||||
}
|
||||
|
||||
for i, panel := range renderedPanels {
|
||||
renderedPanels[i] = strings.ReplaceAll(panel, "\n", Reset.Sprint()+"\n")
|
||||
}
|
||||
|
||||
for _, box := range renderedPanels {
|
||||
height := len(strings.Split(box, "\n"))
|
||||
if height > maxHeight {
|
||||
maxHeight = height
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < maxHeight; i++ {
|
||||
if maxHeight != i {
|
||||
for j, letter := range renderedPanels {
|
||||
var letterLine string
|
||||
letterLines := strings.Split(letter, "\n")
|
||||
var maxLetterWidth int
|
||||
if !p.SameColumnWidth {
|
||||
maxLetterWidth = internal.GetStringMaxWidth(letter)
|
||||
}
|
||||
if len(letterLines) > i {
|
||||
letterLine = letterLines[i]
|
||||
}
|
||||
letterLineLength := runewidth.StringWidth(RemoveColorFromString(letterLine))
|
||||
if !p.SameColumnWidth {
|
||||
if letterLineLength < maxLetterWidth {
|
||||
letterLine += strings.Repeat(" ", maxLetterWidth-letterLineLength)
|
||||
}
|
||||
} else {
|
||||
if letterLineLength < columnMaxHeightMap[j] {
|
||||
letterLine += strings.Repeat(" ", columnMaxHeightMap[j]-letterLineLength)
|
||||
}
|
||||
}
|
||||
letterLine += strings.Repeat(" ", p.Padding)
|
||||
ret += letterLine
|
||||
}
|
||||
ret += "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Render prints the Template to the terminal.
|
||||
func (p PanelPrinter) Render() error {
|
||||
s, _ := p.Srender()
|
||||
Println(s)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultParagraph contains the default values for a ParagraphPrinter.
|
||||
var DefaultParagraph = ParagraphPrinter{
|
||||
MaxWidth: GetTerminalWidth(),
|
||||
}
|
||||
|
||||
// ParagraphPrinter can print paragraphs to a fixed line width.
|
||||
// The text will split between words, so that words will stick together.
|
||||
// It's like in a book.
|
||||
type ParagraphPrinter struct {
|
||||
MaxWidth int
|
||||
}
|
||||
|
||||
// WithMaxWidth returns a new ParagraphPrinter with a specific MaxWidth
|
||||
func (p ParagraphPrinter) WithMaxWidth(width int) *ParagraphPrinter {
|
||||
p.MaxWidth = width
|
||||
return &p
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func (p ParagraphPrinter) Sprint(a ...interface{}) string {
|
||||
if RawOutput {
|
||||
return Sprint(a...)
|
||||
}
|
||||
|
||||
words := strings.Fields(strings.TrimSpace(Sprint(a...)))
|
||||
if len(words) == 0 {
|
||||
return ""
|
||||
}
|
||||
wrapped := words[0]
|
||||
spaceLeft := p.MaxWidth - len(wrapped)
|
||||
for _, word := range words[1:] {
|
||||
if len(word)+1 > spaceLeft {
|
||||
wrapped += "\n" + word
|
||||
spaceLeft = p.MaxWidth - len(word)
|
||||
} else {
|
||||
wrapped += " " + word
|
||||
spaceLeft -= 1 + len(word)
|
||||
}
|
||||
}
|
||||
|
||||
return wrapped
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p ParagraphPrinter) Sprintln(a ...interface{}) string {
|
||||
return p.Sprint(Sprintln(a...)) + "\n"
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func (p ParagraphPrinter) Sprintf(format string, a ...interface{}) string {
|
||||
return p.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p ParagraphPrinter) Sprintfln(format string, a ...interface{}) string {
|
||||
return p.Sprintf(format, a...) + "\n"
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *ParagraphPrinter) Print(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprint(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *ParagraphPrinter) Println(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintln(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func (p *ParagraphPrinter) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintf(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
func (p *ParagraphPrinter) Printfln(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintfln(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p *ParagraphPrinter) PrintOnError(a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p *ParagraphPrinter) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
// GrayBoxStyle wraps text in a gray box.
|
||||
GrayBoxStyle = NewStyle(BgGray, FgLightWhite)
|
||||
)
|
||||
|
||||
var (
|
||||
// Info returns a PrefixPrinter, which can be used to print text with an "info" Prefix.
|
||||
Info = PrefixPrinter{
|
||||
MessageStyle: &ThemeDefault.InfoMessageStyle,
|
||||
Prefix: Prefix{
|
||||
Style: &ThemeDefault.InfoPrefixStyle,
|
||||
Text: "INFO",
|
||||
},
|
||||
}
|
||||
|
||||
// Warning returns a PrefixPrinter, which can be used to print text with a "warning" Prefix.
|
||||
Warning = PrefixPrinter{
|
||||
MessageStyle: &ThemeDefault.WarningMessageStyle,
|
||||
Prefix: Prefix{
|
||||
Style: &ThemeDefault.WarningPrefixStyle,
|
||||
Text: "WARNING",
|
||||
},
|
||||
}
|
||||
|
||||
// Success returns a PrefixPrinter, which can be used to print text with a "success" Prefix.
|
||||
Success = PrefixPrinter{
|
||||
MessageStyle: &ThemeDefault.SuccessMessageStyle,
|
||||
Prefix: Prefix{
|
||||
Style: &ThemeDefault.SuccessPrefixStyle,
|
||||
Text: "SUCCESS",
|
||||
},
|
||||
}
|
||||
|
||||
// Error returns a PrefixPrinter, which can be used to print text with an "error" Prefix.
|
||||
Error = PrefixPrinter{
|
||||
MessageStyle: &ThemeDefault.ErrorMessageStyle,
|
||||
Prefix: Prefix{
|
||||
Style: &ThemeDefault.ErrorPrefixStyle,
|
||||
Text: " ERROR ",
|
||||
},
|
||||
}
|
||||
|
||||
// Fatal returns a PrefixPrinter, which can be used to print text with an "fatal" Prefix.
|
||||
// NOTICE: Fatal terminates the application immediately!
|
||||
Fatal = PrefixPrinter{
|
||||
MessageStyle: &ThemeDefault.FatalMessageStyle,
|
||||
Prefix: Prefix{
|
||||
Style: &ThemeDefault.FatalPrefixStyle,
|
||||
Text: " FATAL ",
|
||||
},
|
||||
Fatal: true,
|
||||
}
|
||||
|
||||
// Debug Prints debug messages. By default it will only print if PrintDebugMessages is true.
|
||||
// You can change PrintDebugMessages with EnableDebugMessages and DisableDebugMessages, or by setting the variable itself.
|
||||
Debug = PrefixPrinter{
|
||||
MessageStyle: &ThemeDefault.DebugMessageStyle,
|
||||
Prefix: Prefix{
|
||||
Text: " DEBUG ",
|
||||
Style: &ThemeDefault.DebugPrefixStyle,
|
||||
},
|
||||
Debugger: true,
|
||||
}
|
||||
|
||||
// Description returns a PrefixPrinter, which can be used to print text with a "description" Prefix.
|
||||
Description = PrefixPrinter{
|
||||
MessageStyle: &ThemeDefault.DescriptionMessageStyle,
|
||||
Prefix: Prefix{
|
||||
Style: &ThemeDefault.DescriptionPrefixStyle,
|
||||
Text: "Description",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// PrefixPrinter is the printer used to print a Prefix.
|
||||
type PrefixPrinter struct {
|
||||
Prefix Prefix
|
||||
Scope Scope
|
||||
MessageStyle *Style
|
||||
Fatal bool
|
||||
ShowLineNumber bool
|
||||
LineNumberOffset int
|
||||
// If Debugger is true, the printer will only print if PrintDebugMessages is set to true.
|
||||
// You can change PrintDebugMessages with EnableDebugMessages and DisableDebugMessages, or by setting the variable itself.
|
||||
Debugger bool
|
||||
}
|
||||
|
||||
// WithPrefix adds a custom prefix to the printer.
|
||||
func (p PrefixPrinter) WithPrefix(prefix Prefix) *PrefixPrinter {
|
||||
p.Prefix = prefix
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithScope adds a scope to the Prefix.
|
||||
func (p PrefixPrinter) WithScope(scope Scope) *PrefixPrinter {
|
||||
p.Scope = scope
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithMessageStyle adds a custom prefix to the printer.
|
||||
func (p PrefixPrinter) WithMessageStyle(style *Style) *PrefixPrinter {
|
||||
p.MessageStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithFatal sets if the printer should panic after printing.
|
||||
// NOTE:
|
||||
// The printer will only panic if either PrefixPrinter.Println, PrefixPrinter.Print
|
||||
// or PrefixPrinter.Printf is called.
|
||||
func (p PrefixPrinter) WithFatal(b ...bool) *PrefixPrinter {
|
||||
p.Fatal = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithShowLineNumber sets if the printer should print the line number from where it's called in a go file.
|
||||
func (p PrefixPrinter) WithShowLineNumber(b ...bool) *PrefixPrinter {
|
||||
p.ShowLineNumber = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithDebugger returns a new Printer with specific Debugger value.
|
||||
// If Debugger is true, the printer will only print if PrintDebugMessages is set to true.
|
||||
// You can change PrintDebugMessages with EnableDebugMessages and DisableDebugMessages, or by setting the variable itself.
|
||||
func (p PrefixPrinter) WithDebugger(b ...bool) *PrefixPrinter {
|
||||
p.Debugger = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithLineNumberOffset can be used to exclude a specific amount of calls in the call stack.
|
||||
// If you make a wrapper function for example, you can set this to one.
|
||||
// The printed line number will then be the line number where your wrapper function is called.
|
||||
func (p PrefixPrinter) WithLineNumberOffset(offset int) *PrefixPrinter {
|
||||
p.LineNumberOffset = offset
|
||||
return &p
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func (p *PrefixPrinter) Sprint(a ...interface{}) string {
|
||||
m := Sprint(a...)
|
||||
if p.Debugger && !PrintDebugMessages {
|
||||
return ""
|
||||
}
|
||||
|
||||
if RawOutput {
|
||||
if p.Prefix.Text != "" {
|
||||
return Sprintf("%s: %s", strings.TrimSpace(p.Prefix.Text), Sprint(a...))
|
||||
} else {
|
||||
return Sprint(a...)
|
||||
}
|
||||
}
|
||||
|
||||
if p.Prefix.Style == nil {
|
||||
p.Prefix.Style = NewStyle()
|
||||
}
|
||||
if p.Scope.Style == nil {
|
||||
p.Scope.Style = NewStyle()
|
||||
}
|
||||
if p.MessageStyle == nil {
|
||||
p.MessageStyle = NewStyle()
|
||||
}
|
||||
|
||||
var ret string
|
||||
var newLine bool
|
||||
|
||||
if strings.HasSuffix(m, "\n") {
|
||||
m = strings.TrimRight(m, "\n")
|
||||
newLine = true
|
||||
}
|
||||
|
||||
messageLines := strings.Split(m, "\n")
|
||||
for i, m := range messageLines {
|
||||
if i == 0 {
|
||||
ret += p.GetFormattedPrefix() + " "
|
||||
if p.Scope.Text != "" {
|
||||
ret += NewStyle(*p.Scope.Style...).Sprint(" (" + p.Scope.Text + ") ")
|
||||
}
|
||||
ret += p.MessageStyle.Sprint(m)
|
||||
} else {
|
||||
ret += "\n" + p.Prefix.Style.Sprint(strings.Repeat(" ", len(p.Prefix.Text)+2)) + " " + p.MessageStyle.Sprint(m)
|
||||
}
|
||||
}
|
||||
|
||||
_, fileName, line, _ := runtime.Caller(3 + p.LineNumberOffset)
|
||||
|
||||
if p.ShowLineNumber {
|
||||
ret += FgGray.Sprint("\n└ " + fmt.Sprintf("(%s:%d)\n", fileName, line))
|
||||
newLine = false
|
||||
}
|
||||
|
||||
if newLine {
|
||||
ret += "\n"
|
||||
}
|
||||
|
||||
return Sprint(ret)
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p PrefixPrinter) Sprintln(a ...interface{}) string {
|
||||
if p.Debugger && !PrintDebugMessages {
|
||||
return ""
|
||||
}
|
||||
str := fmt.Sprintln(a...)
|
||||
return p.Sprint(str)
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func (p PrefixPrinter) Sprintf(format string, a ...interface{}) string {
|
||||
if p.Debugger && !PrintDebugMessages {
|
||||
return ""
|
||||
}
|
||||
return p.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p PrefixPrinter) Sprintfln(format string, a ...interface{}) string {
|
||||
if p.Debugger && !PrintDebugMessages {
|
||||
return ""
|
||||
}
|
||||
return p.Sprintf(format, a...) + "\n"
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *PrefixPrinter) Print(a ...interface{}) *TextPrinter {
|
||||
tp := TextPrinter(p)
|
||||
if p.Debugger && !PrintDebugMessages {
|
||||
return &tp
|
||||
}
|
||||
Print(p.Sprint(a...))
|
||||
checkFatal(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *PrefixPrinter) Println(a ...interface{}) *TextPrinter {
|
||||
tp := TextPrinter(p)
|
||||
if p.Debugger && !PrintDebugMessages {
|
||||
return &tp
|
||||
}
|
||||
Print(p.Sprintln(a...))
|
||||
checkFatal(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func (p *PrefixPrinter) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
tp := TextPrinter(p)
|
||||
if p.Debugger && !PrintDebugMessages {
|
||||
return &tp
|
||||
}
|
||||
Print(p.Sprintf(format, a...))
|
||||
checkFatal(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
func (p *PrefixPrinter) Printfln(format string, a ...interface{}) *TextPrinter {
|
||||
tp := TextPrinter(p)
|
||||
if p.Debugger && !PrintDebugMessages {
|
||||
return &tp
|
||||
}
|
||||
Print(p.Sprintfln(format, a...))
|
||||
checkFatal(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
//
|
||||
// Note: Use WithFatal(true) or Fatal to panic after first non nil error.
|
||||
func (p *PrefixPrinter) PrintOnError(a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p *PrefixPrinter) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// GetFormattedPrefix returns the Prefix as a styled text string.
|
||||
func (p PrefixPrinter) GetFormattedPrefix() string {
|
||||
return p.Prefix.Style.Sprint(" " + p.Prefix.Text + " ")
|
||||
}
|
||||
|
||||
// Prefix contains the data used as the beginning of a printed text via a PrefixPrinter.
|
||||
type Prefix struct {
|
||||
Text string
|
||||
Style *Style
|
||||
}
|
||||
|
||||
// Scope contains the data of the optional scope of a prefix.
|
||||
// If it has a text, it will be printed after the Prefix in brackets.
|
||||
type Scope struct {
|
||||
Text string
|
||||
Style *Style
|
||||
}
|
||||
|
||||
func checkFatal(p *PrefixPrinter) {
|
||||
if p.Fatal {
|
||||
panic("")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
// SetDefaultOutput sets the default output of pterm.
|
||||
func SetDefaultOutput(w io.Writer) {
|
||||
color.SetOutput(w)
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func Sprint(a ...interface{}) string {
|
||||
return color.Sprint(a...)
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return color.Sprintf(format, a...)
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func Sprintfln(format string, a ...interface{}) string {
|
||||
return color.Sprintf(format, a...) + "\n"
|
||||
}
|
||||
|
||||
// Sprintln returns what Println would print to the terminal.
|
||||
func Sprintln(a ...interface{}) string {
|
||||
str := fmt.Sprintln(a...)
|
||||
return Sprint(str)
|
||||
}
|
||||
|
||||
// Sprinto returns what Printo would print.
|
||||
func Sprinto(a ...interface{}) string {
|
||||
return "\r" + Sprint(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.
|
||||
func Print(a ...interface{}) {
|
||||
if !Output {
|
||||
return
|
||||
}
|
||||
|
||||
var ret string
|
||||
var printed bool
|
||||
|
||||
for _, bar := range ActiveProgressBarPrinters {
|
||||
if bar.IsActive {
|
||||
ret += sClearLine()
|
||||
ret += Sprinto(a...)
|
||||
printed = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, spinner := range activeSpinnerPrinters {
|
||||
if spinner.IsActive {
|
||||
ret += sClearLine()
|
||||
ret += Sprinto(a...)
|
||||
printed = true
|
||||
}
|
||||
}
|
||||
|
||||
if !printed {
|
||||
ret = color.Sprint(Sprint(a...))
|
||||
}
|
||||
|
||||
color.Print(Sprint(ret))
|
||||
|
||||
// Refresh all progressbars in case they were overwritten previously. Reference: #302
|
||||
for _, bar := range ActiveProgressBarPrinters {
|
||||
if bar.IsActive {
|
||||
bar.UpdateTitle(bar.Title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Println(a ...interface{}) {
|
||||
Print(Sprintln(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.
|
||||
func Printf(format string, a ...interface{}) {
|
||||
Print(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
func Printfln(format string, a ...interface{}) {
|
||||
Print(Sprintfln(format, a...))
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func PrintOnError(a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func PrintOnErrorf(format string, a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Fprint(writer io.Writer, a ...interface{}) {
|
||||
if !Output {
|
||||
return
|
||||
}
|
||||
|
||||
color.Fprint(writer, Sprint(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.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func Fprintln(writer io.Writer, a ...interface{}) {
|
||||
Fprint(writer, Sprint(a...)+"\n")
|
||||
}
|
||||
|
||||
// Printo overrides the current line in a terminal.
|
||||
// If the current line is empty, the text will be printed like with pterm.Print.
|
||||
// Example:
|
||||
// pterm.Printo("Hello, World")
|
||||
// time.Sleep(time.Second)
|
||||
// pterm.Printo("Hello, Earth!")
|
||||
func Printo(a ...interface{}) {
|
||||
if !Output {
|
||||
return
|
||||
}
|
||||
|
||||
color.Print("\r" + Sprint(a...))
|
||||
}
|
||||
|
||||
// Fprinto prints Printo to a custom writer.
|
||||
func Fprinto(w io.Writer, a ...interface{}) {
|
||||
Fprint(w, "\r", Sprint(a...))
|
||||
}
|
||||
|
||||
// RemoveColorFromString removes color codes from a string.
|
||||
func RemoveColorFromString(a ...interface{}) string {
|
||||
return color.ClearCode(Sprint(a...))
|
||||
}
|
||||
|
||||
func clearLine() {
|
||||
Printo(strings.Repeat(" ", GetTerminalWidth()))
|
||||
}
|
||||
|
||||
func sClearLine() string {
|
||||
return Sprinto(strings.Repeat(" ", GetTerminalWidth()))
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gookit/color"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// ActiveProgressBarPrinters contains all running ProgressbarPrinters.
|
||||
// Generally, there should only be one active ProgressbarPrinter at a time.
|
||||
var ActiveProgressBarPrinters []*ProgressbarPrinter
|
||||
|
||||
var (
|
||||
// DefaultProgressbar is the default ProgressbarPrinter.
|
||||
DefaultProgressbar = ProgressbarPrinter{
|
||||
Total: 100,
|
||||
BarCharacter: "█",
|
||||
LastCharacter: "█",
|
||||
ElapsedTimeRoundingFactor: time.Second,
|
||||
BarStyle: &ThemeDefault.ProgressbarBarStyle,
|
||||
TitleStyle: &ThemeDefault.ProgressbarTitleStyle,
|
||||
ShowTitle: true,
|
||||
ShowCount: true,
|
||||
ShowPercentage: true,
|
||||
ShowElapsedTime: true,
|
||||
BarFiller: " ",
|
||||
}
|
||||
)
|
||||
|
||||
// ProgressbarPrinter shows a progress animation in the terminal.
|
||||
type ProgressbarPrinter struct {
|
||||
Title string
|
||||
Total int
|
||||
Current int
|
||||
BarCharacter string
|
||||
LastCharacter string
|
||||
ElapsedTimeRoundingFactor time.Duration
|
||||
BarFiller string
|
||||
|
||||
ShowElapsedTime bool
|
||||
ShowCount bool
|
||||
ShowTitle bool
|
||||
ShowPercentage bool
|
||||
RemoveWhenDone bool
|
||||
|
||||
TitleStyle *Style
|
||||
BarStyle *Style
|
||||
|
||||
IsActive bool
|
||||
|
||||
startedAt time.Time
|
||||
}
|
||||
|
||||
// WithTitle sets the name of the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) WithTitle(name string) *ProgressbarPrinter {
|
||||
p.Title = name
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTotal sets the total value of the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) WithTotal(total int) *ProgressbarPrinter {
|
||||
p.Total = total
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithCurrent sets the current value of the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) WithCurrent(current int) *ProgressbarPrinter {
|
||||
p.Current = current
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBarCharacter sets the bar character of the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) WithBarCharacter(char string) *ProgressbarPrinter {
|
||||
p.BarCharacter = char
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithLastCharacter sets the last character of the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) WithLastCharacter(char string) *ProgressbarPrinter {
|
||||
p.LastCharacter = char
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithElapsedTimeRoundingFactor sets the rounding factor of the elapsed time.
|
||||
func (p ProgressbarPrinter) WithElapsedTimeRoundingFactor(duration time.Duration) *ProgressbarPrinter {
|
||||
p.ElapsedTimeRoundingFactor = duration
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithShowElapsedTime sets if the elapsed time should be displayed in the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) WithShowElapsedTime(b ...bool) *ProgressbarPrinter {
|
||||
p.ShowElapsedTime = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithShowCount sets if the total and current count should be displayed in the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) WithShowCount(b ...bool) *ProgressbarPrinter {
|
||||
p.ShowCount = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithShowTitle sets if the title should be displayed in the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) WithShowTitle(b ...bool) *ProgressbarPrinter {
|
||||
p.ShowTitle = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithShowPercentage sets if the completed percentage should be displayed in the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) WithShowPercentage(b ...bool) *ProgressbarPrinter {
|
||||
p.ShowPercentage = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTitleStyle sets the style of the title.
|
||||
func (p ProgressbarPrinter) WithTitleStyle(style *Style) *ProgressbarPrinter {
|
||||
p.TitleStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBarStyle sets the style of the bar.
|
||||
func (p ProgressbarPrinter) WithBarStyle(style *Style) *ProgressbarPrinter {
|
||||
p.BarStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithRemoveWhenDone sets if the ProgressbarPrinter should be removed when it is done.
|
||||
func (p ProgressbarPrinter) WithRemoveWhenDone(b ...bool) *ProgressbarPrinter {
|
||||
p.RemoveWhenDone = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// Increment current value by one.
|
||||
func (p *ProgressbarPrinter) Increment() *ProgressbarPrinter {
|
||||
p.Add(1)
|
||||
return p
|
||||
}
|
||||
|
||||
// This method changed the title and re-renders the progressbar
|
||||
func (p *ProgressbarPrinter) UpdateTitle(title string) *ProgressbarPrinter {
|
||||
p.Title = title
|
||||
p.updateProgress()
|
||||
return p
|
||||
}
|
||||
|
||||
// This is the update logic, renders the progressbar
|
||||
func (p *ProgressbarPrinter) updateProgress() *ProgressbarPrinter {
|
||||
if p.TitleStyle == nil {
|
||||
p.TitleStyle = NewStyle()
|
||||
}
|
||||
if p.BarStyle == nil {
|
||||
p.BarStyle = NewStyle()
|
||||
}
|
||||
if p.Total == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var before string
|
||||
var after string
|
||||
|
||||
width := GetTerminalWidth()
|
||||
currentPercentage := int(internal.PercentageRound(float64(int64(p.Total)), float64(int64(p.Current))))
|
||||
|
||||
decoratorCount := Gray("[") + LightWhite(p.Current) + Gray("/") + LightWhite(p.Total) + Gray("]")
|
||||
|
||||
decoratorCurrentPercentage := color.RGB(NewRGB(255, 0, 0).Fade(0, float32(p.Total), float32(p.Current), NewRGB(0, 255, 0)).GetValues()).
|
||||
Sprint(strconv.Itoa(currentPercentage) + "%")
|
||||
|
||||
decoratorTitle := p.TitleStyle.Sprint(p.Title)
|
||||
|
||||
if p.ShowTitle {
|
||||
before += decoratorTitle + " "
|
||||
}
|
||||
if p.ShowCount {
|
||||
before += decoratorCount + " "
|
||||
}
|
||||
|
||||
after += " "
|
||||
|
||||
if p.ShowPercentage {
|
||||
after += decoratorCurrentPercentage + " "
|
||||
}
|
||||
if p.ShowElapsedTime {
|
||||
after += "| " + p.parseElapsedTime()
|
||||
}
|
||||
|
||||
barMaxLength := width - len(RemoveColorFromString(before)) - len(RemoveColorFromString(after)) - 1
|
||||
barCurrentLength := (p.Current * barMaxLength) / p.Total
|
||||
barFiller := strings.Repeat(p.BarFiller, barMaxLength-barCurrentLength)
|
||||
|
||||
bar := p.BarStyle.Sprint(strings.Repeat(p.BarCharacter, barCurrentLength)+p.LastCharacter) + barFiller
|
||||
if !RawOutput {
|
||||
Printo(before + bar + after)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Add to current value.
|
||||
func (p *ProgressbarPrinter) Add(count int) *ProgressbarPrinter {
|
||||
if p.Total == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.Current += count
|
||||
p.updateProgress()
|
||||
|
||||
if p.Current == p.Total {
|
||||
p.Stop()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Start the ProgressbarPrinter.
|
||||
func (p ProgressbarPrinter) Start() (*ProgressbarPrinter, error) {
|
||||
if RawOutput && p.ShowTitle {
|
||||
Println(p.Title)
|
||||
}
|
||||
p.IsActive = true
|
||||
ActiveProgressBarPrinters = append(ActiveProgressBarPrinters, &p)
|
||||
p.startedAt = time.Now()
|
||||
|
||||
p.updateProgress()
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// Stop the ProgressbarPrinter.
|
||||
func (p *ProgressbarPrinter) Stop() (*ProgressbarPrinter, error) {
|
||||
if !p.IsActive {
|
||||
return p, nil
|
||||
}
|
||||
p.IsActive = false
|
||||
if p.RemoveWhenDone {
|
||||
clearLine()
|
||||
Printo()
|
||||
} else {
|
||||
Println()
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// GenericStart runs Start, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Start instead of this in your program.
|
||||
func (p ProgressbarPrinter) GenericStart() (*LivePrinter, error) {
|
||||
p2, _ := p.Start()
|
||||
lp := LivePrinter(p2)
|
||||
return &lp, nil
|
||||
}
|
||||
|
||||
// GenericStop runs Stop, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Stop instead of this in your program.
|
||||
func (p ProgressbarPrinter) GenericStop() (*LivePrinter, error) {
|
||||
p2, _ := p.Stop()
|
||||
lp := LivePrinter(p2)
|
||||
return &lp, nil
|
||||
}
|
||||
|
||||
// GetElapsedTime returns the elapsed time, since the ProgressbarPrinter was started.
|
||||
func (p *ProgressbarPrinter) GetElapsedTime() time.Duration {
|
||||
return time.Since(p.startedAt)
|
||||
}
|
||||
|
||||
func (p *ProgressbarPrinter) parseElapsedTime() string {
|
||||
s := p.GetElapsedTime().Round(p.ElapsedTimeRoundingFactor).String()
|
||||
return s
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// Package pterm is a modern go module to beautify console output.
|
||||
// It can be used without configuration, but if desired, everything can be customized down to the smallest detail.
|
||||
// View the animated examples here: https://github.com/pterm/pterm#-examples
|
||||
package pterm
|
||||
|
||||
import "github.com/gookit/color"
|
||||
|
||||
var (
|
||||
// Output completely disables output from pterm if set to false. Can be used in CLI application quiet mode.
|
||||
Output = true
|
||||
|
||||
// PrintDebugMessages sets if messages printed by the DebugPrinter should be printed.
|
||||
PrintDebugMessages = false
|
||||
|
||||
// RawOutput is set to true if pterm.DisableStyling() was called.
|
||||
// The variable indicates that PTerm will not add additional styling to text.
|
||||
// Use pterm.DisableStyling() or pterm.EnableStyling() to change this variable.
|
||||
// Changing this variable directly, will disable or enable the output of colored text.
|
||||
RawOutput = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
color.ForceColor()
|
||||
}
|
||||
|
||||
// EnableOutput enables the output of PTerm.
|
||||
func EnableOutput() {
|
||||
Output = true
|
||||
}
|
||||
|
||||
// DisableOutput disables the output of PTerm.
|
||||
func DisableOutput() {
|
||||
Output = false
|
||||
}
|
||||
|
||||
// EnableDebugMessages enables the output of debug printers.
|
||||
func EnableDebugMessages() {
|
||||
PrintDebugMessages = true
|
||||
}
|
||||
|
||||
// DisableDebugMessages disables the output of debug printers.
|
||||
func DisableDebugMessages() {
|
||||
PrintDebugMessages = false
|
||||
}
|
||||
|
||||
// EnableStyling enables the default PTerm styling.
|
||||
// This also calls EnableColor.
|
||||
func EnableStyling() {
|
||||
RawOutput = false
|
||||
EnableColor()
|
||||
}
|
||||
|
||||
// DisableStyling sets PTerm to RawOutput mode and disables all of PTerms styling.
|
||||
// You can use this to print to text files etc.
|
||||
// This also calls DisableColor.
|
||||
func DisableStyling() {
|
||||
RawOutput = true
|
||||
DisableColor()
|
||||
}
|
||||
|
||||
// RecalculateTerminalSize updates already initialized terminal dimensions. Has to be called after a termina resize to guarantee proper rendering. Applies only to new instances.
|
||||
func RecalculateTerminalSize() {
|
||||
// keep in sync with DefaultBarChart
|
||||
DefaultBarChart.Width = GetTerminalWidth() * 2 / 3
|
||||
DefaultBarChart.Height = GetTerminalHeight() * 2 / 3
|
||||
DefaultParagraph.MaxWidth = GetTerminalWidth()
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// RGB color model is an additive color model in which red, green, and blue light are added together in various ways to reproduce a broad array of colors.
|
||||
// The name of the model comes from the initials of the three additive primary colors, red, green, and blue.
|
||||
// https://en.wikipedia.org/wiki/RGB_color_model
|
||||
type RGB struct {
|
||||
R uint8
|
||||
G uint8
|
||||
B uint8
|
||||
}
|
||||
|
||||
// GetValues returns the RGB values separately.
|
||||
func (p RGB) GetValues() (r, g, b uint8) {
|
||||
return p.R, p.G, p.B
|
||||
}
|
||||
|
||||
// NewRGB returns a new RGB.
|
||||
func NewRGB(r, g, b uint8) RGB {
|
||||
return RGB{R: r, G: g, B: b}
|
||||
}
|
||||
|
||||
// NewRGBFromHEX converts a HEX and returns a new RGB.
|
||||
func NewRGBFromHEX(hex string) (RGB, error) {
|
||||
hex = strings.ToLower(hex)
|
||||
hex = strings.ReplaceAll(hex, "#", "")
|
||||
hex = strings.ReplaceAll(hex, "0x", "")
|
||||
|
||||
if len(hex) == 3 {
|
||||
hex = string([]byte{hex[0], hex[0], hex[1], hex[1], hex[2], hex[2]})
|
||||
}
|
||||
if len(hex) != 6 {
|
||||
return RGB{}, ErrHexCodeIsInvalid
|
||||
}
|
||||
|
||||
i64, err := strconv.ParseInt(hex, 16, 32)
|
||||
if err != nil {
|
||||
return RGB{}, err
|
||||
}
|
||||
c := int(i64)
|
||||
|
||||
return RGB{
|
||||
R: uint8(c >> 16),
|
||||
G: uint8((c & 0x00FF00) >> 8),
|
||||
B: uint8(c & 0x0000FF),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Fade fades one RGB value (over other RGB values) to another RGB value, by giving the function a minimum, maximum and current value.
|
||||
func (p RGB) Fade(min, max, current float32, end ...RGB) RGB {
|
||||
if min < 0 {
|
||||
max -= min
|
||||
current -= min
|
||||
min = 0
|
||||
}
|
||||
if len(end) == 1 {
|
||||
return RGB{
|
||||
R: uint8(internal.MapRangeToRange(min, max, float32(p.R), float32(end[0].R), current)),
|
||||
G: uint8(internal.MapRangeToRange(min, max, float32(p.G), float32(end[0].G), current)),
|
||||
B: uint8(internal.MapRangeToRange(min, max, float32(p.B), float32(end[0].B), current)),
|
||||
}
|
||||
} else if len(end) > 1 {
|
||||
f := (max - min) / float32(len(end))
|
||||
tempCurrent := current
|
||||
if f > current {
|
||||
return p.Fade(min, f, current, end[0])
|
||||
} else {
|
||||
for i := 0; i < len(end)-1; i++ {
|
||||
tempCurrent -= f
|
||||
if f > tempCurrent {
|
||||
return end[i].Fade(min, min+f, tempCurrent, end[i+1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func (p RGB) Sprint(a ...interface{}) string {
|
||||
return color.RGB(p.R, p.G, p.B).Sprint(a...)
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p RGB) Sprintln(a ...interface{}) string {
|
||||
return p.Sprint(Sprintln(a...))
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func (p RGB) Sprintf(format string, a ...interface{}) string {
|
||||
return p.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p RGB) Sprintfln(format string, a ...interface{}) string {
|
||||
return p.Sprintf(format, a...) + "\n"
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p RGB) Print(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprint(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p RGB) Println(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintln(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func (p RGB) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintf(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
func (p RGB) Printfln(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintfln(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p RGB) PrintOnError(a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p RGB) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultSection is the default section printer.
|
||||
var DefaultSection = SectionPrinter{
|
||||
Style: &ThemeDefault.SectionStyle,
|
||||
Level: 1,
|
||||
TopPadding: 1,
|
||||
BottomPadding: 1,
|
||||
IndentCharacter: "#",
|
||||
}
|
||||
|
||||
// SectionPrinter prints a new section title.
|
||||
// It can be used to structure longer text, or different chapters of your program.
|
||||
type SectionPrinter struct {
|
||||
Style *Style
|
||||
Level int
|
||||
IndentCharacter string
|
||||
TopPadding int
|
||||
BottomPadding int
|
||||
}
|
||||
|
||||
// WithStyle returns a new SectionPrinter with a specific style.
|
||||
func (p SectionPrinter) WithStyle(style *Style) *SectionPrinter {
|
||||
p.Style = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithLevel returns a new SectionPrinter with a specific level.
|
||||
func (p SectionPrinter) WithLevel(level int) *SectionPrinter {
|
||||
p.Level = level
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithIndentCharacter returns a new SectionPrinter with a specific IndentCharacter.
|
||||
func (p SectionPrinter) WithIndentCharacter(char string) *SectionPrinter {
|
||||
p.IndentCharacter = char
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTopPadding returns a new SectionPrinter with a specific top padding.
|
||||
func (p SectionPrinter) WithTopPadding(padding int) *SectionPrinter {
|
||||
p.TopPadding = padding
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBottomPadding returns a new SectionPrinter with a specific top padding.
|
||||
func (p SectionPrinter) WithBottomPadding(padding int) *SectionPrinter {
|
||||
p.BottomPadding = padding
|
||||
return &p
|
||||
}
|
||||
|
||||
// Sprint formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
func (p SectionPrinter) Sprint(a ...interface{}) string {
|
||||
if p.Style == nil {
|
||||
p.Style = NewStyle()
|
||||
}
|
||||
|
||||
var ret string
|
||||
|
||||
for i := 0; i < p.TopPadding; i++ {
|
||||
ret += "\n"
|
||||
}
|
||||
|
||||
if p.Level > 0 {
|
||||
ret += strings.Repeat(p.IndentCharacter, p.Level) + " "
|
||||
}
|
||||
|
||||
ret += p.Style.Sprint(a...)
|
||||
|
||||
for i := 0; i < p.BottomPadding; i++ {
|
||||
ret += "\n"
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Sprintln formats using the default formats for its operands and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p SectionPrinter) Sprintln(a ...interface{}) string {
|
||||
str := fmt.Sprintln(a...)
|
||||
return Sprint(p.Sprint(str))
|
||||
}
|
||||
|
||||
// Sprintf formats according to a format specifier and returns the resulting string.
|
||||
func (p SectionPrinter) Sprintf(format string, a ...interface{}) string {
|
||||
return p.Sprint(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintfln formats according to a format specifier and returns the resulting string.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
func (p SectionPrinter) Sprintfln(format string, a ...interface{}) string {
|
||||
return p.Sprintf(format, a...) + "\n"
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *SectionPrinter) Print(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprint(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *SectionPrinter) Println(a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintln(a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func (p *SectionPrinter) Printf(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintf(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// Printfln formats according to a format specifier 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.
|
||||
func (p *SectionPrinter) Printfln(format string, a ...interface{}) *TextPrinter {
|
||||
Print(p.Sprintfln(format, a...))
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnError prints every error which is not nil.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p *SectionPrinter) PrintOnError(a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
||||
|
||||
// PrintOnErrorf wraps every error which is not nil and prints it.
|
||||
// If every error is nil, nothing will be printed.
|
||||
// This can be used for simple error checking.
|
||||
func (p *SectionPrinter) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
|
||||
for _, arg := range a {
|
||||
if err, ok := arg.(error); ok {
|
||||
if err != nil {
|
||||
p.Println(fmt.Errorf(format, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp := TextPrinter(p)
|
||||
return &tp
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
var activeSpinnerPrinters []*SpinnerPrinter
|
||||
|
||||
// DefaultSpinner is the default SpinnerPrinter.
|
||||
var DefaultSpinner = SpinnerPrinter{
|
||||
Sequence: []string{"▀ ", " ▀", " ▄", "▄ "},
|
||||
Style: &ThemeDefault.SpinnerStyle,
|
||||
Delay: time.Millisecond * 200,
|
||||
ShowTimer: true,
|
||||
TimerRoundingFactor: time.Second,
|
||||
TimerStyle: &ThemeDefault.TimerStyle,
|
||||
MessageStyle: &ThemeDefault.SpinnerTextStyle,
|
||||
SuccessPrinter: &Success,
|
||||
FailPrinter: &Error,
|
||||
WarningPrinter: &Warning,
|
||||
}
|
||||
|
||||
// SpinnerPrinter is a loading animation, which can be used if the progress is unknown.
|
||||
// It's an animation loop, which can have a text and supports throwing errors or warnings.
|
||||
// A TextPrinter is used to display all outputs, after the SpinnerPrinter is done.
|
||||
type SpinnerPrinter struct {
|
||||
Text string
|
||||
Sequence []string
|
||||
Style *Style
|
||||
Delay time.Duration
|
||||
MessageStyle *Style
|
||||
SuccessPrinter TextPrinter
|
||||
FailPrinter TextPrinter
|
||||
WarningPrinter TextPrinter
|
||||
RemoveWhenDone bool
|
||||
ShowTimer bool
|
||||
TimerRoundingFactor time.Duration
|
||||
TimerStyle *Style
|
||||
|
||||
IsActive bool
|
||||
|
||||
startedAt time.Time
|
||||
currentSequence string
|
||||
}
|
||||
|
||||
// WithText adds a text to the SpinnerPrinter.
|
||||
func (s SpinnerPrinter) WithText(text string) *SpinnerPrinter {
|
||||
s.Text = text
|
||||
return &s
|
||||
}
|
||||
|
||||
// WithSequence adds a sequence to the SpinnerPrinter.
|
||||
func (s SpinnerPrinter) WithSequence(sequence ...string) *SpinnerPrinter {
|
||||
s.Sequence = sequence
|
||||
return &s
|
||||
}
|
||||
|
||||
// WithStyle adds a style to the SpinnerPrinter.
|
||||
func (s SpinnerPrinter) WithStyle(style *Style) *SpinnerPrinter {
|
||||
s.Style = style
|
||||
return &s
|
||||
}
|
||||
|
||||
// WithDelay adds a delay to the SpinnerPrinter.
|
||||
func (s SpinnerPrinter) WithDelay(delay time.Duration) *SpinnerPrinter {
|
||||
s.Delay = delay
|
||||
return &s
|
||||
}
|
||||
|
||||
// WithMessageStyle adds a style to the SpinnerPrinter message.
|
||||
func (s SpinnerPrinter) WithMessageStyle(style *Style) *SpinnerPrinter {
|
||||
s.MessageStyle = style
|
||||
return &s
|
||||
}
|
||||
|
||||
// WithRemoveWhenDone removes the SpinnerPrinter after it is done.
|
||||
func (s SpinnerPrinter) WithRemoveWhenDone(b ...bool) *SpinnerPrinter {
|
||||
s.RemoveWhenDone = internal.WithBoolean(b)
|
||||
return &s
|
||||
}
|
||||
|
||||
// WithShowTimer shows how long the spinner is running.
|
||||
func (s SpinnerPrinter) WithShowTimer(b ...bool) *SpinnerPrinter {
|
||||
s.ShowTimer = internal.WithBoolean(b)
|
||||
return &s
|
||||
}
|
||||
|
||||
// WithTimerRoundingFactor sets the rounding factor for the timer.
|
||||
func (s SpinnerPrinter) WithTimerRoundingFactor(factor time.Duration) *SpinnerPrinter {
|
||||
s.TimerRoundingFactor = factor
|
||||
return &s
|
||||
}
|
||||
|
||||
// WithTimerStyle adds a style to the SpinnerPrinter timer.
|
||||
func (s SpinnerPrinter) WithTimerStyle(style *Style) *SpinnerPrinter {
|
||||
s.TimerStyle = style
|
||||
return &s
|
||||
}
|
||||
|
||||
// UpdateText updates the message of the active SpinnerPrinter.
|
||||
// Can be used live.
|
||||
func (s *SpinnerPrinter) UpdateText(text string) {
|
||||
s.Text = text
|
||||
if !RawOutput {
|
||||
clearLine()
|
||||
Printo(s.Style.Sprint(s.currentSequence) + " " + s.MessageStyle.Sprint(s.Text))
|
||||
}
|
||||
if RawOutput {
|
||||
Println(s.Text)
|
||||
}
|
||||
}
|
||||
|
||||
// Start the SpinnerPrinter.
|
||||
func (s SpinnerPrinter) Start(text ...interface{}) (*SpinnerPrinter, error) {
|
||||
s.IsActive = true
|
||||
s.startedAt = time.Now()
|
||||
activeSpinnerPrinters = append(activeSpinnerPrinters, &s)
|
||||
|
||||
if len(text) != 0 {
|
||||
s.Text = Sprint(text...)
|
||||
}
|
||||
|
||||
if RawOutput {
|
||||
Println(s.Text)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for s.IsActive {
|
||||
for _, seq := range s.Sequence {
|
||||
if !s.IsActive || RawOutput {
|
||||
continue
|
||||
}
|
||||
|
||||
var timer string
|
||||
if s.ShowTimer {
|
||||
timer = " (" + time.Since(s.startedAt).Round(s.TimerRoundingFactor).String() + ")"
|
||||
}
|
||||
Printo(s.Style.Sprint(seq) + " " + s.MessageStyle.Sprint(s.Text) + s.TimerStyle.Sprint(timer))
|
||||
s.currentSequence = seq
|
||||
time.Sleep(s.Delay)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// Stop terminates the SpinnerPrinter immediately.
|
||||
// The SpinnerPrinter will not resolve into anything.
|
||||
func (s *SpinnerPrinter) Stop() error {
|
||||
s.IsActive = false
|
||||
if s.RemoveWhenDone {
|
||||
clearLine()
|
||||
Printo()
|
||||
} else {
|
||||
Println()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenericStart runs Start, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Start instead of this in your program.
|
||||
func (s *SpinnerPrinter) GenericStart() (*LivePrinter, error) {
|
||||
_, _ = s.Start()
|
||||
lp := LivePrinter(s)
|
||||
return &lp, nil
|
||||
}
|
||||
|
||||
// GenericStop runs Stop, but returns a LivePrinter.
|
||||
// This is used for the interface LivePrinter.
|
||||
// You most likely want to use Stop instead of this in your program.
|
||||
func (s *SpinnerPrinter) GenericStop() (*LivePrinter, error) {
|
||||
_ = s.Stop()
|
||||
lp := LivePrinter(s)
|
||||
return &lp, nil
|
||||
}
|
||||
|
||||
// Success displays the success printer.
|
||||
// If no message is given, the text of the SpinnerPrinter will be reused as the default message.
|
||||
func (s *SpinnerPrinter) Success(message ...interface{}) {
|
||||
if s.SuccessPrinter == nil {
|
||||
s.SuccessPrinter = &Success
|
||||
}
|
||||
|
||||
if len(message) == 0 {
|
||||
message = []interface{}{s.Text}
|
||||
}
|
||||
clearLine()
|
||||
Printo(s.SuccessPrinter.Sprint(message...))
|
||||
_ = s.Stop()
|
||||
}
|
||||
|
||||
// Fail displays the fail printer.
|
||||
// If no message is given, the text of the SpinnerPrinter will be reused as the default message.
|
||||
func (s *SpinnerPrinter) Fail(message ...interface{}) {
|
||||
if s.FailPrinter == nil {
|
||||
s.FailPrinter = &Error
|
||||
}
|
||||
|
||||
if len(message) == 0 {
|
||||
message = []interface{}{s.Text}
|
||||
}
|
||||
clearLine()
|
||||
Printo(s.FailPrinter.Sprint(message...))
|
||||
_ = s.Stop()
|
||||
}
|
||||
|
||||
// Warning displays the warning printer.
|
||||
// If no message is given, the text of the SpinnerPrinter will be reused as the default message.
|
||||
func (s *SpinnerPrinter) Warning(message ...interface{}) {
|
||||
if s.WarningPrinter == nil {
|
||||
s.WarningPrinter = &Warning
|
||||
}
|
||||
|
||||
if len(message) == 0 {
|
||||
message = []interface{}{s.Text}
|
||||
}
|
||||
clearLine()
|
||||
Printo(s.WarningPrinter.Sprint(message...))
|
||||
_ = s.Stop()
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/pterm/pterm/internal"
|
||||
)
|
||||
|
||||
// DefaultTable contains standards, which can be used to print a TablePrinter.
|
||||
var DefaultTable = TablePrinter{
|
||||
Style: &ThemeDefault.TableStyle,
|
||||
HeaderStyle: &ThemeDefault.TableHeaderStyle,
|
||||
Separator: " | ",
|
||||
SeparatorStyle: &ThemeDefault.TableSeparatorStyle,
|
||||
LeftAlignment: true,
|
||||
RightAlignment: false,
|
||||
}
|
||||
|
||||
// TableData is the type that contains the data of a TablePrinter.
|
||||
type TableData [][]string
|
||||
|
||||
// TablePrinter is able to render tables.
|
||||
type TablePrinter struct {
|
||||
Style *Style
|
||||
HasHeader bool
|
||||
HeaderStyle *Style
|
||||
Separator string
|
||||
SeparatorStyle *Style
|
||||
Data TableData
|
||||
Boxed bool
|
||||
LeftAlignment bool
|
||||
RightAlignment bool
|
||||
}
|
||||
|
||||
// WithStyle returns a new TablePrinter with a specific Style.
|
||||
func (p TablePrinter) WithStyle(style *Style) *TablePrinter {
|
||||
p.Style = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithHasHeader returns a new TablePrinter, where the first line is marked as a header.
|
||||
func (p TablePrinter) WithHasHeader(b ...bool) *TablePrinter {
|
||||
p.HasHeader = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithHeaderStyle returns a new TablePrinter with a specific HeaderStyle.
|
||||
func (p TablePrinter) WithHeaderStyle(style *Style) *TablePrinter {
|
||||
p.HeaderStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithSeparator returns a new TablePrinter with a specific separator.
|
||||
func (p TablePrinter) WithSeparator(separator string) *TablePrinter {
|
||||
p.Separator = separator
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithSeparatorStyle returns a new TablePrinter with a specific SeparatorStyle.
|
||||
func (p TablePrinter) WithSeparatorStyle(style *Style) *TablePrinter {
|
||||
p.SeparatorStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithData returns a new TablePrinter with specific Data.
|
||||
func (p TablePrinter) WithData(data [][]string) *TablePrinter {
|
||||
p.Data = data
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithCSVReader return a new TablePrinter with specified Data extracted from CSV.
|
||||
func (p TablePrinter) WithCSVReader(reader *csv.Reader) *TablePrinter {
|
||||
if records, err := reader.ReadAll(); err == nil {
|
||||
p.Data = records
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithBoxed returns a new TablePrinter with a box around the table.
|
||||
func (p TablePrinter) WithBoxed(b ...bool) *TablePrinter {
|
||||
p.Boxed = internal.WithBoolean(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithLeftAlignment returns a new TablePrinter with left alignment.
|
||||
func (p TablePrinter) WithLeftAlignment(b ...bool) *TablePrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.LeftAlignment = b2
|
||||
p.RightAlignment = false
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithRightAlignment returns a new TablePrinter with right alignment.
|
||||
func (p TablePrinter) WithRightAlignment(b ...bool) *TablePrinter {
|
||||
b2 := internal.WithBoolean(b)
|
||||
p.LeftAlignment = false
|
||||
p.RightAlignment = b2
|
||||
return &p
|
||||
}
|
||||
|
||||
// Srender renders the TablePrinter as a string.
|
||||
func (p TablePrinter) Srender() (string, error) {
|
||||
if p.Style == nil {
|
||||
p.Style = NewStyle()
|
||||
}
|
||||
if p.SeparatorStyle == nil {
|
||||
p.SeparatorStyle = NewStyle()
|
||||
}
|
||||
if p.HeaderStyle == nil {
|
||||
p.HeaderStyle = NewStyle()
|
||||
}
|
||||
|
||||
var ret string
|
||||
maxColumnWidth := make(map[int]int)
|
||||
|
||||
for _, row := range p.Data {
|
||||
for ci, column := range row {
|
||||
columnLength := utf8.RuneCountInString(RemoveColorFromString(column))
|
||||
if columnLength > maxColumnWidth[ci] {
|
||||
maxColumnWidth[ci] = columnLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ri, row := range p.Data {
|
||||
for ci, column := range row {
|
||||
columnString := p.createColumnString(column, maxColumnWidth[ci])
|
||||
|
||||
if ci != len(row) && ci != 0 {
|
||||
ret += p.Style.Sprint(p.SeparatorStyle.Sprint(p.Separator))
|
||||
}
|
||||
|
||||
if p.HasHeader && ri == 0 {
|
||||
ret += p.Style.Sprint(p.HeaderStyle.Sprint(columnString))
|
||||
} else {
|
||||
ret += p.Style.Sprint(columnString)
|
||||
}
|
||||
}
|
||||
|
||||
ret += "\n"
|
||||
}
|
||||
|
||||
ret = strings.TrimSuffix(ret, "\n")
|
||||
|
||||
if p.Boxed {
|
||||
ret = DefaultBox.Sprint(ret)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (p TablePrinter) createColumnString(data string, maxColumnWidth int) string {
|
||||
columnLength := utf8.RuneCountInString(RemoveColorFromString(data))
|
||||
if p.RightAlignment {
|
||||
return strings.Repeat(" ", maxColumnWidth-columnLength) + data
|
||||
}
|
||||
return data + strings.Repeat(" ", maxColumnWidth-columnLength)
|
||||
}
|
||||
|
||||
// Render prints the TablePrinter to the terminal.
|
||||
func (p TablePrinter) Render() error {
|
||||
s, _ := p.Srender()
|
||||
Println(s)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// FallbackTerminalWidth is the value used for GetTerminalWidth, if the actual width can not be detected
|
||||
// You can override that value if necessary.
|
||||
var FallbackTerminalWidth = 80
|
||||
|
||||
// FallbackTerminalHeight is the value used for GetTerminalHeight, if the actual height can not be detected
|
||||
// You can override that value if necessary.
|
||||
var FallbackTerminalHeight = 10
|
||||
|
||||
// forcedTerminalWidth, when set along with forcedTerminalHeight, forces the terminal width value.
|
||||
var forcedTerminalWidth int = 0
|
||||
|
||||
// forcedTerminalHeight, when set along with forcedTerminalWidth, forces the terminal height value.
|
||||
var forcedTerminalHeight int = 0
|
||||
|
||||
// GetTerminalWidth returns the terminal width of the active terminal.
|
||||
func GetTerminalWidth() int {
|
||||
if forcedTerminalWidth > 0 {
|
||||
return forcedTerminalWidth
|
||||
}
|
||||
width, _, _ := GetTerminalSize()
|
||||
return width
|
||||
}
|
||||
|
||||
// GetTerminalHeight returns the terminal height of the active terminal.
|
||||
func GetTerminalHeight() int {
|
||||
if forcedTerminalHeight > 0 {
|
||||
return forcedTerminalHeight
|
||||
}
|
||||
_, height, _ := GetTerminalSize()
|
||||
return height
|
||||
}
|
||||
|
||||
// GetTerminalSize returns the width and the height of the active terminal.
|
||||
func GetTerminalSize() (width, height int, err error) {
|
||||
if forcedTerminalWidth > 0 && forcedTerminalHeight > 0 {
|
||||
return forcedTerminalWidth, forcedTerminalHeight, nil
|
||||
}
|
||||
w, h, err := term.GetSize(int(os.Stdout.Fd()))
|
||||
if w <= 0 {
|
||||
w = FallbackTerminalWidth
|
||||
}
|
||||
if h <= 0 {
|
||||
h = FallbackTerminalHeight
|
||||
}
|
||||
if err != nil {
|
||||
err = ErrTerminalSizeNotDetectable
|
||||
}
|
||||
return w, h, err
|
||||
}
|
||||
|
||||
// setForcedTerminalSize turns off terminal size autodetection. Usuful for unified tests.
|
||||
func SetForcedTerminalSize(width int, height int) {
|
||||
forcedTerminalWidth = width
|
||||
forcedTerminalHeight = height
|
||||
RecalculateTerminalSize()
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
package pterm
|
||||
|
||||
var (
|
||||
// ThemeDefault is the default theme used by PTerm.
|
||||
// If this variable is overwritten, the new value is used as default theme.
|
||||
ThemeDefault = Theme{
|
||||
PrimaryStyle: Style{FgCyan},
|
||||
SecondaryStyle: Style{FgLightMagenta},
|
||||
HighlightStyle: Style{Bold, FgYellow},
|
||||
InfoMessageStyle: Style{FgLightCyan},
|
||||
InfoPrefixStyle: Style{FgBlack, BgCyan},
|
||||
SuccessMessageStyle: Style{FgGreen},
|
||||
SuccessPrefixStyle: Style{FgBlack, BgGreen},
|
||||
WarningMessageStyle: Style{FgYellow},
|
||||
WarningPrefixStyle: Style{FgBlack, BgYellow},
|
||||
ErrorMessageStyle: Style{FgLightRed},
|
||||
ErrorPrefixStyle: Style{FgBlack, BgLightRed},
|
||||
FatalMessageStyle: Style{FgLightRed},
|
||||
FatalPrefixStyle: Style{FgBlack, BgLightRed},
|
||||
DescriptionMessageStyle: Style{FgDefault},
|
||||
DescriptionPrefixStyle: Style{FgLightWhite, BgDarkGray},
|
||||
ScopeStyle: Style{FgGray},
|
||||
ProgressbarBarStyle: Style{FgCyan},
|
||||
ProgressbarTitleStyle: Style{FgLightCyan},
|
||||
HeaderTextStyle: Style{FgLightWhite, Bold},
|
||||
HeaderBackgroundStyle: Style{BgGray},
|
||||
SpinnerStyle: Style{FgLightCyan},
|
||||
SpinnerTextStyle: Style{FgLightWhite},
|
||||
TableStyle: Style{FgDefault},
|
||||
TableHeaderStyle: Style{FgLightCyan},
|
||||
TableSeparatorStyle: Style{FgGray},
|
||||
SectionStyle: Style{Bold, FgYellow},
|
||||
BulletListTextStyle: Style{FgDefault},
|
||||
BulletListBulletStyle: Style{FgGray},
|
||||
TreeStyle: Style{FgGray},
|
||||
TreeTextStyle: Style{FgDefault},
|
||||
LetterStyle: Style{FgDefault},
|
||||
DebugMessageStyle: Style{FgGray},
|
||||
DebugPrefixStyle: Style{FgBlack, BgGray},
|
||||
BoxStyle: Style{FgDefault},
|
||||
BoxTextStyle: Style{FgDefault},
|
||||
BarLabelStyle: Style{FgLightCyan},
|
||||
BarStyle: Style{FgCyan},
|
||||
TimerStyle: Style{FgGray},
|
||||
}
|
||||
)
|
||||
|
||||
// Theme for PTerm.
|
||||
// Theme contains every Style used in PTerm. You can create own themes for your application or use one
|
||||
// of the existing themes.
|
||||
type Theme struct {
|
||||
PrimaryStyle Style
|
||||
SecondaryStyle Style
|
||||
HighlightStyle Style
|
||||
InfoMessageStyle Style
|
||||
InfoPrefixStyle Style
|
||||
SuccessMessageStyle Style
|
||||
SuccessPrefixStyle Style
|
||||
WarningMessageStyle Style
|
||||
WarningPrefixStyle Style
|
||||
ErrorMessageStyle Style
|
||||
ErrorPrefixStyle Style
|
||||
FatalMessageStyle Style
|
||||
FatalPrefixStyle Style
|
||||
DescriptionMessageStyle Style
|
||||
DescriptionPrefixStyle Style
|
||||
ScopeStyle Style
|
||||
ProgressbarBarStyle Style
|
||||
ProgressbarTitleStyle Style
|
||||
HeaderTextStyle Style
|
||||
HeaderBackgroundStyle Style
|
||||
SpinnerStyle Style
|
||||
SpinnerTextStyle Style
|
||||
TimerStyle Style
|
||||
TableStyle Style
|
||||
TableHeaderStyle Style
|
||||
TableSeparatorStyle Style
|
||||
SectionStyle Style
|
||||
BulletListTextStyle Style
|
||||
BulletListBulletStyle Style
|
||||
TreeStyle Style
|
||||
TreeTextStyle Style
|
||||
LetterStyle Style
|
||||
DebugMessageStyle Style
|
||||
DebugPrefixStyle Style
|
||||
BoxStyle Style
|
||||
BoxTextStyle Style
|
||||
BarLabelStyle Style
|
||||
BarStyle Style
|
||||
}
|
||||
|
||||
// WithPrimaryStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithPrimaryStyle(style Style) Theme {
|
||||
t.PrimaryStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithSecondaryStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithSecondaryStyle(style Style) Theme {
|
||||
t.SecondaryStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithHighlightStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithHighlightStyle(style Style) Theme {
|
||||
t.HighlightStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithInfoMessageStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithInfoMessageStyle(style Style) Theme {
|
||||
t.InfoMessageStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithInfoPrefixStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithInfoPrefixStyle(style Style) Theme {
|
||||
t.InfoPrefixStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithSuccessMessageStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithSuccessMessageStyle(style Style) Theme {
|
||||
t.SuccessMessageStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithSuccessPrefixStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithSuccessPrefixStyle(style Style) Theme {
|
||||
t.SuccessPrefixStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithWarningMessageStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithWarningMessageStyle(style Style) Theme {
|
||||
t.WarningMessageStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithWarningPrefixStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithWarningPrefixStyle(style Style) Theme {
|
||||
t.WarningPrefixStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithErrorMessageStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithErrorMessageStyle(style Style) Theme {
|
||||
t.ErrorMessageStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithErrorPrefixStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithErrorPrefixStyle(style Style) Theme {
|
||||
t.ErrorPrefixStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithFatalMessageStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithFatalMessageStyle(style Style) Theme {
|
||||
t.FatalMessageStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithFatalPrefixStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithFatalPrefixStyle(style Style) Theme {
|
||||
t.FatalPrefixStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithDescriptionMessageStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithDescriptionMessageStyle(style Style) Theme {
|
||||
t.DescriptionMessageStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithDescriptionPrefixStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithDescriptionPrefixStyle(style Style) Theme {
|
||||
t.DescriptionPrefixStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithBulletListTextStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithBulletListTextStyle(style Style) Theme {
|
||||
t.BulletListTextStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithBulletListBulletStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithBulletListBulletStyle(style Style) Theme {
|
||||
t.BulletListBulletStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithLetterStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithLetterStyle(style Style) Theme {
|
||||
t.LetterStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithDebugMessageStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithDebugMessageStyle(style Style) Theme {
|
||||
t.DebugMessageStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithDebugPrefixStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithDebugPrefixStyle(style Style) Theme {
|
||||
t.DebugPrefixStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithTreeStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithTreeStyle(style Style) Theme {
|
||||
t.TreeStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithTreeTextStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithTreeTextStyle(style Style) Theme {
|
||||
t.TreeTextStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithBoxStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithBoxStyle(style Style) Theme {
|
||||
t.BoxStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithBoxTextStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithBoxTextStyle(style Style) Theme {
|
||||
t.BoxTextStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithBarLabelStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithBarLabelStyle(style Style) Theme {
|
||||
t.BarLabelStyle = style
|
||||
return t
|
||||
}
|
||||
|
||||
// WithBarStyle returns a new theme with overridden value.
|
||||
func (t Theme) WithBarStyle(style Style) Theme {
|
||||
t.BarStyle = style
|
||||
return t
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
package pterm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TreeNode is used as items in a TreePrinter.
|
||||
type TreeNode struct {
|
||||
Children []TreeNode
|
||||
Text string
|
||||
}
|
||||
|
||||
// LeveledList is a list, which contains multiple LeveledListItem.
|
||||
type LeveledList []LeveledListItem
|
||||
|
||||
// LeveledListItem combines a text with a specific level.
|
||||
// The level is the indent, which would normally be seen in a BulletListPrinter.
|
||||
type LeveledListItem struct {
|
||||
Level int
|
||||
Text string
|
||||
}
|
||||
|
||||
// DefaultTree contains standards, which can be used to render a TreePrinter.
|
||||
var DefaultTree = TreePrinter{
|
||||
TreeStyle: &ThemeDefault.TreeStyle,
|
||||
TextStyle: &ThemeDefault.TreeTextStyle,
|
||||
TopRightCornerString: "└",
|
||||
HorizontalString: "─",
|
||||
TopRightDownString: "├",
|
||||
VerticalString: "│",
|
||||
RightDownLeftString: "┬",
|
||||
Indent: 2,
|
||||
}
|
||||
|
||||
// TreePrinter is able to render a list.
|
||||
type TreePrinter struct {
|
||||
Root TreeNode
|
||||
TreeStyle *Style
|
||||
TextStyle *Style
|
||||
TopRightCornerString string
|
||||
TopRightDownString string
|
||||
HorizontalString string
|
||||
VerticalString string
|
||||
RightDownLeftString string
|
||||
Indent int
|
||||
}
|
||||
|
||||
// WithTreeStyle returns a new list with a specific tree style.
|
||||
func (p TreePrinter) WithTreeStyle(style *Style) *TreePrinter {
|
||||
p.TreeStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTextStyle returns a new list with a specific text style.
|
||||
func (p TreePrinter) WithTextStyle(style *Style) *TreePrinter {
|
||||
p.TextStyle = style
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTopRightCornerString returns a new list with a specific TopRightCornerString.
|
||||
func (p TreePrinter) WithTopRightCornerString(s string) *TreePrinter {
|
||||
p.TopRightCornerString = s
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithTopRightDownStringOngoing returns a new list with a specific TopRightDownString.
|
||||
func (p TreePrinter) WithTopRightDownStringOngoing(s string) *TreePrinter {
|
||||
p.TopRightDownString = s
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithHorizontalString returns a new list with a specific HorizontalString.
|
||||
func (p TreePrinter) WithHorizontalString(s string) *TreePrinter {
|
||||
p.HorizontalString = s
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithVerticalString returns a new list with a specific VerticalString.
|
||||
func (p TreePrinter) WithVerticalString(s string) *TreePrinter {
|
||||
p.VerticalString = s
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithRoot returns a new list with a specific Root.
|
||||
func (p TreePrinter) WithRoot(root TreeNode) *TreePrinter {
|
||||
p.Root = root
|
||||
return &p
|
||||
}
|
||||
|
||||
// WithIndent returns a new list with a specific amount of spacing between the levels.
|
||||
// Indent must be at least 1.
|
||||
func (p TreePrinter) WithIndent(indent int) *TreePrinter {
|
||||
if indent < 1 {
|
||||
indent = 1
|
||||
}
|
||||
p.Indent = indent
|
||||
return &p
|
||||
}
|
||||
|
||||
// Render prints the list to the terminal.
|
||||
func (p TreePrinter) Render() error {
|
||||
s, _ := p.Srender()
|
||||
Println(s)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Srender renders the list as a string.
|
||||
func (p TreePrinter) Srender() (string, error) {
|
||||
if p.TreeStyle == nil {
|
||||
p.TreeStyle = NewStyle()
|
||||
}
|
||||
if p.TextStyle == nil {
|
||||
p.TextStyle = NewStyle()
|
||||
}
|
||||
|
||||
return walkOverTree(p.Root.Children, p, ""), nil
|
||||
}
|
||||
|
||||
// walkOverTree is a recursive function,
|
||||
// which analyzes a TreePrinter and connects the items with specific characters.
|
||||
// Returns TreePrinter as string.
|
||||
func walkOverTree(list []TreeNode, p TreePrinter, prefix string) string {
|
||||
var ret string
|
||||
for i, item := range list {
|
||||
if len(list) > i+1 { // if not last in list
|
||||
if len(item.Children) == 0 { // if there are no children
|
||||
ret += prefix + p.TreeStyle.Sprint(p.TopRightDownString) + strings.Repeat(p.TreeStyle.Sprint(p.HorizontalString), p.Indent) +
|
||||
p.TextStyle.Sprint(item.Text) + "\n"
|
||||
} else { // if there are children
|
||||
ret += prefix + p.TreeStyle.Sprint(p.TopRightDownString) + strings.Repeat(p.TreeStyle.Sprint(p.HorizontalString), p.Indent-1) +
|
||||
p.TreeStyle.Sprint(p.RightDownLeftString) + p.TextStyle.Sprint(item.Text) + "\n"
|
||||
ret += walkOverTree(item.Children, p, prefix+p.TreeStyle.Sprint(p.VerticalString)+strings.Repeat(" ", p.Indent-1))
|
||||
}
|
||||
} else if len(list) == i+1 { // if last in list
|
||||
if len(item.Children) == 0 { // if there are no children
|
||||
ret += prefix + p.TreeStyle.Sprint(p.TopRightCornerString) + strings.Repeat(p.TreeStyle.Sprint(p.HorizontalString), p.Indent) +
|
||||
p.TextStyle.Sprint(item.Text) + "\n"
|
||||
} else { // if there are children
|
||||
ret += prefix + p.TreeStyle.Sprint(p.TopRightCornerString) + strings.Repeat(p.TreeStyle.Sprint(p.HorizontalString), p.Indent-1) +
|
||||
p.TreeStyle.Sprint(p.RightDownLeftString) + p.TextStyle.Sprint(item.Text) + "\n"
|
||||
ret += walkOverTree(item.Children, p, prefix+strings.Repeat(" ", p.Indent))
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// NewTreeFromLeveledList converts a TreeItems list to a TreeNode and returns it.
|
||||
func NewTreeFromLeveledList(leveledListItems LeveledList) TreeNode {
|
||||
if len(leveledListItems) == 0 {
|
||||
return TreeNode{}
|
||||
}
|
||||
|
||||
root := &TreeNode{
|
||||
Children: []TreeNode{},
|
||||
Text: leveledListItems[0].Text,
|
||||
}
|
||||
|
||||
for i, record := range leveledListItems {
|
||||
last := root
|
||||
|
||||
if record.Level < 0 {
|
||||
record.Level = 0
|
||||
leveledListItems[i].Level = 0
|
||||
}
|
||||
|
||||
if len(leveledListItems)-1 != i {
|
||||
if leveledListItems[i+1].Level-1 > record.Level {
|
||||
leveledListItems[i+1].Level = record.Level + 1
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < record.Level; i++ {
|
||||
lastIndex := len(last.Children) - 1
|
||||
last = &last.Children[lastIndex]
|
||||
}
|
||||
last.Children = append(last.Children, TreeNode{
|
||||
Children: []TreeNode{},
|
||||
Text: record.Text,
|
||||
})
|
||||
}
|
||||
|
||||
return *root
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Oliver Kuederle
|
||||
|
||||
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.
|
|
@ -0,0 +1,62 @@
|
|||
# Unicode Text Segmentation for Go
|
||||
|
||||
[![Godoc Reference](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/rivo/uniseg)
|
||||
[![Go Report](https://img.shields.io/badge/go%20report-A%2B-brightgreen.svg)](https://goreportcard.com/report/github.com/rivo/uniseg)
|
||||
|
||||
This Go package implements Unicode Text Segmentation according to [Unicode Standard Annex #29](http://unicode.org/reports/tr29/) (Unicode version 12.0.0).
|
||||
|
||||
At this point, only the determination of grapheme cluster boundaries is implemented.
|
||||
|
||||
## Background
|
||||
|
||||
In Go, [strings are read-only slices of bytes](https://blog.golang.org/strings). They can be turned into Unicode code points using the `for` loop or by casting: `[]rune(str)`. However, multiple code points may be combined into one user-perceived character or what the Unicode specification calls "grapheme cluster". Here are some examples:
|
||||
|
||||
|String|Bytes (UTF-8)|Code points (runes)|Grapheme clusters|
|
||||
|-|-|-|-|
|
||||
|Käse|6 bytes: `4b 61 cc 88 73 65`|5 code points: `4b 61 308 73 65`|4 clusters: `[4b],[61 308],[73],[65]`|
|
||||
|🏳️🌈|14 bytes: `f0 9f 8f b3 ef b8 8f e2 80 8d f0 9f 8c 88`|4 code points: `1f3f3 fe0f 200d 1f308`|1 cluster: `[1f3f3 fe0f 200d 1f308]`|
|
||||
|🇩🇪|8 bytes: `f0 9f 87 a9 f0 9f 87 aa`|2 code points: `1f1e9 1f1ea`|1 cluster: `[1f1e9 1f1ea]`|
|
||||
|
||||
This package provides a tool to iterate over these grapheme clusters. This may be used to determine the number of user-perceived characters, to split strings in their intended places, or to extract individual characters which form a unit.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/rivo/uniseg
|
||||
```
|
||||
|
||||
## Basic Example
|
||||
|
||||
```go
|
||||
package uniseg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gr := uniseg.NewGraphemes("👍🏼!")
|
||||
for gr.Next() {
|
||||
fmt.Printf("%x ", gr.Runes())
|
||||
}
|
||||
// Output: [1f44d 1f3fc] [21]
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Refer to https://godoc.org/github.com/rivo/uniseg for the package's documentation.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This package does not depend on any packages outside the standard library.
|
||||
|
||||
## Your Feedback
|
||||
|
||||
Add your issue here on GitHub. Feel free to get in touch if you have any questions.
|
||||
|
||||
## Version
|
||||
|
||||
Version tags will be introduced once Golang modules are official. Consider this version 0.1.
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
Package uniseg implements Unicode Text Segmentation according to Unicode
|
||||
Standard Annex #29 (http://unicode.org/reports/tr29/).
|
||||
|
||||
At this point, only the determination of grapheme cluster boundaries is
|
||||
implemented.
|
||||
*/
|
||||
package uniseg
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/rivo/uniseg
|
||||
|
||||
go 1.12
|
|
@ -0,0 +1,268 @@
|
|||
package uniseg
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// The states of the grapheme cluster parser.
|
||||
const (
|
||||
grAny = iota
|
||||
grCR
|
||||
grControlLF
|
||||
grL
|
||||
grLVV
|
||||
grLVTT
|
||||
grPrepend
|
||||
grExtendedPictographic
|
||||
grExtendedPictographicZWJ
|
||||
grRIOdd
|
||||
grRIEven
|
||||
)
|
||||
|
||||
// The grapheme cluster parser's breaking instructions.
|
||||
const (
|
||||
grNoBoundary = iota
|
||||
grBoundary
|
||||
)
|
||||
|
||||
// The grapheme cluster parser's state transitions. Maps (state, property) to
|
||||
// (new state, breaking instruction, rule number). The breaking instruction
|
||||
// always refers to the boundary between the last and next code point.
|
||||
//
|
||||
// This map is queried as follows:
|
||||
//
|
||||
// 1. Find specific state + specific property. Stop if found.
|
||||
// 2. Find specific state + any property.
|
||||
// 3. Find any state + specific property.
|
||||
// 4. If only (2) or (3) (but not both) was found, stop.
|
||||
// 5. If both (2) and (3) were found, use state and breaking instruction from
|
||||
// the transition with the lower rule number, prefer (3) if rule numbers
|
||||
// are equal. Stop.
|
||||
// 6. Assume grAny and grBoundary.
|
||||
var grTransitions = map[[2]int][3]int{
|
||||
// GB5
|
||||
{grAny, prCR}: {grCR, grBoundary, 50},
|
||||
{grAny, prLF}: {grControlLF, grBoundary, 50},
|
||||
{grAny, prControl}: {grControlLF, grBoundary, 50},
|
||||
|
||||
// GB4
|
||||
{grCR, prAny}: {grAny, grBoundary, 40},
|
||||
{grControlLF, prAny}: {grAny, grBoundary, 40},
|
||||
|
||||
// GB3.
|
||||
{grCR, prLF}: {grAny, grNoBoundary, 30},
|
||||
|
||||
// GB6.
|
||||
{grAny, prL}: {grL, grBoundary, 9990},
|
||||
{grL, prL}: {grL, grNoBoundary, 60},
|
||||
{grL, prV}: {grLVV, grNoBoundary, 60},
|
||||
{grL, prLV}: {grLVV, grNoBoundary, 60},
|
||||
{grL, prLVT}: {grLVTT, grNoBoundary, 60},
|
||||
|
||||
// GB7.
|
||||
{grAny, prLV}: {grLVV, grBoundary, 9990},
|
||||
{grAny, prV}: {grLVV, grBoundary, 9990},
|
||||
{grLVV, prV}: {grLVV, grNoBoundary, 70},
|
||||
{grLVV, prT}: {grLVTT, grNoBoundary, 70},
|
||||
|
||||
// GB8.
|
||||
{grAny, prLVT}: {grLVTT, grBoundary, 9990},
|
||||
{grAny, prT}: {grLVTT, grBoundary, 9990},
|
||||
{grLVTT, prT}: {grLVTT, grNoBoundary, 80},
|
||||
|
||||
// GB9.
|
||||
{grAny, prExtend}: {grAny, grNoBoundary, 90},
|
||||
{grAny, prZWJ}: {grAny, grNoBoundary, 90},
|
||||
|
||||
// GB9a.
|
||||
{grAny, prSpacingMark}: {grAny, grNoBoundary, 91},
|
||||
|
||||
// GB9b.
|
||||
{grAny, prPreprend}: {grPrepend, grBoundary, 9990},
|
||||
{grPrepend, prAny}: {grAny, grNoBoundary, 92},
|
||||
|
||||
// GB11.
|
||||
{grAny, prExtendedPictographic}: {grExtendedPictographic, grBoundary, 9990},
|
||||
{grExtendedPictographic, prExtend}: {grExtendedPictographic, grNoBoundary, 110},
|
||||
{grExtendedPictographic, prZWJ}: {grExtendedPictographicZWJ, grNoBoundary, 110},
|
||||
{grExtendedPictographicZWJ, prExtendedPictographic}: {grExtendedPictographic, grNoBoundary, 110},
|
||||
|
||||
// GB12 / GB13.
|
||||
{grAny, prRegionalIndicator}: {grRIOdd, grBoundary, 9990},
|
||||
{grRIOdd, prRegionalIndicator}: {grRIEven, grNoBoundary, 120},
|
||||
{grRIEven, prRegionalIndicator}: {grRIOdd, grBoundary, 120},
|
||||
}
|
||||
|
||||
// Graphemes implements an iterator over Unicode extended grapheme clusters,
|
||||
// specified in the Unicode Standard Annex #29. Grapheme clusters correspond to
|
||||
// "user-perceived characters". These characters often consist of multiple
|
||||
// code points (e.g. the "woman kissing woman" emoji consists of 8 code points:
|
||||
// woman + ZWJ + heavy black heart (2 code points) + ZWJ + kiss mark + ZWJ +
|
||||
// woman) and the rules described in Annex #29 must be applied to group those
|
||||
// code points into clusters perceived by the user as one character.
|
||||
type Graphemes struct {
|
||||
// The code points over which this class iterates.
|
||||
codePoints []rune
|
||||
|
||||
// The (byte-based) indices of the code points into the original string plus
|
||||
// len(original string). Thus, len(indices) = len(codePoints) + 1.
|
||||
indices []int
|
||||
|
||||
// The current grapheme cluster to be returned. These are indices into
|
||||
// codePoints/indices. If start == end, we either haven't started iterating
|
||||
// yet (0) or the iteration has already completed (1).
|
||||
start, end int
|
||||
|
||||
// The index of the next code point to be parsed.
|
||||
pos int
|
||||
|
||||
// The current state of the code point parser.
|
||||
state int
|
||||
}
|
||||
|
||||
// NewGraphemes returns a new grapheme cluster iterator.
|
||||
func NewGraphemes(s string) *Graphemes {
|
||||
l := utf8.RuneCountInString(s)
|
||||
codePoints := make([]rune, l)
|
||||
indices := make([]int, l+1)
|
||||
i := 0
|
||||
for pos, r := range s {
|
||||
codePoints[i] = r
|
||||
indices[i] = pos
|
||||
i++
|
||||
}
|
||||
indices[l] = len(s)
|
||||
g := &Graphemes{
|
||||
codePoints: codePoints,
|
||||
indices: indices,
|
||||
}
|
||||
g.Next() // Parse ahead.
|
||||
return g
|
||||
}
|
||||
|
||||
// Next advances the iterator by one grapheme cluster and returns false if no
|
||||
// clusters are left. This function must be called before the first cluster is
|
||||
// accessed.
|
||||
func (g *Graphemes) Next() bool {
|
||||
g.start = g.end
|
||||
|
||||
// The state transition gives us a boundary instruction BEFORE the next code
|
||||
// point so we always need to stay ahead by one code point.
|
||||
|
||||
// Parse the next code point.
|
||||
for g.pos <= len(g.codePoints) {
|
||||
// GB2.
|
||||
if g.pos == len(g.codePoints) {
|
||||
g.end = g.pos
|
||||
g.pos++
|
||||
break
|
||||
}
|
||||
|
||||
// Determine the property of the next character.
|
||||
nextProperty := property(g.codePoints[g.pos])
|
||||
g.pos++
|
||||
|
||||
// Find the applicable transition.
|
||||
var boundary bool
|
||||
transition, ok := grTransitions[[2]int{g.state, nextProperty}]
|
||||
if ok {
|
||||
// We have a specific transition. We'll use it.
|
||||
g.state = transition[0]
|
||||
boundary = transition[1] == grBoundary
|
||||
} else {
|
||||
// No specific transition found. Try the less specific ones.
|
||||
transAnyProp, okAnyProp := grTransitions[[2]int{g.state, prAny}]
|
||||
transAnyState, okAnyState := grTransitions[[2]int{grAny, nextProperty}]
|
||||
if okAnyProp && okAnyState {
|
||||
// Both apply. We'll use a mix (see comments for grTransitions).
|
||||
g.state = transAnyState[0]
|
||||
boundary = transAnyState[1] == grBoundary
|
||||
if transAnyProp[2] < transAnyState[2] {
|
||||
g.state = transAnyProp[0]
|
||||
boundary = transAnyProp[1] == grBoundary
|
||||
}
|
||||
} else if okAnyProp {
|
||||
// We only have a specific state.
|
||||
g.state = transAnyProp[0]
|
||||
boundary = transAnyProp[1] == grBoundary
|
||||
// This branch will probably never be reached because okAnyState will
|
||||
// always be true given the current transition map. But we keep it here
|
||||
// for future modifications to the transition map where this may not be
|
||||
// true anymore.
|
||||
} else if okAnyState {
|
||||
// We only have a specific property.
|
||||
g.state = transAnyState[0]
|
||||
boundary = transAnyState[1] == grBoundary
|
||||
} else {
|
||||
// No known transition. GB999: Any x Any.
|
||||
g.state = grAny
|
||||
boundary = true
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a cluster boundary, let's stop here. The current cluster will
|
||||
// be the one that just ended.
|
||||
if g.pos-1 == 0 /* GB1 */ || boundary {
|
||||
g.end = g.pos - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return g.start != g.end
|
||||
}
|
||||
|
||||
// Runes returns a slice of runes (code points) which corresponds to the current
|
||||
// grapheme cluster. If the iterator is already past the end or Next() has not
|
||||
// yet been called, nil is returned.
|
||||
func (g *Graphemes) Runes() []rune {
|
||||
if g.start == g.end {
|
||||
return nil
|
||||
}
|
||||
return g.codePoints[g.start:g.end]
|
||||
}
|
||||
|
||||
// Str returns a substring of the original string which corresponds to the
|
||||
// current grapheme cluster. If the iterator is already past the end or Next()
|
||||
// has not yet been called, an empty string is returned.
|
||||
func (g *Graphemes) Str() string {
|
||||
if g.start == g.end {
|
||||
return ""
|
||||
}
|
||||
return string(g.codePoints[g.start:g.end])
|
||||
}
|
||||
|
||||
// Bytes returns a byte slice which corresponds to the current grapheme cluster.
|
||||
// If the iterator is already past the end or Next() has not yet been called,
|
||||
// nil is returned.
|
||||
func (g *Graphemes) Bytes() []byte {
|
||||
if g.start == g.end {
|
||||
return nil
|
||||
}
|
||||
return []byte(string(g.codePoints[g.start:g.end]))
|
||||
}
|
||||
|
||||
// Positions returns the interval of the current grapheme cluster as byte
|
||||
// positions into the original string. The first returned value "from" indexes
|
||||
// the first byte and the second returned value "to" indexes the first byte that
|
||||
// is not included anymore, i.e. str[from:to] is the current grapheme cluster of
|
||||
// the original string "str". If Next() has not yet been called, both values are
|
||||
// 0. If the iterator is already past the end, both values are 1.
|
||||
func (g *Graphemes) Positions() (int, int) {
|
||||
return g.indices[g.start], g.indices[g.end]
|
||||
}
|
||||
|
||||
// Reset puts the iterator into its initial state such that the next call to
|
||||
// Next() sets it to the first grapheme cluster again.
|
||||
func (g *Graphemes) Reset() {
|
||||
g.start, g.end, g.pos, g.state = 0, 0, 0, grAny
|
||||
g.Next() // Parse ahead again.
|
||||
}
|
||||
|
||||
// GraphemeClusterCount returns the number of user-perceived characters
|
||||
// (grapheme clusters) for the given string. To calculate this number, it
|
||||
// iterates through the string using the Graphemes iterator.
|
||||
func GraphemeClusterCount(s string) (n int) {
|
||||
g := NewGraphemes(s)
|
||||
for g.Next() {
|
||||
n++
|
||||
}
|
||||
return
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,8 @@
|
|||
*.out
|
||||
*.swp
|
||||
*.8
|
||||
*.6
|
||||
_obj
|
||||
_test*
|
||||
markdown
|
||||
tags
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue