diff --git a/Godeps b/Godeps/Godeps.json similarity index 87% rename from Godeps rename to Godeps/Godeps.json index 4fd5d33..084f949 100644 --- a/Godeps +++ b/Godeps/Godeps.json @@ -5,7 +5,7 @@ { "ImportPath": "bitbucket.org/kardianos/osext", "Comment": "null-7", - "Rev": "84d8cbacbe13" + "Rev": "84d8cbacbe137d1c00cdc0762b3ad585a4d64418" }, { "ImportPath": "github.com/daviddengcn/go-colortext", @@ -13,6 +13,7 @@ }, { "ImportPath": "github.com/ddollar/dist", + "Comment": "v0.2.0", "Rev": "95b3220dbc9bb70c4cbda93b4064655a1ff86a06" }, { @@ -29,6 +30,7 @@ }, { "ImportPath": "github.com/kr/pretty", + "Comment": "go.weekly.2011-12-22-18-gbc9499c", "Rev": "bc9499caa0f45ee5edb2f0209fbd61fbf3d9018f" }, { @@ -37,6 +39,7 @@ }, { "ImportPath": "github.com/subosito/gotenv", + "Comment": "v0.1.0", "Rev": "a37a0e8fb3298354bf97daad07b38feb2d0fa263" } ] diff --git a/Godeps/Readme b/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore new file mode 100644 index 0000000..f037d68 --- /dev/null +++ b/Godeps/_workspace/.gitignore @@ -0,0 +1,2 @@ +/pkg +/bin diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE new file mode 100644 index 0000000..18527a2 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012 Daniel Theophanes + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go new file mode 100644 index 0000000..37efbb2 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext.go @@ -0,0 +1,32 @@ +// Copyright 2012 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. + +// Extensions to the standard "os" package. +package osext + +import "path/filepath" + +// Executable returns an absolute path that can be used to +// re-invoke the current program. +// It may not be valid after the current program exits. +func Executable() (string, error) { + p, err := executable() + return filepath.Clean(p), err +} + +// Returns same path as Executable, returns just the folder +// path. Excludes the executable name. +func ExecutableFolder() (string, error) { + p, err := Executable() + if err != nil { + return "", err + } + folder, _ := filepath.Split(p) + return folder, nil +} + +// Depricated. Same as Executable(). +func GetExePath() (exePath string, err error) { + return Executable() +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go new file mode 100644 index 0000000..e88c1e0 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_plan9.go @@ -0,0 +1,16 @@ +// Copyright 2012 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. + +package osext + +import "syscall" + +func executable() (string, error) { + f, err := Open("/proc/" + itoa(Getpid()) + "/text") + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go new file mode 100644 index 0000000..546fec9 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_procfs.go @@ -0,0 +1,25 @@ +// Copyright 2012 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 netbsd openbsd + +package osext + +import ( + "errors" + "os" + "runtime" +) + +func executable() (string, error) { + switch runtime.GOOS { + case "linux": + return os.Readlink("/proc/self/exe") + case "netbsd": + return os.Readlink("/proc/curproc/exe") + case "openbsd": + return os.Readlink("/proc/curproc/file") + } + return "", errors.New("ExecPath not implemented for " + runtime.GOOS) +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go new file mode 100644 index 0000000..d764646 --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_sysctl.go @@ -0,0 +1,64 @@ +// Copyright 2012 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 darwin freebsd + +package osext + +import ( + "os" + "runtime" + "syscall" + "unsafe" +) + +var startUpcwd, getwdError = os.Getwd() + +func executable() (string, error) { + var mib [4]int32 + switch runtime.GOOS { + case "freebsd": + mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} + case "darwin": + mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} + } + + n := uintptr(0) + // get length + _, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if err != 0 { + return "", err + } + if n == 0 { // shouldn't happen + return "", nil + } + buf := make([]byte, n) + _, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if err != 0 { + return "", err + } + if n == 0 { // shouldn't happen + return "", nil + } + for i, v := range buf { + if v == 0 { + buf = buf[:i] + break + } + } + if buf[0] != '/' { + if getwdError != nil { + return string(buf), getwdError + } else { + if buf[0] == '.' { + buf = buf[1:] + } + if startUpcwd[len(startUpcwd)-1] != '/' { + return startUpcwd + "/" + string(buf), nil + } + return startUpcwd + string(buf), nil + } + } + return string(buf), nil +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go new file mode 100644 index 0000000..dc661db --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_test.go @@ -0,0 +1,79 @@ +// Copyright 2012 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 darwin linux freebsd netbsd windows + +package osext + +import ( + "fmt" + "os" + oexec "os/exec" + "path/filepath" + "runtime" + "testing" +) + +const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH" + +func TestExecPath(t *testing.T) { + ep, err := Executable() + if err != nil { + t.Fatalf("ExecPath failed: %v", err) + } + // we want fn to be of the form "dir/prog" + dir := filepath.Dir(filepath.Dir(ep)) + fn, err := filepath.Rel(dir, ep) + if err != nil { + t.Fatalf("filepath.Rel: %v", err) + } + cmd := &oexec.Cmd{} + // make child start with a relative program path + cmd.Dir = dir + cmd.Path = fn + // forge argv[0] for child, so that we can verify we could correctly + // get real path of the executable without influenced by argv[0]. + cmd.Args = []string{"-", "-test.run=XXXX"} + cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)} + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("exec(self) failed: %v", err) + } + outs := string(out) + if !filepath.IsAbs(outs) { + t.Fatalf("Child returned %q, want an absolute path", out) + } + if !sameFile(outs, ep) { + t.Fatalf("Child returned %q, not the same file as %q", out, ep) + } +} + +func sameFile(fn1, fn2 string) bool { + fi1, err := os.Stat(fn1) + if err != nil { + return false + } + fi2, err := os.Stat(fn2) + if err != nil { + return false + } + return os.SameFile(fi1, fi2) +} + +func init() { + if e := os.Getenv(execPath_EnvVar); e != "" { + // first chdir to another path + dir := "/" + if runtime.GOOS == "windows" { + dir = filepath.VolumeName(".") + } + os.Chdir(dir) + if ep, err := Executable(); err != nil { + fmt.Fprint(os.Stderr, "ERROR: ", err) + } else { + fmt.Fprint(os.Stderr, ep) + } + os.Exit(0) + } +} diff --git a/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go new file mode 100644 index 0000000..72d282c --- /dev/null +++ b/Godeps/_workspace/src/bitbucket.org/kardianos/osext/osext_windows.go @@ -0,0 +1,34 @@ +// Copyright 2012 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. + +package osext + +import ( + "syscall" + "unicode/utf16" + "unsafe" +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +// GetModuleFileName() with hModule = NULL +func executable() (exePath string, err error) { + return getModuleFileName() +} + +func getModuleFileName() (string, error) { + var n uint32 + b := make([]uint16, syscall.MAX_PATH) + size := uint32(len(b)) + + r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +} diff --git a/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/.gitignore b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/.gitignore new file mode 100644 index 0000000..0026861 --- /dev/null +++ b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/README.md b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/README.md new file mode 100644 index 0000000..6ee0197 --- /dev/null +++ b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/README.md @@ -0,0 +1,17 @@ +go-colortext package +==================== + +This is a package to change the color of the text and background in the console, working both in windows and other systems. + +Under windows, the console APIs are used, and otherwise ANSI text is used. + +Docs: http://godoc.org/github.com/daviddengcn/go-colortext ([packages that import ct](http://go-search.org/view?id=github.com%2fdaviddengcn%2fgo-colortext)) + +Usage: +```go +ChangeColor(Red, true, White, false) +fmt.Println(...) +ChangeColor(Green, false, None, false) +fmt.Println(...) +ResetColor() +``` diff --git a/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct.go b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct.go new file mode 100644 index 0000000..a3d7657 --- /dev/null +++ b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct.go @@ -0,0 +1,37 @@ +/* +ct package provides functions to change the color of console text. + +Under windows platform, the Console api is used. Under other systems, ANSI text mode is used. +*/ +package ct + +// Color is the type of color to be set. +type Color int + +const ( + // No change of color + None = Color(iota) + Black + Red + Green + Yellow + Blue + Magenta + Cyan + White +) + +/* +ResetColor resets the foreground and background to original colors +*/ +func ResetColor() { + resetColor() +} + +// ChangeColor sets the foreground and background colors. If the value of the color is None, +// the corresponding color keeps unchanged. +// If fgBright or bgBright is set true, corresponding color use bright color. bgBright may be +// ignored in some OS environment. +func ChangeColor(fg Color, fgBright bool, bg Color, bgBright bool) { + changeColor(fg, fgBright, bg, bgBright) +} diff --git a/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct_ansi.go b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct_ansi.go new file mode 100644 index 0000000..6b0c5bc --- /dev/null +++ b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct_ansi.go @@ -0,0 +1,35 @@ +// +build !windows + +package ct + +import ( + "fmt" +) + +func resetColor() { + fmt.Print("\x1b[0m") +} + +func changeColor(fg Color, fgBright bool, bg Color, bgBright bool) { + if fg == None && bg == None { + return + } // if + + s := "" + if fg != None { + s = fmt.Sprintf("%s%d", s, 30+(int)(fg-Black)) + if fgBright { + s += ";1" + } // if + } // if + + if bg != None { + if s != "" { + s += ";" + } // if + s = fmt.Sprintf("%s%d", s, 40+(int)(bg-Black)) + } // if + + s = "\x1b[0;" + s + "m" + fmt.Print(s) +} diff --git a/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct_test.go b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct_test.go new file mode 100644 index 0000000..b2db738 --- /dev/null +++ b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct_test.go @@ -0,0 +1,26 @@ +package ct + +import ( + "fmt" + "testing" +) + +func TestChangeColor(t *testing.T) { + defer ResetColor() + fmt.Println("Normal text...") + text := "This is an demo of using ChangeColor to output colorful texts" + i := 1 + for _, c := range text { + ChangeColor(Color(i/2%8)+Black, i%2 == 1, Color((i+2)/2%8)+Black, false) + fmt.Print(string(c)) + i++ + } // for c + fmt.Println() + ChangeColor(Red, true, White, false) + fmt.Println("Before reset.") + ChangeColor(Red, false, White, true) + fmt.Println("Before reset.") + ResetColor() + fmt.Println("After reset.") + fmt.Println("After reset.") +} diff --git a/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct_win.go b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct_win.go new file mode 100644 index 0000000..6b41644 --- /dev/null +++ b/Godeps/_workspace/src/github.com/daviddengcn/go-colortext/ct_win.go @@ -0,0 +1,139 @@ +// +build windows + +package ct + +import ( + "syscall" + "unsafe" +) + +var fg_colors = []uint16{ + 0, + 0, + foreground_red, + foreground_green, + foreground_red | foreground_green, + foreground_blue, + foreground_red | foreground_blue, + foreground_green | foreground_blue, + foreground_red | foreground_green | foreground_blue} + +var bg_colors = []uint16{ + 0, + 0, + background_red, + background_green, + background_red | background_green, + background_blue, + background_red | background_blue, + background_green | background_blue, + background_red | background_green | background_blue} + +const ( + foreground_blue = uint16(0x0001) + foreground_green = uint16(0x0002) + foreground_red = uint16(0x0004) + foreground_intensity = uint16(0x0008) + background_blue = uint16(0x0010) + background_green = uint16(0x0020) + background_red = uint16(0x0040) + background_intensity = uint16(0x0080) + + foreground_mask = foreground_blue | foreground_green | foreground_red | foreground_intensity + background_mask = background_blue | background_green | background_red | background_intensity +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + + procGetStdHandle = kernel32.NewProc("GetStdHandle") + procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + + hStdout uintptr + initScreenInfo *console_screen_buffer_info +) + +func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool { + ret, _, _ := procSetConsoleTextAttribute.Call( + hConsoleOutput, + uintptr(wAttributes)) + return ret != 0 +} + +type coord struct { + X, Y int16 +} + +type small_rect struct { + Left, Top, Right, Bottom int16 +} + +type console_screen_buffer_info struct { + DwSize coord + DwCursorPosition coord + WAttributes uint16 + SrWindow small_rect + DwMaximumWindowSize coord +} + +func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *console_screen_buffer_info { + var csbi console_screen_buffer_info + ret, _, _ := procGetConsoleScreenBufferInfo.Call( + hConsoleOutput, + uintptr(unsafe.Pointer(&csbi))) + if ret == 0 { + return nil + } + return &csbi +} + +const ( + std_output_handle = uint32(-11 & 0xFFFFFFFF) +) + +func init() { + kernel32 := syscall.NewLazyDLL("kernel32.dll") + + procGetStdHandle = kernel32.NewProc("GetStdHandle") + + hStdout, _, _ = procGetStdHandle.Call(uintptr(std_output_handle)) + + initScreenInfo = getConsoleScreenBufferInfo(hStdout) + + syscall.LoadDLL("") +} + +func resetColor() { + if initScreenInfo == nil { // No console info - Ex: stdout redirection + return + } + setConsoleTextAttribute(hStdout, initScreenInfo.WAttributes) +} + +func changeColor(fg Color, fgBright bool, bg Color, bgBright bool) { + attr := uint16(0) + if fg == None || bg == None { + cbufinfo := getConsoleScreenBufferInfo(hStdout) + if cbufinfo == nil { // No console info - Ex: stdout redirection + return + } + attr = getConsoleScreenBufferInfo(hStdout).WAttributes + } // if + + if fg != None { + attr = attr & ^foreground_mask | fg_colors[fg] + if fgBright { + attr |= foreground_intensity + } // if + } // if + + if bg != None { + attr = attr & ^background_mask | bg_colors[bg] + if bgBright { + attr |= background_intensity + } // if + } // if + + setConsoleTextAttribute(hStdout, attr) +} diff --git a/Godeps/_workspace/src/github.com/ddollar/dist/dist.go b/Godeps/_workspace/src/github.com/ddollar/dist/dist.go new file mode 100644 index 0000000..814593c --- /dev/null +++ b/Godeps/_workspace/src/github.com/ddollar/dist/dist.go @@ -0,0 +1,176 @@ +// Handle updating new releases from a godist server +package dist + +import ( + "bitbucket.org/kardianos/osext" + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "github.com/inconshreveable/go-update" + "github.com/kr/binarydist" + "io/ioutil" + "net/http" + "os" + "runtime" + "strings" +) + +var DigicertHighAssuranceCert = `-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE-----` + +type Dist struct { + Host string + Name string + Project string + Version string +} + +type Release struct { + Version string + Url string +} + +// Initialize a new godist client, speciying a project name +// e.g. "ddollar/forego" +func NewDist(project string, version string) (d *Dist) { + d = new(Dist) + d.Host = "https://godist.herokuapp.com" + d.Name = strings.Split(project, "/")[1] + d.Project = project + d.Version = version + return +} + +// Update the currently running binary to the latest version +func (d *Dist) Update() (to string, err error) { + releases, err := d.fetchReleases() + if len(releases) < 1 { + return "", errors.New("no releases") + } + to = releases[0].Version + return to, d.UpdateTo(to) +} + +// Update the currently running binary to a specific version +func (d *Dist) UpdateTo(to string) (err error) { + if d.Version == to { + return errors.New("nothing to update") + } + binary, _ := osext.Executable() + reader, err := os.Open(binary) + if err != nil { + return err + } + defer reader.Close() + url := fmt.Sprintf("%s/projects/%s/diff/%s/%s/%s-%s", d.Host, d.Project, d.Version, to, runtime.GOOS, runtime.GOARCH) + patch, err := d.httpGet(url) + if err != nil { + return err + } + writer := new(bytes.Buffer) + err = binarydist.Patch(reader, writer, bytes.NewReader(patch)) + if err != nil { + return err + } + reader.Close() + err, _ = update.FromStream(writer) + return +} + +func (d *Dist) fetchReleases() (releases []Release, err error) { + body, err := d.httpGet(fmt.Sprintf("%s/projects/%s/releases/%s-%s", d.Host, d.Project, runtime.GOOS, runtime.GOARCH)) + if err != nil { + return nil, err + } + err = json.Unmarshal(body, &releases) + return +} + +func (d *Dist) httpClient() (client *http.Client) { + chain := d.rootCertificate() + config := tls.Config{} + config.RootCAs = x509.NewCertPool() + for _, cert := range chain.Certificate { + x509Cert, err := x509.ParseCertificate(cert) + if err != nil { + panic(err) + } + config.RootCAs.AddCert(x509Cert) + } + config.BuildNameToCertificate() + tr := http.Transport{TLSClientConfig: &config} + client = &http.Client{Transport: &tr} + return +} + +func (d *Dist) httpGet(url string) ([]byte, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Add("User-Agent", fmt.Sprintf("%s/%s dist/%s (%s-%s)", d.Name, d.Version, Version, runtime.GOOS, runtime.GOARCH)) + res, err := d.httpClient().Do(req) + defer res.Body.Close() + if res.StatusCode != 200 { + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + return nil, errors.New(string(body)) + } + if err != nil { + return nil, err + } + return ioutil.ReadAll(res.Body) +} + +func (d *Dist) updateFromUrl(url string) (err error) { + client := d.httpClient() + res, err := client.Get(url) + if err != nil { + return err + } + defer res.Body.Close() + err, _ = update.FromStream(res.Body) + return +} + +func (d *Dist) rootCertificate() (cert tls.Certificate) { + certPEMBlock := []byte(DigicertHighAssuranceCert) + var certDERBlock *pem.Block + for { + certDERBlock, certPEMBlock = pem.Decode(certPEMBlock) + if certDERBlock == nil { + break + } + if certDERBlock.Type == "CERTIFICATE" { + cert.Certificate = append(cert.Certificate, certDERBlock.Bytes) + } + } + return +} diff --git a/Godeps/_workspace/src/github.com/ddollar/dist/version.go b/Godeps/_workspace/src/github.com/ddollar/dist/version.go new file mode 100644 index 0000000..f3c71da --- /dev/null +++ b/Godeps/_workspace/src/github.com/ddollar/dist/version.go @@ -0,0 +1,5 @@ +package dist + +const ( + Version = "0.2.0" +) diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/.gitignore b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/README b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/README new file mode 100644 index 0000000..5a6d8f4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/README @@ -0,0 +1,90 @@ +PACKAGE DOCUMENTATION + +package execpath + import "github.com/inconshreveable/go-execpath" + + Package execpath provides APIs for returning the absolute path to the + executable file of the running program. + + exePath, err := execpath.Get() + if err != nil { + fmt.Println(err.Error()) + } + // Prints /path/to/your/executable + fmt.Printf(exePath) + + Determining the path to the executable of the currently running program + is difficult. Many programs read os.Args[0], sometimes combining it with + the working directory to determine the path, but this approach is + fragile and yields incorrect results because: + + - The program may be in the PATH, and invoked without specifying any + path to the executable. + + - The program may have changed directories after it was invoked. + + - The value of os.Args[0] is not required to be set to the invoking + path. Most shells follow a convention of setting os.Args[0] to the path + used to invoke the executable, but your program may be invoked by an + unconventional shell, forked from a non-shell process, started as a + Windows service, etc. + + Each operating system has a platform-specific API that is guaranteed to + return a proper path to the executable file. Package execpath provides + GetNative(), which uses the following platform-specific APIs which will + always return the proper executable path, if successful: + + - Windows: GetModuleFileName + + - Linux: /proc/self/exe + + - OS X: _NSGetExecutablePath + + - FreeBSD: /proc/curproc/file + + - OpenBSD: /proc/curproc/file + + - NetBSD: /proc/curproc/exe + + Package execpath also implements the naive heuristic methods which + involve examining os.Args[0] or checking directories in the PATH. + + Lastly, execpath provides Get(), the API you should prefer, which calls + GetNative() and falls back to using heuristic methods if GetNative() + fails. + + +FUNCTIONS + +func Get() (path string, err error) + Get returns the path to the current executable or an error on failure. + This is the preferred API because it attempts to use the native method + and falls back to heuristic methods on failure. In order, it calls: + + GetNative() + + GetArg0() if GetNative() fails + + GetPath() if GetArg0() fails + +func GetArg0() (p string, err error) + GetArg0 returns the executable path by examining the value of + os.Args[0], combining it with the working directory if necessary. + Because os.Args[0] is set by convention, the returned path is not + guaranteed to be correct, even if GetArg0 does not return an error. + +func GetNative() (string, error) + GetNative returns the executable path by calling a platform-specific + API. The API used for each platform is specified as follows: + + - Windows: GetModuleFileName - Linux: /proc/self/exe - OS X: + _NSGetExecutablePath - FreeBSD: /proc/curproc/file - OpenBSD: + /proc/curproc/file - NetBSD: /proc/curproc/exe + +func GetPath() (p string, err error) + GetPath returns the executable path by searching for the the executable + name (os.Args[0]) in each directory of the PATH environment variable. + Because os.Args[0] is set by convention, the returned path is not + guaranteed to be correct, even if GetPath does not return an error. + + diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/darwin.go b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/darwin.go new file mode 100644 index 0000000..50fd8ac --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/darwin.go @@ -0,0 +1,11 @@ +// +build darwin,!cgo + +package execpath + +import ( + "fmt" +) + +func GetNative() (string, error) { + return "", fmt.Errorf("GetNative() not implemented when cross-compiling to darwin") +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/darwin_native.go b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/darwin_native.go new file mode 100644 index 0000000..7500498 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/darwin_native.go @@ -0,0 +1,32 @@ +// +build darwin,cgo + +package execpath + +import ( + "fmt" +) + +/* +#include +#include +*/ +import "C" + +func GetNative() (string, error) { + var buflen C.uint32_t = C.uint32_t(maxPathSize) + buf := make([]C.char, buflen) + + ret := C._NSGetExecutablePath(&buf[0], &buflen) + if ret == -1 { + // buflen wasn't large enough, _NSGetExecutablePath set it to the necessary size + // so recreate the buffer and try again + buf = make([]C.char, buflen) + ret = C._NSGetExecutablePath(&buf[0], &buflen) + if ret == -1 { + // this should never happen + return "", fmt.Errorf("_NSGetExecutable failed to get the executable path") + } + } + pathlen := C.strlen(&buf[0]) + return C.GoStringN(&buf[0], C.int(pathlen)), nil +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/execpath.go b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/execpath.go new file mode 100644 index 0000000..8bedd68 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/execpath.go @@ -0,0 +1,143 @@ +/* +Package execpath provides APIs for returning the absolute path to the +executable file of the running program. + + exePath, err := execpath.Get() + if err != nil { + fmt.Println(err.Error()) + } + + // Prints /path/to/your/executable + fmt.Printf(exePath) + +Determining the path to the executable of the currently running program is +difficult. Many programs read os.Args[0], sometimes combining it with the +working directory to determine the path, but this approach is fragile and +yields incorrect results because: + +- The program may be in the PATH, and invoked without specifying any path to the executable. + +- The program may have changed directories after it was invoked. + +- The value of os.Args[0] is not required to be set to the invoking path. +Most shells follow a convention of setting os.Args[0] to the path used to +invoke the executable, but your program may be invoked by an unconventional +shell, forked from a non-shell process, started as a Windows service, etc. + +Each operating system has a platform-specific API that is guaranteed to return +a proper path to the executable file. Package execpath provides GetNative(), +which uses the following platform-specific APIs which will always return the +proper executable path, if successful: + +- Windows: GetModuleFileName + +- Linux: /proc/self/exe + +- OS X: _NSGetExecutablePath + +- FreeBSD: /proc/curproc/file + +- OpenBSD: /proc/curproc/file + +- NetBSD: /proc/curproc/exe + +Package execpath also implements the naive heuristic methods which involve +examining os.Args[0] or checking directories in the PATH. + +Lastly, execpath provides Get(), the API you should prefer, which calls +GetNative() and falls back to using heuristic methods if GetNative() fails. +*/ +package execpath + +import ( + "fmt" + "os" + "os/exec" + "path" +) + +var maxPathSize = 1024 + +// Get returns the path to the current executable or an error on failure. +// This is the preferred API because it attempts to use the native +// method and falls back to heuristic methods on failure. In order, it calls: +// +// GetNative() +// +// GetArg0() if GetNative() fails +// +// GetPath() if GetArg0() fails +func Get() (path string, err error) { + if path, err = GetNative(); err == nil { + return + } + + if path, err = GetArg0(); err == nil { + return + } + + if path, err = GetPath(); err == nil { + return + } + + return +} + +// returns true if the path exists, false otherwise +// an error is returned on failure (e.g. insufficient +// permissions) +func pathExists(p string) (bool, error) { + _, err := os.Stat(p) + if err == nil { + return true, nil + } + + if os.IsNotExist(err) { + return false, nil + } else { + return false, err + } +} + +func makeAbsolute(p string) (fullPath string, err error) { + if path.IsAbs(p) { + //it's an absolute path, we're done + fullPath = p + return + } + + // the path is relative, combine p with the current working directory + wd, err := os.Getwd() + if err != nil { + return + } + + fullPath = path.Join(wd, p) + return +} + +// GetArg0 returns the executable path by examining the value of os.Args[0], +// combining it with the working directory if necessary. Because os.Args[0] is +// set by convention, the returned path is not guaranteed to be correct, even +// if GetArg0 does not return an error. +func GetArg0() (p string, err error) { + p, err = makeAbsolute(os.Args[0]) + if err != nil { + return + } + + var exists bool + if exists, err = pathExists(p); !exists { + err = fmt.Errorf("Can't determine executable path from os.Args[0]") + } + + return +} + +// GetPath returns the executable path by searching for the the executable name +// (os.Args[0]) in each directory of the PATH environment variable. Because +// os.Args[0] is set by convention, the returned path is not guaranteed to be +// correct, even if GetPath does not return an error. +func GetPath() (p string, err error) { + return exec.LookPath(os.Args[0]) +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/freebsd.go b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/freebsd.go new file mode 100644 index 0000000..9fccf76 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/freebsd.go @@ -0,0 +1,11 @@ +// +build freebsd + +package execpath + +import ( + "os" +) + +func GetNative() (path string, err error) { + return os.Readlink("/proc/curproc/file") +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/linux.go b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/linux.go new file mode 100644 index 0000000..fa18485 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/linux.go @@ -0,0 +1,20 @@ +// +build linux + +package execpath + +import ( + "os" +) + +// GetNative returns the executable path by calling a platform-specific API. The API +// used for each platform is specified as follows: +// +// - Windows: GetModuleFileName +// - Linux: /proc/self/exe +// - OS X: _NSGetExecutablePath +// - FreeBSD: /proc/curproc/file +// - OpenBSD: /proc/curproc/file +// - NetBSD: /proc/curproc/exe +func GetNative() (string, error) { + return os.Readlink("/proc/self/exe") +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/netbsd.go b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/netbsd.go new file mode 100644 index 0000000..aad0c46 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/netbsd.go @@ -0,0 +1,11 @@ +// +build netbsd + +package execpath + +import ( + "os" +) + +func GetNative() (path string, err error) { + return os.Readlink("/proc/curproc/exe") +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/openbsd.go b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/openbsd.go new file mode 100644 index 0000000..6914b8b --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/openbsd.go @@ -0,0 +1,11 @@ +// +build openbsd + +package execpath + +import ( + "os" +) + +func GetNative() (path string, err error) { + return os.Readlink("/proc/curproc/file") +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/plan9.go b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/plan9.go new file mode 100644 index 0000000..6337baa --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/plan9.go @@ -0,0 +1,11 @@ +// +build plan9 + +package execpath + +import ( + "fmt" +) + +func GetNative() (string, error) { + return "", fmt.Errorf("GetNative() not implemented on plan9") +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/windows.go b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/windows.go new file mode 100644 index 0000000..2f816fb --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-execpath/windows.go @@ -0,0 +1,50 @@ +// +build windows + +package execpath + +import ( + "fmt" + "syscall" + "unicode/utf16" + "unsafe" +) + +func GetNative() (path string, err error) { + h, err := syscall.LoadLibrary("kernel32.dll") + if err != nil { + return + } + defer syscall.FreeLibrary(h) + + addr, err := syscall.GetProcAddress(h, "GetModuleFileNameW") + if err != nil { + return + } + + bufSize := uint32(maxPathSize) + for { + buf := make([]uint16, bufSize) + retptr, _, errno := syscall.Syscall(addr, 3, + /* NULL */ uintptr(0), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&bufSize))) + + if errno != 0 { + err = errno + return + } + + retval := uint32(retptr) + if retval == 0 { + err = fmt.Errorf("GetModuleFileName failed to get executable path") + return + } else if retval == bufSize { + // buffer is too small, double it and try again + bufSize = bufSize * 2 + } else { + // success + path = string(utf16.Decode(buf[0:retval])) + return + } + } +} diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-update/README b/Godeps/_workspace/src/github.com/inconshreveable/go-update/README new file mode 100644 index 0000000..467def8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-update/README @@ -0,0 +1,137 @@ +PACKAGE DOCUMENTATION + +package update + import "github.com/inconshreveable/go-update" + + Package update allows a program to "self-update", replacing its + executable file with new bytes. + + Package update provides the facility to create user experiences like + auto-updating or user-approved updates which manifest as user prompts in + commercial applications with copy similar to "Restart to being using the + new version of X". + + Updating your program to a new version is as easy as: + + err := update.FromUrl("http://release.example.com/2.0/myprogram") + if err != nil { + fmt.Printf("Update failed: %v", err) + } + + The most low-level API is FromStream() which updates the current + executable with the bytes read from an io.Reader. + + Additional APIs are provided for common update strategies which include + updating from a file with FromFile() and updating from the internet with + FromUrl(). + + Using the more advaced Download.UpdateFromUrl() API gives you the + ability to resume an interrupted download to enable large updates to + complete even over intermittent or slow connections. This API also + enables more fine-grained control over how the update is downloaded from + the internet as well as access to download progress, + + +VARIABLES + +var ( + // Returned when the remote server indicates that no download is available + UpdateUnavailable error +) + + +FUNCTIONS + +func FromFile(filepath string) (err error) + FromFile reads the contents of the given file and uses them to update + the current program's executable file by calling FromStream(). + +func FromStream(newBinary io.Reader) (err error) + FromStream reads the contents of the supplied io.Reader newBinary and + uses them to update the current program's executable file. + + FromStream performs the following actions to ensure a cross-platform + safe update: + + - Renames the current program's executable file from + /path/to/program-name to /path/to/.program-name.old + + - Opens the now-empty path /path/to/program-name with mode 0755 and + copies the contents of newBinary into the file. + + - If the copy is successful, it erases /path/to/.program.old. If this + operation fails, no error is reported. + + - If the copy is unsuccessful, it attempts to rename + /path/to/.program-name.old back to /path/to/program-name. If this + operation fails, the error is not reported in order to not mask the + error that caused the rename recovery attempt. + +func FromUrl(url string) error + FromUrl downloads the contents of the given url and uses them to update + the current program's executable file. It is a convenience function + which is equivalent to + + NewDownload().UpdateFromUrl(url) + + See Download.UpdateFromUrl for more details. + + +TYPES + +type Download struct { + // net/http.Client to use when downloading the update. + // If nil, a default http.Client is used + HttpClient *http.Client + + // Path on the file system to dowload the update to + // If empty, a temporary file is used. + // After the download begins, this path will be set + // so that the client can use it to resume aborted + // downloads + Path string + + // Progress returns the percentage of the download + // completed as an integer between 0 and 100 + Progress chan (int) + + // HTTP Method to use in the download request. Default is "GET" + Method string +} + Type Download encapsulates the necessary parameters and state needed to + download an update from the internet. Create an instance with the + NewDownload() factory function. + + +func NewDownload() *Download + NewDownload initializes a new Download object + + +func (d *Download) UpdateFromUrl(url string) (err error) + UpdateFromUrl downloads the given url from the internet to a file on + disk and then calls FromStream() to update the current program's + executable file with the contents of that file. + + If the update is successful, the downloaded file will be erased from + disk. Otherwise, it will remain in d.Path to allow the download to + resume later or be skipped entirely. + + Only HTTP/1.1 servers that implement the Range header are supported. + + UpdateFromUrl() uses HTTP status codes to determine what action to take. + + - The HTTP server should return 200 or 206 for the update to be + downloaded. + + - The HTTP server should return 204 if no update is available at this + time. This will cause UpdateFromUrl to return the error + UpdateUnavailable. + + - If the HTTP server returns a 3XX redirect, it will be followed + according to d.HttpClient's redirect policy. + + - Any other HTTP status code will cause UpdateFromUrl to return an + error. + + + diff --git a/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go b/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go new file mode 100644 index 0000000..55f2039 --- /dev/null +++ b/Godeps/_workspace/src/github.com/inconshreveable/go-update/update.go @@ -0,0 +1,448 @@ +/* +Package update allows a program to "self-update", replacing its executable file +with new bytes. + +Package update provides the facility to create user experiences like auto-updating +or user-approved updates which manifest as user prompts in commercial applications +with copy similar to "Restart to being using the new version of X". + +Updating your program to a new version is as easy as: + + err := update.FromUrl("http://release.example.com/2.0/myprogram") + if err != nil { + fmt.Printf("Update failed: %v", err) + } + +The most low-level API is FromStream() which updates the current executable +with the bytes read from an io.Reader. + +Additional APIs are provided for common update strategies which include +updating from a file with FromFile() and updating from the internet with +FromUrl(). + +Using the more advaced Download.UpdateFromUrl() API gives you the ability +to resume an interrupted download to enable large updates to complete even +over intermittent or slow connections. This API also enables more fine-grained +control over how the update is downloaded from the internet as well as access to +download progress, +*/ +package update + +import ( + "compress/gzip" + "fmt" + execpath "github.com/inconshreveable/go-execpath" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" +) + +type MeteredReader struct { + rd io.ReadCloser + totalSize int64 + progress chan int + totalRead int64 + ticks int64 +} + +func (m *MeteredReader) Close() error { + return m.rd.Close() +} + +func (m *MeteredReader) Read(b []byte) (n int, err error) { + chunkSize := (m.totalSize / 100) + 1 + lenB := int64(len(b)) + + var nChunk int + for start := int64(0); start < lenB; start += int64(nChunk) { + end := start + chunkSize + if end > lenB { + end = lenB + } + + nChunk, err = m.rd.Read(b[start:end]) + + n += nChunk + m.totalRead += int64(nChunk) + + if m.totalRead > (m.ticks * chunkSize) { + m.ticks += 1 + // try to send on channel, but don't block if it's full + select { + case m.progress <- int(m.ticks + 1): + default: + } + + // give the progress channel consumer a chance to run + runtime.Gosched() + } + + if err != nil { + return + } + } + + return +} + +// We wrap the round tripper when making requests +// because we need to add headers to the requests we make +// even when they are requests made after a redirect +type RoundTripper struct { + RoundTripFn func(*http.Request) (*http.Response, error) +} + +func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + return rt.RoundTripFn(r) +} + +// Type Download encapsulates the necessary parameters and state +// needed to download an update from the internet. Create an instance +// with the NewDownload() factory function. +// +// You may only use a Download once, +type Download struct { + // net/http.Client to use when downloading the update. + // If nil, a default http.Client is used + HttpClient *http.Client + + // Path on the file system to dowload the update to + // If empty, a temporary file is used. + // After the download begins, this path will be set + // so that the client can use it to resume aborted + // downloads + Path string + + // Progress returns the percentage of the download + // completed as an integer between 0 and 100 + Progress chan (int) + + // HTTP Method to use in the download request. Default is "GET" + Method string + + // HTTP URL to issue the download request to + Url string + + // Set to true when the server confirms a new version is available + // even if the updating process encounters an error later on + Available bool +} + +// NewDownload initializes a new Download object +func NewDownload(url string) *Download { + return &Download{ + HttpClient: new(http.Client), + Progress: make(chan int), + Method: "GET", + Url: url, + } +} + +func (d *Download) sharedHttp(offset int64) (resp *http.Response, err error) { + // create the download request + req, err := http.NewRequest(d.Method, d.Url, nil) + if err != nil { + return + } + + // we have to add headers like this so they get used across redirects + trans := d.HttpClient.Transport + if trans == nil { + trans = http.DefaultTransport + } + + d.HttpClient.Transport = &RoundTripper{ + RoundTripFn: func(r *http.Request) (*http.Response, error) { + // add header for download continuation + if offset > 0 { + r.Header.Add("Range", fmt.Sprintf("%d-", offset)) + } + + // ask for gzipped content so that net/http won't unzip it for us + // and destroy the content length header we need for progress calculations + r.Header.Add("Accept-Encoding", "gzip") + + return trans.RoundTrip(r) + }, + } + + // issue the download request + return d.HttpClient.Do(req) +} + +func (d *Download) Check() (available bool, err error) { + resp, err := d.sharedHttp(0) + if err != nil { + return + } + resp.Body.Close() + + switch resp.StatusCode { + // ok + case 200, 206: + available = true + + // no update available + case 204: + available = false + + // server error + default: + err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status) + return + } + + return +} + +// Get() downloads the given url from the internet to a file on disk +// and then calls FromStream() to update the current program's executable file +// with the contents of that file. +// +// If the update is successful, the downloaded file will be erased from disk. +// Otherwise, it will remain in d.Path to allow the download to resume later +// or be skipped entirely. +// +// Only HTTP/1.1 servers that implement the Range header support resuming a +// partially completed download. +// +// UpdateFromUrl() uses HTTP status codes to determine what action to take. +// +// - The HTTP server should return 200 or 206 for the update to be downloaded. +// +// - The HTTP server should return 204 if no update is available at this time. +// +// - If the HTTP server returns a 3XX redirect, it will be followed +// according to d.HttpClient's redirect policy. +// +// - Any other HTTP status code will cause UpdateFromUrl to return an error. +func (d *Download) Get() (err error) { + var offset int64 = 0 + var fp *os.File + + // Close the progress channel whenever this function completes + defer close(d.Progress) + + // open a file where we will stream the downloaded update to + // we do this first because if the caller specified a non-empty dlpath + // we need to determine how large it is in order to resume the download + if d.Path == "" { + // no dlpath specified, use a random tempfile + fp, err = ioutil.TempFile("", "update") + if err != nil { + return + } + defer fp.Close() + + // remember the path + d.Path = fp.Name() + } else { + fp, err = os.OpenFile(d.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600) + if err != nil { + return + } + defer fp.Close() + + // determine the file size so we can resume the download, if possible + var fi os.FileInfo + fi, err = fp.Stat() + if err != nil { + return + } + + offset = fi.Size() + } + + // start downloading the file + resp, err := d.sharedHttp(offset) + if err != nil { + return + } + defer resp.Body.Close() + + switch resp.StatusCode { + // ok + case 200, 206: + d.Available = true + + // no update available + case 204: + return + + // server error + default: + err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status) + return + } + + // Determine how much we have to download + // net/http sets this to -1 when it is unknown + clength := resp.ContentLength + + // Read the content from the response body + rd := resp.Body + + // meter the rate at which we download content for + // progress reporting if we know how much to expect + if clength > 0 { + rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress} + } + + // Decompress the content if necessary + if resp.Header.Get("Content-Encoding") == "gzip" { + rd, err = gzip.NewReader(rd) + if err != nil { + return + } + } + + // Download the update + _, err = io.Copy(fp, rd) + if err != nil { + return + } + + return +} + +func (d *Download) GetAndUpdate() (err error, errRecover error) { + // check before we download if this will work + if err = SanityCheck(); err != nil { + // keep the contract that d.Progress will close whenever Get() terminates + close(d.Progress) + return + } + + // download the update + if err = d.Get(); err != nil || !d.Available { + return + } + + // apply the update + if err, errRecover = FromFile(d.Path); err != nil || errRecover != nil { + return + } + + // remove the temporary file + os.Remove(d.Path) + return +} + +// FromUrl downloads the contents of the given url and uses them to update +// the current program's executable file. It is a convenience function which is equivalent to +// +// NewDownload(url).GetAndUpdate() +// +// See Download.Get() for more details. +func FromUrl(url string) (err error, errRecover error) { + return NewDownload(url).GetAndUpdate() +} + +// FromFile reads the contents of the given file and uses them +// to update the current program's executable file by calling FromStream(). +func FromFile(filepath string) (err error, errRecover error) { + // open the new binary + fp, err := os.Open(filepath) + if err != nil { + return + } + defer fp.Close() + + // do the update + return FromStream(fp) +} + +// FromStream reads the contents of the supplied io.Reader newBinary +// and uses them to update the current program's executable file. +// +// FromStream performs the following actions to ensure a cross-platform safe +// update: +// +// - Creates a new file, /path/to/.program-name.new with mode 0755 and copies +// the contents of newBinary into the file +// +// - Renames the current program's executable file from /path/to/program-name +// to /path/to/.program-name.old +// +// - Renames /path/to/.program-name.new to /path/to/program-name +// +// - If the rename is successful, it erases /path/to/.program.old. If this operation +// fails, no error is reported. +// +// - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old +// back to /path/to/program-name. If this operation fails, the error is not reported +// in order to not mask the error that caused the rename recovery attempt. +func FromStream(newBinary io.Reader) (err error, errRecover error) { + // get the path to the executable + thisExecPath, err := execpath.Get() + if err != nil { + return + } + + // get the directory the executable exists in + execDir := filepath.Dir(thisExecPath) + execName := filepath.Base(thisExecPath) + + // Copy the contents of of newbinary to a the new executable file + newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName)) + fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return + } + defer fp.Close() + _, err = io.Copy(fp, newBinary) + + // if we don't call fp.Close(), windows won't let us move the new executable + // because the file will still be "in use" + fp.Close() + + // move the existing executable to a new file in the same directory + oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName)) + err = os.Rename(thisExecPath, oldExecPath) + if err != nil { + return + } + + // move the new exectuable in to become the new program + err = os.Rename(newExecPath, thisExecPath) + + if err != nil { + // copy unsuccessful + errRecover = os.Rename(oldExecPath, thisExecPath) + } else { + // copy successful, remove the old binary + _ = os.Remove(oldExecPath) + } + + return +} + +// SanityCheck() attempts to determine whether an in-place executable update could +// succeed by performing preliminary checks (to establish valid permissions, etc). +// This helps avoid downloading updates when we know the update can't be successfully +// applied later. +func SanityCheck() (err error) { + // get the path to the executable + thisExecPath, err := execpath.Get() + if err != nil { + return + } + + // get the directory the executable exists in + execDir := filepath.Dir(thisExecPath) + execName := filepath.Base(thisExecPath) + + // attempt to open a file in the executable's directory + newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName)) + fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return + } + defer fp.Close() + + os.Remove(newExecPath) + return +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore b/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore new file mode 100644 index 0000000..653f160 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/.gitignore @@ -0,0 +1 @@ +test.* diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/License b/Godeps/_workspace/src/github.com/kr/binarydist/License new file mode 100644 index 0000000..183c389 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/License @@ -0,0 +1,22 @@ +Copyright 2012 Keith Rarick + +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. diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md b/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md new file mode 100644 index 0000000..dadc368 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/Readme.md @@ -0,0 +1,7 @@ +# binarydist + +Package binarydist implements binary diff and patch as described on +. It reads and writes files +compatible with the tools there. + +Documentation at . diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go b/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go new file mode 100644 index 0000000..a2516b8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/bzip2.go @@ -0,0 +1,40 @@ +package binarydist + +import ( + "io" + "os/exec" +) + +type bzip2Writer struct { + c *exec.Cmd + w io.WriteCloser +} + +func (w bzip2Writer) Write(b []byte) (int, error) { + return w.w.Write(b) +} + +func (w bzip2Writer) Close() error { + if err := w.w.Close(); err != nil { + return err + } + return w.c.Wait() +} + +// Package compress/bzip2 implements only decompression, +// so we'll fake it by running bzip2 in another process. +func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) { + var bw bzip2Writer + bw.c = exec.Command("bzip2", "-c") + bw.c.Stdout = w + + if bw.w, err = bw.c.StdinPipe(); err != nil { + return nil, err + } + + if err = bw.c.Start(); err != nil { + return nil, err + } + + return bw, nil +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go b/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go new file mode 100644 index 0000000..af51616 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/common_test.go @@ -0,0 +1,93 @@ +package binarydist + +import ( + "crypto/rand" + "io" + "io/ioutil" + "os" +) + +func mustOpen(path string) *os.File { + f, err := os.Open(path) + if err != nil { + panic(err) + } + + return f +} + +func mustReadAll(r io.Reader) []byte { + b, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + return b +} + +func fileCmp(a, b *os.File) int64 { + sa, err := a.Seek(0, 2) + if err != nil { + panic(err) + } + + sb, err := b.Seek(0, 2) + if err != nil { + panic(err) + } + + if sa != sb { + return sa + } + + _, err = a.Seek(0, 0) + if err != nil { + panic(err) + } + + _, err = b.Seek(0, 0) + if err != nil { + panic(err) + } + + pa, err := ioutil.ReadAll(a) + if err != nil { + panic(err) + } + + pb, err := ioutil.ReadAll(b) + if err != nil { + panic(err) + } + + for i := range pa { + if pa[i] != pb[i] { + return int64(i) + } + } + return -1 +} + +func mustWriteRandFile(path string, size int) *os.File { + p := make([]byte, size) + _, err := rand.Read(p) + if err != nil { + panic(err) + } + + f, err := os.Create(path) + if err != nil { + panic(err) + } + + _, err = f.Write(p) + if err != nil { + panic(err) + } + + _, err = f.Seek(0, 0) + if err != nil { + panic(err) + } + + return f +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/diff.go b/Godeps/_workspace/src/github.com/kr/binarydist/diff.go new file mode 100644 index 0000000..1d2d951 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/diff.go @@ -0,0 +1,408 @@ +package binarydist + +import ( + "bytes" + "encoding/binary" + "io" + "io/ioutil" +) + +func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] } + +func split(I, V []int, start, length, h int) { + var i, j, k, x, jj, kk int + + if length < 16 { + for k = start; k < start+length; k += j { + j = 1 + x = V[I[k]+h] + for i = 1; k+i < start+length; i++ { + if V[I[k+i]+h] < x { + x = V[I[k+i]+h] + j = 0 + } + if V[I[k+i]+h] == x { + swap(I, k+i, k+j) + j++ + } + } + for i = 0; i < j; i++ { + V[I[k+i]] = k + j - 1 + } + if j == 1 { + I[k] = -1 + } + } + return + } + + x = V[I[start+length/2]+h] + jj = 0 + kk = 0 + for i = start; i < start+length; i++ { + if V[I[i]+h] < x { + jj++ + } + if V[I[i]+h] == x { + kk++ + } + } + jj += start + kk += jj + + i = start + j = 0 + k = 0 + for i < jj { + if V[I[i]+h] < x { + i++ + } else if V[I[i]+h] == x { + swap(I, i, jj+j) + j++ + } else { + swap(I, i, kk+k) + k++ + } + } + + for jj+j < kk { + if V[I[jj+j]+h] == x { + j++ + } else { + swap(I, jj+j, kk+k) + k++ + } + } + + if jj > start { + split(I, V, start, jj-start, h) + } + + for i = 0; i < kk-jj; i++ { + V[I[jj+i]] = kk - 1 + } + if jj == kk-1 { + I[jj] = -1 + } + + if start+length > kk { + split(I, V, kk, start+length-kk, h) + } +} + +func qsufsort(obuf []byte) []int { + var buckets [256]int + var i, h int + I := make([]int, len(obuf)+1) + V := make([]int, len(obuf)+1) + + for _, c := range obuf { + buckets[c]++ + } + for i = 1; i < 256; i++ { + buckets[i] += buckets[i-1] + } + copy(buckets[1:], buckets[:]) + buckets[0] = 0 + + for i, c := range obuf { + buckets[c]++ + I[buckets[c]] = i + } + + I[0] = len(obuf) + for i, c := range obuf { + V[i] = buckets[c] + } + + V[len(obuf)] = 0 + for i = 1; i < 256; i++ { + if buckets[i] == buckets[i-1]+1 { + I[buckets[i]] = -1 + } + } + I[0] = -1 + + for h = 1; I[0] != -(len(obuf) + 1); h += h { + var n int + for i = 0; i < len(obuf)+1; { + if I[i] < 0 { + n -= I[i] + i -= I[i] + } else { + if n != 0 { + I[i-n] = -n + } + n = V[I[i]] + 1 - i + split(I, V, i, n, h) + i += n + n = 0 + } + } + if n != 0 { + I[i-n] = -n + } + } + + for i = 0; i < len(obuf)+1; i++ { + I[V[i]] = i + } + return I +} + +func matchlen(a, b []byte) (i int) { + for i < len(a) && i < len(b) && a[i] == b[i] { + i++ + } + return i +} + +func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) { + if en-st < 2 { + x := matchlen(obuf[I[st]:], nbuf) + y := matchlen(obuf[I[en]:], nbuf) + + if x > y { + return I[st], x + } else { + return I[en], y + } + } + + x := st + (en-st)/2 + if bytes.Compare(obuf[I[x]:], nbuf) < 0 { + return search(I, obuf, nbuf, x, en) + } else { + return search(I, obuf, nbuf, st, x) + } + panic("unreached") +} + +// Diff computes the difference between old and new, according to the bsdiff +// algorithm, and writes the result to patch. +func Diff(old, new io.Reader, patch io.Writer) error { + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf, err := ioutil.ReadAll(new) + if err != nil { + return err + } + + pbuf, err := diffBytes(obuf, nbuf) + if err != nil { + return err + } + + _, err = patch.Write(pbuf) + return err +} + +func diffBytes(obuf, nbuf []byte) ([]byte, error) { + var patch seekBuffer + err := diff(obuf, nbuf, &patch) + if err != nil { + return nil, err + } + return patch.buf, nil +} + +func diff(obuf, nbuf []byte, patch io.WriteSeeker) error { + var lenf int + I := qsufsort(obuf) + db := make([]byte, len(nbuf)) + eb := make([]byte, len(nbuf)) + var dblen, eblen int + + var hdr header + hdr.Magic = magic + hdr.NewSize = int64(len(nbuf)) + err := binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + + // Compute the differences, writing ctrl as we go + pfbz2, err := newBzip2Writer(patch) + if err != nil { + return err + } + var scan, pos, length int + var lastscan, lastpos, lastoffset int + for scan < len(nbuf) { + var oldscore int + scan += length + for scsc := scan; scan < len(nbuf); scan++ { + pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf)) + + for ; scsc < scan+length; scsc++ { + if scsc+lastoffset < len(obuf) && + obuf[scsc+lastoffset] == nbuf[scsc] { + oldscore++ + } + } + + if (length == oldscore && length != 0) || length > oldscore+8 { + break + } + + if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] { + oldscore-- + } + } + + if length != oldscore || scan == len(nbuf) { + var s, Sf int + lenf = 0 + for i := 0; lastscan+i < scan && lastpos+i < len(obuf); { + if obuf[lastpos+i] == nbuf[lastscan+i] { + s++ + } + i++ + if s*2-i > Sf*2-lenf { + Sf = s + lenf = i + } + } + + lenb := 0 + if scan < len(nbuf) { + var s, Sb int + for i := 1; (scan >= lastscan+i) && (pos >= i); i++ { + if obuf[pos-i] == nbuf[scan-i] { + s++ + } + if s*2-i > Sb*2-lenb { + Sb = s + lenb = i + } + } + } + + if lastscan+lenf > scan-lenb { + overlap := (lastscan + lenf) - (scan - lenb) + s := 0 + Ss := 0 + lens := 0 + for i := 0; i < overlap; i++ { + if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] { + s++ + } + if nbuf[scan-lenb+i] == obuf[pos-lenb+i] { + s-- + } + if s > Ss { + Ss = s + lens = i + 1 + } + } + + lenf += lens - overlap + lenb -= lens + } + + for i := 0; i < lenf; i++ { + db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i] + } + for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ { + eb[eblen+i] = nbuf[lastscan+lenf+i] + } + + dblen += lenf + eblen += (scan - lenb) - (lastscan + lenf) + + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf)) + if err != nil { + pfbz2.Close() + return err + } + + val := (scan - lenb) - (lastscan + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + val = (pos - lenb) - (lastpos + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + lastscan = scan - lenb + lastpos = pos - lenb + lastoffset = pos - scan + } + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed ctrl data + l64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.CtrlLen = int64(l64 - 32) + + // Write compressed diff data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err := pfbz2.Write(db[:dblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != dblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed diff data + n64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.DiffLen = n64 - l64 + + // Write compressed extra data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err = pfbz2.Write(eb[:eblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != eblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Seek to the beginning, write the header, and close the file + _, err = patch.Seek(0, 0) + if err != nil { + return err + } + err = binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go b/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go new file mode 100644 index 0000000..9baa492 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/diff_test.go @@ -0,0 +1,67 @@ +package binarydist + +import ( + "bytes" + "io/ioutil" + "os" + "os/exec" + "testing" +) + +var diffT = []struct { + old *os.File + new *os.File +}{ + { + old: mustWriteRandFile("test.old", 1e3), + new: mustWriteRandFile("test.new", 1e3), + }, + { + old: mustOpen("testdata/sample.old"), + new: mustOpen("testdata/sample.new"), + }, +} + +func TestDiff(t *testing.T) { + for _, s := range diffT { + got, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + os.Remove(got.Name()) + + exp, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + + cmd := exec.Command("bsdiff", s.old.Name(), s.new.Name(), exp.Name()) + cmd.Stdout = os.Stdout + err = cmd.Run() + os.Remove(exp.Name()) + if err != nil { + panic(err) + } + + err = Diff(s.old, s.new, got) + if err != nil { + t.Fatal("err", err) + } + + _, err = got.Seek(0, 0) + if err != nil { + panic(err) + } + gotBuf := mustReadAll(got) + expBuf := mustReadAll(exp) + + if !bytes.Equal(gotBuf, expBuf) { + t.Fail() + t.Logf("diff %s %s", s.old.Name(), s.new.Name()) + t.Logf("%s: len(got) = %d", got.Name(), len(gotBuf)) + t.Logf("%s: len(exp) = %d", exp.Name(), len(expBuf)) + i := matchlen(gotBuf, expBuf) + t.Logf("produced different output at pos %d; %d != %d", i, gotBuf[i], expBuf[i]) + } + } +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/doc.go b/Godeps/_workspace/src/github.com/kr/binarydist/doc.go new file mode 100644 index 0000000..3c92d87 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/doc.go @@ -0,0 +1,24 @@ +// Package binarydist implements binary diff and patch as described on +// http://www.daemonology.net/bsdiff/. It reads and writes files +// compatible with the tools there. +package binarydist + +var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'} + +// File format: +// 0 8 "BSDIFF40" +// 8 8 X +// 16 8 Y +// 24 8 sizeof(newfile) +// 32 X bzip2(control block) +// 32+X Y bzip2(diff block) +// 32+X+Y ??? bzip2(extra block) +// with control block a set of triples (x,y,z) meaning "add x bytes +// from oldfile to x bytes from the diff block; copy y bytes from the +// extra block; seek forwards in oldfile by z bytes". +type header struct { + Magic [8]byte + CtrlLen int64 + DiffLen int64 + NewSize int64 +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go b/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go new file mode 100644 index 0000000..75ba585 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/encoding.go @@ -0,0 +1,53 @@ +package binarydist + +// SignMagLittleEndian is the numeric encoding used by the bsdiff tools. +// It implements binary.ByteOrder using a sign-magnitude format +// and little-endian byte order. Only methods Uint64 and String +// have been written; the rest panic. +type signMagLittleEndian struct{} + +func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") } + +func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") } + +func (signMagLittleEndian) Uint64(b []byte) uint64 { + y := int64(b[0]) | + int64(b[1])<<8 | + int64(b[2])<<16 | + int64(b[3])<<24 | + int64(b[4])<<32 | + int64(b[5])<<40 | + int64(b[6])<<48 | + int64(b[7]&0x7f)<<56 + + if b[7]&0x80 != 0 { + y = -y + } + return uint64(y) +} + +func (signMagLittleEndian) PutUint64(b []byte, v uint64) { + x := int64(v) + neg := x < 0 + if neg { + x = -x + } + + b[0] = byte(x) + b[1] = byte(x >> 8) + b[2] = byte(x >> 16) + b[3] = byte(x >> 24) + b[4] = byte(x >> 32) + b[5] = byte(x >> 40) + b[6] = byte(x >> 48) + b[7] = byte(x >> 56) + if neg { + b[7] |= 0x80 + } +} + +func (signMagLittleEndian) String() string { return "signMagLittleEndian" } diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/patch.go b/Godeps/_workspace/src/github.com/kr/binarydist/patch.go new file mode 100644 index 0000000..eb03225 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/patch.go @@ -0,0 +1,109 @@ +package binarydist + +import ( + "bytes" + "compress/bzip2" + "encoding/binary" + "errors" + "io" + "io/ioutil" +) + +var ErrCorrupt = errors.New("corrupt patch") + +// Patch applies patch to old, according to the bspatch algorithm, +// and writes the result to new. +func Patch(old io.Reader, new io.Writer, patch io.Reader) error { + var hdr header + err := binary.Read(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + if hdr.Magic != magic { + return ErrCorrupt + } + if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 { + return ErrCorrupt + } + + ctrlbuf := make([]byte, hdr.CtrlLen) + _, err = io.ReadFull(patch, ctrlbuf) + if err != nil { + return err + } + cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf)) + + diffbuf := make([]byte, hdr.DiffLen) + _, err = io.ReadFull(patch, diffbuf) + if err != nil { + return err + } + dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf)) + + // The entire rest of the file is the extra block. + epfbz2 := bzip2.NewReader(patch) + + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf := make([]byte, hdr.NewSize) + + var oldpos, newpos int64 + for newpos < hdr.NewSize { + var ctrl struct{ Add, Copy, Seek int64 } + err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl) + if err != nil { + return err + } + + // Sanity-check + if newpos+ctrl.Add > hdr.NewSize { + return ErrCorrupt + } + + // Read diff string + _, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add]) + if err != nil { + return ErrCorrupt + } + + // Add old data to diff string + for i := int64(0); i < ctrl.Add; i++ { + if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) { + nbuf[newpos+i] += obuf[oldpos+i] + } + } + + // Adjust pointers + newpos += ctrl.Add + oldpos += ctrl.Add + + // Sanity-check + if newpos+ctrl.Copy > hdr.NewSize { + return ErrCorrupt + } + + // Read extra string + _, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy]) + if err != nil { + return ErrCorrupt + } + + // Adjust pointers + newpos += ctrl.Copy + oldpos += ctrl.Seek + } + + // Write the new file + for len(nbuf) > 0 { + n, err := new.Write(nbuf) + if err != nil { + return err + } + nbuf = nbuf[n:] + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go b/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go new file mode 100644 index 0000000..840a919 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/patch_test.go @@ -0,0 +1,62 @@ +package binarydist + +import ( + "io/ioutil" + "os" + "os/exec" + "testing" +) + +func TestPatch(t *testing.T) { + mustWriteRandFile("test.old", 1e3) + mustWriteRandFile("test.new", 1e3) + + got, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + os.Remove(got.Name()) + + err = exec.Command("bsdiff", "test.old", "test.new", "test.patch").Run() + if err != nil { + panic(err) + } + + err = Patch(mustOpen("test.old"), got, mustOpen("test.patch")) + if err != nil { + t.Fatal("err", err) + } + + ref, err := got.Seek(0, 2) + if err != nil { + panic(err) + } + + t.Logf("got %d bytes", ref) + if n := fileCmp(got, mustOpen("test.new")); n > -1 { + t.Fatalf("produced different output at pos %d", n) + } +} + +func TestPatchHk(t *testing.T) { + got, err := ioutil.TempFile("/tmp", "bspatch.") + if err != nil { + panic(err) + } + os.Remove(got.Name()) + + err = Patch(mustOpen("testdata/sample.old"), got, mustOpen("testdata/sample.patch")) + if err != nil { + t.Fatal("err", err) + } + + ref, err := got.Seek(0, 2) + if err != nil { + panic(err) + } + + t.Logf("got %d bytes", ref) + if n := fileCmp(got, mustOpen("testdata/sample.new")); n > -1 { + t.Fatalf("produced different output at pos %d", n) + } +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/seek.go b/Godeps/_workspace/src/github.com/kr/binarydist/seek.go new file mode 100644 index 0000000..96c0346 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/seek.go @@ -0,0 +1,43 @@ +package binarydist + +import ( + "errors" +) + +type seekBuffer struct { + buf []byte + pos int +} + +func (b *seekBuffer) Write(p []byte) (n int, err error) { + n = copy(b.buf[b.pos:], p) + if n == len(p) { + b.pos += n + return n, nil + } + b.buf = append(b.buf, p[n:]...) + b.pos += len(p) + return len(p), nil +} + +func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) { + var abs int64 + switch whence { + case 0: + abs = offset + case 1: + abs = int64(b.pos) + offset + case 2: + abs = int64(len(b.buf)) + offset + default: + return 0, errors.New("binarydist: invalid whence") + } + if abs < 0 { + return 0, errors.New("binarydist: negative position") + } + if abs >= 1<<31 { + return 0, errors.New("binarydist: position out of range") + } + b.pos = int(abs) + return abs, nil +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go b/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go new file mode 100644 index 0000000..be483c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/binarydist/sort_test.go @@ -0,0 +1,33 @@ +package binarydist + +import ( + "bytes" + "crypto/rand" + "testing" +) + +var sortT = [][]byte{ + mustRandBytes(1000), + mustReadAll(mustOpen("test.old")), + []byte("abcdefabcdef"), +} + +func TestQsufsort(t *testing.T) { + for _, s := range sortT { + I := qsufsort(s) + for i := 1; i < len(I); i++ { + if bytes.Compare(s[I[i-1]:], s[I[i]:]) > 0 { + t.Fatalf("unsorted at %d", i) + } + } + } +} + +func mustRandBytes(n int) []byte { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + panic(err) + } + return b +} diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new b/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new new file mode 100644 index 0000000..592cdbe Binary files /dev/null and b/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.new differ diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old b/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old new file mode 100644 index 0000000..7bc64da Binary files /dev/null and b/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.old differ diff --git a/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.patch b/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.patch new file mode 100644 index 0000000..516a3a9 Binary files /dev/null and b/Godeps/_workspace/src/github.com/kr/binarydist/testdata/sample.patch differ diff --git a/Godeps/_workspace/src/github.com/kr/pretty/.gitignore b/Godeps/_workspace/src/github.com/kr/pretty/.gitignore new file mode 100644 index 0000000..1f0a99f --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/.gitignore @@ -0,0 +1,4 @@ +[568].out +_go* +_test* +_obj diff --git a/Godeps/_workspace/src/github.com/kr/pretty/License b/Godeps/_workspace/src/github.com/kr/pretty/License new file mode 100644 index 0000000..480a328 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/License @@ -0,0 +1,19 @@ +Copyright 2012 Keith Rarick + +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. diff --git a/Godeps/_workspace/src/github.com/kr/pretty/Readme b/Godeps/_workspace/src/github.com/kr/pretty/Readme new file mode 100644 index 0000000..c589fc6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/Readme @@ -0,0 +1,9 @@ +package pretty + + import "github.com/kr/pretty" + + Package pretty provides pretty-printing for Go values. + +Documentation + + http://godoc.org/github.com/kr/pretty diff --git a/Godeps/_workspace/src/github.com/kr/pretty/diff.go b/Godeps/_workspace/src/github.com/kr/pretty/diff.go new file mode 100644 index 0000000..64fac64 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/diff.go @@ -0,0 +1,148 @@ +package pretty + +import ( + "fmt" + "io" + "reflect" +) + +type sbuf []string + +func (s *sbuf) Write(b []byte) (int, error) { + *s = append(*s, string(b)) + return len(b), nil +} + +// Diff returns a slice where each element describes +// a difference between a and b. +func Diff(a, b interface{}) (desc []string) { + Fdiff((*sbuf)(&desc), a, b) + return desc +} + +// Fdiff writes to w a description of the differences between a and b. +func Fdiff(w io.Writer, a, b interface{}) { + diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b)) +} + +type diffWriter struct { + w io.Writer + l string // label +} + +func (w diffWriter) printf(f string, a ...interface{}) { + var l string + if w.l != "" { + l = w.l + ": " + } + fmt.Fprintf(w.w, l+f, a...) +} + +func (w diffWriter) diff(av, bv reflect.Value) { + if !av.IsValid() && bv.IsValid() { + w.printf("nil != %#v", bv.Interface()) + return + } + if av.IsValid() && !bv.IsValid() { + w.printf("%#v != nil", av.Interface()) + return + } + if !av.IsValid() && !bv.IsValid() { + return + } + + at := av.Type() + bt := bv.Type() + if at != bt { + w.printf("%v != %v", at, bt) + return + } + + // numeric types, including bool + if at.Kind() < reflect.Array { + a, b := av.Interface(), bv.Interface() + if a != b { + w.printf("%#v != %#v", a, b) + } + return + } + + switch at.Kind() { + case reflect.String: + a, b := av.Interface(), bv.Interface() + if a != b { + w.printf("%q != %q", a, b) + } + case reflect.Ptr: + switch { + case av.IsNil() && !bv.IsNil(): + w.printf("nil != %v", bv.Interface()) + case !av.IsNil() && bv.IsNil(): + w.printf("%v != nil", av.Interface()) + case !av.IsNil() && !bv.IsNil(): + w.diff(av.Elem(), bv.Elem()) + } + case reflect.Struct: + for i := 0; i < av.NumField(); i++ { + w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i)) + } + case reflect.Map: + ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys()) + for _, k := range ak { + w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) + w.printf("%q != (missing)", av.MapIndex(k)) + } + for _, k := range both { + w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) + w.diff(av.MapIndex(k), bv.MapIndex(k)) + } + for _, k := range bk { + w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) + w.printf("(missing) != %q", bv.MapIndex(k)) + } + case reflect.Interface: + w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface())) + default: + if !reflect.DeepEqual(av.Interface(), bv.Interface()) { + w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface())) + } + } +} + +func (d diffWriter) relabel(name string) (d1 diffWriter) { + d1 = d + if d.l != "" && name[0] != '[' { + d1.l += "." + } + d1.l += name + return d1 +} + +func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) { + for _, av := range a { + inBoth := false + for _, bv := range b { + if reflect.DeepEqual(av.Interface(), bv.Interface()) { + inBoth = true + both = append(both, av) + break + } + } + if !inBoth { + ak = append(ak, av) + } + } + for _, bv := range b { + inBoth := false + for _, av := range a { + if reflect.DeepEqual(av.Interface(), bv.Interface()) { + inBoth = true + break + } + } + if !inBoth { + bk = append(bk, bv) + } + } + return +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/diff_test.go b/Godeps/_workspace/src/github.com/kr/pretty/diff_test.go new file mode 100644 index 0000000..02d1953 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/diff_test.go @@ -0,0 +1,73 @@ +package pretty + +import ( + "testing" +) + +type difftest struct { + a interface{} + b interface{} + exp []string +} + +type S struct { + A int + S *S + I interface{} + C []int +} + +var diffs = []difftest{ + {a: nil, b: nil}, + {a: S{A: 1}, b: S{A: 1}}, + + {0, "", []string{`int != string`}}, + {0, 1, []string{`0 != 1`}}, + {S{}, new(S), []string{`pretty.S != *pretty.S`}}, + {"a", "b", []string{`"a" != "b"`}}, + {S{}, S{A: 1}, []string{`A: 0 != 1`}}, + {new(S), &S{A: 1}, []string{`A: 0 != 1`}}, + {S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}}, + {S{}, S{I: 0}, []string{`I: nil != 0`}}, + {S{I: 1}, S{I: "x"}, []string{`I: int != string`}}, + {S{}, S{C: []int{1}}, []string{`C: []int(nil) != []int{1}`}}, + {S{C: []int{}}, S{C: []int{1}}, []string{`C: []int{} != []int{1}`}}, + {S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &{0 []}`}}, +} + +func TestDiff(t *testing.T) { + for _, tt := range diffs { + got := Diff(tt.a, tt.b) + eq := len(got) == len(tt.exp) + if eq { + for i := range got { + eq = eq && got[i] == tt.exp[i] + } + } + if !eq { + t.Errorf("diffing % #v", tt.a) + t.Errorf("with % #v", tt.b) + diffdiff(t, got, tt.exp) + continue + } + } +} + +func diffdiff(t *testing.T, got, exp []string) { + minus(t, "unexpected:", got, exp) + minus(t, "missing:", exp, got) +} + +func minus(t *testing.T, s string, a, b []string) { + var i, j int + for i = 0; i < len(a); i++ { + for j = 0; j < len(b); j++ { + if a[i] == b[j] { + break + } + } + if j == len(b) { + t.Error(s, a[i]) + } + } +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/example_test.go b/Godeps/_workspace/src/github.com/kr/pretty/example_test.go new file mode 100644 index 0000000..ecf40f3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/example_test.go @@ -0,0 +1,20 @@ +package pretty_test + +import ( + "fmt" + "github.com/kr/pretty" +) + +func Example() { + type myType struct { + a, b int + } + var x = []myType{{1, 2}, {3, 4}, {5, 6}} + fmt.Printf("%# v", pretty.Formatter(x)) + // output: + // []pretty_test.myType{ + // {a:1, b:2}, + // {a:3, b:4}, + // {a:5, b:6}, + // } +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/formatter.go b/Godeps/_workspace/src/github.com/kr/pretty/formatter.go new file mode 100644 index 0000000..1161de7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/formatter.go @@ -0,0 +1,300 @@ +package pretty + +import ( + "fmt" + "github.com/kr/text" + "io" + "reflect" + "strconv" + "text/tabwriter" +) + +const ( + limit = 50 +) + +type formatter struct { + x interface{} + force bool + quote bool +} + +// Formatter makes a wrapper, f, that will format x as go source with line +// breaks and tabs. Object f responds to the "%v" formatting verb when both the +// "#" and " " (space) flags are set, for example: +// +// fmt.Sprintf("%# v", Formatter(x)) +// +// If one of these two flags is not set, or any other verb is used, f will +// format x according to the usual rules of package fmt. +// In particular, if x satisfies fmt.Formatter, then x.Format will be called. +func Formatter(x interface{}) (f fmt.Formatter) { + return formatter{x: x, quote: true} +} + +func (fo formatter) String() string { + return fmt.Sprint(fo.x) // unwrap it +} + +func (fo formatter) passThrough(f fmt.State, c rune) { + s := "%" + for i := 0; i < 128; i++ { + if f.Flag(i) { + s += string(i) + } + } + if w, ok := f.Width(); ok { + s += fmt.Sprintf("%d", w) + } + if p, ok := f.Precision(); ok { + s += fmt.Sprintf(".%d", p) + } + s += string(c) + fmt.Fprintf(f, s, fo.x) +} + +func (fo formatter) Format(f fmt.State, c rune) { + if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') { + w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0) + p := &printer{tw: w, Writer: w} + p.printValue(reflect.ValueOf(fo.x), true, fo.quote) + w.Flush() + return + } + fo.passThrough(f, c) +} + +type printer struct { + io.Writer + tw *tabwriter.Writer +} + +func (p *printer) indent() *printer { + q := *p + q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0) + q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'}) + return &q +} + +func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) { + if showType { + io.WriteString(p, v.Type().String()) + fmt.Fprintf(p, "(%#v)", x) + } else { + fmt.Fprintf(p, "%#v", x) + } +} + +func (p *printer) printValue(v reflect.Value, showType, quote bool) { + switch v.Kind() { + case reflect.Bool: + p.printInline(v, v.Bool(), showType) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p.printInline(v, v.Int(), showType) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p.printInline(v, v.Uint(), showType) + case reflect.Float32, reflect.Float64: + p.printInline(v, v.Float(), showType) + case reflect.Complex64, reflect.Complex128: + fmt.Fprintf(p, "%#v", v.Complex()) + case reflect.String: + p.fmtString(v.String(), quote) + case reflect.Map: + t := v.Type() + if showType { + io.WriteString(p, t.String()) + } + writeByte(p, '{') + if nonzero(v) { + expand := !canInline(v.Type()) + pp := p + if expand { + writeByte(p, '\n') + pp = p.indent() + } + keys := v.MapKeys() + for i := 0; i < v.Len(); i++ { + showTypeInStruct := true + k := keys[i] + mv := v.MapIndex(k) + pp.printValue(k, false, true) + writeByte(pp, ':') + if expand { + writeByte(pp, '\t') + } + showTypeInStruct = t.Elem().Kind() == reflect.Interface + pp.printValue(mv, showTypeInStruct, true) + if expand { + io.WriteString(pp, ",\n") + } else if i < v.Len()-1 { + io.WriteString(pp, ", ") + } + } + if expand { + pp.tw.Flush() + } + } + writeByte(p, '}') + case reflect.Struct: + t := v.Type() + if showType { + io.WriteString(p, t.String()) + } + writeByte(p, '{') + if nonzero(v) { + expand := !canInline(v.Type()) + pp := p + if expand { + writeByte(p, '\n') + pp = p.indent() + } + for i := 0; i < v.NumField(); i++ { + showTypeInStruct := true + if f := t.Field(i); f.Name != "" { + io.WriteString(pp, f.Name) + writeByte(pp, ':') + if expand { + writeByte(pp, '\t') + } + showTypeInStruct = f.Type.Kind() == reflect.Interface + } + pp.printValue(getField(v, i), showTypeInStruct, true) + if expand { + io.WriteString(pp, ",\n") + } else if i < v.NumField()-1 { + io.WriteString(pp, ", ") + } + } + if expand { + pp.tw.Flush() + } + } + writeByte(p, '}') + case reflect.Interface: + switch e := v.Elem(); { + case e.Kind() == reflect.Invalid: + io.WriteString(p, "nil") + case e.IsValid(): + p.printValue(e, showType, true) + default: + io.WriteString(p, v.Type().String()) + io.WriteString(p, "(nil)") + } + case reflect.Array, reflect.Slice: + t := v.Type() + if showType { + io.WriteString(p, t.String()) + } + if v.Kind() == reflect.Slice && v.IsNil() && showType { + io.WriteString(p, "(nil)") + break + } + if v.Kind() == reflect.Slice && v.IsNil() { + io.WriteString(p, "nil") + break + } + writeByte(p, '{') + expand := !canInline(v.Type()) + pp := p + if expand { + writeByte(p, '\n') + pp = p.indent() + } + for i := 0; i < v.Len(); i++ { + showTypeInSlice := t.Elem().Kind() == reflect.Interface + pp.printValue(v.Index(i), showTypeInSlice, true) + if expand { + io.WriteString(pp, ",\n") + } else if i < v.Len()-1 { + io.WriteString(pp, ", ") + } + } + if expand { + pp.tw.Flush() + } + writeByte(p, '}') + case reflect.Ptr: + e := v.Elem() + if !e.IsValid() { + writeByte(p, '(') + io.WriteString(p, v.Type().String()) + io.WriteString(p, ")(nil)") + } else { + writeByte(p, '&') + p.printValue(e, true, true) + } + case reflect.Chan: + x := v.Pointer() + if showType { + writeByte(p, '(') + io.WriteString(p, v.Type().String()) + fmt.Fprintf(p, ")(%#v)", x) + } else { + fmt.Fprintf(p, "%#v", x) + } + case reflect.Func: + io.WriteString(p, v.Type().String()) + io.WriteString(p, " {...}") + case reflect.UnsafePointer: + p.printInline(v, v.Pointer(), showType) + case reflect.Invalid: + io.WriteString(p, "nil") + } +} + +func canInline(t reflect.Type) bool { + switch t.Kind() { + case reflect.Map: + return !canExpand(t.Elem()) + case reflect.Struct: + for i := 0; i < t.NumField(); i++ { + if canExpand(t.Field(i).Type) { + return false + } + } + return true + case reflect.Interface: + return false + case reflect.Array, reflect.Slice: + return !canExpand(t.Elem()) + case reflect.Ptr: + return false + case reflect.Chan, reflect.Func, reflect.UnsafePointer: + return false + } + return true +} + +func canExpand(t reflect.Type) bool { + switch t.Kind() { + case reflect.Map, reflect.Struct, + reflect.Interface, reflect.Array, reflect.Slice, + reflect.Ptr: + return true + } + return false +} + +func (p *printer) fmtString(s string, quote bool) { + if quote { + s = strconv.Quote(s) + } + io.WriteString(p, s) +} + +func tryDeepEqual(a, b interface{}) bool { + defer func() { recover() }() + return reflect.DeepEqual(a, b) +} + +func writeByte(w io.Writer, b byte) { + w.Write([]byte{b}) +} + +func getField(v reflect.Value, i int) reflect.Value { + val := v.Field(i) + if val.Kind() == reflect.Interface && !val.IsNil() { + val = val.Elem() + } + return val +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/formatter_test.go b/Godeps/_workspace/src/github.com/kr/pretty/formatter_test.go new file mode 100644 index 0000000..303f033 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/formatter_test.go @@ -0,0 +1,146 @@ +package pretty + +import ( + "fmt" + "io" + "testing" + "unsafe" +) + +type test struct { + v interface{} + s string +} + +type LongStructTypeName struct { + longFieldName interface{} + otherLongFieldName interface{} +} + +type SA struct { + t *T +} + +type T struct { + x, y int +} + +type F int + +func (f F) Format(s fmt.State, c rune) { + fmt.Fprintf(s, "F(%d)", int(f)) +} + +var long = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +var gosyntax = []test{ + {nil, `nil`}, + {"", `""`}, + {"a", `"a"`}, + {1, "int(1)"}, + {1.0, "float64(1)"}, + {[]int(nil), "[]int(nil)"}, + {[0]int{}, "[0]int{}"}, + {complex(1, 0), "(1+0i)"}, + //{make(chan int), "(chan int)(0x1234)"}, + {unsafe.Pointer(uintptr(1)), "unsafe.Pointer(0x1)"}, + {func(int) {}, "func(int) {...}"}, + {map[int]int{1: 1}, "map[int]int{1:1}"}, + {int32(1), "int32(1)"}, + {io.EOF, `&errors.errorString{s:"EOF"}`}, + {[]string{"a"}, `[]string{"a"}`}, + { + []string{long}, + `[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`, + }, + {F(5), "pretty.F(5)"}, + { + SA{&T{1, 2}}, + `pretty.SA{ + t: &pretty.T{x:1, y:2}, +}`, + }, + { + map[int][]byte{1: []byte{}}, + `map[int][]uint8{ + 1: {}, +}`, + }, + { + map[int]T{1: T{}}, + `map[int]pretty.T{ + 1: {}, +}`, + }, + { + long, + `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"`, + }, + { + LongStructTypeName{ + longFieldName: LongStructTypeName{}, + otherLongFieldName: long, + }, + `pretty.LongStructTypeName{ + longFieldName: pretty.LongStructTypeName{}, + otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", +}`, + }, + { + &LongStructTypeName{ + longFieldName: &LongStructTypeName{}, + otherLongFieldName: (*LongStructTypeName)(nil), + }, + `&pretty.LongStructTypeName{ + longFieldName: &pretty.LongStructTypeName{}, + otherLongFieldName: (*pretty.LongStructTypeName)(nil), +}`, + }, + { + []LongStructTypeName{ + {nil, nil}, + {3, 3}, + {long, nil}, + }, + `[]pretty.LongStructTypeName{ + {}, + { + longFieldName: int(3), + otherLongFieldName: int(3), + }, + { + longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + otherLongFieldName: nil, + }, +}`, + }, + { + []interface{}{ + LongStructTypeName{nil, nil}, + []byte{1, 2, 3}, + T{3, 4}, + LongStructTypeName{long, nil}, + }, + `[]interface {}{ + pretty.LongStructTypeName{}, + []uint8{0x1, 0x2, 0x3}, + pretty.T{x:3, y:4}, + pretty.LongStructTypeName{ + longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + otherLongFieldName: nil, + }, +}`, + }, +} + +func TestGoSyntax(t *testing.T) { + for _, tt := range gosyntax { + s := fmt.Sprintf("%# v", Formatter(tt.v)) + if tt.s != s { + t.Errorf("expected %q", tt.s) + t.Errorf("got %q", s) + t.Errorf("expraw\n%s", tt.s) + t.Errorf("gotraw\n%s", s) + } + } +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/pretty.go b/Godeps/_workspace/src/github.com/kr/pretty/pretty.go new file mode 100644 index 0000000..d3df868 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/pretty.go @@ -0,0 +1,98 @@ +// Package pretty provides pretty-printing for Go values. This is +// useful during debugging, to avoid wrapping long output lines in +// the terminal. +// +// It provides a function, Formatter, that can be used with any +// function that accepts a format string. It also provides +// convenience wrappers for functions in packages fmt and log. +package pretty + +import ( + "fmt" + "io" + "log" +) + +// Errorf is a convenience wrapper for fmt.Errorf. +// +// Calling Errorf(f, x, y) is equivalent to +// fmt.Errorf(f, Formatter(x), Formatter(y)). +func Errorf(format string, a ...interface{}) error { + return fmt.Errorf(format, wrap(a, false)...) +} + +// Fprintf is a convenience wrapper for fmt.Fprintf. +// +// Calling Fprintf(w, f, x, y) is equivalent to +// fmt.Fprintf(w, f, Formatter(x), Formatter(y)). +func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) { + return fmt.Fprintf(w, format, wrap(a, false)...) +} + +// Log is a convenience wrapper for log.Printf. +// +// Calling Log(x, y) is equivalent to +// log.Print(Formatter(x), Formatter(y)), but each operand is +// formatted with "%# v". +func Log(a ...interface{}) { + log.Print(wrap(a, true)...) +} + +// Logf is a convenience wrapper for log.Printf. +// +// Calling Logf(f, x, y) is equivalent to +// log.Printf(f, Formatter(x), Formatter(y)). +func Logf(format string, a ...interface{}) { + log.Printf(format, wrap(a, false)...) +} + +// Logln is a convenience wrapper for log.Printf. +// +// Calling Logln(x, y) is equivalent to +// log.Println(Formatter(x), Formatter(y)), but each operand is +// formatted with "%# v". +func Logln(a ...interface{}) { + log.Println(wrap(a, true)...) +} + +// Print pretty-prints its operands and writes to standard output. +// +// Calling Print(x, y) is equivalent to +// fmt.Print(Formatter(x), Formatter(y)), but each operand is +// formatted with "%# v". +func Print(a ...interface{}) (n int, errno error) { + return fmt.Print(wrap(a, true)...) +} + +// Printf is a convenience wrapper for fmt.Printf. +// +// Calling Printf(f, x, y) is equivalent to +// fmt.Printf(f, Formatter(x), Formatter(y)). +func Printf(format string, a ...interface{}) (n int, errno error) { + return fmt.Printf(format, wrap(a, false)...) +} + +// Println pretty-prints its operands and writes to standard output. +// +// Calling Print(x, y) is equivalent to +// fmt.Println(Formatter(x), Formatter(y)), but each operand is +// formatted with "%# v". +func Println(a ...interface{}) (n int, errno error) { + return fmt.Println(wrap(a, true)...) +} + +// Sprintf is a convenience wrapper for fmt.Sprintf. +// +// Calling Sprintf(f, x, y) is equivalent to +// fmt.Sprintf(f, Formatter(x), Formatter(y)). +func Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, wrap(a, false)...) +} + +func wrap(a []interface{}, force bool) []interface{} { + w := make([]interface{}, len(a)) + for i, x := range a { + w[i] = formatter{x: x, force: force} + } + return w +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/zero.go b/Godeps/_workspace/src/github.com/kr/pretty/zero.go new file mode 100644 index 0000000..abb5b6f --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/zero.go @@ -0,0 +1,41 @@ +package pretty + +import ( + "reflect" +) + +func nonzero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Bool: + return v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() != 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() != 0 + case reflect.Float32, reflect.Float64: + return v.Float() != 0 + case reflect.Complex64, reflect.Complex128: + return v.Complex() != complex(0, 0) + case reflect.String: + return v.String() != "" + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + if nonzero(getField(v, i)) { + return true + } + } + return false + case reflect.Array: + for i := 0; i < v.Len(); i++ { + if nonzero(v.Index(i)) { + return true + } + } + return false + case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func: + return !v.IsNil() + case reflect.UnsafePointer: + return v.Pointer() != 0 + } + return true +} diff --git a/Godeps/_workspace/src/github.com/kr/text/Readme b/Godeps/_workspace/src/github.com/kr/text/Readme new file mode 100644 index 0000000..7e6e7c0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/Readme @@ -0,0 +1,3 @@ +This is a Go package for manipulating paragraphs of text. + +See http://go.pkgdoc.org/github.com/kr/text for full documentation. diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/LICENSE b/Godeps/_workspace/src/github.com/kr/text/colwriter/LICENSE new file mode 100644 index 0000000..6110e31 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/colwriter/LICENSE @@ -0,0 +1,257 @@ +Copyright © 2000-2009 Lucent Technologies. All Rights Reserved. +Portions Copyright © 2001-2008 Russ Cox +Portions Copyright © 2008-2009 Google Inc. + +=================================================================== + +The bulk of this software is derived from Plan 9 and is thus distributed +under the Lucent Public License, Version 1.02, reproduced below. + +There are a few exceptions: libutf, libfmt, and libregexp are distributed +under simpler BSD-like boilerplates. See the LICENSE files in those +directories. There are other exceptions, also marked with LICENSE files +in their directories or marked at the top of the file. + +The bitmap fonts in the font/luc, font/lucm, font/lucsans, and font/pelm +directory are copyright B&H Inc. and distributed under more restricted +terms under agreement with B&H. See the NOTICE file in those directories. + +=================================================================== + +Lucent Public License Version 1.02 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE +PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original + Program, and + b. in the case of each Contributor, + + i. changes to the Program, and + ii. additions to the Program; + + where such changes and/or additions to the Program were added to the + Program by such Contributor itself or anyone acting on such + Contributor's behalf, and the Contributor explicitly consents, in + accordance with Section 3C, to characterization of the changes and/or + additions as Contributions. + +"Contributor" means LUCENT and any other entity that has Contributed a +Contribution to the Program. + +"Distributor" means a Recipient that distributes the Program, +modifications to the Program, or any part thereof. + +"Licensed Patents" mean patent claims licensable by a Contributor +which are necessarily infringed by the use or sale of its Contribution +alone or when combined with the Program. + +"Original Program" means the original version of the software +accompanying this Agreement as released by LUCENT, including source +code, object code and documentation, if any. + +"Program" means the Original Program and Contributions or any part +thereof + +"Recipient" means anyone who receives the Program under this +Agreement, including all Contributors. + +2. GRANT OF RIGHTS + + a. Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare derivative works of, publicly display, + publicly perform, distribute and sublicense the Contribution of such + Contributor, if any, and such derivative works, in source code and + object code form. + + b. Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, if + any, in source code and object code form. The patent license granted + by a Contributor shall also apply to the combination of the + Contribution of that Contributor and the Program if, at the time the + Contribution is added by the Contributor, such addition of the + Contribution causes such combination to be covered by the Licensed + Patents. The patent license granted by a Contributor shall not apply + to (i) any other combinations which include the Contribution, nor to + (ii) Contributions of other Contributors. No hardware per se is + licensed hereunder. + + c. Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. Each + Contributor disclaims any liability to Recipient for claims brought by + any other entity based on infringement of intellectual property rights + or otherwise. As a condition to exercising the rights and licenses + granted hereunder, each Recipient hereby assumes sole responsibility + to secure any other intellectual property rights needed, if any. For + example, if a third party patent license is required to allow + Recipient to distribute the Program, it is Recipient's responsibility + to acquire that license before distributing the Program. + + d. Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A. Distributor may choose to distribute the Program in any form under +this Agreement or under its own license agreement, provided that: + + a. it complies with the terms and conditions of this Agreement; + + b. if the Program is distributed in source code or other tangible + form, a copy of this Agreement or Distributor's own license agreement + is included with each copy of the Program; and + + c. if distributed under Distributor's own license agreement, such + license agreement: + + i. effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii. effectively excludes on behalf of all Contributors all liability + for damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; and + iii. states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party. + +B. Each Distributor must include the following in a conspicuous + location in the Program: + + Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights + Reserved. + +C. In addition, each Contributor must identify itself as the +originator of its Contribution in a manner that reasonably allows +subsequent Recipients to identify the originator of the Contribution. +Also, each Contributor must agree that the additions and/or changes +are intended to be a Contribution. Once a Contribution is contributed, +it may not thereafter be revoked. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain +responsibilities with respect to end users, business partners and the +like. While this license is intended to facilitate the commercial use +of the Program, the Distributor who includes the Program in a +commercial product offering should do so in a manner which does not +create potential liability for Contributors. Therefore, if a +Distributor includes the Program in a commercial product offering, +such Distributor ("Commercial Distributor") hereby agrees to defend +and indemnify every Contributor ("Indemnified Contributor") against +any losses, damages and costs (collectively"Losses") arising from +claims, lawsuits and other legal actions brought by a third party +against the Indemnified Contributor to the extent caused by the acts +or omissions of such Commercial Distributor in connection with its +distribution of the Program in a commercial product offering. The +obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. +In order to qualify, an Indemnified Contributor must: a) promptly +notify the Commercial Distributor in writing of such claim, and b) +allow the Commercial Distributor to control, and cooperate with the +Commercial Distributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such +claim at its own expense. + +For example, a Distributor might include the Program in a commercial +product offering, Product X. That Distributor is then a Commercial +Distributor. If that Commercial Distributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Distributor's responsibility +alone. Under this section, the Commercial Distributor would have to +defend claims against the Contributors related to those performance +claims and warranties, and if a court requires any Contributor to pay +any damages as a result, the Commercial Distributor must pay those +damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS +PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY +WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY +OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement, including but not limited to +the risks and costs of program errors, compliance with applicable +laws, damage to or loss of data, programs or equipment, and +unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR +ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING +WITHOUT LIMITATION LOST PROFITS), 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 OR +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. EXPORT CONTROL + +Recipient agrees that Recipient alone is responsible for compliance +with the United States export administration regulations (and the +export control laws and regulation of any other countries). + +8. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against a Contributor with +respect to a patent applicable to software (including a cross-claim or +counterclaim in a lawsuit), then any patent licenses granted by that +Contributor to such Recipient under this Agreement shall terminate as +of the date such litigation is filed. In addition, if Recipient +institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program +itself (excluding combinations of the Program with other software or +hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and +survive. + +LUCENT may publish new versions (including revisions) of this +Agreement from time to time. Each new version of the Agreement will be +given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new +version of the Agreement is published, Contributor may elect to +distribute the Program (including its Contributions) under the new +version. No one other than LUCENT has the right to modify this +Agreement. Except as expressly stated in Sections 2(a) and 2(b) above, +Recipient receives no rights or licenses to the intellectual property +of any Contributor under this Agreement, whether expressly, by +implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and +the intellectual property laws of the United States of America. No +party to this Agreement will bring a legal action under this Agreement +more than one year after the cause of action arose. Each party waives +its rights to a jury trial in any resulting litigation. + diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/Readme b/Godeps/_workspace/src/github.com/kr/text/colwriter/Readme new file mode 100644 index 0000000..1c1f4e6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/colwriter/Readme @@ -0,0 +1,5 @@ +Package colwriter provides a write filter that formats +input lines in multiple columns. + +The package is a straightforward translation from +/src/cmd/draw/mc.c in Plan 9 from User Space. diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/column.go b/Godeps/_workspace/src/github.com/kr/text/colwriter/column.go new file mode 100644 index 0000000..7302ce9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/colwriter/column.go @@ -0,0 +1,147 @@ +// Package colwriter provides a write filter that formats +// input lines in multiple columns. +// +// The package is a straightforward translation from +// /src/cmd/draw/mc.c in Plan 9 from User Space. +package colwriter + +import ( + "bytes" + "io" + "unicode/utf8" +) + +const ( + tab = 4 +) + +const ( + // Print each input line ending in a colon ':' separately. + BreakOnColon uint = 1 << iota +) + +// A Writer is a filter that arranges input lines in as many columns as will +// fit in its width. Tab '\t' chars in the input are translated to sequences +// of spaces ending at multiples of 4 positions. +// +// If BreakOnColon is set, each input line ending in a colon ':' is written +// separately. +// +// The Writer assumes that all Unicode code points have the same width; this +// may not be true in some fonts. +type Writer struct { + w io.Writer + buf []byte + width int + flag uint +} + +// NewWriter allocates and initializes a new Writer writing to w. +// Parameter width controls the total number of characters on each line +// across all columns. +func NewWriter(w io.Writer, width int, flag uint) *Writer { + return &Writer{ + w: w, + width: width, + flag: flag, + } +} + +// Write writes p to the writer w. The only errors returned are ones +// encountered while writing to the underlying output stream. +func (w *Writer) Write(p []byte) (n int, err error) { + var linelen int + var lastWasColon bool + for i, c := range p { + w.buf = append(w.buf, c) + linelen++ + if c == '\t' { + w.buf[len(w.buf)-1] = ' ' + for linelen%tab != 0 { + w.buf = append(w.buf, ' ') + linelen++ + } + } + if w.flag&BreakOnColon != 0 && c == ':' { + lastWasColon = true + } else if lastWasColon { + if c == '\n' { + pos := bytes.LastIndex(w.buf[:len(w.buf)-1], []byte{'\n'}) + if pos < 0 { + pos = 0 + } + line := w.buf[pos:] + w.buf = w.buf[:pos] + if err = w.columnate(); err != nil { + if len(line) < i { + return i - len(line), err + } + return 0, err + } + if n, err := w.w.Write(line); err != nil { + if r := len(line) - n; r < i { + return i - r, err + } + return 0, err + } + } + lastWasColon = false + } + if c == '\n' { + linelen = 0 + } + } + return len(p), nil +} + +// Flush should be called after the last call to Write to ensure that any data +// buffered in the Writer is written to output. +func (w *Writer) Flush() error { + return w.columnate() +} + +func (w *Writer) columnate() error { + words := bytes.Split(w.buf, []byte{'\n'}) + w.buf = nil + if len(words[len(words)-1]) == 0 { + words = words[:len(words)-1] + } + maxwidth := 0 + for _, wd := range words { + if n := utf8.RuneCount(wd); n > maxwidth { + maxwidth = n + } + } + maxwidth++ // space char + wordsPerLine := w.width / maxwidth + if wordsPerLine <= 0 { + wordsPerLine = 1 + } + nlines := (len(words) + wordsPerLine - 1) / wordsPerLine + for i := 0; i < nlines; i++ { + col := 0 + endcol := 0 + for j := i; j < len(words); j += nlines { + endcol += maxwidth + _, err := w.w.Write(words[j]) + if err != nil { + return err + } + col += utf8.RuneCount(words[j]) + if j+nlines < len(words) { + for col < endcol { + _, err := w.w.Write([]byte{' '}) + if err != nil { + return err + } + col++ + } + } + } + _, err := w.w.Write([]byte{'\n'}) + if err != nil { + return err + } + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go b/Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go new file mode 100644 index 0000000..8d0bf8f --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go @@ -0,0 +1,90 @@ +package colwriter + +import ( + "bytes" + "testing" +) + +var src = ` +.git +.gitignore +.godir +Procfile: +README.md +api.go +apps.go +auth.go +darwin.go +data.go +dyno.go: +env.go +git.go +help.go +hkdist +linux.go +ls.go +main.go +plugin.go +run.go +scale.go +ssh.go +tail.go +term +unix.go +update.go +version.go +windows.go +`[1:] + +var tests = []struct{ + wid int + flag uint + src string + want string +}{ + {80, 0, "", ""}, + {80, 0, src, ` +.git README.md darwin.go git.go ls.go scale.go unix.go +.gitignore api.go data.go help.go main.go ssh.go update.go +.godir apps.go dyno.go: hkdist plugin.go tail.go version.go +Procfile: auth.go env.go linux.go run.go term windows.go +`[1:]}, + {80, BreakOnColon, src, ` +.git .gitignore .godir + +Procfile: +README.md api.go apps.go auth.go darwin.go data.go + +dyno.go: +env.go hkdist main.go scale.go term version.go +git.go linux.go plugin.go ssh.go unix.go windows.go +help.go ls.go run.go tail.go update.go +`[1:]}, + {20, 0, ` +Hello +Γειά σου +안녕 +今日は +`[1:], ` +Hello 안녕 +Γειά σου 今日は +`[1:]}, +} + +func TestWriter(t *testing.T) { + for _, test := range tests { + b := new(bytes.Buffer) + w := NewWriter(b, test.wid, test.flag) + if _, err := w.Write([]byte(test.src)); err != nil { + t.Error(err) + } + if err := w.Flush(); err != nil { + t.Error(err) + } + if g := b.String(); test.want != g { + t.Log("\n" + test.want) + t.Log("\n" + g) + t.Errorf("%q != %q", test.want, g) + } + } +} diff --git a/Godeps/_workspace/src/github.com/kr/text/doc.go b/Godeps/_workspace/src/github.com/kr/text/doc.go new file mode 100644 index 0000000..cf4c198 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/doc.go @@ -0,0 +1,3 @@ +// Package text provides rudimentary functions for manipulating text in +// paragraphs. +package text diff --git a/Godeps/_workspace/src/github.com/kr/text/indent.go b/Godeps/_workspace/src/github.com/kr/text/indent.go new file mode 100644 index 0000000..4ebac45 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/indent.go @@ -0,0 +1,74 @@ +package text + +import ( + "io" +) + +// Indent inserts prefix at the beginning of each non-empty line of s. The +// end-of-line marker is NL. +func Indent(s, prefix string) string { + return string(IndentBytes([]byte(s), []byte(prefix))) +} + +// IndentBytes inserts prefix at the beginning of each non-empty line of b. +// The end-of-line marker is NL. +func IndentBytes(b, prefix []byte) []byte { + var res []byte + bol := true + for _, c := range b { + if bol && c != '\n' { + res = append(res, prefix...) + } + res = append(res, c) + bol = c == '\n' + } + return res +} + +// Writer indents each line of its input. +type indentWriter struct { + w io.Writer + bol bool + pre [][]byte + sel int + off int +} + +// NewIndentWriter makes a new write filter that indents the input +// lines. Each line is prefixed in order with the corresponding +// element of pre. If there are more lines than elements, the last +// element of pre is repeated for each subsequent line. +func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer { + return &indentWriter{ + w: w, + pre: pre, + bol: true, + } +} + +// The only errors returned are from the underlying indentWriter. +func (w *indentWriter) Write(p []byte) (n int, err error) { + for _, c := range p { + if w.bol { + var i int + i, err = w.w.Write(w.pre[w.sel][w.off:]) + w.off += i + if err != nil { + return n, err + } + } + _, err = w.w.Write([]byte{c}) + if err != nil { + return n, err + } + n++ + w.bol = c == '\n' + if w.bol { + w.off = 0 + if w.sel < len(w.pre)-1 { + w.sel++ + } + } + } + return n, nil +} diff --git a/Godeps/_workspace/src/github.com/kr/text/indent_test.go b/Godeps/_workspace/src/github.com/kr/text/indent_test.go new file mode 100644 index 0000000..5c723ee --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/indent_test.go @@ -0,0 +1,119 @@ +package text + +import ( + "bytes" + "testing" +) + +type T struct { + inp, exp, pre string +} + +var tests = []T{ + { + "The quick brown fox\njumps over the lazy\ndog.\nBut not quickly.\n", + "xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\nxxxBut not quickly.\n", + "xxx", + }, + { + "The quick brown fox\njumps over the lazy\ndog.\n\nBut not quickly.", + "xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\n\nxxxBut not quickly.", + "xxx", + }, +} + +func TestIndent(t *testing.T) { + for _, test := range tests { + got := Indent(test.inp, test.pre) + if got != test.exp { + t.Errorf("mismatch %q != %q", got, test.exp) + } + } +} + +type IndentWriterTest struct { + inp, exp string + pre []string +} + +var ts = []IndentWriterTest{ + { + ` +The quick brown fox +jumps over the lazy +dog. +But not quickly. +`[1:], + ` +xxxThe quick brown fox +xxxjumps over the lazy +xxxdog. +xxxBut not quickly. +`[1:], + []string{"xxx"}, + }, + { + ` +The quick brown fox +jumps over the lazy +dog. +But not quickly. +`[1:], + ` +xxaThe quick brown fox +xxxjumps over the lazy +xxxdog. +xxxBut not quickly. +`[1:], + []string{"xxa", "xxx"}, + }, + { + ` +The quick brown fox +jumps over the lazy +dog. +But not quickly. +`[1:], + ` +xxaThe quick brown fox +xxbjumps over the lazy +xxcdog. +xxxBut not quickly. +`[1:], + []string{"xxa", "xxb", "xxc", "xxx"}, + }, + { + ` +The quick brown fox +jumps over the lazy +dog. + +But not quickly.`[1:], + ` +xxaThe quick brown fox +xxxjumps over the lazy +xxxdog. +xxx +xxxBut not quickly.`[1:], + []string{"xxa", "xxx"}, + }, +} + +func TestIndentWriter(t *testing.T) { + for _, test := range ts { + b := new(bytes.Buffer) + pre := make([][]byte, len(test.pre)) + for i := range test.pre { + pre[i] = []byte(test.pre[i]) + } + w := NewIndentWriter(b, pre...) + if _, err := w.Write([]byte(test.inp)); err != nil { + t.Error(err) + } + if got := b.String(); got != test.exp { + t.Errorf("mismatch %q != %q", got, test.exp) + t.Log(got) + t.Log(test.exp) + } + } +} diff --git a/Godeps/_workspace/src/github.com/kr/text/mc/Readme b/Godeps/_workspace/src/github.com/kr/text/mc/Readme new file mode 100644 index 0000000..519ddc0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/mc/Readme @@ -0,0 +1,9 @@ +Command mc prints in multiple columns. + + Usage: mc [-] [-N] [file...] + +Mc splits the input into as many columns as will fit in N +print positions. If the output is a tty, the default N is +the number of characters in a terminal line; otherwise the +default N is 80. Under option - each input line ending in +a colon ':' is printed separately. diff --git a/Godeps/_workspace/src/github.com/kr/text/mc/mc.go b/Godeps/_workspace/src/github.com/kr/text/mc/mc.go new file mode 100644 index 0000000..00169a3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/mc/mc.go @@ -0,0 +1,62 @@ +// Command mc prints in multiple columns. +// +// Usage: mc [-] [-N] [file...] +// +// Mc splits the input into as many columns as will fit in N +// print positions. If the output is a tty, the default N is +// the number of characters in a terminal line; otherwise the +// default N is 80. Under option - each input line ending in +// a colon ':' is printed separately. +package main + +import ( + "github.com/kr/pty" + "github.com/kr/text/colwriter" + "io" + "log" + "os" + "strconv" +) + +func main() { + var width int + var flag uint + args := os.Args[1:] + for len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' { + if len(args[0]) > 1 { + width, _ = strconv.Atoi(args[0][1:]) + } else { + flag |= colwriter.BreakOnColon + } + args = args[1:] + } + if width < 1 { + _, width, _ = pty.Getsize(os.Stdout) + } + if width < 1 { + width = 80 + } + + w := colwriter.NewWriter(os.Stdout, width, flag) + if len(args) > 0 { + for _, s := range args { + if f, err := os.Open(s); err == nil { + copyin(w, f) + f.Close() + } else { + log.Println(err) + } + } + } else { + copyin(w, os.Stdin) + } +} + +func copyin(w *colwriter.Writer, r io.Reader) { + if _, err := io.Copy(w, r); err != nil { + log.Println(err) + } + if err := w.Flush(); err != nil { + log.Println(err) + } +} diff --git a/Godeps/_workspace/src/github.com/kr/text/wrap.go b/Godeps/_workspace/src/github.com/kr/text/wrap.go new file mode 100644 index 0000000..ca88565 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/wrap.go @@ -0,0 +1,86 @@ +package text + +import ( + "bytes" + "math" +) + +var ( + nl = []byte{'\n'} + sp = []byte{' '} +) + +const defaultPenalty = 1e5 + +// Wrap wraps s into a paragraph of lines of length lim, with minimal +// raggedness. +func Wrap(s string, lim int) string { + return string(WrapBytes([]byte(s), lim)) +} + +// WrapBytes wraps b into a paragraph of lines of length lim, with minimal +// raggedness. +func WrapBytes(b []byte, lim int) []byte { + words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp) + var lines [][]byte + for _, line := range WrapWords(words, 1, lim, defaultPenalty) { + lines = append(lines, bytes.Join(line, sp)) + } + return bytes.Join(lines, nl) +} + +// WrapWords is the low-level line-breaking algorithm, useful if you need more +// control over the details of the text wrapping process. For most uses, either +// Wrap or WrapBytes will be sufficient and more convenient. +// +// WrapWords splits a list of words into lines with minimal "raggedness", +// treating each byte as one unit, accounting for spc units between adjacent +// words on each line, and attempting to limit lines to lim units. Raggedness +// is the total error over all lines, where error is the square of the +// difference of the length of the line and lim. Too-long lines (which only +// happen when a single word is longer than lim units) have pen penalty units +// added to the error. +func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte { + n := len(words) + + length := make([][]int, n) + for i := 0; i < n; i++ { + length[i] = make([]int, n) + length[i][i] = len(words[i]) + for j := i + 1; j < n; j++ { + length[i][j] = length[i][j-1] + spc + len(words[j]) + } + } + + nbrk := make([]int, n) + cost := make([]int, n) + for i := range cost { + cost[i] = math.MaxInt32 + } + for i := n - 1; i >= 0; i-- { + if length[i][n-1] <= lim { + cost[i] = 0 + nbrk[i] = n + } else { + for j := i + 1; j < n; j++ { + d := lim - length[i][j-1] + c := d*d + cost[j] + if length[i][j-1] > lim { + c += pen // too-long lines get a worse penalty + } + if c < cost[i] { + cost[i] = c + nbrk[i] = j + } + } + } + } + + var lines [][][]byte + i := 0 + for i < n { + lines = append(lines, words[i:nbrk[i]]) + i = nbrk[i] + } + return lines +} diff --git a/Godeps/_workspace/src/github.com/kr/text/wrap_test.go b/Godeps/_workspace/src/github.com/kr/text/wrap_test.go new file mode 100644 index 0000000..90f065c --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/wrap_test.go @@ -0,0 +1,44 @@ +package text + +import ( + "bytes" + "testing" +) + +var text = "The quick brown fox jumps over the lazy dog." + +func TestWrap(t *testing.T) { + exp := [][]string{ + {"The", "quick", "brown", "fox"}, + {"jumps", "over", "the", "lazy", "dog."}, + } + words := bytes.Split([]byte(text), sp) + got := WrapWords(words, 1, 24, defaultPenalty) + if len(exp) != len(got) { + t.Fail() + } + for i := range exp { + if len(exp[i]) != len(got[i]) { + t.Fail() + } + for j := range exp[i] { + if exp[i][j] != string(got[i][j]) { + t.Fatal(i, exp[i][j], got[i][j]) + } + } + } +} + +func TestWrapNarrow(t *testing.T) { + exp := "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog." + if Wrap(text, 5) != exp { + t.Fail() + } +} + +func TestWrapOneLine(t *testing.T) { + exp := "The quick brown fox jumps over the lazy dog." + if Wrap(text, 500) != exp { + t.Fail() + } +} diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/.env b/Godeps/_workspace/src/github.com/subosito/gotenv/.env new file mode 100644 index 0000000..6405eca --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/.env @@ -0,0 +1 @@ +HELLO=world diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/.gitignore b/Godeps/_workspace/src/github.com/subosito/gotenv/.gitignore new file mode 100644 index 0000000..7c7dff2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/.gitignore @@ -0,0 +1,2 @@ +*.test +annotate.json diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/LICENSE b/Godeps/_workspace/src/github.com/subosito/gotenv/LICENSE new file mode 100644 index 0000000..f64ccae --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Alif Rachmawadi + +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. diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/README.md b/Godeps/_workspace/src/github.com/subosito/gotenv/README.md new file mode 100644 index 0000000..196683f --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/README.md @@ -0,0 +1,102 @@ +# gotenv + +Load environment variables dynamically in Go. + +|- | - | +|---------------|----------------------------------------------------| +| Build Status | [![Build Status][drone-img]][drone-url] | +| Coverage | [![Coverage Status][coveralls-img]][coveralls-url] | +| Documentation | http://godoc.org/github.com/subosito/gotenv | + +## Installation + +```bash +$ go get github.com/subosito/gotenv +``` + +## Usage + +Store your configuration to `.env` file on your root directory of your project: + +``` +APP_ID=1234567 +APP_SECRET=abcdef +``` + +Put the gotenv package on your `import` statement: + +```go +import "github.com/subosito/gotenv" +``` + +Then somewhere on your application code, put: + +```go +gotenv.Load() +``` + +Behind the scene it will then load `.env` file and export the valid variables to the environment variables. Make sure you call the method as soon as possible to ensure all variables are loaded, say, put it on `init()` function. + +Once loaded you can use `os.Getenv()` to get the value of the variable. + +Here's the final example: + +```go +package main + +import ( + "github.com/subosito/gotenv" + "log" + "os" +) + +func init() { + gotenv.Load() +} + +func main() { + log.Println(os.Getenv("APP_ID")) // "1234567" + log.Println(os.Getenv("APP_SECRET")) // "abcdef" +} +``` + +You can also load other than `.env` file if you wish. Just supply filenames when calling `Load()`: + +```go +gotenv.Load(".env.production", "credentials") +``` + +That's it :) + +### Another Scenario + +Just in case you want to parse environment variables from any `io.Reader`, gotenv keeps its `Parse()` function as public API so you can utilize that. + +```go +// import "strings" + +pairs := gotenv.Parse(strings.NewReader("FOO=test\nBAR=$FOO")) +// gotenv.Env{"FOO": "test", "BAR": "test"} + +pairs = gotenv.Parse(strings.NewReader(`FOO="bar"`)) +// gotenv.Env{"FOO": "bar"} +``` + +Parse ignores invalid lines and returns `Env` of valid environment variables. + +### Formats + +The gotenv supports various format for defining environment variables. You can see more about it on: + +- [fixtures](./fixtures) +- [gotenv_test.go](./gotenv_test.go) + +## Notes + +The gotenv package is a Go port of [`dotenv`](https://github.com/bkeepers/dotenv) project. Most logic and regexp pattern is taken from there and aims will be compatible as close as possible. + +[drone-img]: https://drone.io/github.com/subosito/gotenv/status.png +[drone-url]: https://drone.io/github.com/subosito/gotenv/latest +[coveralls-img]: https://coveralls.io/repos/subosito/gotenv/badge.png?branch=master +[coveralls-url]: https://coveralls.io/r/subosito/gotenv?branch=master + diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/examples_test.go b/Godeps/_workspace/src/github.com/subosito/gotenv/examples_test.go new file mode 100644 index 0000000..0cbc6c9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/examples_test.go @@ -0,0 +1,15 @@ +package gotenv_test + +import ( + "strings" + "fmt" + "github.com/subosito/gotenv" +) + +func ExampleParse() { + pairs := gotenv.Parse(strings.NewReader("FOO=test\nBAR=$FOO")) + fmt.Printf("%+v\n", pairs) // gotenv.Env{"FOO": "test", "BAR": "test"} + + pairs = gotenv.Parse(strings.NewReader(`FOO="bar"`)) + fmt.Printf("%+v\n", pairs) // gotenv.Env{"FOO": "bar"} +} diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/exported.env b/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/exported.env new file mode 100644 index 0000000..5821377 --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/exported.env @@ -0,0 +1,2 @@ +export OPTION_A=2 +export OPTION_B='\n' diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/plain.env b/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/plain.env new file mode 100644 index 0000000..c983b06 --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/plain.env @@ -0,0 +1,5 @@ +OPTION_A=1 +OPTION_B=2 +OPTION_C= 3 +OPTION_D =4 +OPTION_E = 5 diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/quoted.env b/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/quoted.env new file mode 100644 index 0000000..a03ce24 --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/quoted.env @@ -0,0 +1,8 @@ +OPTION_A='1' +OPTION_B='2' +OPTION_C='' +OPTION_D='\n' +OPTION_E="1" +OPTION_F="2" +OPTION_G="" +OPTION_H="\n" diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/yaml.env b/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/yaml.env new file mode 100644 index 0000000..ea7ec65 --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/fixtures/yaml.env @@ -0,0 +1,4 @@ +OPTION_A: 1 +OPTION_B: '2' +OPTION_C: '' +OPTION_D: '\n' diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/gotenv.go b/Godeps/_workspace/src/github.com/subosito/gotenv/gotenv.go new file mode 100644 index 0000000..c7a7b09 --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/gotenv.go @@ -0,0 +1,119 @@ +// Package gotenv provides functionality to dynamically load the environment variables +package gotenv + +import ( + "bufio" + "io" + "os" + "regexp" + "strings" +) + +const ( + // Pattern for detecting valid line format + linePattern = `\A(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?(?:\s*\#.*)?\z` + + // Pattern for detecting valid variable within a value + variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)\}?)` +) + +// Holds key/value pair of valid environment variable +type Env map[string]string + +/* +Load is function to load a file or multiple files and then export the valid variables which found into environment variables. +When it's called with no argument, it will load `.env` file on the current path and set the environment variables. +Otherwise, it will loop over the filenames parameter and set the proper environment variables. + + // processing `.env` + gotenv.Load() + + // processing multiple files + gotenv.Load("production.env", "credentials") + +*/ +func Load(filenames ...string) error { + if len(filenames) == 0 { + filenames = []string{".env"} + } + + for _, filename := range filenames { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + + // set environment + env := Parse(f) + for key, val := range env { + os.Setenv(key, val) + } + } + + return nil +} + +// Parse if a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables. +// It expands the value of a variable from environment variable, but does not set the value to the environment itself. +// This function is skipping any invalid lines and only processing the valid one. +func Parse(r io.Reader) Env { + env := make(Env) + scanner := bufio.NewScanner(r) + + for scanner.Scan() { + parseLine(scanner.Text(), env) + } + + return env +} + +func parseLine(s string, env Env) { + r := regexp.MustCompile(linePattern) + matches := r.FindStringSubmatch(s) + if len(matches) == 0 { + return + } + + key := matches[1] + val := matches[2] + + // determine if string has quote prefix + hq := strings.HasPrefix(val, `"`) + + // trim whitespace + val = strings.Trim(val, " ") + + // remove quotes '' or "" + rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`) + val = rq.ReplaceAllString(val, "$2") + + if hq { + val = strings.Replace(val, `\n`, "\n", -1) + // Unescape all characters except $ so variables can be escaped properly + re := regexp.MustCompile(`\\([^$])`) + val = re.ReplaceAllString(val, "$1") + } + + rv := regexp.MustCompile(variablePattern) + xv := rv.FindStringSubmatch(val) + + if len(xv) > 0 { + var replace string + var ok bool + + if xv[1] == "\\" { + replace = strings.Join(xv[2:4], "") + } else { + replace, ok = env[xv[4]] + if !ok { + replace = os.Getenv(xv[4]) + } + } + + val = strings.Replace(val, strings.Join(xv[0:1], ""), replace, -1) + } + + env[key] = val + return +} diff --git a/Godeps/_workspace/src/github.com/subosito/gotenv/gotenv_test.go b/Godeps/_workspace/src/github.com/subosito/gotenv/gotenv_test.go new file mode 100644 index 0000000..82c3a19 --- /dev/null +++ b/Godeps/_workspace/src/github.com/subosito/gotenv/gotenv_test.go @@ -0,0 +1,187 @@ +package gotenv + +import ( + "fmt" + "os" + "strings" + "testing" +) + +var formats = []struct { + in string + out Env + preset bool +}{ + // parses unquoted values + {`FOO=bar`, Env{"FOO": "bar"}, false}, + + // parses values with spaces around equal sign + {`FOO =bar`, Env{"FOO": "bar"}, false}, + {`FOO= bar`, Env{"FOO": "bar"}, false}, + + // parses double quoted values + {`FOO="bar"`, Env{"FOO": "bar"}, false}, + + // parses single quoted values + {`FOO='bar'`, Env{"FOO": "bar"}, false}, + + // parses escaped double quotes + {`FOO="escaped\"bar"`, Env{"FOO": `escaped"bar`}, false}, + + // parses empty values + {`FOO=`, Env{"FOO": ""}, false}, + + // expands variables found in values + {"FOO=test\nBAR=$FOO", Env{"FOO": "test", "BAR": "test"}, false}, + + // parses variables wrapped in brackets + {"FOO=test\nBAR=${FOO}bar", Env{"FOO": "test", "BAR": "testbar"}, false}, + + // reads variables from ENV when expanding if not found in local env + {`BAR=$FOO`, Env{"BAR": "test"}, true}, + + // expands undefined variables to an empty string + {`BAR=$FOO`, Env{"BAR": ""}, false}, + + // expands variables in quoted strings + {"FOO=test\nBAR='quote $FOO'", Env{"FOO": "test", "BAR": "quote test"}, false}, + + // does not expand escaped variables + {`FOO="foo\$BAR"`, Env{"FOO": "foo$BAR"}, false}, + {`FOO="foo\${BAR}"`, Env{"FOO": "foo${BAR}"}, false}, + + // parses yaml style options + {"OPTION_A: 1", Env{"OPTION_A": "1"}, false}, + + // parses export keyword + {"export OPTION_A=2", Env{"OPTION_A": "2"}, false}, + + // expands newlines in quoted strings + {`FOO="bar\nbaz"`, Env{"FOO": "bar\nbaz"}, false}, + + // parses varibales with "." in the name + {`FOO.BAR=foobar`, Env{"FOO.BAR": "foobar"}, false}, + + // strips unquoted values + {`foo=bar `, Env{"foo": "bar"}, false}, // not 'bar ' + + // ignores empty lines + {"\n \t \nfoo=bar\n \nfizz=buzz", Env{"foo": "bar", "fizz": "buzz"}, false}, + + // ignores inline comments + {"foo=bar # this is foo", Env{"foo": "bar"}, false}, + + // allows # in quoted value + {`foo="bar#baz" # comment`, Env{"foo": "bar#baz"}, false}, + + // ignores comment lines + {"\n\n\n # HERE GOES FOO \nfoo=bar", Env{"foo": "bar"}, false}, + + // parses # in quoted values + {`foo="ba#r"`, Env{"foo": "ba#r"}, false}, + {"foo='ba#r'", Env{"foo": "ba#r"}, false}, + + // incorrect line format + {"lol$wut", Env{}, false}, +} + +var fixtures = []struct { + filename string + results Env +}{ + { + "fixtures/exported.env", + Env{ + "OPTION_A": "2", + "OPTION_B": `\n`, + }, + }, + { + "fixtures/plain.env", + Env{ + "OPTION_A": "1", + "OPTION_B": "2", + "OPTION_C": "3", + "OPTION_D": "4", + "OPTION_E": "5", + }, + }, + { + "fixtures/quoted.env", + Env{ + "OPTION_A": "1", + "OPTION_B": "2", + "OPTION_C": "", + "OPTION_D": `\n`, + "OPTION_E": "1", + "OPTION_F": "2", + "OPTION_G": "", + "OPTION_H": "\n", + }, + }, + { + "fixtures/yaml.env", + Env{ + "OPTION_A": "1", + "OPTION_B": "2", + "OPTION_C": "", + "OPTION_D": `\n`, + }, + }, +} + +func TestParse(t *testing.T) { + for i, tt := range formats { + if tt.preset { + os.Setenv("FOO", "test") + } + + exp := Parse(strings.NewReader(tt.in)) + + x := fmt.Sprintf("%+v\n", exp) + o := fmt.Sprintf("%+v\n", tt.out) + + if x != o { + t.Logf("%q\n", tt.in) + t.Errorf("(%d) %s != %s\n", i, x, o) + } + + os.Clearenv() + } +} + +func TestLoad(t *testing.T) { + for i, tt := range fixtures { + Load(tt.filename) + + for key, val := range tt.results { + if eval := os.Getenv(key); eval != val { + t.Errorf("(%d) %s => %s != %s", i, key, eval, val) + } + } + + os.Clearenv() + } +} + +func TestLoadEnv(t *testing.T) { + Load() + + tkey := "HELLO" + val := "world" + + if tval := os.Getenv(tkey); tval != val { + t.Errorf("%s => %s != %s", tkey, tval, val) + } + + os.Clearenv() +} + +func TestLoadNonExist(t *testing.T) { + file := ".nonexist.env" + + err := Load(file) + if err == nil { + t.Errorf("Load(`%s`) => error: `no such file or directory` != nil", file) + } +}