mirror of https://github.com/minio/minio.git
server: terminal width should fallback to 80x25. (#1895)
Some environments might disable access to `/dev/tty`, fall back to '80' in such scenarios. Move to 'cheggaaa/pb' package for better cross platform support on fetching terminal width. Fixes #1891
This commit is contained in:
parent
276282957e
commit
4ab57f7d60
17
notifier.go
17
notifier.go
|
@ -21,12 +21,12 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cheggaaa/pb"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/olekukonko/ts"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// colorizeUpdateMessage - inspired from Yeoman project npm package https://github.com/yeoman/update-notifier
|
// colorizeUpdateMessage - inspired from Yeoman project npm package https://github.com/yeoman/update-notifier
|
||||||
func colorizeUpdateMessage(updateString string) (string, error) {
|
func colorizeUpdateMessage(updateString string) string {
|
||||||
// Initialize coloring.
|
// Initialize coloring.
|
||||||
cyan := color.New(color.FgCyan, color.Bold).SprintFunc()
|
cyan := color.New(color.FgCyan, color.Bold).SprintFunc()
|
||||||
yellow := color.New(color.FgYellow, color.Bold).SprintfFunc()
|
yellow := color.New(color.FgYellow, color.Bold).SprintfFunc()
|
||||||
|
@ -44,14 +44,17 @@ func colorizeUpdateMessage(updateString string) (string, error) {
|
||||||
maxContentWidth := line1Length
|
maxContentWidth := line1Length
|
||||||
line1Rest := maxContentWidth - line1Length
|
line1Rest := maxContentWidth - line1Length
|
||||||
|
|
||||||
terminal, err := ts.GetSize()
|
// termWidth is set to a default one to use when we are
|
||||||
if err != nil {
|
// not able to calculate terminal width via OS syscalls
|
||||||
return "", err
|
termWidth := 25
|
||||||
|
|
||||||
|
if width, err := pb.GetTerminalWidth(); err == nil {
|
||||||
|
termWidth = width
|
||||||
}
|
}
|
||||||
|
|
||||||
var message string
|
var message string
|
||||||
switch {
|
switch {
|
||||||
case len(line1Str) > terminal.Col():
|
case len(line1Str) > termWidth:
|
||||||
message = "\n" + line1InColor + "\n"
|
message = "\n" + line1InColor + "\n"
|
||||||
default:
|
default:
|
||||||
// On windows terminal turn off unicode characters.
|
// On windows terminal turn off unicode characters.
|
||||||
|
@ -75,5 +78,5 @@ func colorizeUpdateMessage(updateString string) (string, error) {
|
||||||
bottom + "\n"
|
bottom + "\n"
|
||||||
}
|
}
|
||||||
// Return the final message.
|
// Return the final message.
|
||||||
return message, nil
|
return message
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/olekukonko/ts"
|
"github.com/cheggaaa/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
const errorFmt = "%5d: %s <-- "
|
const errorFmt = "%5d: %s <-- "
|
||||||
|
@ -53,8 +53,8 @@ func FormatJSONSyntaxError(data io.Reader, sErr *json.SyntaxError) error {
|
||||||
// dynamically to avoid an eventual bug after modifying errorFmt
|
// dynamically to avoid an eventual bug after modifying errorFmt
|
||||||
errorShift := len(fmt.Sprintf(errorFmt, 1, ""))
|
errorShift := len(fmt.Sprintf(errorFmt, 1, ""))
|
||||||
|
|
||||||
if termSize, err := ts.GetSize(); err == nil {
|
if width, err := pb.GetTerminalWidth(); err == nil {
|
||||||
termWidth = termSize.Col()
|
termWidth = width
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
|
@ -88,8 +88,7 @@ func (u updateMessage) String() string {
|
||||||
updateMessage := color.New(color.FgGreen, color.Bold).SprintfFunc()
|
updateMessage := color.New(color.FgGreen, color.Bold).SprintfFunc()
|
||||||
return updateMessage("You are already running the most recent version of ‘minio’.")
|
return updateMessage("You are already running the most recent version of ‘minio’.")
|
||||||
}
|
}
|
||||||
msg, err := colorizeUpdateMessage(u.Download)
|
msg := colorizeUpdateMessage(u.Download)
|
||||||
fatalIf(err, "Unable to colorize update notice ‘"+msg+"’.")
|
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
Copyright (c) 2012-2015, Sergey Cherepanov
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,176 @@
|
||||||
|
# Terminal progress bar for Go
|
||||||
|
|
||||||
|
Simple progress bar for console programs.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
go get gopkg.in/cheggaaa/pb.v1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```Go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/cheggaaa/pb.v1"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
count := 100000
|
||||||
|
bar := pb.StartNew(count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
bar.Increment()
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
bar.FinishPrint("The End!")
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Result will be like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
> go run test.go
|
||||||
|
37158 / 100000 [================>_______________________________] 37.16% 1m11s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
```Go
|
||||||
|
// create bar
|
||||||
|
bar := pb.New(count)
|
||||||
|
|
||||||
|
// refresh info every second (default 200ms)
|
||||||
|
bar.SetRefreshRate(time.Second)
|
||||||
|
|
||||||
|
// show percents (by default already true)
|
||||||
|
bar.ShowPercent = true
|
||||||
|
|
||||||
|
// show bar (by default already true)
|
||||||
|
bar.ShowBar = true
|
||||||
|
|
||||||
|
// no counters
|
||||||
|
bar.ShowCounters = false
|
||||||
|
|
||||||
|
// show "time left"
|
||||||
|
bar.ShowTimeLeft = true
|
||||||
|
|
||||||
|
// show average speed
|
||||||
|
bar.ShowSpeed = true
|
||||||
|
|
||||||
|
// sets the width of the progress bar
|
||||||
|
bar.SetWidth(80)
|
||||||
|
|
||||||
|
// sets the width of the progress bar, but if terminal size smaller will be ignored
|
||||||
|
bar.SetMaxWidth(80)
|
||||||
|
|
||||||
|
// convert output to readable format (like KB, MB)
|
||||||
|
bar.SetUnits(pb.U_BYTES)
|
||||||
|
|
||||||
|
// and start
|
||||||
|
bar.Start()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Progress bar for IO Operations
|
||||||
|
|
||||||
|
```go
|
||||||
|
// create and start bar
|
||||||
|
bar := pb.New(myDataLen).SetUnits(pb.U_BYTES)
|
||||||
|
bar.Start()
|
||||||
|
|
||||||
|
// my io.Reader
|
||||||
|
r := myReader
|
||||||
|
|
||||||
|
// my io.Writer
|
||||||
|
w := myWriter
|
||||||
|
|
||||||
|
// create proxy reader
|
||||||
|
reader := bar.NewProxyReader(r)
|
||||||
|
|
||||||
|
// and copy from pb reader
|
||||||
|
io.Copy(w, reader)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// create and start bar
|
||||||
|
bar := pb.New(myDataLen).SetUnits(pb.U_BYTES)
|
||||||
|
bar.Start()
|
||||||
|
|
||||||
|
// my io.Reader
|
||||||
|
r := myReader
|
||||||
|
|
||||||
|
// my io.Writer
|
||||||
|
w := myWriter
|
||||||
|
|
||||||
|
// create multi writer
|
||||||
|
writer := io.MultiWriter(w, bar)
|
||||||
|
|
||||||
|
// and copy
|
||||||
|
io.Copy(writer, r)
|
||||||
|
|
||||||
|
bar.Finish()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Progress Bar Look-and-feel
|
||||||
|
|
||||||
|
```go
|
||||||
|
bar.Format("<.- >")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multiple Progress Bars (experimental and unstable)
|
||||||
|
|
||||||
|
Do not print to terminal while pool is active.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/cheggaaa/pb.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// create bars
|
||||||
|
first := pb.New(200).Prefix("First ")
|
||||||
|
second := pb.New(200).Prefix("Second ")
|
||||||
|
third := pb.New(200).Prefix("Third ")
|
||||||
|
// start pool
|
||||||
|
pool, err := pb.StartPool(first, second, third)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// update bars
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
for _, bar := range []*pb.ProgressBar{first, second, third} {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(cb *pb.ProgressBar) {
|
||||||
|
for n := 0; n < 200; n++ {
|
||||||
|
cb.Increment()
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
|
||||||
|
}
|
||||||
|
cb.Finish()
|
||||||
|
wg.Done()
|
||||||
|
}(bar)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
// close pool
|
||||||
|
pool.Stop()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The result will be as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go run example/multiple.go
|
||||||
|
First 141 / 1000 [===============>---------------------------------------] 14.10 % 44s
|
||||||
|
Second 139 / 1000 [==============>---------------------------------------] 13.90 % 44s
|
||||||
|
Third 152 / 1000 [================>--------------------------------------] 15.20 % 40s
|
||||||
|
```
|
|
@ -0,0 +1,87 @@
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Units int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// U_NO are default units, they represent a simple value and are not formatted at all.
|
||||||
|
U_NO Units = iota
|
||||||
|
// U_BYTES units are formatted in a human readable way (b, Bb, Mb, ...)
|
||||||
|
U_BYTES
|
||||||
|
// U_DURATION units are formatted in a human readable way (3h14m15s)
|
||||||
|
U_DURATION
|
||||||
|
)
|
||||||
|
|
||||||
|
func Format(i int64) *formatter {
|
||||||
|
return &formatter{n: i}
|
||||||
|
}
|
||||||
|
|
||||||
|
type formatter struct {
|
||||||
|
n int64
|
||||||
|
unit Units
|
||||||
|
width int
|
||||||
|
perSec bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *formatter) Value(n int64) *formatter {
|
||||||
|
f.n = n
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *formatter) To(unit Units) *formatter {
|
||||||
|
f.unit = unit
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *formatter) Width(width int) *formatter {
|
||||||
|
f.width = width
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *formatter) PerSec() *formatter {
|
||||||
|
f.perSec = true
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *formatter) String() (out string) {
|
||||||
|
switch f.unit {
|
||||||
|
case U_BYTES:
|
||||||
|
out = formatBytes(f.n)
|
||||||
|
case U_DURATION:
|
||||||
|
d := time.Duration(f.n)
|
||||||
|
if d > time.Hour*24 {
|
||||||
|
out = fmt.Sprintf("%dd", d/24/time.Hour)
|
||||||
|
d -= (d / time.Hour / 24) * (time.Hour * 24)
|
||||||
|
}
|
||||||
|
out = fmt.Sprintf("%s%v", out, d)
|
||||||
|
default:
|
||||||
|
out = fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n)
|
||||||
|
}
|
||||||
|
if f.perSec {
|
||||||
|
out += "/s"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert bytes to human readable string. Like a 2 MB, 64.2 KB, 52 B
|
||||||
|
func formatBytes(i int64) (result string) {
|
||||||
|
switch {
|
||||||
|
case i > (1024 * 1024 * 1024 * 1024):
|
||||||
|
result = fmt.Sprintf("%.02f TB", float64(i)/1024/1024/1024/1024)
|
||||||
|
case i > (1024 * 1024 * 1024):
|
||||||
|
result = fmt.Sprintf("%.02f GB", float64(i)/1024/1024/1024)
|
||||||
|
case i > (1024 * 1024):
|
||||||
|
result = fmt.Sprintf("%.02f MB", float64(i)/1024/1024)
|
||||||
|
case i > 1024:
|
||||||
|
result = fmt.Sprintf("%.02f KB", float64(i)/1024)
|
||||||
|
default:
|
||||||
|
result = fmt.Sprintf("%d B", i)
|
||||||
|
}
|
||||||
|
result = strings.Trim(result, " ")
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,424 @@
|
||||||
|
// Simple console progress bars
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Current version
|
||||||
|
const Version = "1.0.2"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Default refresh rate - 200ms
|
||||||
|
DEFAULT_REFRESH_RATE = time.Millisecond * 200
|
||||||
|
FORMAT = "[=>-]"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DEPRECATED
|
||||||
|
// variables for backward compatibility, from now do not work
|
||||||
|
// use pb.Format and pb.SetRefreshRate
|
||||||
|
var (
|
||||||
|
DefaultRefreshRate = DEFAULT_REFRESH_RATE
|
||||||
|
BarStart, BarEnd, Empty, Current, CurrentN string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create new progress bar object
|
||||||
|
func New(total int) *ProgressBar {
|
||||||
|
return New64(int64(total))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new progress bar object using int64 as total
|
||||||
|
func New64(total int64) *ProgressBar {
|
||||||
|
pb := &ProgressBar{
|
||||||
|
Total: total,
|
||||||
|
RefreshRate: DEFAULT_REFRESH_RATE,
|
||||||
|
ShowPercent: true,
|
||||||
|
ShowCounters: true,
|
||||||
|
ShowBar: true,
|
||||||
|
ShowTimeLeft: true,
|
||||||
|
ShowFinalTime: true,
|
||||||
|
Units: U_NO,
|
||||||
|
ManualUpdate: false,
|
||||||
|
finish: make(chan struct{}),
|
||||||
|
currentValue: -1,
|
||||||
|
mu: new(sync.Mutex),
|
||||||
|
}
|
||||||
|
return pb.Format(FORMAT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new object and start
|
||||||
|
func StartNew(total int) *ProgressBar {
|
||||||
|
return New(total).Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback for custom output
|
||||||
|
// For example:
|
||||||
|
// bar.Callback = func(s string) {
|
||||||
|
// mySuperPrint(s)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type Callback func(out string)
|
||||||
|
|
||||||
|
type ProgressBar struct {
|
||||||
|
current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278)
|
||||||
|
|
||||||
|
Total int64
|
||||||
|
RefreshRate time.Duration
|
||||||
|
ShowPercent, ShowCounters bool
|
||||||
|
ShowSpeed, ShowTimeLeft, ShowBar bool
|
||||||
|
ShowFinalTime bool
|
||||||
|
Output io.Writer
|
||||||
|
Callback Callback
|
||||||
|
NotPrint bool
|
||||||
|
Units Units
|
||||||
|
Width int
|
||||||
|
ForceWidth bool
|
||||||
|
ManualUpdate bool
|
||||||
|
|
||||||
|
// Default width for the time box.
|
||||||
|
UnitsWidth int
|
||||||
|
TimeBoxWidth int
|
||||||
|
|
||||||
|
finishOnce sync.Once //Guards isFinish
|
||||||
|
finish chan struct{}
|
||||||
|
isFinish bool
|
||||||
|
|
||||||
|
startTime time.Time
|
||||||
|
startValue int64
|
||||||
|
currentValue int64
|
||||||
|
|
||||||
|
prefix, postfix string
|
||||||
|
|
||||||
|
mu *sync.Mutex
|
||||||
|
lastPrint string
|
||||||
|
|
||||||
|
BarStart string
|
||||||
|
BarEnd string
|
||||||
|
Empty string
|
||||||
|
Current string
|
||||||
|
CurrentN string
|
||||||
|
|
||||||
|
AlwaysUpdate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start print
|
||||||
|
func (pb *ProgressBar) Start() *ProgressBar {
|
||||||
|
pb.startTime = time.Now()
|
||||||
|
pb.startValue = pb.current
|
||||||
|
if pb.Total == 0 {
|
||||||
|
pb.ShowTimeLeft = false
|
||||||
|
pb.ShowPercent = false
|
||||||
|
}
|
||||||
|
if !pb.ManualUpdate {
|
||||||
|
pb.Update() // Initial printing of the bar before running the bar refresher.
|
||||||
|
go pb.refresher()
|
||||||
|
}
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment current value
|
||||||
|
func (pb *ProgressBar) Increment() int {
|
||||||
|
return pb.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set current value
|
||||||
|
func (pb *ProgressBar) Set(current int) *ProgressBar {
|
||||||
|
return pb.Set64(int64(current))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set64 sets the current value as int64
|
||||||
|
func (pb *ProgressBar) Set64(current int64) *ProgressBar {
|
||||||
|
atomic.StoreInt64(&pb.current, current)
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to current value
|
||||||
|
func (pb *ProgressBar) Add(add int) int {
|
||||||
|
return int(pb.Add64(int64(add)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *ProgressBar) Add64(add int64) int64 {
|
||||||
|
return atomic.AddInt64(&pb.current, add)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set prefix string
|
||||||
|
func (pb *ProgressBar) Prefix(prefix string) *ProgressBar {
|
||||||
|
pb.prefix = prefix
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set postfix string
|
||||||
|
func (pb *ProgressBar) Postfix(postfix string) *ProgressBar {
|
||||||
|
pb.postfix = postfix
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set custom format for bar
|
||||||
|
// Example: bar.Format("[=>_]")
|
||||||
|
// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter
|
||||||
|
func (pb *ProgressBar) Format(format string) *ProgressBar {
|
||||||
|
var formatEntries []string
|
||||||
|
if len(format) == 5 {
|
||||||
|
formatEntries = strings.Split(format, "")
|
||||||
|
} else {
|
||||||
|
formatEntries = strings.Split(format, "\x00")
|
||||||
|
}
|
||||||
|
if len(formatEntries) == 5 {
|
||||||
|
pb.BarStart = formatEntries[0]
|
||||||
|
pb.BarEnd = formatEntries[4]
|
||||||
|
pb.Empty = formatEntries[3]
|
||||||
|
pb.Current = formatEntries[1]
|
||||||
|
pb.CurrentN = formatEntries[2]
|
||||||
|
}
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set bar refresh rate
|
||||||
|
func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar {
|
||||||
|
pb.RefreshRate = rate
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set units
|
||||||
|
// bar.SetUnits(U_NO) - by default
|
||||||
|
// bar.SetUnits(U_BYTES) - for Mb, Kb, etc
|
||||||
|
func (pb *ProgressBar) SetUnits(units Units) *ProgressBar {
|
||||||
|
pb.Units = units
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set max width, if width is bigger than terminal width, will be ignored
|
||||||
|
func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar {
|
||||||
|
pb.Width = width
|
||||||
|
pb.ForceWidth = false
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set bar width
|
||||||
|
func (pb *ProgressBar) SetWidth(width int) *ProgressBar {
|
||||||
|
pb.Width = width
|
||||||
|
pb.ForceWidth = true
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
// End print
|
||||||
|
func (pb *ProgressBar) Finish() {
|
||||||
|
//Protect multiple calls
|
||||||
|
pb.finishOnce.Do(func() {
|
||||||
|
close(pb.finish)
|
||||||
|
pb.write(atomic.LoadInt64(&pb.current))
|
||||||
|
if !pb.NotPrint {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
pb.isFinish = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// End print and write string 'str'
|
||||||
|
func (pb *ProgressBar) FinishPrint(str string) {
|
||||||
|
pb.Finish()
|
||||||
|
fmt.Println(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement io.Writer
|
||||||
|
func (pb *ProgressBar) Write(p []byte) (n int, err error) {
|
||||||
|
n = len(p)
|
||||||
|
pb.Add(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement io.Reader
|
||||||
|
func (pb *ProgressBar) Read(p []byte) (n int, err error) {
|
||||||
|
n = len(p)
|
||||||
|
pb.Add(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new proxy reader over bar
|
||||||
|
func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader {
|
||||||
|
return &Reader{r, pb}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *ProgressBar) write(current int64) {
|
||||||
|
width := pb.GetWidth()
|
||||||
|
|
||||||
|
var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string
|
||||||
|
|
||||||
|
// percents
|
||||||
|
if pb.ShowPercent {
|
||||||
|
var percent float64
|
||||||
|
if pb.Total > 0 {
|
||||||
|
percent = float64(current) / (float64(pb.Total) / float64(100))
|
||||||
|
} else {
|
||||||
|
percent = float64(current) / float64(100)
|
||||||
|
}
|
||||||
|
percentBox = fmt.Sprintf(" %6.02f%%", percent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// counters
|
||||||
|
if pb.ShowCounters {
|
||||||
|
current := Format(current).To(pb.Units).Width(pb.UnitsWidth)
|
||||||
|
if pb.Total > 0 {
|
||||||
|
total := Format(pb.Total).To(pb.Units).Width(pb.UnitsWidth)
|
||||||
|
countersBox = fmt.Sprintf(" %s / %s ", current, total)
|
||||||
|
} else {
|
||||||
|
countersBox = fmt.Sprintf(" %s / ? ", current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// time left
|
||||||
|
fromStart := time.Now().Sub(pb.startTime)
|
||||||
|
currentFromStart := current - pb.startValue
|
||||||
|
select {
|
||||||
|
case <-pb.finish:
|
||||||
|
if pb.ShowFinalTime {
|
||||||
|
var left time.Duration
|
||||||
|
if pb.Total > 0 {
|
||||||
|
left = (fromStart / time.Second) * time.Second
|
||||||
|
} else {
|
||||||
|
left = (time.Duration(currentFromStart) / time.Second) * time.Second
|
||||||
|
}
|
||||||
|
timeLeftBox = fmt.Sprintf(" %s", left.String())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if pb.ShowTimeLeft && currentFromStart > 0 {
|
||||||
|
perEntry := fromStart / time.Duration(currentFromStart)
|
||||||
|
var left time.Duration
|
||||||
|
if pb.Total > 0 {
|
||||||
|
left = time.Duration(pb.Total-currentFromStart) * perEntry
|
||||||
|
left = (left / time.Second) * time.Second
|
||||||
|
} else {
|
||||||
|
left = time.Duration(currentFromStart) * perEntry
|
||||||
|
left = (left / time.Second) * time.Second
|
||||||
|
}
|
||||||
|
timeLeft := Format(int64(left)).To(U_DURATION).String()
|
||||||
|
timeLeftBox = fmt.Sprintf(" %s", timeLeft)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(timeLeftBox) < pb.TimeBoxWidth {
|
||||||
|
timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox)
|
||||||
|
}
|
||||||
|
|
||||||
|
// speed
|
||||||
|
if pb.ShowSpeed && currentFromStart > 0 {
|
||||||
|
fromStart := time.Now().Sub(pb.startTime)
|
||||||
|
speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second))
|
||||||
|
speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix)
|
||||||
|
// bar
|
||||||
|
if pb.ShowBar {
|
||||||
|
size := width - barWidth
|
||||||
|
if size > 0 {
|
||||||
|
if pb.Total > 0 {
|
||||||
|
curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size)))
|
||||||
|
emptCount := size - curCount
|
||||||
|
barBox = pb.BarStart
|
||||||
|
if emptCount < 0 {
|
||||||
|
emptCount = 0
|
||||||
|
}
|
||||||
|
if curCount > size {
|
||||||
|
curCount = size
|
||||||
|
}
|
||||||
|
if emptCount <= 0 {
|
||||||
|
barBox += strings.Repeat(pb.Current, curCount)
|
||||||
|
} else if curCount > 0 {
|
||||||
|
barBox += strings.Repeat(pb.Current, curCount-1) + pb.CurrentN
|
||||||
|
}
|
||||||
|
barBox += strings.Repeat(pb.Empty, emptCount) + pb.BarEnd
|
||||||
|
} else {
|
||||||
|
barBox = pb.BarStart
|
||||||
|
pos := size - int(current)%int(size)
|
||||||
|
if pos-1 > 0 {
|
||||||
|
barBox += strings.Repeat(pb.Empty, pos-1)
|
||||||
|
}
|
||||||
|
barBox += pb.Current
|
||||||
|
if size-pos-1 > 0 {
|
||||||
|
barBox += strings.Repeat(pb.Empty, size-pos-1)
|
||||||
|
}
|
||||||
|
barBox += pb.BarEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check len
|
||||||
|
out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix
|
||||||
|
if escapeAwareRuneCountInString(out) < width {
|
||||||
|
end = strings.Repeat(" ", width-utf8.RuneCountInString(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// and print!
|
||||||
|
pb.mu.Lock()
|
||||||
|
pb.lastPrint = out + end
|
||||||
|
pb.mu.Unlock()
|
||||||
|
switch {
|
||||||
|
case pb.isFinish:
|
||||||
|
return
|
||||||
|
case pb.Output != nil:
|
||||||
|
fmt.Fprint(pb.Output, "\r"+out+end)
|
||||||
|
case pb.Callback != nil:
|
||||||
|
pb.Callback(out + end)
|
||||||
|
case !pb.NotPrint:
|
||||||
|
fmt.Print("\r" + out + end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTerminalWidth - returns terminal width for all platforms.
|
||||||
|
func GetTerminalWidth() (int, error) {
|
||||||
|
return terminalWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *ProgressBar) GetWidth() int {
|
||||||
|
if pb.ForceWidth {
|
||||||
|
return pb.Width
|
||||||
|
}
|
||||||
|
|
||||||
|
width := pb.Width
|
||||||
|
termWidth, _ := terminalWidth()
|
||||||
|
if width == 0 || termWidth <= width {
|
||||||
|
width = termWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the current state of the progressbar
|
||||||
|
func (pb *ProgressBar) Update() {
|
||||||
|
c := atomic.LoadInt64(&pb.current)
|
||||||
|
if pb.AlwaysUpdate || c != pb.currentValue {
|
||||||
|
pb.write(c)
|
||||||
|
pb.currentValue = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *ProgressBar) String() string {
|
||||||
|
return pb.lastPrint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal loop for refreshing the progressbar
|
||||||
|
func (pb *ProgressBar) refresher() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-pb.finish:
|
||||||
|
return
|
||||||
|
case <-time.After(pb.RefreshRate):
|
||||||
|
pb.Update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type window struct {
|
||||||
|
Row uint16
|
||||||
|
Col uint16
|
||||||
|
Xpixel uint16
|
||||||
|
Ypixel uint16
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
// +build linux darwin freebsd netbsd openbsd dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const sysIoctl = syscall.SYS_IOCTL
|
|
@ -0,0 +1,6 @@
|
||||||
|
// +build solaris
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
const sysIoctl = 54
|
|
@ -0,0 +1,141 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tty = os.Stdin
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
// GetConsoleScreenBufferInfo retrieves information about the
|
||||||
|
// specified console screen buffer.
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx
|
||||||
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
|
||||||
|
// GetConsoleMode retrieves the current input mode of a console's
|
||||||
|
// input buffer or the current output mode of a console screen buffer.
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
|
||||||
|
getConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||||
|
|
||||||
|
// SetConsoleMode sets the input mode of a console's input buffer
|
||||||
|
// or the output mode of a console screen buffer.
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||||
|
setConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||||
|
|
||||||
|
// SetConsoleCursorPosition sets the cursor position in the
|
||||||
|
// specified console screen buffer.
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx
|
||||||
|
setConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Defines the coordinates of the upper left and lower right corners
|
||||||
|
// of a rectangle.
|
||||||
|
// See
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311(v=vs.85).aspx
|
||||||
|
smallRect struct {
|
||||||
|
Left, Top, Right, Bottom int16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines the coordinates of a character cell in a console screen
|
||||||
|
// buffer. The origin of the coordinate system (0,0) is at the top, left cell
|
||||||
|
// of the buffer.
|
||||||
|
// See
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx
|
||||||
|
coordinates struct {
|
||||||
|
X, Y int16
|
||||||
|
}
|
||||||
|
|
||||||
|
word int16
|
||||||
|
|
||||||
|
// Contains information about a console screen buffer.
|
||||||
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
|
||||||
|
consoleScreenBufferInfo struct {
|
||||||
|
dwSize coordinates
|
||||||
|
dwCursorPosition coordinates
|
||||||
|
wAttributes word
|
||||||
|
srWindow smallRect
|
||||||
|
dwMaximumWindowSize coordinates
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// terminalWidth returns width of the terminal.
|
||||||
|
func terminalWidth() (width int, err error) {
|
||||||
|
var info consoleScreenBufferInfo
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return 0, error(e)
|
||||||
|
}
|
||||||
|
return int(info.dwSize.X) - 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCursorPos() (pos coordinates, err error) {
|
||||||
|
var info consoleScreenBufferInfo
|
||||||
|
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return info.dwCursorPosition, error(e)
|
||||||
|
}
|
||||||
|
return info.dwCursorPosition, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCursorPos(pos coordinates) error {
|
||||||
|
_, _, e := syscall.Syscall(setConsoleCursorPosition.Addr(), 2, uintptr(syscall.Stdout), uintptr(uint32(uint16(pos.Y))<<16|uint32(uint16(pos.X))), 0)
|
||||||
|
if e != 0 {
|
||||||
|
return error(e)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrPoolWasStarted = errors.New("Bar pool was started")
|
||||||
|
|
||||||
|
var echoLocked bool
|
||||||
|
var echoLockMutex sync.Mutex
|
||||||
|
|
||||||
|
var oldState word
|
||||||
|
|
||||||
|
func lockEcho() (quit chan int, err error) {
|
||||||
|
echoLockMutex.Lock()
|
||||||
|
defer echoLockMutex.Unlock()
|
||||||
|
if echoLocked {
|
||||||
|
err = ErrPoolWasStarted
|
||||||
|
return
|
||||||
|
}
|
||||||
|
echoLocked = true
|
||||||
|
|
||||||
|
if _, _, e := syscall.Syscall(getConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&oldState)), 0); e != 0 {
|
||||||
|
err = fmt.Errorf("Can't get terminal settings: %v", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState
|
||||||
|
const ENABLE_ECHO_INPUT = 0x0004
|
||||||
|
const ENABLE_LINE_INPUT = 0x0002
|
||||||
|
newState = newState & (^(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT))
|
||||||
|
if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(newState), 0); e != 0 {
|
||||||
|
err = fmt.Errorf("Can't set terminal settings: %v", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockEcho() (err error) {
|
||||||
|
echoLockMutex.Lock()
|
||||||
|
defer echoLockMutex.Unlock()
|
||||||
|
if !echoLocked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
echoLocked = false
|
||||||
|
if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(oldState), 0); e != 0 {
|
||||||
|
err = fmt.Errorf("Can't set terminal settings")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
// +build linux darwin freebsd netbsd openbsd solaris dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TIOCGWINSZ = 0x5413
|
||||||
|
TIOCGWINSZ_OSX = 1074295912
|
||||||
|
)
|
||||||
|
|
||||||
|
var tty *os.File
|
||||||
|
|
||||||
|
var ErrPoolWasStarted = errors.New("Bar pool was started")
|
||||||
|
|
||||||
|
var echoLocked bool
|
||||||
|
var echoLockMutex sync.Mutex
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
tty, err = os.Open("/dev/tty")
|
||||||
|
if err != nil {
|
||||||
|
tty = os.Stdin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminalWidth returns width of the terminal.
|
||||||
|
func terminalWidth() (int, error) {
|
||||||
|
w := new(window)
|
||||||
|
tio := syscall.TIOCGWINSZ
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
tio = TIOCGWINSZ_OSX
|
||||||
|
}
|
||||||
|
res, _, err := syscall.Syscall(sysIoctl,
|
||||||
|
tty.Fd(),
|
||||||
|
uintptr(tio),
|
||||||
|
uintptr(unsafe.Pointer(w)),
|
||||||
|
)
|
||||||
|
if int(res) == -1 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(w.Col), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldState syscall.Termios
|
||||||
|
|
||||||
|
func lockEcho() (quit chan int, err error) {
|
||||||
|
echoLockMutex.Lock()
|
||||||
|
defer echoLockMutex.Unlock()
|
||||||
|
if echoLocked {
|
||||||
|
err = ErrPoolWasStarted
|
||||||
|
return
|
||||||
|
}
|
||||||
|
echoLocked = true
|
||||||
|
|
||||||
|
fd := tty.Fd()
|
||||||
|
if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); e != 0 {
|
||||||
|
err = fmt.Errorf("Can't get terminal settings: %v", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||||
|
newState.Iflag |= syscall.ICRNL
|
||||||
|
if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); e != 0 {
|
||||||
|
err = fmt.Errorf("Can't set terminal settings: %v", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
quit = make(chan int, 1)
|
||||||
|
go catchTerminate(quit)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockEcho() (err error) {
|
||||||
|
echoLockMutex.Lock()
|
||||||
|
defer echoLockMutex.Unlock()
|
||||||
|
if !echoLocked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
echoLocked = false
|
||||||
|
fd := tty.Fd()
|
||||||
|
if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); e != 0 {
|
||||||
|
err = fmt.Errorf("Can't set terminal settings")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen exit signals and restore terminal state
|
||||||
|
func catchTerminate(quit chan int) {
|
||||||
|
sig := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sig, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL)
|
||||||
|
defer signal.Stop(sig)
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
unlockEcho()
|
||||||
|
case <-sig:
|
||||||
|
unlockEcho()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// +build linux darwin freebsd netbsd openbsd solaris dragonfly windows
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create and start new pool with given bars
|
||||||
|
// You need call pool.Stop() after work
|
||||||
|
func StartPool(pbs ...*ProgressBar) (pool *Pool, err error) {
|
||||||
|
pool = new(Pool)
|
||||||
|
if err = pool.start(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pool.add(pbs...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pool struct {
|
||||||
|
RefreshRate time.Duration
|
||||||
|
bars []*ProgressBar
|
||||||
|
quit chan int
|
||||||
|
finishOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) add(pbs ...*ProgressBar) {
|
||||||
|
for _, bar := range pbs {
|
||||||
|
bar.ManualUpdate = true
|
||||||
|
bar.NotPrint = true
|
||||||
|
bar.Start()
|
||||||
|
p.bars = append(p.bars, bar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) start() (err error) {
|
||||||
|
p.RefreshRate = DefaultRefreshRate
|
||||||
|
quit, err := lockEcho()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.quit = make(chan int)
|
||||||
|
go p.writer(quit)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) writer(finish chan int) {
|
||||||
|
var first = true
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-time.After(p.RefreshRate):
|
||||||
|
if p.print(first) {
|
||||||
|
p.print(false)
|
||||||
|
finish <- 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
case <-p.quit:
|
||||||
|
finish <- 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore terminal state and close pool
|
||||||
|
func (p *Pool) Stop() error {
|
||||||
|
// Wait until one final refresh has passed.
|
||||||
|
time.Sleep(p.RefreshRate)
|
||||||
|
|
||||||
|
p.finishOnce.Do(func() {
|
||||||
|
close(p.quit)
|
||||||
|
})
|
||||||
|
return unlockEcho()
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Pool) print(first bool) bool {
|
||||||
|
var out string
|
||||||
|
if !first {
|
||||||
|
coords, err := getCursorPos()
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
coords.Y -= int16(len(p.bars))
|
||||||
|
coords.X = 0
|
||||||
|
|
||||||
|
err = setCursorPos(coords)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isFinished := true
|
||||||
|
for _, bar := range p.bars {
|
||||||
|
if !bar.isFinish {
|
||||||
|
isFinished = false
|
||||||
|
}
|
||||||
|
bar.Update()
|
||||||
|
out += fmt.Sprintf("\r%s\n", bar.String())
|
||||||
|
}
|
||||||
|
fmt.Print(out)
|
||||||
|
return isFinished
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
// +build linux darwin freebsd netbsd openbsd solaris dragonfly
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func (p *Pool) print(first bool) bool {
|
||||||
|
var out string
|
||||||
|
if !first {
|
||||||
|
out = fmt.Sprintf("\033[%dA", len(p.bars))
|
||||||
|
}
|
||||||
|
isFinished := true
|
||||||
|
for _, bar := range p.bars {
|
||||||
|
if !bar.isFinish {
|
||||||
|
isFinished = false
|
||||||
|
}
|
||||||
|
bar.Update()
|
||||||
|
out += fmt.Sprintf("\r%s\n", bar.String())
|
||||||
|
}
|
||||||
|
fmt.Print(out)
|
||||||
|
return isFinished
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// It's proxy reader, implement io.Reader
|
||||||
|
type Reader struct {
|
||||||
|
io.Reader
|
||||||
|
bar *ProgressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = r.Reader.Read(p)
|
||||||
|
r.bar.Add(n)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Finds the control character sequences (like colors)
|
||||||
|
var ctrlFinder = regexp.MustCompile("\x1b\x5b[0-9]+\x6d")
|
||||||
|
|
||||||
|
func escapeAwareRuneCountInString(s string) int {
|
||||||
|
n := utf8.RuneCountInString(s)
|
||||||
|
for _, sm := range ctrlFinder.FindAllString(s, -1) {
|
||||||
|
n -= len(sm)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
// +build darwin freebsd netbsd openbsd dragonfly
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
const ioctlWriteTermios = syscall.TIOCSETA
|
|
@ -0,0 +1,7 @@
|
||||||
|
// +build linux solaris
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
const ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||||
|
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
|
@ -1,19 +0,0 @@
|
||||||
Copyright (C) 2014 by Oleku Konko
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
|
@ -1,28 +0,0 @@
|
||||||
ts (Terminal Size)
|
|
||||||
==
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/olekukonko/ts.png?branch=master)](https://travis-ci.org/olekukonko/ts) [![Total views](https://sourcegraph.com/api/repos/github.com/olekukonko/ts/counters/views.png)](https://sourcegraph.com/github.com/olekukonko/ts)
|
|
||||||
|
|
||||||
Simple go Application to get Terminal Size. So Many Implementations do not support windows but `ts` has full windows support.
|
|
||||||
Run `go get github.com/olekukonko/ts` to download and install
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/olekukonko/ts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
size, _ := ts.GetSize()
|
|
||||||
fmt.Println(size.Col()) // Get Width
|
|
||||||
fmt.Println(size.Row()) // Get Height
|
|
||||||
fmt.Println(size.PosX()) // Get X position
|
|
||||||
fmt.Println(size.PosY()) // Get Y position
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[See Documentation](http://godoc.org/github.com/olekukonko/ts)
|
|
|
@ -1,36 +0,0 @@
|
||||||
// Copyright 2014 Oleku Konko All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This module is a Terminal API for the Go Programming Language.
|
|
||||||
// The protocols were written in pure Go and works on windows and unix systems
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
Simple go Application to get Terminal Size. So Many Implementations do not support windows but `ts` has full windows support.
|
|
||||||
Run `go get github.com/olekukonko/ts` to download and install
|
|
||||||
|
|
||||||
Installation
|
|
||||||
|
|
||||||
Minimum requirements are Go 1.1+ with fill Windows support
|
|
||||||
|
|
||||||
Example
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/olekukonko/ts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
size, _ := ts.GetSize()
|
|
||||||
fmt.Println(size.Col()) // Get Width
|
|
||||||
fmt.Println(size.Row()) // Get Height
|
|
||||||
fmt.Println(size.PosX()) // Get X position
|
|
||||||
fmt.Println(size.PosY()) // Get Y position
|
|
||||||
}
|
|
||||||
|
|
||||||
**/
|
|
||||||
|
|
||||||
package ts
|
|
|
@ -1,36 +0,0 @@
|
||||||
// Copyright 2014 Oleku Konko All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This module is a Terminal API for the Go Programming Language.
|
|
||||||
// The protocols were written in pure Go and works on windows and unix systems
|
|
||||||
|
|
||||||
package ts
|
|
||||||
|
|
||||||
// Return System Size
|
|
||||||
type Size struct {
|
|
||||||
row uint16
|
|
||||||
col uint16
|
|
||||||
posX uint16
|
|
||||||
posY uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Terminal Width
|
|
||||||
func (w Size) Col() int {
|
|
||||||
return int(w.col)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Terminal Height
|
|
||||||
func (w Size) Row() int {
|
|
||||||
return int(w.row)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Position X
|
|
||||||
func (w Size) PosX() int {
|
|
||||||
return int(w.posX)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Position Y
|
|
||||||
func (w Size) PosY() int {
|
|
||||||
return int(w.posY)
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
// +build darwin
|
|
||||||
|
|
||||||
// Copyright 2014 Oleku Konko All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This module is a Terminal API for the Go Programming Language.
|
|
||||||
// The protocols were written in pure Go and works on windows and unix systems
|
|
||||||
|
|
||||||
package ts
|
|
||||||
|
|
||||||
const (
|
|
||||||
TIOCGWINSZ = 0x40087468
|
|
||||||
)
|
|
|
@ -1,13 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
// Copyright 2014 Oleku Konko All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This module is a Terminal API for the Go Programming Language.
|
|
||||||
// The protocols were written in pure Go and works on windows and unix systems
|
|
||||||
package ts
|
|
||||||
|
|
||||||
const (
|
|
||||||
TIOCGWINSZ = 0x5413
|
|
||||||
)
|
|
|
@ -1,14 +0,0 @@
|
||||||
// +build !windows,!darwin,!freebsd,!netbsd,!openbsd,!linux
|
|
||||||
|
|
||||||
// Copyright 2014 Oleku Konko All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This module is a Terminal API for the Go Programming Language.
|
|
||||||
// The protocols were written in pure Go and works on windows and unix systems
|
|
||||||
|
|
||||||
package ts
|
|
||||||
|
|
||||||
const (
|
|
||||||
TIOCGWINSZ = 0
|
|
||||||
)
|
|
|
@ -1,14 +0,0 @@
|
||||||
// +build freebsd netbsd openbsd
|
|
||||||
|
|
||||||
// Copyright 2014 Oleku Konko All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This module is a Terminal API for the Go Programming Language.
|
|
||||||
// The protocols were written in pure Go and works on windows and unix systems
|
|
||||||
|
|
||||||
package ts
|
|
||||||
|
|
||||||
const (
|
|
||||||
TIOCGWINSZ = 0x40087468
|
|
||||||
)
|
|
|
@ -1,64 +0,0 @@
|
||||||
// +build windows
|
|
||||||
|
|
||||||
// Copyright 2014 Oleku Konko All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This module is a Terminal API for the Go Programming Language.
|
|
||||||
// The protocols were written in pure Go and works on windows and unix systems
|
|
||||||
|
|
||||||
package ts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
|
|
||||||
// Retrieves information about the specified console screen buffer.
|
|
||||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx
|
|
||||||
screenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Contains information about a console screen buffer.
|
|
||||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
|
|
||||||
type CONSOLE_SCREEN_BUFFER_INFO struct {
|
|
||||||
DwSize COORD
|
|
||||||
DwCursorPosition COORD
|
|
||||||
WAttributes uint16
|
|
||||||
SrWindow SMALL_RECT
|
|
||||||
DwMaximumWindowSize COORD
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the coordinates of a character cell in a console screen buffer.
|
|
||||||
// The origin of the coordinate system (0,0) is at the top, left cell of the buffer.
|
|
||||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx
|
|
||||||
type COORD struct {
|
|
||||||
X, Y uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the coordinates of the upper left and lower right corners of a rectangle.
|
|
||||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311(v=vs.85).aspx
|
|
||||||
type SMALL_RECT struct {
|
|
||||||
Left, Top, Right, Bottom uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSize() (ws Size, err error) {
|
|
||||||
var info CONSOLE_SCREEN_BUFFER_INFO
|
|
||||||
rc, _, err := screenBufferInfo.Call(
|
|
||||||
uintptr(syscall.Stdout),
|
|
||||||
uintptr(unsafe.Pointer(&info)))
|
|
||||||
|
|
||||||
if rc == 0 {
|
|
||||||
return ws, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ws = Size{info.SrWindow.Bottom,
|
|
||||||
info.SrWindow.Right,
|
|
||||||
info.DwCursorPosition.X,
|
|
||||||
info.DwCursorPosition.Y}
|
|
||||||
|
|
||||||
return ws, nil
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
// Copyright 2014 Oleku Konko All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This module is a Terminal API for the Go Programming Language.
|
|
||||||
// The protocols were written in pure Go and works on windows and unix systems
|
|
||||||
|
|
||||||
package ts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get Windows Size
|
|
||||||
func GetSize() (ws Size, err error) {
|
|
||||||
_, _, ec := syscall.Syscall(syscall.SYS_IOCTL,
|
|
||||||
uintptr(syscall.Stdout),
|
|
||||||
uintptr(TIOCGWINSZ),
|
|
||||||
uintptr(unsafe.Pointer(&ws)))
|
|
||||||
|
|
||||||
err = getError(ec)
|
|
||||||
|
|
||||||
if TIOCGWINSZ == 0 && err != nil {
|
|
||||||
ws = Size{80, 25, 0, 0}
|
|
||||||
}
|
|
||||||
return ws, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getError(ec interface{}) (err error) {
|
|
||||||
switch v := ec.(type) {
|
|
||||||
|
|
||||||
case syscall.Errno: // Some implementation return syscall.Errno number
|
|
||||||
if v != 0 {
|
|
||||||
err = syscall.Errno(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
case error: // Some implementation return error
|
|
||||||
err = ec.(error)
|
|
||||||
default:
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -7,6 +7,11 @@
|
||||||
"revision": "418b41d23a1bf978c06faea5313ba194650ac088",
|
"revision": "418b41d23a1bf978c06faea5313ba194650ac088",
|
||||||
"revisionTime": "2015-09-08T20:46:18Z"
|
"revisionTime": "2015-09-08T20:46:18Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "github.com/cheggaaa/pb",
|
||||||
|
"revision": "c1f48d5ce4f292dfb775ef52aaedd15be323510d",
|
||||||
|
"revisionTime": "2016-05-20T13:10:51+03:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/dgrijalva/jwt-go",
|
"path": "github.com/dgrijalva/jwt-go",
|
||||||
"revision": "afef698c326bfd906b11659432544e5aae441d44",
|
"revision": "afef698c326bfd906b11659432544e5aae441d44",
|
||||||
|
@ -102,11 +107,6 @@
|
||||||
"revision": "d682a8f0cf139663a984ff12528da460ca963de9",
|
"revision": "d682a8f0cf139663a984ff12528da460ca963de9",
|
||||||
"revisionTime": "2015-10-24T22:24:27-07:00"
|
"revisionTime": "2015-10-24T22:24:27-07:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "github.com/olekukonko/ts",
|
|
||||||
"revision": "ecf753e7c962639ab5a1fb46f7da627d4c0a04b8",
|
|
||||||
"revisionTime": "2014-04-12T15:01:45-07:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "github.com/pkg/profile",
|
"path": "github.com/pkg/profile",
|
||||||
"revision": "c78aac22bd43883fd2817833b982153dcac17b3b",
|
"revision": "c78aac22bd43883fd2817833b982153dcac17b3b",
|
||||||
|
|
Loading…
Reference in New Issue