Removed implementation of the ftp protocol and the usage of cheggaaa's progress-bar as suggested by @SwampDragons. Replaced some of the old smoke-tests that were based on the ftp-protocol non-existing with a "non-existent://" protocol that's guaranteed to not exist.

pull/2906/head
Ali Rizvi-Santiago 8 years ago
parent 5a3e98b529
commit 5d97b105a8

@ -76,6 +76,17 @@ func TestNewConfig_sourcePath(t *testing.T) {
t.Fatalf("Nonexistant file should throw a validation error!")
}
// Bad
c = testConfig(t)
c["source_path"] = "nonexistent-protocol://i/dont/exist"
_, warns, err = NewConfig(c)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatalf("should error")
}
// Good
tf := getTempFile(t)
defer os.Remove(tf.Name())

@ -44,7 +44,7 @@ func (s *stepCreateVMX) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
// Convert the iso_path into a path relative to the .vmx file if possible
if relativeIsoPath, err := filepath.Rel(config.VMXTemplatePath, filepath.FromSlash(isoPath)); err == nil {
if relativeIsoPath, err := filepath.Abs(config.VMXTemplatePath, filepath.FromSlash(isoPath)); err == nil {
isoPath = relativeIsoPath
}

@ -48,7 +48,7 @@ func ChooseString(vals ...string) string {
func DownloadableURL(original string) (string, error) {
// Verify that the scheme is something we support in our common downloader.
supported := []string{"file", "http", "https", "ftp", "smb"}
supported := []string{"file", "http", "https", "smb"}
found := false
for _, s := range supported {
if strings.HasPrefix(strings.ToLower(original), s+"://") {

@ -43,6 +43,12 @@ func TestDownloadableURL(t *testing.T) {
t.Fatalf("expected err : %s", err)
}
// Invalid: unsupported scheme
_, err = DownloadableURL("nonexistent-protocol://host.com/path")
if err == nil {
t.Fatalf("expected err : %s", err)
}
// Valid: http
u, err := DownloadableURL("HTTP://packer.io/path")
if err != nil {
@ -190,7 +196,9 @@ func TestDownloadableURL_FilePaths(t *testing.T) {
t.Fatalf("err: %s", err)
}
expected := "file://" + strings.Replace(tfPath, `\`, `/`, -1)
expected := fmt.Sprintf("%s%s",
filePrefix,
strings.Replace(tfPath, `\`, `/`, -1))
if u != expected {
t.Fatalf("unexpected: %#v != %#v", u, expected)
}

@ -20,8 +20,6 @@ import (
// imports related to each Downloader implementation
import (
"github.com/cheggaaa/pb"
"github.com/jlaffaye/ftp"
"io"
"net/http"
"path/filepath"
@ -83,24 +81,24 @@ func HashForType(t string) hash.Hash {
// NewDownloadClient returns a new DownloadClient for the given
// configuration.
func NewDownloadClient(c *DownloadConfig, bar pb.ProgressBar) *DownloadClient {
func NewDownloadClient(c *DownloadConfig) *DownloadClient {
const mtu = 1500 /* ethernet */ - 20 /* ipv4 */ - 20 /* tcp */
// Create downloader map if it hasn't been specified already.
if c.DownloaderMap == nil {
// XXX: Make sure you add any new protocols you implement
// to the DownloadableURL implementation in config.go!
c.DownloaderMap = map[string]Downloader{
"file": &FileDownloader{progress: &bar, bufferSize: nil},
"ftp": &FTPDownloader{progress: &bar, userInfo: url.UserPassword("anonymous", "anonymous@"), mtu: mtu},
"http": &HTTPDownloader{progress: &bar, userAgent: c.UserAgent},
"https": &HTTPDownloader{progress: &bar, userAgent: c.UserAgent},
"smb": &SMBDownloader{progress: &bar, bufferSize: nil},
"file": &FileDownloader{bufferSize: nil},
"http": &HTTPDownloader{userAgent: c.UserAgent},
"https": &HTTPDownloader{userAgent: c.UserAgent},
"smb": &SMBDownloader{bufferSize: nil},
}
}
return &DownloadClient{config: c}
}
// A downloader implements the ability to transfer a file, and cancel or resume
// it.
// A downloader implements the ability to transfer, cancel, or resume a file.
type Downloader interface {
Resume()
Cancel()
@ -209,6 +207,14 @@ func (d *DownloadClient) Get() (string, error) {
return finalPath, err
}
func (d *DownloadClient) PercentProgress() int {
if (d.downloader == nil) {
return -1
}
return int((float64(d.downloader.Progress()) / float64(d.downloader.Total())) * 100)
}
// VerifyChecksum tests that the path matches the checksum for the
// download.
func (d *DownloadClient) VerifyChecksum(path string) (bool, error) {
@ -234,8 +240,6 @@ type HTTPDownloader struct {
current uint64
total uint64
userAgent string
progress *pb.ProgressBar
}
func (d *HTTPDownloader) Cancel() {
@ -300,10 +304,6 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
d.total = d.current + uint64(resp.ContentLength)
d.progress.Total = int64(d.total)
progressBar := d.progress.Start()
progressBar.Set64(int64(d.current))
var buffer [4096]byte
for {
n, err := resp.Body.Read(buffer[:])
@ -312,7 +312,6 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
}
d.current += uint64(n)
progressBar.Set64(int64(d.current))
if _, werr := dst.Write(buffer[:n]); werr != nil {
return werr
@ -322,7 +321,6 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
break
}
}
progressBar.Finish()
return nil
}
@ -334,152 +332,6 @@ func (d *HTTPDownloader) Total() uint64 {
return d.total
}
// FTPDownloader is an implementation of Downloader that downloads
// files over FTP.
type FTPDownloader struct {
userInfo *url.Userinfo
mtu uint
active bool
current uint64
total uint64
progress *pb.ProgressBar
}
func (d *FTPDownloader) Progress() uint64 {
return d.current
}
func (d *FTPDownloader) Total() uint64 {
return d.total
}
func (d *FTPDownloader) Cancel() {
d.active = false
}
func (d *FTPDownloader) Resume() {
// TODO: Implement
}
func (d *FTPDownloader) Download(dst *os.File, src *url.URL) error {
var userinfo *url.Userinfo
userinfo = d.userInfo
d.active = false
// check the uri is correct
if src == nil || src.Scheme != "ftp" {
return fmt.Errorf("Unexpected uri scheme: %s", src.Scheme)
}
uri := src
// connect to ftp server
var cli *ftp.ServerConn
log.Printf("Starting download over FTP: %s : %s\n", uri.Host, uri.Path)
cli, err := ftp.Dial(uri.Host)
if err != nil {
return nil
}
defer cli.Quit()
// handle authentication
if uri.User != nil {
userinfo = uri.User
}
pass, ok := userinfo.Password()
if !ok {
pass = "ftp@"
}
log.Printf("Authenticating to FTP server: %s : %s\n", userinfo.Username(), pass)
err = cli.Login(userinfo.Username(), pass)
if err != nil {
return err
}
// locate specified path
p := path.Dir(uri.Path)
log.Printf("Changing to FTP directory : %s\n", p)
err = cli.ChangeDir(p)
if err != nil {
return nil
}
curpath, err := cli.CurrentDir()
if err != nil {
return err
}
log.Printf("Current FTP directory : %s\n", curpath)
// collect stats about the specified file
var name string
var entry *ftp.Entry
_, name = path.Split(uri.Path)
entry = nil
entries, err := cli.List(curpath)
for _, e := range entries {
if e.Type == ftp.EntryTypeFile && e.Name == name {
entry = e
break
}
}
if entry == nil {
return fmt.Errorf("Unable to find file: %s", uri.Path)
}
log.Printf("Found file : %s : %v bytes\n", entry.Name, entry.Size)
d.current = 0
d.total = entry.Size
d.progress.Total = int64(d.total)
progressBar := d.progress.Start()
// download specified file
d.active = true
reader, err := cli.RetrFrom(uri.Path, d.current)
if err != nil {
return nil
}
// do it in a goro so that if someone wants to cancel it, they can
errch := make(chan error)
go func(d *FTPDownloader, r io.Reader, w io.Writer, e chan error) {
for d.active {
n, err := io.CopyN(w, r, int64(d.mtu))
if err != nil {
break
}
d.current += uint64(n)
progressBar.Set64(int64(d.current))
}
d.active = false
e <- err
}(d, reader, dst, errch)
// spin until it's done
err = <-errch
progressBar.Finish()
reader.Close()
if err == nil && d.current != d.total {
err = fmt.Errorf("FTP total transfer size was %d when %d was expected", d.current, d.total)
}
// log out
cli.Logout()
return err
}
// FileDownloader is an implementation of Downloader that downloads
// files using the regular filesystem.
type FileDownloader struct {
@ -488,8 +340,6 @@ type FileDownloader struct {
active bool
current uint64
total uint64
progress *pb.ProgressBar
}
func (d *FileDownloader) Progress() uint64 {
@ -580,9 +430,6 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error {
}
d.total = uint64(fi.Size())
d.progress.Total = int64(d.total)
progressBar := d.progress.Start()
// no bufferSize specified, so copy synchronously.
if d.bufferSize == nil {
var n int64
@ -590,7 +437,6 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error {
d.active = false
d.current += uint64(n)
progressBar.Set64(int64(d.current))
// use a goro in case someone else wants to enable cancel/resume
} else {
@ -603,7 +449,6 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error {
}
d.current += uint64(n)
progressBar.Set64(int64(d.current))
}
d.active = false
e <- err
@ -612,7 +457,6 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error {
// ...and we spin until it's done
err = <-errch
}
progressBar.Finish()
f.Close()
return err
}
@ -625,8 +469,6 @@ type SMBDownloader struct {
active bool
current uint64
total uint64
progress *pb.ProgressBar
}
func (d *SMBDownloader) Progress() uint64 {
@ -699,9 +541,6 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error {
}
d.total = uint64(fi.Size())
d.progress.Total = int64(d.total)
progressBar := d.progress.Start()
// no bufferSize specified, so copy synchronously.
if d.bufferSize == nil {
var n int64
@ -709,7 +548,6 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error {
d.active = false
d.current += uint64(n)
progressBar.Set64(int64(d.current))
// use a goro in case someone else wants to enable cancel/resume
} else {
@ -722,7 +560,6 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error {
}
d.current += uint64(n)
progressBar.Set64(int64(d.current))
}
d.active = false
e <- err
@ -731,7 +568,6 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error {
// ...and as usual we spin until it's done
err = <-errch
}
progressBar.Finish()
f.Close()
return err
}

@ -12,8 +12,6 @@ import (
"runtime"
"strings"
"testing"
"github.com/cheggaaa/pb"
)
func TestDownloadClientVerifyChecksum(t *testing.T) {
@ -38,7 +36,7 @@ func TestDownloadClientVerifyChecksum(t *testing.T) {
Checksum: checksum,
}
d := NewDownloadClient(config, *pb.New64(0))
d := NewDownloadClient(config)
result, err := d.VerifyChecksum(tf.Name())
if err != nil {
t.Fatalf("Verify err: %s", err)
@ -61,7 +59,7 @@ func TestDownloadClient_basic(t *testing.T) {
Url: ts.URL + "/basic.txt",
TargetPath: tf.Name(),
CopyFile: true,
}, *pb.New64(0))
})
path, err := client.Get()
if err != nil {
@ -97,7 +95,7 @@ func TestDownloadClient_checksumBad(t *testing.T) {
Hash: HashForType("md5"),
Checksum: checksum,
CopyFile: true,
}, *pb.New64(0))
})
if _, err := client.Get(); err == nil {
t.Fatal("should error")
}
@ -122,7 +120,7 @@ func TestDownloadClient_checksumGood(t *testing.T) {
Hash: HashForType("md5"),
Checksum: checksum,
CopyFile: true,
}, *pb.New64(0))
})
path, err := client.Get()
if err != nil {
t.Fatalf("err: %s", err)
@ -153,7 +151,7 @@ func TestDownloadClient_checksumNoDownload(t *testing.T) {
Hash: HashForType("md5"),
Checksum: checksum,
CopyFile: true,
}, *pb.New64(0))
})
path, err := client.Get()
if err != nil {
t.Fatalf("err: %s", err)
@ -192,7 +190,7 @@ func TestDownloadClient_resume(t *testing.T) {
Url: ts.URL,
TargetPath: tf.Name(),
CopyFile: true,
}, *pb.New64(0))
})
path, err := client.Get()
if err != nil {
t.Fatalf("err: %s", err)
@ -252,7 +250,7 @@ func TestDownloadClient_usesDefaultUserAgent(t *testing.T) {
CopyFile: true,
}
client := NewDownloadClient(config, *pb.New64(0))
client := NewDownloadClient(config)
_, err = client.Get()
if err != nil {
t.Fatal(err)
@ -284,7 +282,7 @@ func TestDownloadClient_setsUserAgent(t *testing.T) {
CopyFile: true,
}
client := NewDownloadClient(config, *pb.New64(0))
client := NewDownloadClient(config)
_, err = client.Get()
if err != nil {
t.Fatal(err)
@ -383,7 +381,7 @@ func TestDownloadFileUrl(t *testing.T) {
CopyFile: false,
}
client := NewDownloadClient(config, *pb.New64(0))
client := NewDownloadClient(config)
// Verify that we fail to match the checksum
_, err = client.Get()
@ -414,7 +412,7 @@ func SimulateFileUriDownload(t *testing.T, uri string) (string, error) {
}
// go go go
client := NewDownloadClient(config, *pb.New64(0))
client := NewDownloadClient(config)
path, err := client.Get()
// ignore any non-important checksum errors if it's not a unc path

@ -7,7 +7,6 @@ import (
"log"
"time"
"github.com/cheggaaa/pb"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
)
@ -62,10 +61,6 @@ func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction {
ui.Say(fmt.Sprintf("Downloading or copying %s", s.Description))
// Get a default-looking progress bar and connect it to the ui.
bar := GetDefaultProgressBar()
bar.Callback = ui.Message
// First try to use any already downloaded file
// If it fails, proceed to regualar download logic
@ -99,7 +94,7 @@ func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction {
}
downloadConfigs[i] = config
if match, _ := NewDownloadClient(config, bar).VerifyChecksum(config.TargetPath); match {
if match, _ := NewDownloadClient(config).VerifyChecksum(config.TargetPath); match {
ui.Message(fmt.Sprintf("Found already downloaded, initial checksum matched, no download needed: %s", url))
finalPath = config.TargetPath
break
@ -141,32 +136,10 @@ func (s *StepDownload) Run(state multistep.StateBag) multistep.StepAction {
func (s *StepDownload) Cleanup(multistep.StateBag) {}
func GetDefaultProgressBar() pb.ProgressBar {
bar := pb.New64(0)
bar.ShowPercent = true
bar.ShowCounters = true
bar.ShowSpeed = false
bar.ShowBar = true
bar.ShowTimeLeft = false
bar.ShowFinalTime = false
bar.SetUnits(pb.U_BYTES)
bar.Format("[=>-]")
bar.SetRefreshRate(1 * time.Second)
bar.SetWidth(25)
return *bar
}
func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) {
var path string
ui := state.Get("ui").(packer.Ui)
// Get a default looking progress bar and connect it to the ui.
bar := GetDefaultProgressBar()
bar.Callback = ui.Message
// Create download client with config and progress bar
download := NewDownloadClient(config, bar)
download := NewDownloadClient(config)
downloadCompleteCh := make(chan error, 1)
go func() {
@ -175,19 +148,24 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag
downloadCompleteCh <- err
}()
progressTicker := time.NewTicker(5 * time.Second)
defer progressTicker.Stop()
for {
select {
case err := <-downloadCompleteCh:
bar.Finish()
if err != nil {
return "", err, true
}
return path, nil, true
return path, nil, true
case <-progressTicker.C:
progress := download.PercentProgress()
if progress >= 0 {
ui.Message(fmt.Sprintf("Download progress: %d%%", progress))
}
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
bar.Finish()
ui.Say("Interrupt received. Cancelling download...")
return "", nil, false
}

@ -67,7 +67,7 @@ func NewCore(c *CoreConfig) (*Core, error) {
return nil, err
}
// Go through and interpolate all the build names. We should be able
// Go through and interpolate all the build names. We shuld be able
// to do this at this point with the variables.
result.builds = make(map[string]*template.Builder)
for _, b := range c.Template.Builders {

@ -1,12 +0,0 @@
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.

@ -1,179 +0,0 @@
# Terminal progress bar for Go
[![Join the chat at https://gitter.im/cheggaaa/pb](https://badges.gitter.im/cheggaaa/pb.svg)](https://gitter.im/cheggaaa/pb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Simple progress bar for console programs.
Please check the new version https://github.com/cheggaaa/pb/tree/v2 (currently, it's beta)
## 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
```

@ -1,118 +0,0 @@
package pb
import (
"fmt"
"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, KiB, MiB, ...)
U_BYTES
// U_BYTES_DEC units are like U_BYTES, but base 10 (B, KB, MB, ...)
U_BYTES_DEC
// U_DURATION units are formatted in a human readable way (3h14m15s)
U_DURATION
)
const (
KiB = 1024
MiB = 1048576
GiB = 1073741824
TiB = 1099511627776
KB = 1e3
MB = 1e6
GB = 1e9
TB = 1e12
)
func Format(i int64) *formatter {
return &formatter{n: i}
}
type formatter struct {
n int64
unit Units
width int
perSec bool
}
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_BYTES_DEC:
out = formatBytesDec(f.n)
case U_DURATION:
out = formatDuration(f.n)
default:
out = fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n)
}
if f.perSec {
out += "/s"
}
return
}
// Convert bytes to human readable string. Like 2 MiB, 64.2 KiB, 52 B
func formatBytes(i int64) (result string) {
switch {
case i >= TiB:
result = fmt.Sprintf("%.02f TiB", float64(i)/TiB)
case i >= GiB:
result = fmt.Sprintf("%.02f GiB", float64(i)/GiB)
case i >= MiB:
result = fmt.Sprintf("%.02f MiB", float64(i)/MiB)
case i >= KiB:
result = fmt.Sprintf("%.02f KiB", float64(i)/KiB)
default:
result = fmt.Sprintf("%d B", i)
}
return
}
// Convert bytes to base-10 human readable string. Like 2 MB, 64.2 KB, 52 B
func formatBytesDec(i int64) (result string) {
switch {
case i >= TB:
result = fmt.Sprintf("%.02f TB", float64(i)/TB)
case i >= GB:
result = fmt.Sprintf("%.02f GB", float64(i)/GB)
case i >= MB:
result = fmt.Sprintf("%.02f MB", float64(i)/MB)
case i >= KB:
result = fmt.Sprintf("%.02f KB", float64(i)/KB)
default:
result = fmt.Sprintf("%d B", i)
}
return
}
func formatDuration(n int64) (result string) {
d := time.Duration(n)
if d > time.Hour*24 {
result = fmt.Sprintf("%dd", d/24/time.Hour)
d -= (d / time.Hour / 24) * (time.Hour * 24)
}
result = fmt.Sprintf("%s%v", result, d)
return
}

469
vendor/github.com/cheggaaa/pb/pb.go generated vendored

@ -1,469 +0,0 @@
// Simple console progress bars
package pb
import (
"fmt"
"io"
"math"
"strings"
"sync"
"sync/atomic"
"time"
"unicode/utf8"
)
// Current version
const Version = "1.0.19"
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{}),
}
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)
previous int64
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
AutoStat 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
changeTime time.Time
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 = atomic.LoadInt64(&pb.current)
if pb.Total == 0 {
pb.ShowTimeLeft = false
pb.ShowPercent = false
pb.AutoStat = 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)
}
// Get current value
func (pb *ProgressBar) Get() int64 {
c := atomic.LoadInt64(&pb.current)
return c
}
// 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 utf8.RuneCountInString(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))
pb.mu.Lock()
defer pb.mu.Unlock()
switch {
case pb.Output != nil:
fmt.Fprintln(pb.Output)
case !pb.NotPrint:
fmt.Println()
}
pb.isFinish = true
})
}
// IsFinished return boolean
func (pb *ProgressBar) IsFinished() bool {
pb.mu.Lock()
defer pb.mu.Unlock()
return pb.isFinish
}
// End print and write string 'str'
func (pb *ProgressBar) FinishPrint(str string) {
pb.Finish()
if pb.Output != nil {
fmt.Fprintln(pb.Output, str)
} else {
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
// Takes io.Reader or io.ReadCloser
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
pb.mu.Lock()
currentFromStart := current - pb.startValue
fromStart := time.Now().Sub(pb.startTime)
lastChangeTime := pb.changeTime
fromChange := lastChangeTime.Sub(pb.startTime)
pb.mu.Unlock()
select {
case <-pb.finish:
if pb.ShowFinalTime {
var left time.Duration
left = (fromStart / time.Second) * time.Second
timeLeftBox = fmt.Sprintf(" %s", left.String())
}
default:
if pb.ShowTimeLeft && currentFromStart > 0 {
perEntry := fromChange / time.Duration(currentFromStart)
var left time.Duration
if pb.Total > 0 {
left = time.Duration(pb.Total-currentFromStart) * perEntry
left -= time.Since(lastChangeTime)
left = (left / time.Second) * time.Second
} else {
left = time.Duration(currentFromStart) * perEntry
left = (left / time.Second) * time.Second
}
if left > 0 {
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 {
curSize := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size)))
emptySize := size - curSize
barBox = pb.BarStart
if emptySize < 0 {
emptySize = 0
}
if curSize > size {
curSize = size
}
cursorLen := escapeAwareRuneCountInString(pb.Current)
if emptySize <= 0 {
barBox += strings.Repeat(pb.Current, curSize/cursorLen)
} else if curSize > 0 {
cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN)
cursorRepetitions := (curSize - cursorEndLen) / cursorLen
barBox += strings.Repeat(pb.Current, cursorRepetitions)
barBox += pb.CurrentN
}
emptyLen := escapeAwareRuneCountInString(pb.Empty)
barBox += strings.Repeat(pb.Empty, emptySize/emptyLen)
barBox += pb.BarEnd
} else {
pos := size - int(current)%int(size)
barBox = pb.BarStart
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 cl := escapeAwareRuneCountInString(out); cl < width {
end = strings.Repeat(" ", width-cl)
}
// and print!
pb.mu.Lock()
pb.lastPrint = out + end
isFinish := pb.isFinish
pb.mu.Unlock()
switch {
case 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)
p := atomic.LoadInt64(&pb.previous)
if p != c {
pb.mu.Lock()
pb.changeTime = time.Now()
pb.mu.Unlock()
atomic.StoreInt64(&pb.previous, c)
}
pb.write(c)
if pb.AutoStat {
if c == 0 {
pb.startTime = time.Now()
pb.startValue = 0
} else if c >= pb.Total && pb.isFinish != true {
pb.Finish()
}
}
}
// String return the last bar print
func (pb *ProgressBar) String() string {
pb.mu.Lock()
defer pb.mu.Unlock()
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()
}
}
}

@ -1,141 +0,0 @@
// +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
}

@ -1,108 +0,0 @@
// +build linux darwin freebsd netbsd openbsd solaris dragonfly
// +build !appengine
package pb
import (
"errors"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"golang.org/x/sys/unix"
)
var ErrPoolWasStarted = errors.New("Bar pool was started")
var (
echoLockMutex sync.Mutex
origTermStatePtr *unix.Termios
tty *os.File
)
func init() {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
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) {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
fd := int(tty.Fd())
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
if err != nil {
return 0, err
}
return int(ws.Col), nil
}
func lockEcho() (quit chan int, err error) {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
if origTermStatePtr != nil {
return quit, ErrPoolWasStarted
}
fd := int(tty.Fd())
oldTermStatePtr, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
if err != nil {
return nil, fmt.Errorf("Can't get terminal settings: %v", err)
}
oldTermios := *oldTermStatePtr
newTermios := oldTermios
newTermios.Lflag &^= syscall.ECHO
newTermios.Lflag |= syscall.ICANON | syscall.ISIG
newTermios.Iflag |= syscall.ICRNL
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newTermios); err != nil {
return nil, fmt.Errorf("Can't set terminal settings: %v", err)
}
quit = make(chan int, 1)
go catchTerminate(quit)
return
}
func unlockEcho() error {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
if origTermStatePtr == nil {
return nil
}
fd := int(tty.Fd())
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, origTermStatePtr); err != nil {
return fmt.Errorf("Can't set terminal settings: %v", err)
}
origTermStatePtr = nil
return nil
}
// 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()
}
}

@ -1,82 +0,0 @@
// +build linux darwin freebsd netbsd openbsd solaris dragonfly windows
package pb
import (
"io"
"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 {
Output io.Writer
RefreshRate time.Duration
bars []*ProgressBar
lastBarsCount int
quit chan int
m sync.Mutex
finishOnce sync.Once
}
// Add progress bars.
func (p *Pool) Add(pbs ...*ProgressBar) {
p.m.Lock()
defer p.m.Unlock()
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()
}

@ -1,45 +0,0 @@
// +build windows
package pb
import (
"fmt"
"log"
)
func (p *Pool) print(first bool) bool {
p.m.Lock()
defer p.m.Unlock()
var out string
if !first {
coords, err := getCursorPos()
if err != nil {
log.Panic(err)
}
coords.Y -= int16(p.lastBarsCount)
if coords.Y < 0 {
coords.Y = 0
}
coords.X = 0
err = setCursorPos(coords)
if err != nil {
log.Panic(err)
}
}
isFinished := true
for _, bar := range p.bars {
if !bar.IsFinished() {
isFinished = false
}
bar.Update()
out += fmt.Sprintf("\r%s\n", bar.String())
}
if p.Output != nil {
fmt.Fprint(p.Output, out)
} else {
fmt.Print(out)
}
p.lastBarsCount = len(p.bars)
return isFinished
}

@ -1,29 +0,0 @@
// +build linux darwin freebsd netbsd openbsd solaris dragonfly
package pb
import "fmt"
func (p *Pool) print(first bool) bool {
p.m.Lock()
defer p.m.Unlock()
var out string
if !first {
out = fmt.Sprintf("\033[%dA", p.lastBarsCount)
}
isFinished := true
for _, bar := range p.bars {
if !bar.IsFinished() {
isFinished = false
}
bar.Update()
out += fmt.Sprintf("\r%s\n", bar.String())
}
if p.Output != nil {
fmt.Fprint(p.Output, out)
} else {
fmt.Print(out)
}
p.lastBarsCount = len(p.bars)
return isFinished
}

@ -1,25 +0,0 @@
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
}
// Close the reader when it implements io.Closer
func (r *Reader) Close() (err error) {
if closer, ok := r.Reader.(io.Closer); ok {
return closer.Close()
}
return
}

@ -1,17 +0,0 @@
package pb
import (
"github.com/mattn/go-runewidth"
"regexp"
)
// Finds the control character sequences (like colors)
var ctrlFinder = regexp.MustCompile("\x1b\x5b[0-9]+\x6d")
func escapeAwareRuneCountInString(s string) int {
n := runewidth.StringWidth(s)
for _, sm := range ctrlFinder.FindAllString(s, -1) {
n -= runewidth.StringWidth(sm)
}
return n
}

@ -1,9 +0,0 @@
// +build darwin freebsd netbsd openbsd dragonfly
// +build !appengine
package pb
import "syscall"
const ioctlReadTermios = syscall.TIOCGETA
const ioctlWriteTermios = syscall.TIOCSETA

@ -1,13 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux solaris
// +build !appengine
package pb
import "golang.org/x/sys/unix"
const ioctlReadTermios = unix.TCGETS
const ioctlWriteTermios = unix.TCSETS

@ -1,13 +0,0 @@
Copyright (c) 2011-2013, Julien Laffaye <jlaffaye@FreeBSD.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

@ -1,17 +0,0 @@
# goftp #
[![Build Status](https://travis-ci.org/jlaffaye/ftp.svg?branch=master)](https://travis-ci.org/jlaffaye/ftp)
[![Coverage Status](https://coveralls.io/repos/jlaffaye/ftp/badge.svg?branch=master&service=github)](https://coveralls.io/github/jlaffaye/ftp?branch=master)
[![Go ReportCard](http://goreportcard.com/badge/jlaffaye/ftp)](http://goreportcard.com/report/jlaffaye/ftp)
A FTP client package for Go
## Install ##
```
go get -u github.com/jlaffaye/ftp
```
## Documentation ##
http://godoc.org/github.com/jlaffaye/ftp

@ -1,671 +0,0 @@
// Package ftp implements a FTP client as described in RFC 959.
package ftp
import (
"bufio"
"errors"
"io"
"net"
"net/textproto"
"strconv"
"strings"
"time"
)
// EntryType describes the different types of an Entry.
type EntryType int
// The differents types of an Entry
const (
EntryTypeFile EntryType = iota
EntryTypeFolder
EntryTypeLink
)
// ServerConn represents the connection to a remote FTP server.
type ServerConn struct {
conn *textproto.Conn
host string
timeout time.Duration
features map[string]string
}
// Entry describes a file and is returned by List().
type Entry struct {
Name string
Type EntryType
Size uint64
Time time.Time
}
// response represent a data-connection
type response struct {
conn net.Conn
c *ServerConn
}
// Connect is an alias to Dial, for backward compatibility
func Connect(addr string) (*ServerConn, error) {
return Dial(addr)
}
// Dial is like DialTimeout with no timeout
func Dial(addr string) (*ServerConn, error) {
return DialTimeout(addr, 0)
}
// DialTimeout initializes the connection to the specified ftp server address.
//
// It is generally followed by a call to Login() as most FTP commands require
// an authenticated user.
func DialTimeout(addr string, timeout time.Duration) (*ServerConn, error) {
tconn, err := net.DialTimeout("tcp", addr, timeout)
if err != nil {
return nil, err
}
// Use the resolved IP address in case addr contains a domain name
// If we use the domain name, we might not resolve to the same IP.
remoteAddr := tconn.RemoteAddr().String()
host, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
return nil, err
}
conn := textproto.NewConn(tconn)
c := &ServerConn{
conn: conn,
host: host,
timeout: timeout,
features: make(map[string]string),
}
_, _, err = c.conn.ReadResponse(StatusReady)
if err != nil {
c.Quit()
return nil, err
}
err = c.feat()
if err != nil {
c.Quit()
return nil, err
}
return c, nil
}
// Login authenticates the client with specified user and password.
//
// "anonymous"/"anonymous" is a common user/password scheme for FTP servers
// that allows anonymous read-only accounts.
func (c *ServerConn) Login(user, password string) error {
code, message, err := c.cmd(-1, "USER %s", user)
if err != nil {
return err
}
switch code {
case StatusLoggedIn:
case StatusUserOK:
_, _, err = c.cmd(StatusLoggedIn, "PASS %s", password)
if err != nil {
return err
}
default:
return errors.New(message)
}
// Switch to binary mode
_, _, err = c.cmd(StatusCommandOK, "TYPE I")
if err != nil {
return err
}
return nil
}
// feat issues a FEAT FTP command to list the additional commands supported by
// the remote FTP server.
// FEAT is described in RFC 2389
func (c *ServerConn) feat() error {
code, message, err := c.cmd(-1, "FEAT")
if err != nil {
return err
}
if code != StatusSystem {
// The server does not support the FEAT command. This is not an
// error: we consider that there is no additional feature.
return nil
}
lines := strings.Split(message, "\n")
for _, line := range lines {
if !strings.HasPrefix(line, " ") {
continue
}
line = strings.TrimSpace(line)
featureElements := strings.SplitN(line, " ", 2)
command := featureElements[0]
var commandDesc string
if len(featureElements) == 2 {
commandDesc = featureElements[1]
}
c.features[command] = commandDesc
}
return nil
}
// epsv issues an "EPSV" command to get a port number for a data connection.
func (c *ServerConn) epsv() (port int, err error) {
_, line, err := c.cmd(StatusExtendedPassiveMode, "EPSV")
if err != nil {
return
}
start := strings.Index(line, "|||")
end := strings.LastIndex(line, "|")
if start == -1 || end == -1 {
err = errors.New("Invalid EPSV response format")
return
}
port, err = strconv.Atoi(line[start+3 : end])
return
}
// pasv issues a "PASV" command to get a port number for a data connection.
func (c *ServerConn) pasv() (port int, err error) {
_, line, err := c.cmd(StatusPassiveMode, "PASV")
if err != nil {
return
}
// PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
start := strings.Index(line, "(")
end := strings.LastIndex(line, ")")
if start == -1 || end == -1 {
return 0, errors.New("Invalid PASV response format")
}
// We have to split the response string
pasvData := strings.Split(line[start+1:end], ",")
if len(pasvData) < 6 {
return 0, errors.New("Invalid PASV response format")
}
// Let's compute the port number
portPart1, err1 := strconv.Atoi(pasvData[4])
if err1 != nil {
err = err1
return
}
portPart2, err2 := strconv.Atoi(pasvData[5])
if err2 != nil {
err = err2
return
}
// Recompose port
port = portPart1*256 + portPart2
return
}
// openDataConn creates a new FTP data connection.
func (c *ServerConn) openDataConn() (net.Conn, error) {
var (
port int
err error
)
if port, err = c.epsv(); err != nil {
if port, err = c.pasv(); err != nil {
return nil, err
}
}
return net.DialTimeout("tcp", net.JoinHostPort(c.host, strconv.Itoa(port)), c.timeout)
}
// cmd is a helper function to execute a command and check for the expected FTP
// return code
func (c *ServerConn) cmd(expected int, format string, args ...interface{}) (int, string, error) {
_, err := c.conn.Cmd(format, args...)
if err != nil {
return 0, "", err
}
return c.conn.ReadResponse(expected)
}
// cmdDataConnFrom executes a command which require a FTP data connection.
// Issues a REST FTP command to specify the number of bytes to skip for the transfer.
func (c *ServerConn) cmdDataConnFrom(offset uint64, format string, args ...interface{}) (net.Conn, error) {
conn, err := c.openDataConn()
if err != nil {
return nil, err
}
if offset != 0 {
_, _, err := c.cmd(StatusRequestFilePending, "REST %d", offset)
if err != nil {
return nil, err
}
}
_, err = c.conn.Cmd(format, args...)
if err != nil {
conn.Close()
return nil, err
}
code, msg, err := c.conn.ReadResponse(-1)
if err != nil {
conn.Close()
return nil, err
}
if code != StatusAlreadyOpen && code != StatusAboutToSend {
conn.Close()
return nil, &textproto.Error{Code: code, Msg: msg}
}
return conn, nil
}
var errUnsupportedListLine = errors.New("Unsupported LIST line")
// parseRFC3659ListLine parses the style of directory line defined in RFC 3659.
func parseRFC3659ListLine(line string) (*Entry, error) {
iSemicolon := strings.Index(line, ";")
iWhitespace := strings.Index(line, " ")
if iSemicolon < 0 || iSemicolon > iWhitespace {
return nil, errUnsupportedListLine
}
e := &Entry{
Name: line[iWhitespace+1:],
}
for _, field := range strings.Split(line[:iWhitespace-1], ";") {
i := strings.Index(field, "=")
if i < 1 {
return nil, errUnsupportedListLine
}
key := field[:i]
value := field[i+1:]
switch key {
case "modify":
var err error
e.Time, err = time.Parse("20060102150405", value)
if err != nil {
return nil, err
}
case "type":
switch value {
case "dir", "cdir", "pdir":
e.Type = EntryTypeFolder
case "file":
e.Type = EntryTypeFile
}
case "size":
e.setSize(value)
}
}
return e, nil
}
// parseLsListLine parses a directory line in a format based on the output of
// the UNIX ls command.
func parseLsListLine(line string) (*Entry, error) {
fields := strings.Fields(line)
if len(fields) >= 7 && fields[1] == "folder" && fields[2] == "0" {
e := &Entry{
Type: EntryTypeFolder,
Name: strings.Join(fields[6:], " "),
}
if err := e.setTime(fields[3:6]); err != nil {
return nil, err
}
return e, nil
}
if len(fields) < 8 {
return nil, errUnsupportedListLine
}
if fields[1] == "0" {
e := &Entry{
Type: EntryTypeFile,
Name: strings.Join(fields[7:], " "),
}
if err := e.setSize(fields[2]); err != nil {
return nil, err
}
if err := e.setTime(fields[4:7]); err != nil {
return nil, err
}
return e, nil
}
if len(fields) < 9 {
return nil, errUnsupportedListLine
}
e := &Entry{}
switch fields[0][0] {
case '-':
e.Type = EntryTypeFile
if err := e.setSize(fields[4]); err != nil {
return nil, err
}
case 'd':
e.Type = EntryTypeFolder
case 'l':
e.Type = EntryTypeLink
default:
return nil, errors.New("Unknown entry type")
}
if err := e.setTime(fields[5:8]); err != nil {
return nil, err
}
e.Name = strings.Join(fields[8:], " ")
return e, nil
}
var dirTimeFormats = []string{
"01-02-06 03:04PM",
"2006-01-02 15:04",
}
// parseDirListLine parses a directory line in a format based on the output of
// the MS-DOS DIR command.
func parseDirListLine(line string) (*Entry, error) {
e := &Entry{}
var err error
// Try various time formats that DIR might use, and stop when one works.
for _, format := range dirTimeFormats {
if len(line) > len(format) {
e.Time, err = time.Parse(format, line[:len(format)])
if err == nil {
line = line[len(format):]
break
}
}
}
if err != nil {
// None of the time formats worked.
return nil, errUnsupportedListLine
}
line = strings.TrimLeft(line, " ")
if strings.HasPrefix(line, "<DIR>") {
e.Type = EntryTypeFolder
line = strings.TrimPrefix(line, "<DIR>")
} else {
space := strings.Index(line, " ")
if space == -1 {
return nil, errUnsupportedListLine
}
e.Size, err = strconv.ParseUint(line[:space], 10, 64)
if err != nil {
return nil, errUnsupportedListLine
}
e.Type = EntryTypeFile
line = line[space:]
}
e.Name = strings.TrimLeft(line, " ")
return e, nil
}
var listLineParsers = []func(line string) (*Entry, error){
parseRFC3659ListLine,
parseLsListLine,
parseDirListLine,
}
// parseListLine parses the various non-standard format returned by the LIST
// FTP command.
func parseListLine(line string) (*Entry, error) {
for _, f := range listLineParsers {
e, err := f(line)
if err == errUnsupportedListLine {
// Try another format.
continue
}
return e, err
}
return nil, errUnsupportedListLine
}
func (e *Entry) setSize(str string) (err error) {
e.Size, err = strconv.ParseUint(str, 0, 64)
return
}
func (e *Entry) setTime(fields []string) (err error) {
var timeStr string
if strings.Contains(fields[2], ":") { // this year
thisYear, _, _ := time.Now().Date()
timeStr = fields[1] + " " + fields[0] + " " + strconv.Itoa(thisYear)[2:4] + " " + fields[2] + " GMT"
} else { // not this year
if len(fields[2]) != 4 {
return errors.New("Invalid year format in time string")
}
timeStr = fields[1] + " " + fields[0] + " " + fields[2][2:4] + " 00:00 GMT"
}
e.Time, err = time.Parse("_2 Jan 06 15:04 MST", timeStr)
return
}
// NameList issues an NLST FTP command.
func (c *ServerConn) NameList(path string) (entries []string, err error) {
conn, err := c.cmdDataConnFrom(0, "NLST %s", path)
if err != nil {
return
}
r := &response{conn, c}
defer r.Close()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
entries = append(entries, scanner.Text())
}
if err = scanner.Err(); err != nil {
return entries, err
}
return
}
// List issues a LIST FTP command.
func (c *ServerConn) List(path string) (entries []*Entry, err error) {
conn, err := c.cmdDataConnFrom(0, "LIST %s", path)
if err != nil {
return
}
r := &response{conn, c}
defer r.Close()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
entry, err := parseListLine(line)
if err == nil {
entries = append(entries, entry)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return
}
// ChangeDir issues a CWD FTP command, which changes the current directory to
// the specified path.
func (c *ServerConn) ChangeDir(path string) error {
_, _, err := c.cmd(StatusRequestedFileActionOK, "CWD %s", path)
return err
}
// ChangeDirToParent issues a CDUP FTP command, which changes the current
// directory to the parent directory. This is similar to a call to ChangeDir
// with a path set to "..".
func (c *ServerConn) ChangeDirToParent() error {
_, _, err := c.cmd(StatusRequestedFileActionOK, "CDUP")
return err
}
// CurrentDir issues a PWD FTP command, which Returns the path of the current
// directory.
func (c *ServerConn) CurrentDir() (string, error) {
_, msg, err := c.cmd(StatusPathCreated, "PWD")
if err != nil {
return "", err
}
start := strings.Index(msg, "\"")
end := strings.LastIndex(msg, "\"")
if start == -1 || end == -1 {
return "", errors.New("Unsuported PWD response format")
}
return msg[start+1 : end], nil
}
// Retr issues a RETR FTP command to fetch the specified file from the remote
// FTP server.
//
// The returned ReadCloser must be closed to cleanup the FTP data connection.
func (c *ServerConn) Retr(path string) (io.ReadCloser, error) {
return c.RetrFrom(path, 0)
}
// RetrFrom issues a RETR FTP command to fetch the specified file from the remote
// FTP server, the server will not send the offset first bytes of the file.
//
// The returned ReadCloser must be closed to cleanup the FTP data connection.
func (c *ServerConn) RetrFrom(path string, offset uint64) (io.ReadCloser, error) {
conn, err := c.cmdDataConnFrom(offset, "RETR %s", path)
if err != nil {
return nil, err
}
return &response{conn, c}, nil
}
// Stor issues a STOR FTP command to store a file to the remote FTP server.
// Stor creates the specified file with the content of the io.Reader.
//
// Hint: io.Pipe() can be used if an io.Writer is required.
func (c *ServerConn) Stor(path string, r io.Reader) error {
return c.StorFrom(path, r, 0)
}
// StorFrom issues a STOR FTP command to store a file to the remote FTP server.
// Stor creates the specified file with the content of the io.Reader, writing
// on the server will start at the given file offset.
//
// Hint: io.Pipe() can be used if an io.Writer is required.
func (c *ServerConn) StorFrom(path string, r io.Reader, offset uint64) error {
conn, err := c.cmdDataConnFrom(offset, "STOR %s", path)
if err != nil {
return err
}
_, err = io.Copy(conn, r)
conn.Close()
if err != nil {
return err
}
_, _, err = c.conn.ReadResponse(StatusClosingDataConnection)
return err
}
// Rename renames a file on the remote FTP server.
func (c *ServerConn) Rename(from, to string) error {
_, _, err := c.cmd(StatusRequestFilePending, "RNFR %s", from)
if err != nil {
return err
}
_, _, err = c.cmd(StatusRequestedFileActionOK, "RNTO %s", to)
return err
}
// Delete issues a DELE FTP command to delete the specified file from the
// remote FTP server.
func (c *ServerConn) Delete(path string) error {
_, _, err := c.cmd(StatusRequestedFileActionOK, "DELE %s", path)
return err
}
// MakeDir issues a MKD FTP command to create the specified directory on the
// remote FTP server.
func (c *ServerConn) MakeDir(path string) error {
_, _, err := c.cmd(StatusPathCreated, "MKD %s", path)
return err
}
// RemoveDir issues a RMD FTP command to remove the specified directory from
// the remote FTP server.
func (c *ServerConn) RemoveDir(path string) error {
_, _, err := c.cmd(StatusRequestedFileActionOK, "RMD %s", path)
return err
}
// NoOp issues a NOOP FTP command.
// NOOP has no effects and is usually used to prevent the remote FTP server to
// close the otherwise idle connection.
func (c *ServerConn) NoOp() error {
_, _, err := c.cmd(StatusCommandOK, "NOOP")
return err
}
// Logout issues a REIN FTP command to logout the current user.
func (c *ServerConn) Logout() error {
_, _, err := c.cmd(StatusReady, "REIN")
return err
}
// Quit issues a QUIT FTP command to properly close the connection from the
// remote FTP server.
func (c *ServerConn) Quit() error {
c.conn.Cmd("QUIT")
return c.conn.Close()
}
// Read implements the io.Reader interface on a FTP data connection.
func (r *response) Read(buf []byte) (int, error) {
return r.conn.Read(buf)
}
// Close implements the io.Closer interface on a FTP data connection.
func (r *response) Close() error {
err := r.conn.Close()
_, _, err2 := r.c.conn.ReadResponse(StatusClosingDataConnection)
if err2 != nil {
err = err2
}
return err
}

@ -1,106 +0,0 @@
package ftp
// FTP status codes, defined in RFC 959
const (
StatusInitiating = 100
StatusRestartMarker = 110
StatusReadyMinute = 120
StatusAlreadyOpen = 125
StatusAboutToSend = 150
StatusCommandOK = 200
StatusCommandNotImplemented = 202
StatusSystem = 211
StatusDirectory = 212
StatusFile = 213
StatusHelp = 214
StatusName = 215
StatusReady = 220
StatusClosing = 221
StatusDataConnectionOpen = 225
StatusClosingDataConnection = 226
StatusPassiveMode = 227
StatusLongPassiveMode = 228
StatusExtendedPassiveMode = 229
StatusLoggedIn = 230
StatusLoggedOut = 231
StatusLogoutAck = 232
StatusRequestedFileActionOK = 250
StatusPathCreated = 257
StatusUserOK = 331
StatusLoginNeedAccount = 332
StatusRequestFilePending = 350
StatusNotAvailable = 421
StatusCanNotOpenDataConnection = 425
StatusTransfertAborted = 426
StatusInvalidCredentials = 430
StatusHostUnavailable = 434
StatusFileActionIgnored = 450
StatusActionAborted = 451
Status452 = 452
StatusBadCommand = 500
StatusBadArguments = 501
StatusNotImplemented = 502
StatusBadSequence = 503
StatusNotImplementedParameter = 504
StatusNotLoggedIn = 530
StatusStorNeedAccount = 532
StatusFileUnavailable = 550
StatusPageTypeUnknown = 551
StatusExceededStorage = 552
StatusBadFileName = 553
)
var statusText = map[int]string{
// 200
StatusCommandOK: "Command okay.",
StatusCommandNotImplemented: "Command not implemented, superfluous at this site.",
StatusSystem: "System status, or system help reply.",
StatusDirectory: "Directory status.",
StatusFile: "File status.",
StatusHelp: "Help message.",
StatusName: "",
StatusReady: "Service ready for new user.",
StatusClosing: "Service closing control connection.",
StatusDataConnectionOpen: "Data connection open; no transfer in progress.",
StatusClosingDataConnection: "Closing data connection. Requested file action successful.",
StatusPassiveMode: "Entering Passive Mode.",
StatusLongPassiveMode: "Entering Long Passive Mode.",
StatusExtendedPassiveMode: "Entering Extended Passive Mode.",
StatusLoggedIn: "User logged in, proceed.",
StatusLoggedOut: "User logged out; service terminated.",
StatusLogoutAck: "Logout command noted, will complete when transfer done.",
StatusRequestedFileActionOK: "Requested file action okay, completed.",
StatusPathCreated: "Path created.",
// 300
StatusUserOK: "User name okay, need password.",
StatusLoginNeedAccount: "Need account for login.",
StatusRequestFilePending: "Requested file action pending further information.",
// 400
StatusNotAvailable: "Service not available, closing control connection.",
StatusCanNotOpenDataConnection: "Can't open data connection.",
StatusTransfertAborted: "Connection closed; transfer aborted.",
StatusInvalidCredentials: "Invalid username or password.",
StatusHostUnavailable: "Requested host unavailable.",
StatusFileActionIgnored: "Requested file action not taken.",
StatusActionAborted: "Requested action aborted. Local error in processing.",
Status452: "Insufficient storage space in system.",
// 500
StatusBadCommand: "Command unrecognized.",
StatusBadArguments: "Syntax error in parameters or arguments.",
StatusNotImplemented: "Command not implemented.",
StatusBadSequence: "Bad sequence of commands.",
StatusNotImplementedParameter: "Command not implemented for that parameter.",
StatusNotLoggedIn: "Not logged in.",
StatusStorNeedAccount: "Need account for storing files.",
StatusFileUnavailable: "File unavailable.",
StatusPageTypeUnknown: "Page type unknown.",
StatusExceededStorage: "Exceeded storage allocation.",
StatusBadFileName: "File name not allowed.",
}

@ -1,21 +0,0 @@
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.

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

File diff suppressed because it is too large Load Diff

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

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

@ -1,25 +0,0 @@
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
}

28
vendor/vendor.json vendored

@ -565,12 +565,6 @@
"path": "github.com/biogo/hts/bgzf",
"revision": "50da7d4131a3b5c9d063932461cab4d1fafb20b0"
},
{
"checksumSHA1": "ymc5+iJ+1ipls3ihqPdzMjFYCqo=",
"path": "github.com/cheggaaa/pb",
"revision": "18d384da9bdc1e5a08fc2a62a494c321d9ae74ea",
"revisionTime": "2017-12-14T13:20:59Z"
},
{
"checksumSHA1": "Lf3uUXTkKK5DJ37BxQvxO1Fq+K8=",
"comment": "v1.0.0-3-g6d21280",
@ -822,12 +816,6 @@
"path": "github.com/hashicorp/yamux",
"revision": "df949784da9ed028ee76df44652e42d37a09d7e4"
},
{
"checksumSHA1": "28nznojcl6Ejtm6I1tKozgy0isg=",
"path": "github.com/jlaffaye/ftp",
"revision": "025815df64030c144b86e368fedf80ee195fe464",
"revisionTime": "2016-02-29T21:52:38Z"
},
{
"checksumSHA1": "3/Bhy+ua/DCv2ElMD5GzOYSGN6g=",
"comment": "0.2.2-2-gc01cf91",
@ -929,12 +917,6 @@
"path": "github.com/mattn/go-isatty",
"revision": "56b76bdf51f7708750eac80fa38b952bb9f32639"
},
{
"checksumSHA1": "MNkKJyk2TazKMJYbal5wFHybpyA=",
"path": "github.com/mattn/go-runewidth",
"revision": "14207d285c6c197daabb5c9793d63e7af9ab2d50",
"revisionTime": "2017-02-01T02:35:40Z"
},
{
"checksumSHA1": "UP+pXl+ic9y6qrpZA5MqDIAuGfw=",
"path": "github.com/mitchellh/cli",
@ -1498,16 +1480,6 @@
"checksumSHA1": "U7dGDNwEHORvJFMoNSXErKE7ITg=",
"path": "google.golang.org/cloud/internal",
"revision": "5a3b06f8b5da3b7c3a93da43163b872c86c509ef"
},
{
"checksumSHA1": "gY2M/3SCxqgrPz+P/N6JQ+m/wnA=",
"path": "gopkg.in/xmlpath.v2",
"revision": "860cbeca3ebcc600db0b213c0e83ad6ce91f5739"
},
{
"checksumSHA1": "0dDWcU08d0FS4d99NCgCkGLjjqw=",
"path": "gopkg.in/xmlpath.v2/cmd/webpath",
"revision": "860cbeca3ebcc600db0b213c0e83ad6ce91f5739"
}
],
"rootPath": "github.com/hashicorp/packer"

Loading…
Cancel
Save