diff --git a/vendor-log b/vendor-log new file mode 100644 index 0000000..e86c482 --- /dev/null +++ b/vendor-log @@ -0,0 +1,15 @@ +c02ca9a983da5807ddf7d796784928f5be4afd09 github.com/GeertJohan/go.rice +c02ca9a983da5807ddf7d796784928f5be4afd09 github.com/GeertJohan/go.rice/embedded +a00a8beb369cafd88bb7b32f31fc4ff3219c3565 github.com/Xe/gopreload +b685d4edebe855f8edbb4e605c0bf74e1e60b0e9 github.com/Xe/jsonfeed +f759b797c0ff6b2c514202198fe5e8ba90094c14 github.com/Xe/ln +a5fe2436ffcb3236e175e5149162b41cd28bd27d github.com/daaku/go.zipexe +62f833fc9f6c4d3223bdb37bd0c2f8951bed8596 github.com/google/gops/agent +62f833fc9f6c4d3223bdb37bd0c2f8951bed8596 github.com/google/gops/internal +62f833fc9f6c4d3223bdb37bd0c2f8951bed8596 github.com/google/gops/signal +441264de03a8117ed530ae8e049d8f601a33a099 github.com/gorilla/feeds +c2c54e542fb797ad986b31721e1baedf214ca413 github.com/kardianos/osext +ff09b135c25aae272398c51a07235b90a75aa4f0 github.com/pkg/errors +0ba0f2b6ed7c475a92e4df8641825cb7a11d1fa3 github.com/russross/blackfriday +739be213b0a1c496dccaf9e5df1514150c9548e4 github.com/tj/front +9f9df34309c04878acc86042b16630b0f696e1de gopkg.in/yaml.v1 diff --git a/vendor/github.com/GeertJohan/go.rice/appended.go b/vendor/github.com/GeertJohan/go.rice/appended.go new file mode 100644 index 0000000..a986a0c --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/appended.go @@ -0,0 +1,138 @@ +package rice + +import ( + "archive/zip" + "log" + "os" + "path/filepath" + "strings" + "time" + + "github.com/daaku/go.zipexe" + "github.com/kardianos/osext" +) + +// appendedBox defines an appended box +type appendedBox struct { + Name string // box name + Files map[string]*appendedFile // appended files (*zip.File) by full path +} + +type appendedFile struct { + zipFile *zip.File + dir bool + dirInfo *appendedDirInfo + children []*appendedFile + content []byte +} + +// appendedBoxes is a public register of appendes boxes +var appendedBoxes = make(map[string]*appendedBox) + +func init() { + // find if exec is appended + thisFile, err := osext.Executable() + if err != nil { + return // not appended or cant find self executable + } + closer, rd, err := zipexe.OpenCloser(thisFile) + if err != nil { + return // not appended + } + defer closer.Close() + + for _, f := range rd.File { + // get box and file name from f.Name + fileParts := strings.SplitN(strings.TrimLeft(filepath.ToSlash(f.Name), "/"), "/", 2) + boxName := fileParts[0] + var fileName string + if len(fileParts) > 1 { + fileName = fileParts[1] + } + + // find box or create new one if doesn't exist + box := appendedBoxes[boxName] + if box == nil { + box = &appendedBox{ + Name: boxName, + Files: make(map[string]*appendedFile), + } + appendedBoxes[boxName] = box + } + + // create and add file to box + af := &appendedFile{ + zipFile: f, + } + if f.Comment == "dir" { + af.dir = true + af.dirInfo = &appendedDirInfo{ + name: filepath.Base(af.zipFile.Name), + //++ TODO: use zip modtime when that is set correctly: af.zipFile.ModTime() + time: time.Now(), + } + } else { + // this is a file, we need it's contents so we can create a bytes.Reader when the file is opened + // make a new byteslice + af.content = make([]byte, af.zipFile.FileInfo().Size()) + // ignore reading empty files from zip (empty file still is a valid file to be read though!) + if len(af.content) > 0 { + // open io.ReadCloser + rc, err := af.zipFile.Open() + if err != nil { + af.content = nil // this will cause an error when the file is being opened or seeked (which is good) + // TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet.. + log.Printf("error opening appended file %s: %v", af.zipFile.Name, err) + } else { + _, err = rc.Read(af.content) + rc.Close() + if err != nil { + af.content = nil // this will cause an error when the file is being opened or seeked (which is good) + // TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet.. + log.Printf("error reading data for appended file %s: %v", af.zipFile.Name, err) + } + } + } + } + + // add appendedFile to box file list + box.Files[fileName] = af + + // add to parent dir (if any) + dirName := filepath.Dir(fileName) + if dirName == "." { + dirName = "" + } + if fileName != "" { // don't make box root dir a child of itself + if dir := box.Files[dirName]; dir != nil { + dir.children = append(dir.children, af) + } + } + } +} + +// implements os.FileInfo. +// used for Readdir() +type appendedDirInfo struct { + name string + time time.Time +} + +func (adi *appendedDirInfo) Name() string { + return adi.name +} +func (adi *appendedDirInfo) Size() int64 { + return 0 +} +func (adi *appendedDirInfo) Mode() os.FileMode { + return os.ModeDir +} +func (adi *appendedDirInfo) ModTime() time.Time { + return adi.time +} +func (adi *appendedDirInfo) IsDir() bool { + return true +} +func (adi *appendedDirInfo) Sys() interface{} { + return nil +} diff --git a/vendor/github.com/GeertJohan/go.rice/box.go b/vendor/github.com/GeertJohan/go.rice/box.go new file mode 100644 index 0000000..71482e2 --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/box.go @@ -0,0 +1,337 @@ +package rice + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/GeertJohan/go.rice/embedded" +) + +// Box abstracts a directory for resources/files. +// It can either load files from disk, or from embedded code (when `rice --embed` was ran). +type Box struct { + name string + absolutePath string + embed *embedded.EmbeddedBox + appendd *appendedBox +} + +var defaultLocateOrder = []LocateMethod{LocateEmbedded, LocateAppended, LocateFS} + +func findBox(name string, order []LocateMethod) (*Box, error) { + b := &Box{name: name} + + // no support for absolute paths since gopath can be different on different machines. + // therefore, required box must be located relative to package requiring it. + if filepath.IsAbs(name) { + return nil, errors.New("given name/path is absolute") + } + + var err error + for _, method := range order { + switch method { + case LocateEmbedded: + if embed := embedded.EmbeddedBoxes[name]; embed != nil { + b.embed = embed + return b, nil + } + + case LocateAppended: + appendedBoxName := strings.Replace(name, `/`, `-`, -1) + if appendd := appendedBoxes[appendedBoxName]; appendd != nil { + b.appendd = appendd + return b, nil + } + + case LocateFS: + // resolve absolute directory path + err := b.resolveAbsolutePathFromCaller() + if err != nil { + continue + } + // check if absolutePath exists on filesystem + info, err := os.Stat(b.absolutePath) + if err != nil { + continue + } + // check if absolutePath is actually a directory + if !info.IsDir() { + err = errors.New("given name/path is not a directory") + continue + } + return b, nil + case LocateWorkingDirectory: + // resolve absolute directory path + err := b.resolveAbsolutePathFromWorkingDirectory() + if err != nil { + continue + } + // check if absolutePath exists on filesystem + info, err := os.Stat(b.absolutePath) + if err != nil { + continue + } + // check if absolutePath is actually a directory + if !info.IsDir() { + err = errors.New("given name/path is not a directory") + continue + } + return b, nil + } + } + + if err == nil { + err = fmt.Errorf("could not locate box %q", name) + } + + return nil, err +} + +// FindBox returns a Box instance for given name. +// When the given name is a relative path, it's base path will be the calling pkg/cmd's source root. +// When the given name is absolute, it's absolute. derp. +// Make sure the path doesn't contain any sensitive information as it might be placed into generated go source (embedded). +func FindBox(name string) (*Box, error) { + return findBox(name, defaultLocateOrder) +} + +// MustFindBox returns a Box instance for given name, like FindBox does. +// It does not return an error, instead it panics when an error occurs. +func MustFindBox(name string) *Box { + box, err := findBox(name, defaultLocateOrder) + if err != nil { + panic(err) + } + return box +} + +// This is injected as a mutable function literal so that we can mock it out in +// tests and return a fixed test file. +var resolveAbsolutePathFromCaller = func(name string, nStackFrames int) (string, error) { + _, callingGoFile, _, ok := runtime.Caller(nStackFrames) + if !ok { + return "", errors.New("couldn't find caller on stack") + } + + // resolve to proper path + pkgDir := filepath.Dir(callingGoFile) + // fix for go cover + const coverPath = "_test/_obj_test" + if !filepath.IsAbs(pkgDir) { + if i := strings.Index(pkgDir, coverPath); i >= 0 { + pkgDir = pkgDir[:i] + pkgDir[i+len(coverPath):] // remove coverPath + pkgDir = filepath.Join(os.Getenv("GOPATH"), "src", pkgDir) // make absolute + } + } + return filepath.Join(pkgDir, name), nil +} + +func (b *Box) resolveAbsolutePathFromCaller() error { + path, err := resolveAbsolutePathFromCaller(b.name, 4) + if err != nil { + return err + } + b.absolutePath = path + return nil + +} + +func (b *Box) resolveAbsolutePathFromWorkingDirectory() error { + path, err := os.Getwd() + if err != nil { + return err + } + b.absolutePath = filepath.Join(path, b.name) + return nil +} + +// IsEmbedded indicates wether this box was embedded into the application +func (b *Box) IsEmbedded() bool { + return b.embed != nil +} + +// IsAppended indicates wether this box was appended to the application +func (b *Box) IsAppended() bool { + return b.appendd != nil +} + +// Time returns how actual the box is. +// When the box is embedded, it's value is saved in the embedding code. +// When the box is live, this methods returns time.Now() +func (b *Box) Time() time.Time { + if b.IsEmbedded() { + return b.embed.Time + } + + //++ TODO: return time for appended box + + return time.Now() +} + +// Open opens a File from the box +// If there is an error, it will be of type *os.PathError. +func (b *Box) Open(name string) (*File, error) { + if Debug { + fmt.Printf("Open(%s)\n", name) + } + + if b.IsEmbedded() { + if Debug { + fmt.Println("Box is embedded") + } + + // trim prefix (paths are relative to box) + name = strings.TrimLeft(name, "/") + if Debug { + fmt.Printf("Trying %s\n", name) + } + + // search for file + ef := b.embed.Files[name] + if ef == nil { + if Debug { + fmt.Println("Didn't find file in embed") + } + // file not found, try dir + ed := b.embed.Dirs[name] + if ed == nil { + if Debug { + fmt.Println("Didn't find dir in embed") + } + // dir not found, error out + return nil, &os.PathError{ + Op: "open", + Path: name, + Err: os.ErrNotExist, + } + } + if Debug { + fmt.Println("Found dir. Returning virtual dir") + } + vd := newVirtualDir(ed) + return &File{virtualD: vd}, nil + } + + // box is embedded + if Debug { + fmt.Println("Found file. Returning virtual file") + } + vf := newVirtualFile(ef) + return &File{virtualF: vf}, nil + } + + if b.IsAppended() { + // trim prefix (paths are relative to box) + name = strings.TrimLeft(name, "/") + + // search for file + appendedFile := b.appendd.Files[name] + if appendedFile == nil { + return nil, &os.PathError{ + Op: "open", + Path: name, + Err: os.ErrNotExist, + } + } + + // create new file + f := &File{ + appendedF: appendedFile, + } + + // if this file is a directory, we want to be able to read and seek + if !appendedFile.dir { + // looks like malformed data in zip, error now + if appendedFile.content == nil { + return nil, &os.PathError{ + Op: "open", + Path: "name", + Err: errors.New("error reading data from zip file"), + } + } + // create new bytes.Reader + f.appendedFileReader = bytes.NewReader(appendedFile.content) + } + + // all done + return f, nil + } + + // perform os open + if Debug { + fmt.Printf("Using os.Open(%s)", filepath.Join(b.absolutePath, name)) + } + file, err := os.Open(filepath.Join(b.absolutePath, name)) + if err != nil { + return nil, err + } + return &File{realF: file}, nil +} + +// Bytes returns the content of the file with given name as []byte. +func (b *Box) Bytes(name string) ([]byte, error) { + file, err := b.Open(name) + if err != nil { + return nil, err + } + defer file.Close() + + content, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + + return content, nil +} + +// MustBytes returns the content of the file with given name as []byte. +// panic's on error. +func (b *Box) MustBytes(name string) []byte { + bts, err := b.Bytes(name) + if err != nil { + panic(err) + } + return bts +} + +// String returns the content of the file with given name as string. +func (b *Box) String(name string) (string, error) { + // check if box is embedded, optimized fast path + if b.IsEmbedded() { + // find file in embed + ef := b.embed.Files[name] + if ef == nil { + return "", os.ErrNotExist + } + // return as string + return ef.Content, nil + } + + bts, err := b.Bytes(name) + if err != nil { + return "", err + } + return string(bts), nil +} + +// MustString returns the content of the file with given name as string. +// panic's on error. +func (b *Box) MustString(name string) string { + str, err := b.String(name) + if err != nil { + panic(err) + } + return str +} + +// Name returns the name of the box +func (b *Box) Name() string { + return b.name +} diff --git a/vendor/github.com/GeertJohan/go.rice/config.go b/vendor/github.com/GeertJohan/go.rice/config.go new file mode 100644 index 0000000..45eb398 --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/config.go @@ -0,0 +1,39 @@ +package rice + +// LocateMethod defines how a box is located. +type LocateMethod int + +const ( + LocateFS = LocateMethod(iota) // Locate on the filesystem according to package path. + LocateAppended // Locate boxes appended to the executable. + LocateEmbedded // Locate embedded boxes. + LocateWorkingDirectory // Locate on the binary working directory +) + +// Config allows customizing the box lookup behavior. +type Config struct { + // LocateOrder defines the priority order that boxes are searched for. By + // default, the package global FindBox searches for embedded boxes first, + // then appended boxes, and then finally boxes on the filesystem. That + // search order may be customized by provided the ordered list here. Leaving + // out a particular method will omit that from the search space. For + // example, []LocateMethod{LocateEmbedded, LocateAppended} will never search + // the filesystem for boxes. + LocateOrder []LocateMethod +} + +// FindBox searches for boxes using the LocateOrder of the config. +func (c *Config) FindBox(boxName string) (*Box, error) { + return findBox(boxName, c.LocateOrder) +} + +// MustFindBox searches for boxes using the LocateOrder of the config, like +// FindBox does. It does not return an error, instead it panics when an error +// occurs. +func (c *Config) MustFindBox(boxName string) *Box { + box, err := findBox(boxName, c.LocateOrder) + if err != nil { + panic(err) + } + return box +} diff --git a/vendor/github.com/GeertJohan/go.rice/debug.go b/vendor/github.com/GeertJohan/go.rice/debug.go new file mode 100644 index 0000000..2e68c84 --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/debug.go @@ -0,0 +1,4 @@ +package rice + +// Debug can be set to true to enable debugging. +var Debug = false diff --git a/vendor/github.com/GeertJohan/go.rice/embedded.go b/vendor/github.com/GeertJohan/go.rice/embedded.go new file mode 100644 index 0000000..4f03fe1 --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/embedded.go @@ -0,0 +1,90 @@ +package rice + +import ( + "os" + "time" + + "github.com/GeertJohan/go.rice/embedded" +) + +// re-type to make exported methods invisible to user (godoc) +// they're not required for the user +// embeddedDirInfo implements os.FileInfo +type embeddedDirInfo embedded.EmbeddedDir + +// Name returns the base name of the directory +// (implementing os.FileInfo) +func (ed *embeddedDirInfo) Name() string { + return ed.Filename +} + +// Size always returns 0 +// (implementing os.FileInfo) +func (ed *embeddedDirInfo) Size() int64 { + return 0 +} + +// Mode returns the file mode bits +// (implementing os.FileInfo) +func (ed *embeddedDirInfo) Mode() os.FileMode { + return os.FileMode(0555 | os.ModeDir) // dr-xr-xr-x +} + +// ModTime returns the modification time +// (implementing os.FileInfo) +func (ed *embeddedDirInfo) ModTime() time.Time { + return ed.DirModTime +} + +// IsDir returns the abbreviation for Mode().IsDir() (always true) +// (implementing os.FileInfo) +func (ed *embeddedDirInfo) IsDir() bool { + return true +} + +// Sys returns the underlying data source (always nil) +// (implementing os.FileInfo) +func (ed *embeddedDirInfo) Sys() interface{} { + return nil +} + +// re-type to make exported methods invisible to user (godoc) +// they're not required for the user +// embeddedFileInfo implements os.FileInfo +type embeddedFileInfo embedded.EmbeddedFile + +// Name returns the base name of the file +// (implementing os.FileInfo) +func (ef *embeddedFileInfo) Name() string { + return ef.Filename +} + +// Size returns the length in bytes for regular files; system-dependent for others +// (implementing os.FileInfo) +func (ef *embeddedFileInfo) Size() int64 { + return int64(len(ef.Content)) +} + +// Mode returns the file mode bits +// (implementing os.FileInfo) +func (ef *embeddedFileInfo) Mode() os.FileMode { + return os.FileMode(0555) // r-xr-xr-x +} + +// ModTime returns the modification time +// (implementing os.FileInfo) +func (ef *embeddedFileInfo) ModTime() time.Time { + return ef.FileModTime +} + +// IsDir returns the abbreviation for Mode().IsDir() (always false) +// (implementing os.FileInfo) +func (ef *embeddedFileInfo) IsDir() bool { + return false +} + +// Sys returns the underlying data source (always nil) +// (implementing os.FileInfo) +func (ef *embeddedFileInfo) Sys() interface{} { + return nil +} diff --git a/vendor/github.com/GeertJohan/go.rice/embedded/embedded.go b/vendor/github.com/GeertJohan/go.rice/embedded/embedded.go new file mode 100644 index 0000000..bba8e58 --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/embedded/embedded.go @@ -0,0 +1,80 @@ +// Package embedded defines embedded data types that are shared between the go.rice package and generated code. +package embedded + +import ( + "fmt" + "path/filepath" + "strings" + "time" +) + +const ( + EmbedTypeGo = 0 + EmbedTypeSyso = 1 +) + +// EmbeddedBox defines an embedded box +type EmbeddedBox struct { + Name string // box name + Time time.Time // embed time + EmbedType int // kind of embedding + Files map[string]*EmbeddedFile // ALL embedded files by full path + Dirs map[string]*EmbeddedDir // ALL embedded dirs by full path +} + +// Link creates the ChildDirs and ChildFiles links in all EmbeddedDir's +func (e *EmbeddedBox) Link() { + for path, ed := range e.Dirs { + fmt.Println(path) + ed.ChildDirs = make([]*EmbeddedDir, 0) + ed.ChildFiles = make([]*EmbeddedFile, 0) + } + for path, ed := range e.Dirs { + parentDirpath, _ := filepath.Split(path) + if strings.HasSuffix(parentDirpath, "/") { + parentDirpath = parentDirpath[:len(parentDirpath)-1] + } + parentDir := e.Dirs[parentDirpath] + if parentDir == nil { + panic("parentDir `" + parentDirpath + "` is missing in embedded box") + } + parentDir.ChildDirs = append(parentDir.ChildDirs, ed) + } + for path, ef := range e.Files { + dirpath, _ := filepath.Split(path) + if strings.HasSuffix(dirpath, "/") { + dirpath = dirpath[:len(dirpath)-1] + } + dir := e.Dirs[dirpath] + if dir == nil { + panic("dir `" + dirpath + "` is missing in embedded box") + } + dir.ChildFiles = append(dir.ChildFiles, ef) + } +} + +// EmbeddedDir is instanced in the code generated by the rice tool and contains all necicary information about an embedded file +type EmbeddedDir struct { + Filename string + DirModTime time.Time + ChildDirs []*EmbeddedDir // direct childs, as returned by virtualDir.Readdir() + ChildFiles []*EmbeddedFile // direct childs, as returned by virtualDir.Readdir() +} + +// EmbeddedFile is instanced in the code generated by the rice tool and contains all necicary information about an embedded file +type EmbeddedFile struct { + Filename string // filename + FileModTime time.Time + Content string +} + +// EmbeddedBoxes is a public register of embedded boxes +var EmbeddedBoxes = make(map[string]*EmbeddedBox) + +// RegisterEmbeddedBox registers an EmbeddedBox +func RegisterEmbeddedBox(name string, box *EmbeddedBox) { + if _, exists := EmbeddedBoxes[name]; exists { + panic(fmt.Sprintf("EmbeddedBox with name `%s` exists already", name)) + } + EmbeddedBoxes[name] = box +} diff --git a/vendor/github.com/GeertJohan/go.rice/file.go b/vendor/github.com/GeertJohan/go.rice/file.go new file mode 100644 index 0000000..606a188 --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/file.go @@ -0,0 +1,144 @@ +package rice + +import ( + "bytes" + "errors" + "os" + "path/filepath" +) + +// File implements the io.Reader, io.Seeker, io.Closer and http.File interfaces +type File struct { + // File abstracts file methods so the user doesn't see the difference between rice.virtualFile, rice.virtualDir and os.File + // TODO: maybe use internal File interface and four implementations: *os.File, appendedFile, virtualFile, virtualDir + + // real file on disk + realF *os.File + + // when embedded (go) + virtualF *virtualFile + virtualD *virtualDir + + // when appended (zip) + appendedF *appendedFile + appendedFileReader *bytes.Reader + // TODO: is appendedFileReader subject of races? Might need a lock here.. +} + +// Close is like (*os.File).Close() +// Visit http://golang.org/pkg/os/#File.Close for more information +func (f *File) Close() error { + if f.appendedF != nil { + if f.appendedFileReader == nil { + return errors.New("already closed") + } + f.appendedFileReader = nil + return nil + } + if f.virtualF != nil { + return f.virtualF.close() + } + if f.virtualD != nil { + return f.virtualD.close() + } + return f.realF.Close() +} + +// Stat is like (*os.File).Stat() +// Visit http://golang.org/pkg/os/#File.Stat for more information +func (f *File) Stat() (os.FileInfo, error) { + if f.appendedF != nil { + if f.appendedF.dir { + return f.appendedF.dirInfo, nil + } + if f.appendedFileReader == nil { + return nil, errors.New("file is closed") + } + return f.appendedF.zipFile.FileInfo(), nil + } + if f.virtualF != nil { + return f.virtualF.stat() + } + if f.virtualD != nil { + return f.virtualD.stat() + } + return f.realF.Stat() +} + +// Readdir is like (*os.File).Readdir() +// Visit http://golang.org/pkg/os/#File.Readdir for more information +func (f *File) Readdir(count int) ([]os.FileInfo, error) { + if f.appendedF != nil { + if f.appendedF.dir { + fi := make([]os.FileInfo, 0, len(f.appendedF.children)) + for _, childAppendedFile := range f.appendedF.children { + if childAppendedFile.dir { + fi = append(fi, childAppendedFile.dirInfo) + } else { + fi = append(fi, childAppendedFile.zipFile.FileInfo()) + } + } + return fi, nil + } + //++ TODO: is os.ErrInvalid the correct error for Readdir on file? + return nil, os.ErrInvalid + } + if f.virtualF != nil { + return f.virtualF.readdir(count) + } + if f.virtualD != nil { + return f.virtualD.readdir(count) + } + return f.realF.Readdir(count) +} + +// Read is like (*os.File).Read() +// Visit http://golang.org/pkg/os/#File.Read for more information +func (f *File) Read(bts []byte) (int, error) { + if f.appendedF != nil { + if f.appendedFileReader == nil { + return 0, &os.PathError{ + Op: "read", + Path: filepath.Base(f.appendedF.zipFile.Name), + Err: errors.New("file is closed"), + } + } + if f.appendedF.dir { + return 0, &os.PathError{ + Op: "read", + Path: filepath.Base(f.appendedF.zipFile.Name), + Err: errors.New("is a directory"), + } + } + return f.appendedFileReader.Read(bts) + } + if f.virtualF != nil { + return f.virtualF.read(bts) + } + if f.virtualD != nil { + return f.virtualD.read(bts) + } + return f.realF.Read(bts) +} + +// Seek is like (*os.File).Seek() +// Visit http://golang.org/pkg/os/#File.Seek for more information +func (f *File) Seek(offset int64, whence int) (int64, error) { + if f.appendedF != nil { + if f.appendedFileReader == nil { + return 0, &os.PathError{ + Op: "seek", + Path: filepath.Base(f.appendedF.zipFile.Name), + Err: errors.New("file is closed"), + } + } + return f.appendedFileReader.Seek(offset, whence) + } + if f.virtualF != nil { + return f.virtualF.seek(offset, whence) + } + if f.virtualD != nil { + return f.virtualD.seek(offset, whence) + } + return f.realF.Seek(offset, whence) +} diff --git a/vendor/github.com/GeertJohan/go.rice/http.go b/vendor/github.com/GeertJohan/go.rice/http.go new file mode 100644 index 0000000..3a61f0e --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/http.go @@ -0,0 +1,21 @@ +package rice + +import ( + "net/http" +) + +// HTTPBox implements http.FileSystem which allows the use of Box with a http.FileServer. +// e.g.: http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox())) +type HTTPBox struct { + *Box +} + +// HTTPBox creates a new HTTPBox from an existing Box +func (b *Box) HTTPBox() *HTTPBox { + return &HTTPBox{b} +} + +// Open returns a File using the http.File interface +func (hb *HTTPBox) Open(name string) (http.File, error) { + return hb.Box.Open(name) +} diff --git a/vendor/github.com/GeertJohan/go.rice/sort.go b/vendor/github.com/GeertJohan/go.rice/sort.go new file mode 100644 index 0000000..cd83c65 --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/sort.go @@ -0,0 +1,19 @@ +package rice + +import "os" + +// SortByName allows an array of os.FileInfo objects +// to be easily sorted by filename using sort.Sort(SortByName(array)) +type SortByName []os.FileInfo + +func (f SortByName) Len() int { return len(f) } +func (f SortByName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } +func (f SortByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } + +// SortByModified allows an array of os.FileInfo objects +// to be easily sorted by modified date using sort.Sort(SortByModified(array)) +type SortByModified []os.FileInfo + +func (f SortByModified) Len() int { return len(f) } +func (f SortByModified) Less(i, j int) bool { return f[i].ModTime().Unix() > f[j].ModTime().Unix() } +func (f SortByModified) Swap(i, j int) { f[i], f[j] = f[j], f[i] } diff --git a/vendor/github.com/GeertJohan/go.rice/virtual.go b/vendor/github.com/GeertJohan/go.rice/virtual.go new file mode 100644 index 0000000..50bff16 --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/virtual.go @@ -0,0 +1,252 @@ +package rice + +import ( + "errors" + "io" + "os" + "path/filepath" + "sort" + + "github.com/GeertJohan/go.rice/embedded" +) + +//++ TODO: IDEA: merge virtualFile and virtualDir, this decreases work done by rice.File + +// Error indicating some function is not implemented yet (but available to satisfy an interface) +var ErrNotImplemented = errors.New("not implemented yet") + +// virtualFile is a 'stateful' virtual file. +// virtualFile wraps an *EmbeddedFile for a call to Box.Open() and virtualizes 'read cursor' (offset) and 'closing'. +// virtualFile is only internally visible and should be exposed through rice.File +type virtualFile struct { + *embedded.EmbeddedFile // the actual embedded file, embedded to obtain methods + offset int64 // read position on the virtual file + closed bool // closed when true +} + +// create a new virtualFile for given EmbeddedFile +func newVirtualFile(ef *embedded.EmbeddedFile) *virtualFile { + vf := &virtualFile{ + EmbeddedFile: ef, + offset: 0, + closed: false, + } + return vf +} + +//++ TODO check for nil pointers in all these methods. When so: return os.PathError with Err: os.ErrInvalid + +func (vf *virtualFile) close() error { + if vf.closed { + return &os.PathError{ + Op: "close", + Path: vf.EmbeddedFile.Filename, + Err: errors.New("already closed"), + } + } + vf.EmbeddedFile = nil + vf.closed = true + return nil +} + +func (vf *virtualFile) stat() (os.FileInfo, error) { + if vf.closed { + return nil, &os.PathError{ + Op: "stat", + Path: vf.EmbeddedFile.Filename, + Err: errors.New("bad file descriptor"), + } + } + return (*embeddedFileInfo)(vf.EmbeddedFile), nil +} + +func (vf *virtualFile) readdir(count int) ([]os.FileInfo, error) { + if vf.closed { + return nil, &os.PathError{ + Op: "readdir", + Path: vf.EmbeddedFile.Filename, + Err: errors.New("bad file descriptor"), + } + } + //TODO: return proper error for a readdir() call on a file + return nil, ErrNotImplemented +} + +func (vf *virtualFile) read(bts []byte) (int, error) { + if vf.closed { + return 0, &os.PathError{ + Op: "read", + Path: vf.EmbeddedFile.Filename, + Err: errors.New("bad file descriptor"), + } + } + + end := vf.offset + int64(len(bts)) + + if end >= int64(len(vf.Content)) { + // end of file, so return what we have + EOF + n := copy(bts, vf.Content[vf.offset:]) + vf.offset = 0 + return n, io.EOF + } + + n := copy(bts, vf.Content[vf.offset:end]) + vf.offset += int64(n) + return n, nil + +} + +func (vf *virtualFile) seek(offset int64, whence int) (int64, error) { + if vf.closed { + return 0, &os.PathError{ + Op: "seek", + Path: vf.EmbeddedFile.Filename, + Err: errors.New("bad file descriptor"), + } + } + var e error + + //++ TODO: check if this is correct implementation for seek + switch whence { + case os.SEEK_SET: + //++ check if new offset isn't out of bounds, set e when it is, then break out of switch + vf.offset = offset + case os.SEEK_CUR: + //++ check if new offset isn't out of bounds, set e when it is, then break out of switch + vf.offset += offset + case os.SEEK_END: + //++ check if new offset isn't out of bounds, set e when it is, then break out of switch + vf.offset = int64(len(vf.EmbeddedFile.Content)) - offset + } + + if e != nil { + return 0, &os.PathError{ + Op: "seek", + Path: vf.Filename, + Err: e, + } + } + + return vf.offset, nil +} + +// virtualDir is a 'stateful' virtual directory. +// virtualDir wraps an *EmbeddedDir for a call to Box.Open() and virtualizes 'closing'. +// virtualDir is only internally visible and should be exposed through rice.File +type virtualDir struct { + *embedded.EmbeddedDir + offset int // readdir position on the directory + closed bool +} + +// create a new virtualDir for given EmbeddedDir +func newVirtualDir(ed *embedded.EmbeddedDir) *virtualDir { + vd := &virtualDir{ + EmbeddedDir: ed, + offset: 0, + closed: false, + } + return vd +} + +func (vd *virtualDir) close() error { + //++ TODO: needs sync mutex? + if vd.closed { + return &os.PathError{ + Op: "close", + Path: vd.EmbeddedDir.Filename, + Err: errors.New("already closed"), + } + } + vd.closed = true + return nil +} + +func (vd *virtualDir) stat() (os.FileInfo, error) { + if vd.closed { + return nil, &os.PathError{ + Op: "stat", + Path: vd.EmbeddedDir.Filename, + Err: errors.New("bad file descriptor"), + } + } + return (*embeddedDirInfo)(vd.EmbeddedDir), nil +} + +func (vd *virtualDir) readdir(n int) (fi []os.FileInfo, err error) { + + if vd.closed { + return nil, &os.PathError{ + Op: "readdir", + Path: vd.EmbeddedDir.Filename, + Err: errors.New("bad file descriptor"), + } + } + + // Build up the array of our contents + var files []os.FileInfo + + // Add the child directories + for _, child := range vd.ChildDirs { + child.Filename = filepath.Base(child.Filename) + files = append(files, (*embeddedDirInfo)(child)) + } + + // Add the child files + for _, child := range vd.ChildFiles { + child.Filename = filepath.Base(child.Filename) + files = append(files, (*embeddedFileInfo)(child)) + } + + // Sort it by filename (lexical order) + sort.Sort(SortByName(files)) + + // Return all contents if that's what is requested + if n <= 0 { + vd.offset = 0 + return files, nil + } + + // If user has requested past the end of our list + // return what we can and send an EOF + if vd.offset+n >= len(files) { + offset := vd.offset + vd.offset = 0 + return files[offset:], io.EOF + } + + offset := vd.offset + vd.offset += n + return files[offset : offset+n], nil + +} + +func (vd *virtualDir) read(bts []byte) (int, error) { + if vd.closed { + return 0, &os.PathError{ + Op: "read", + Path: vd.EmbeddedDir.Filename, + Err: errors.New("bad file descriptor"), + } + } + return 0, &os.PathError{ + Op: "read", + Path: vd.EmbeddedDir.Filename, + Err: errors.New("is a directory"), + } +} + +func (vd *virtualDir) seek(offset int64, whence int) (int64, error) { + if vd.closed { + return 0, &os.PathError{ + Op: "seek", + Path: vd.EmbeddedDir.Filename, + Err: errors.New("bad file descriptor"), + } + } + return 0, &os.PathError{ + Op: "seek", + Path: vd.Filename, + Err: errors.New("is a directory"), + } +} diff --git a/vendor/github.com/GeertJohan/go.rice/walk.go b/vendor/github.com/GeertJohan/go.rice/walk.go new file mode 100644 index 0000000..3042aea --- /dev/null +++ b/vendor/github.com/GeertJohan/go.rice/walk.go @@ -0,0 +1,122 @@ +package rice + +import ( + "os" + "path/filepath" + "sort" + "strings" +) + +// Walk is like filepath.Walk() +// Visit http://golang.org/pkg/path/filepath/#Walk for more information +func (b *Box) Walk(path string, walkFn filepath.WalkFunc) error { + + pathFile, err := b.Open(path) + if err != nil { + return err + } + defer pathFile.Close() + + pathInfo, err := pathFile.Stat() + if err != nil { + return err + } + + if b.IsAppended() || b.IsEmbedded() { + return b.walk(path, pathInfo, walkFn) + } + + // We don't have any embedded or appended box so use live filesystem mode + return filepath.Walk(b.absolutePath+string(os.PathSeparator)+path, func(path string, info os.FileInfo, err error) error { + + // Strip out the box name from the returned paths + path = strings.TrimPrefix(path, b.absolutePath+string(os.PathSeparator)) + return walkFn(path, info, err) + + }) + +} + +// walk recursively descends path. +// See walk() in $GOROOT/src/pkg/path/filepath/path.go +func (b *Box) walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { + + err := walkFn(path, info, nil) + if err != nil { + if info.IsDir() && err == filepath.SkipDir { + return nil + } + return err + } + + if !info.IsDir() { + return nil + } + + names, err := b.readDirNames(path) + if err != nil { + return walkFn(path, info, err) + } + + for _, name := range names { + + filename := filepath.Join(path, name) + fileObject, err := b.Open(filename) + if err != nil { + return err + } + defer fileObject.Close() + + fileInfo, err := fileObject.Stat() + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = b.walk(filename, fileInfo, walkFn) + if err != nil { + if !fileInfo.IsDir() || err != filepath.SkipDir { + return err + } + } + } + } + + return nil + +} + +// readDirNames reads the directory named by path and returns a sorted list of directory entries. +// See readDirNames() in $GOROOT/pkg/path/filepath/path.go +func (b *Box) readDirNames(path string) ([]string, error) { + + f, err := b.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + stat, err := f.Stat() + if err != nil { + return nil, err + } + + if !stat.IsDir() { + return nil, nil + } + + infos, err := f.Readdir(0) + if err != nil { + return nil, err + } + + var names []string + + for _, info := range infos { + names = append(names, info.Name()) + } + + sort.Strings(names) + return names, nil + +} diff --git a/vendor/github.com/Xe/gopreload/doc.go b/vendor/github.com/Xe/gopreload/doc.go new file mode 100644 index 0000000..720c5c1 --- /dev/null +++ b/vendor/github.com/Xe/gopreload/doc.go @@ -0,0 +1,7 @@ +/* +Package gopreload is a bit of a hack to emulate the behavior of LD_PRELOAD [ld-preload]. +This allows you to have automatically starting instrumentation, etc. + +[ld-preload]: http://man7.org/linux/man-pages/man8/ld.so.8.html (see LD_PRELOAD section) +*/ +package gopreload diff --git a/vendor/github.com/Xe/gopreload/preload.go b/vendor/github.com/Xe/gopreload/preload.go new file mode 100644 index 0000000..1b5a0c9 --- /dev/null +++ b/vendor/github.com/Xe/gopreload/preload.go @@ -0,0 +1,26 @@ +//+build linux,go1.8 + +package gopreload + +import ( + "log" + "os" + "plugin" + "strings" +) + +func init() { + gpv := os.Getenv("GO_PRELOAD") + if gpv == "" { + return + } + + for _, elem := range strings.Split(gpv, ",") { + log.Printf("gopreload: trying to open: %s", elem) + _, err := plugin.Open(elem) + if err != nil { + log.Printf("%v from GO_PRELOAD cannot be loaded: %v", elem, err) + continue + } + } +} diff --git a/vendor/github.com/Xe/jsonfeed/jsonfeed.go b/vendor/github.com/Xe/jsonfeed/jsonfeed.go new file mode 100644 index 0000000..e9fa0f2 --- /dev/null +++ b/vendor/github.com/Xe/jsonfeed/jsonfeed.go @@ -0,0 +1,242 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +/* +Package jsonfeed is a set of types and convenience functions for reading and +parsing JSON Feed version 1 as defined here: https://jsonfeed.org/version/1 +*/ +package jsonfeed + +import ( + "encoding/json" + "io" + "time" +) + +// CurrentVersion will point to the current specification of JSON feed +// that this package implements. +const CurrentVersion = "https://jsonfeed.org/version/1" + +// Item is a single article or link in a JSON Feed. +type Item struct { + // ID is unique for that item for that feed over time. If an item + // is ever updated, the id should be unchanged. New items should + // never use a previously-used id. If an id is presented as a number + // or other type, a JSON Feed reader must coerce it to a string. + // Ideally, the id is the full URL of the resource described by the + // item, since URLs make great unique identifiers. + ID string `json:"id"` + + // URL is the URL of the resource described by the item. It’s the + // permalink. This may be the same as the id — but should be present + // regardless. + URL string `json:"url,omitempty"` + + // ExternalURL is the URL of a page elsewhere. This is especially + // useful for linkblogs. If url links to where you’re talking about + // a thing, then this links to the thing you’re talking about. + ExternalURL string `json:"external_url,omitempty"` + + // Title (optional, string) is plain text. Microblog items in + // particular may omit titles. + Title string `json:"title,omitempty"` + + // ContentHTML and ContentText are each optional strings — but one + // or both must be present. This is the HTML or plain text of the + // item. Important: the only place HTML is allowed in this format + // is in content_html. A Twitter-like service might use content_text, + // while a blog might use content_html. Use whichever makes sense + // for your resource. (It doesn’t even have to be the same for each + // item in a feed.) + ContentHTML string `json:"content_html,omitempty"` + ContentText string `json:"content_text,omitempty"` + + // Summary is a plain text sentence or two describing the item. + // This might be presented in a timeline, for instance, where a + // detail view would display all of ContentHTML or ContentText. + Summary string `json:"summary,omitempty"` + + // Image is the URL of the main image for the item. This image + // may also appear in the content_html — if so, it’s a hint to + // the feed reader that this is the main, featured image. Feed + // readers may use the image as a preview (probably resized as + // a thumbnail and placed in a timeline). + Image string `json:"image,omitempty"` + + // BannerImage is the URL of an image to use as a banner. Some + // blogging systems (such as Medium) display a different banner + // image chosen to go with each post, but that image wouldn’t + // otherwise appear in the content_html. A feed reader with a + // detail view may choose to show this banner image at the top + // of the detail view, possibly with the title overlaid. + BannerImage string `json:"banner_image,omitempty"` + + // DatePublished specifies the date of this Item's publication. + DatePublished time.Time `json:"date_published,omitempty"` + + // DateModified specifies the date of this Item's last modification + // (if applicable) + DateModified time.Time `json:"date_modified,omitempty"` + + // Author has the same structure as the top-level author. If not + // specified in an item, then the top-level author, if present, + // is the author of the item. + Author *Author `json:"author,omitempty"` + + // Tags can have any plain text values you want. Tags tend to be + // just one word, but they may be anything. Note: they are not + // the equivalent of Twitter hashtags. Some blogging systems and + // other feed formats call these categories. + Tags []string `json:"tags,omitempty"` + + // Attachments (optional, array) lists related resources. Podcasts, + // for instance, would include an attachment that’s an audio or + // video file. + Attachments []Attachment `json:"attachments,omitempty"` +} + +// Author specifies the feed author. The author object has several members. +// These are all optional, but if you provide an author object, then at +// least one is required. +type Author struct { + // Name is the author's name. + Name string `json:"name,omitempty"` + + // URL is the URL of a site owned by the author. It could be a + // blog, micro-blog, Twitter account, and so on. Ideally the linked-to + // page provides a way to contact the author, but that’s not + // required. The URL could be a mailto: link, though we suspect + // that will be rare. + URL string `json:"url,omitempty"` + + // Avatar is the URL for an image for the author. As with icon, + // it should be square and relatively large — such as 512 x 512 — + // and should use transparency where appropriate, since it may + // be rendered on a non-white background. + Avatar string `json:"avatar,omitempty"` +} + +// Hub describes endpoints that can be used to subscribe to real-time +// notifications from the publisher of this feed. Each object has a type +// and url, both of which are required. +type Hub struct { + Type string `json:"type"` + URL string `json:"url"` +} + +// Attachment is a related resource to an Item. If the Feed describes a +// podcast, this would refer to the episodes of said podcast. +type Attachment struct { + // URL specifies the location of the attachment. + URL string `json:"url"` + + // MIMEType specifies the type of the attachment, such as "audio/mpeg". + MIMEType string `json:"mime_type"` + + // Title is a name for the attachment. Important: if there are multiple + // attachments, and two or more have the exact same title (when title + // is present), then they are considered as alternate representations + // of the same thing. In this way a podcaster, for instance, might + // provide an audio recording in different formats. + Title string `json:"title,omitifempty"` + + // SizeInBytes specifies the attachment filesize in bytes. + SizeInBytes int64 `json:"size_in_bytes,omitempty"` + + // DurationInSeconds specifies how long the attachment takes to listen + // to or watch. + DurationInSeconds int64 `json:"duration_in_seconds,omitempty"` +} + +// Feed is a list that may change over time, and the individual items in the +// list may change. +// +// Think of a blog or microblog, Twitter or Facebook timeline, set of commits +// to a repository, or even a server log. These are all lists, and each could +// be described by a Feed. +// +// A JSON Feed starts with some info at the top: it says where the Feed comes +// from, and may say who created it and so on. +type Feed struct { + // Version is the URL of the version of the format the Feed uses. + Version string `json:"version"` + + // Title is the name of the Feed, which will often correspond to the + // name of the website (blog, for instance), though not necessarily. + Title string `json:"title"` + + // HomePageURL is the URL of the resource that the Feed describes. + // This resource may or may not actually be a “home” page, but it + // should be an HTML page. If a Feed is published on the public web, + // this should be considered as required. But it may not make sense + // in the case of a file created on a desktop computer, when that + // file is not shared or is shared only privately. + // + // This field is strongly reccomended, but not required. + HomePageURL string `json:"home_page_url,omitempty"` + + // FeedURL is the URL of the Feed, and serves as the unique identifier + // for the Feed. As with home_page_url, this should be considered + // required for Feeds on the public web. + // + // This field is strongly reccomended, but not required. + FeedURL string `json:"Feed_url,omitempty"` + + // Description provides more detail, beyond the title, on what the Feed + // is about. A Feed reader may display this text. + Description string `json:"description,omitempty"` + + // UserComment is a description of the purpose of the Feed. This is for + // the use of people looking at the raw JSON, and should be ignored by + // Feed readers. + UserComment string `json:"user_comment,omitempty"` + + // NextURL is the URL of a Feed that provides the next n items, where + // n is determined by the publisher. This allows for pagination, but + // with the expectation that reader software is not required to use it + // and probably won’t use it very often. next_url must not be the same + // as Feed_url, and it must not be the same as a previous next_url + // (to avoid infinite loops). + NextURL string `json:"next_url,omitempty"` + + // Icon is the URL of an image for the Feed suitable to be used in a + // timeline, much the way an avatar might be used. It should be square + // and relatively large — such as 512 x 512 — so that it can be scaled-down + // and so that it can look good on retina displays. It should use transparency + // where appropriate, since it may be rendered on a non-white background. + Icon string `json:"icon,omitempty"` + + // Favicon is the URL of an image for the Feed suitable to be used in a + // source list. It should be square and relatively small, but not smaller + // than 64 x 64 (so that it can look good on retina displays). As with icon, + // this image should use transparency where appropriate, since it may be + // rendered on a non-white background. + Favicon string `json:"favicon,omitempty"` + + // Author specifies the Feed author. + Author Author `json:"author,omitempty"` + + // Expired specifies if the Feed will never update again. A Feed for a + // temporary event, such as an instance of the Olympics, could expire. + // If the value is true, then it’s expired. Any other value, or the + // absence of expired, means the Feed may continue to update. + Expired bool `json:"expired,omitempty"` + + // Hubs describes endpoints that can be used to subscribe to real-time + // notifications from the publisher of this Feed. + Hubs []Hub `json:"hubs,omitempty"` + + // Items is the list of Items in this Feed. + Items []Item `json:"items"` +} + +// Parse reads a JSON feed object out of a reader. +func Parse(r io.Reader) (Feed, error) { + var feed Feed + decoder := json.NewDecoder(r) + if err := decoder.Decode(&feed); err != nil { + return Feed{}, err + } + return feed, nil +} diff --git a/vendor/github.com/Xe/ln/filter.go b/vendor/github.com/Xe/ln/filter.go new file mode 100644 index 0000000..586efef --- /dev/null +++ b/vendor/github.com/Xe/ln/filter.go @@ -0,0 +1,66 @@ +package ln + +import ( + "io" + "sync" +) + +// Filter interface for defining chain filters +type Filter interface { + Apply(Event) bool + Run() + Close() +} + +// FilterFunc allows simple functions to implement the Filter interface +type FilterFunc func(e Event) bool + +// Apply implements the Filter interface +func (ff FilterFunc) Apply(e Event) bool { + return ff(e) +} + +// Run implements the Filter interface +func (ff FilterFunc) Run() {} + +// Close implements the Filter interface +func (ff FilterFunc) Close() {} + +// WriterFilter implements a filter, which arbitrarily writes to an io.Writer +type WriterFilter struct { + sync.Mutex + Out io.Writer + Formatter Formatter +} + +// NewWriterFilter creates a filter to add to the chain +func NewWriterFilter(out io.Writer, format Formatter) *WriterFilter { + if format == nil { + format = DefaultFormatter + } + return &WriterFilter{ + Out: out, + Formatter: format, + } +} + +// Apply implements the Filter interface +func (w *WriterFilter) Apply(e Event) bool { + output, err := w.Formatter.Format(e) + if err == nil { + w.Lock() + w.Out.Write(output) + w.Unlock() + } + + return true +} + +// Run implements the Filter interface +func (w *WriterFilter) Run() {} + +// Close implements the Filter interface +func (w *WriterFilter) Close() {} + +// NilFilter is safe to return as a Filter, but does nothing +var NilFilter = FilterFunc(func(e Event) bool { return true }) diff --git a/vendor/github.com/Xe/ln/formatter.go b/vendor/github.com/Xe/ln/formatter.go new file mode 100644 index 0000000..ecd4743 --- /dev/null +++ b/vendor/github.com/Xe/ln/formatter.go @@ -0,0 +1,110 @@ +package ln + +import ( + "bytes" + "fmt" + "time" +) + +var ( + // DefaultTimeFormat represents the way in which time will be formatted by default + DefaultTimeFormat = time.RFC3339 +) + +// Formatter defines the formatting of events +type Formatter interface { + Format(Event) ([]byte, error) +} + +// DefaultFormatter is the default way in which to format events +var DefaultFormatter Formatter + +func init() { + DefaultFormatter = NewTextFormatter() +} + +// TextFormatter formats events as key value pairs. +// Any remaining text not wrapped in an instance of `F` will be +// placed at the end. +type TextFormatter struct { + TimeFormat string +} + +// NewTextFormatter returns a Formatter that outputs as text. +func NewTextFormatter() Formatter { + return &TextFormatter{TimeFormat: DefaultTimeFormat} +} + +// Format implements the Formatter interface +func (t *TextFormatter) Format(e Event) ([]byte, error) { + var writer bytes.Buffer + + writer.WriteString("time=\"") + writer.WriteString(e.Time.Format(t.TimeFormat)) + writer.WriteString("\"") + + keys := make([]string, len(e.Data)) + i := 0 + + for k := range e.Data { + keys[i] = k + i++ + } + + for _, k := range keys { + v := e.Data[k] + + writer.WriteByte(' ') + if shouldQuote(k) { + writer.WriteString(fmt.Sprintf("%q", k)) + } else { + writer.WriteString(k) + } + + writer.WriteByte('=') + + switch v.(type) { + case string: + vs, _ := v.(string) + if shouldQuote(vs) { + fmt.Fprintf(&writer, "%q", vs) + } else { + writer.WriteString(vs) + } + case error: + tmperr, _ := v.(error) + es := tmperr.Error() + + if shouldQuote(es) { + fmt.Fprintf(&writer, "%q", es) + } else { + writer.WriteString(es) + } + case time.Time: + tmptime, _ := v.(time.Time) + writer.WriteString(tmptime.Format(time.RFC3339)) + default: + fmt.Fprint(&writer, v) + } + } + + if len(e.Message) > 0 { + fmt.Fprintf(&writer, " _msg=%q", e.Message) + } + + writer.WriteByte('\n') + return writer.Bytes(), nil +} + +func shouldQuote(s string) bool { + for _, b := range s { + if !((b >= 'A' && b <= 'Z') || + (b >= 'a' && b <= 'z') || + (b >= '0' && b <= '9') || + (b == '-' || b == '.' || b == '#' || + b == '/' || b == '_')) { + return true + } + } + return false +} diff --git a/vendor/github.com/Xe/ln/logger.go b/vendor/github.com/Xe/ln/logger.go new file mode 100644 index 0000000..cdfe89e --- /dev/null +++ b/vendor/github.com/Xe/ln/logger.go @@ -0,0 +1,141 @@ +package ln + +import ( + "fmt" + "os" + "time" + + "github.com/pkg/errors" +) + +// Logger holds the current priority and list of filters +type Logger struct { + Filters []Filter +} + +// DefaultLogger is the default implementation of Logger +var DefaultLogger *Logger + +func init() { + var defaultFilters []Filter + + // Default to STDOUT for logging, but allow LN_OUT to change it. + out := os.Stdout + if os.Getenv("LN_OUT") == "" { + out = os.Stderr + } + + defaultFilters = append(defaultFilters, NewWriterFilter(out, nil)) + + DefaultLogger = &Logger{ + Filters: defaultFilters, + } + +} + +// F is a key-value mapping for structured data. +type F map[string]interface{} + +type Fer interface { + F() map[string]interface{} +} + +// Event represents an event +type Event struct { + Time time.Time + Data F + Message string +} + +// Log is the generic logging method. +func (l *Logger) Log(xs ...interface{}) { + var bits []interface{} + event := Event{Time: time.Now()} + + addF := func(bf F) { + if event.Data == nil { + event.Data = bf + } else { + for k, v := range bf { + event.Data[k] = v + } + } + } + + // Assemble the event + for _, b := range xs { + if bf, ok := b.(F); ok { + addF(bf) + } else if fer, ok := b.(Fer); ok { + addF(F(fer.F())) + } else { + bits = append(bits, b) + } + } + + event.Message = fmt.Sprint(bits...) + + if os.Getenv("LN_DEBUG_ALL_EVENTS") == "1" { + frame := callersFrame() + if event.Data == nil { + event.Data = make(F) + } + event.Data["_lineno"] = frame.lineno + event.Data["_function"] = frame.function + event.Data["_filename"] = frame.filename + } + + l.filter(event) +} + +func (l *Logger) filter(e Event) { + for _, f := range l.Filters { + if !f.Apply(e) { + return + } + } +} + +// Error logs an error and information about the context of said error. +func (l *Logger) Error(err error, xs ...interface{}) { + data := F{} + frame := callersFrame() + + data["_lineno"] = frame.lineno + data["_function"] = frame.function + data["_filename"] = frame.filename + data["err"] = err + + cause := errors.Cause(err) + if cause != nil { + data["cause"] = cause.Error() + } + + xs = append(xs, data) + + l.Log(xs...) +} + +// Fatal logs this set of values, then exits with status code 1. +func (l *Logger) Fatal(xs ...interface{}) { + l.Log(xs...) + + os.Exit(1) +} + +// Default Implementation + +// Log is the generic logging method. +func Log(xs ...interface{}) { + DefaultLogger.Log(xs...) +} + +// Error logs an error and information about the context of said error. +func Error(err error, xs ...interface{}) { + DefaultLogger.Error(err, xs...) +} + +// Fatal logs this set of values, then exits with status code 1. +func Fatal(xs ...interface{}) { + DefaultLogger.Fatal(xs...) +} diff --git a/vendor/github.com/Xe/ln/stack.go b/vendor/github.com/Xe/ln/stack.go new file mode 100644 index 0000000..1cf1e7a --- /dev/null +++ b/vendor/github.com/Xe/ln/stack.go @@ -0,0 +1,44 @@ +package ln + +import ( + "os" + "runtime" + "strings" +) + +type frame struct { + filename string + function string + lineno int +} + +// skips 2 frames, since Caller returns the current frame, and we need +// the caller's caller. +func callersFrame() frame { + var out frame + pc, file, line, ok := runtime.Caller(3) + if !ok { + return out + } + srcLoc := strings.LastIndex(file, "/src/") + if srcLoc >= 0 { + file = file[srcLoc+5:] + } + out.filename = file + out.function = functionName(pc) + out.lineno = line + + return out +} + +func functionName(pc uintptr) string { + fn := runtime.FuncForPC(pc) + if fn == nil { + return "???" + } + name := fn.Name() + beg := strings.LastIndex(name, string(os.PathSeparator)) + return name[beg+1:] + // end := strings.LastIndex(name, string(os.PathSeparator)) + // return name[end+1 : len(name)] +} diff --git a/vendor/github.com/daaku/go.zipexe/zipexe.go b/vendor/github.com/daaku/go.zipexe/zipexe.go new file mode 100644 index 0000000..6004606 --- /dev/null +++ b/vendor/github.com/daaku/go.zipexe/zipexe.go @@ -0,0 +1,142 @@ +// Package zipexe attempts to open an executable binary file as a zip file. +package zipexe + +import ( + "archive/zip" + "debug/elf" + "debug/macho" + "debug/pe" + "errors" + "io" + "os" +) + +// Opens a zip file by path. +func Open(path string) (*zip.Reader, error) { + _, rd, err := OpenCloser(path) + return rd, err +} + +// OpenCloser is like Open but returns an additional Closer to avoid leaking open files. +func OpenCloser(path string) (io.Closer, *zip.Reader, error) { + file, err := os.Open(path) + if err != nil { + return nil, nil, err + } + finfo, err := file.Stat() + if err != nil { + return nil, nil, err + } + zr, err := NewReader(file, finfo.Size()) + if err != nil { + return nil, nil, err + } + return file, zr, nil +} + +// Open a zip file, specially handling various binaries that may have been +// augmented with zip data. +func NewReader(rda io.ReaderAt, size int64) (*zip.Reader, error) { + handlers := []func(io.ReaderAt, int64) (*zip.Reader, error){ + zip.NewReader, + zipExeReaderMacho, + zipExeReaderElf, + zipExeReaderPe, + } + + for _, handler := range handlers { + zfile, err := handler(rda, size) + if err == nil { + return zfile, nil + } + } + return nil, errors.New("Couldn't Open As Executable") +} + +// zipExeReaderMacho treats the file as a Mach-O binary +// (Mac OS X / Darwin executable) and attempts to find a zip archive. +func zipExeReaderMacho(rda io.ReaderAt, size int64) (*zip.Reader, error) { + file, err := macho.NewFile(rda) + if err != nil { + return nil, err + } + + var max int64 + for _, load := range file.Loads { + seg, ok := load.(*macho.Segment) + if ok { + // Check if the segment contains a zip file + if zfile, err := zip.NewReader(seg, int64(seg.Filesz)); err == nil { + return zfile, nil + } + + // Otherwise move end of file pointer + end := int64(seg.Offset + seg.Filesz) + if end > max { + max = end + } + } + } + + // No zip file within binary, try appended to end + section := io.NewSectionReader(rda, max, size-max) + return zip.NewReader(section, section.Size()) +} + +// zipExeReaderPe treats the file as a Portable Exectuable binary +// (Windows executable) and attempts to find a zip archive. +func zipExeReaderPe(rda io.ReaderAt, size int64) (*zip.Reader, error) { + file, err := pe.NewFile(rda) + if err != nil { + return nil, err + } + + var max int64 + for _, sec := range file.Sections { + // Check if this section has a zip file + if zfile, err := zip.NewReader(sec, int64(sec.Size)); err == nil { + return zfile, nil + } + + // Otherwise move end of file pointer + end := int64(sec.Offset + sec.Size) + if end > max { + max = end + } + } + + // No zip file within binary, try appended to end + section := io.NewSectionReader(rda, max, size-max) + return zip.NewReader(section, section.Size()) +} + +// zipExeReaderElf treats the file as a ELF binary +// (linux/BSD/etc... executable) and attempts to find a zip archive. +func zipExeReaderElf(rda io.ReaderAt, size int64) (*zip.Reader, error) { + file, err := elf.NewFile(rda) + if err != nil { + return nil, err + } + + var max int64 + for _, sect := range file.Sections { + if sect.Type == elf.SHT_NOBITS { + continue + } + + // Check if this section has a zip file + if zfile, err := zip.NewReader(sect, int64(sect.Size)); err == nil { + return zfile, nil + } + + // Otherwise move end of file pointer + end := int64(sect.Offset + sect.Size) + if end > max { + max = end + } + } + + // No zip file within binary, try appended to end + section := io.NewSectionReader(rda, max, size-max) + return zip.NewReader(section, section.Size()) +} diff --git a/vendor/github.com/google/gops/agent/agent.go b/vendor/github.com/google/gops/agent/agent.go new file mode 100644 index 0000000..5708391 --- /dev/null +++ b/vendor/github.com/google/gops/agent/agent.go @@ -0,0 +1,237 @@ +// Copyright 2016 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 agent provides hooks programs can register to retrieve +// diagnostics data by using gops. +package agent + +import ( + "fmt" + "io" + "io/ioutil" + "net" + "os" + gosignal "os/signal" + "runtime" + "runtime/pprof" + "runtime/trace" + "strconv" + "sync" + "time" + + "bufio" + + "github.com/google/gops/internal" + "github.com/google/gops/signal" + "github.com/kardianos/osext" +) + +const defaultAddr = "127.0.0.1:0" + +var ( + mu sync.Mutex + portfile string + listener net.Listener + + units = []string{" bytes", "KB", "MB", "GB", "TB", "PB"} +) + +// Options allows configuring the started agent. +type Options struct { + // Addr is the host:port the agent will be listening at. + // Optional. + Addr string + + // NoShutdownCleanup tells the agent not to automatically cleanup + // resources if the running process receives an interrupt. + // Optional. + NoShutdownCleanup bool +} + +// Listen starts the gops agent on a host process. Once agent started, users +// can use the advanced gops features. The agent will listen to Interrupt +// signals and exit the process, if you need to perform further work on the +// Interrupt signal use the options parameter to configure the agent +// accordingly. +// +// Note: The agent exposes an endpoint via a TCP connection that can be used by +// any program on the system. Review your security requirements before starting +// the agent. +func Listen(opts *Options) error { + mu.Lock() + defer mu.Unlock() + + if opts == nil { + opts = &Options{} + } + if portfile != "" { + return fmt.Errorf("gops: agent already listening at: %v", listener.Addr()) + } + + gopsdir, err := internal.ConfigDir() + if err != nil { + return err + } + err = os.MkdirAll(gopsdir, os.ModePerm) + if err != nil { + return err + } + if !opts.NoShutdownCleanup { + gracefulShutdown() + } + + addr := opts.Addr + if addr == "" { + addr = defaultAddr + } + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + listener = ln + port := listener.Addr().(*net.TCPAddr).Port + portfile = fmt.Sprintf("%s/%d", gopsdir, os.Getpid()) + err = ioutil.WriteFile(portfile, []byte(strconv.Itoa(port)), os.ModePerm) + if err != nil { + return err + } + + go listen() + return nil +} + +func listen() { + buf := make([]byte, 1) + for { + fd, err := listener.Accept() + if err != nil { + fmt.Fprintf(os.Stderr, "gops: %v", err) + if netErr, ok := err.(net.Error); ok && !netErr.Temporary() { + break + } + continue + } + if _, err := fd.Read(buf); err != nil { + fmt.Fprintf(os.Stderr, "gops: %v", err) + continue + } + if err := handle(fd, buf); err != nil { + fmt.Fprintf(os.Stderr, "gops: %v", err) + continue + } + fd.Close() + } +} + +func gracefulShutdown() { + c := make(chan os.Signal, 1) + gosignal.Notify(c, os.Interrupt) + go func() { + // cleanup the socket on shutdown. + <-c + Close() + os.Exit(1) + }() +} + +// Close closes the agent, removing temporary files and closing the TCP listener. +// If no agent is listening, Close does nothing. +func Close() { + mu.Lock() + defer mu.Unlock() + + if portfile != "" { + os.Remove(portfile) + portfile = "" + } + if listener != nil { + listener.Close() + } +} + +func formatBytes(val uint64) string { + var i int + var target uint64 + for i = range units { + target = 1 << uint(10*(i+1)) + if val < target { + break + } + } + if i > 0 { + return fmt.Sprintf("%0.2f%s (%d bytes)", float64(val)/(float64(target)/1024), units[i], val) + } + return fmt.Sprintf("%d bytes", val) +} + +func handle(conn io.Writer, msg []byte) error { + switch msg[0] { + case signal.StackTrace: + return pprof.Lookup("goroutine").WriteTo(conn, 2) + case signal.GC: + runtime.GC() + _, err := conn.Write([]byte("ok")) + return err + case signal.MemStats: + var s runtime.MemStats + runtime.ReadMemStats(&s) + fmt.Fprintf(conn, "alloc: %v\n", formatBytes(s.Alloc)) + fmt.Fprintf(conn, "total-alloc: %v\n", formatBytes(s.TotalAlloc)) + fmt.Fprintf(conn, "sys: %v\n", formatBytes(s.Sys)) + fmt.Fprintf(conn, "lookups: %v\n", s.Lookups) + fmt.Fprintf(conn, "mallocs: %v\n", s.Mallocs) + fmt.Fprintf(conn, "frees: %v\n", s.Frees) + fmt.Fprintf(conn, "heap-alloc: %v\n", formatBytes(s.HeapAlloc)) + fmt.Fprintf(conn, "heap-sys: %v\n", formatBytes(s.HeapSys)) + fmt.Fprintf(conn, "heap-idle: %v\n", formatBytes(s.HeapIdle)) + fmt.Fprintf(conn, "heap-in-use: %v\n", formatBytes(s.HeapInuse)) + fmt.Fprintf(conn, "heap-released: %v\n", formatBytes(s.HeapReleased)) + fmt.Fprintf(conn, "heap-objects: %v\n", s.HeapObjects) + fmt.Fprintf(conn, "stack-in-use: %v\n", formatBytes(s.StackInuse)) + fmt.Fprintf(conn, "stack-sys: %v\n", formatBytes(s.StackSys)) + fmt.Fprintf(conn, "next-gc: when heap-alloc >= %v\n", formatBytes(s.NextGC)) + lastGC := "-" + if s.LastGC != 0 { + lastGC = fmt.Sprint(time.Unix(0, int64(s.LastGC))) + } + fmt.Fprintf(conn, "last-gc: %v\n", lastGC) + fmt.Fprintf(conn, "gc-pause: %v\n", time.Duration(s.PauseTotalNs)) + fmt.Fprintf(conn, "num-gc: %v\n", s.NumGC) + fmt.Fprintf(conn, "enable-gc: %v\n", s.EnableGC) + fmt.Fprintf(conn, "debug-gc: %v\n", s.DebugGC) + case signal.Version: + fmt.Fprintf(conn, "%v\n", runtime.Version()) + case signal.HeapProfile: + pprof.WriteHeapProfile(conn) + case signal.CPUProfile: + if err := pprof.StartCPUProfile(conn); err != nil { + return err + } + time.Sleep(30 * time.Second) + pprof.StopCPUProfile() + case signal.Stats: + fmt.Fprintf(conn, "goroutines: %v\n", runtime.NumGoroutine()) + fmt.Fprintf(conn, "OS threads: %v\n", pprof.Lookup("threadcreate").Count()) + fmt.Fprintf(conn, "GOMAXPROCS: %v\n", runtime.GOMAXPROCS(0)) + fmt.Fprintf(conn, "num CPU: %v\n", runtime.NumCPU()) + case signal.BinaryDump: + path, err := osext.Executable() + if err != nil { + return err + } + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + _, err = bufio.NewReader(f).WriteTo(conn) + return err + case signal.Trace: + trace.Start(conn) + time.Sleep(5 * time.Second) + trace.Stop() + } + return nil +} diff --git a/vendor/github.com/google/gops/internal/internal.go b/vendor/github.com/google/gops/internal/internal.go new file mode 100644 index 0000000..1382822 --- /dev/null +++ b/vendor/github.com/google/gops/internal/internal.go @@ -0,0 +1,52 @@ +package internal + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" +) + +func ConfigDir() (string, error) { + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("APPDATA"), "gops"), nil + } + homeDir := guessUnixHomeDir() + if homeDir == "" { + return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty") + } + return filepath.Join(homeDir, ".config", "gops"), nil +} + +func guessUnixHomeDir() string { + usr, err := user.Current() + if err == nil { + return usr.HomeDir + } + return os.Getenv("HOME") +} + +func PIDFile(pid int) (string, error) { + gopsdir, err := ConfigDir() + if err != nil { + return "", err + } + return fmt.Sprintf("%s/%d", gopsdir, pid), nil +} + +func GetPort(pid int) (string, error) { + portfile, err := PIDFile(pid) + if err != nil { + return "", err + } + b, err := ioutil.ReadFile(portfile) + if err != nil { + return "", err + } + port := strings.TrimSpace(string(b)) + return port, nil +} diff --git a/vendor/github.com/google/gops/signal/signal.go b/vendor/github.com/google/gops/signal/signal.go new file mode 100644 index 0000000..b2bfbe1 --- /dev/null +++ b/vendor/github.com/google/gops/signal/signal.go @@ -0,0 +1,35 @@ +// Copyright 2016 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 signal contains signals used to communicate to the gops agents. +package signal + +const ( + // StackTrace represents a command to print stack trace. + StackTrace = byte(0x1) + + // GC runs the garbage collector. + GC = byte(0x2) + + // MemStats reports memory stats. + MemStats = byte(0x3) + + // Version prints the Go version. + Version = byte(0x4) + + // HeapProfile starts `go tool pprof` with the current memory profile. + HeapProfile = byte(0x5) + + // CPUProfile starts `go tool pprof` with the current CPU profile + CPUProfile = byte(0x6) + + // Stats returns Go runtime statistics such as number of goroutines, GOMAXPROCS, and NumCPU. + Stats = byte(0x7) + + // Trace starts the Go execution tracer, waits 5 seconds and launches the trace tool. + Trace = byte(0x8) + + // BinaryDump returns running binary file. + BinaryDump = byte(0x9) +) diff --git a/vendor/github.com/gorilla/feeds/atom.go b/vendor/github.com/gorilla/feeds/atom.go new file mode 100644 index 0000000..512976b --- /dev/null +++ b/vendor/github.com/gorilla/feeds/atom.go @@ -0,0 +1,163 @@ +package feeds + +import ( + "encoding/xml" + "fmt" + "net/url" + "strconv" + "time" +) + +// Generates Atom feed as XML + +const ns = "http://www.w3.org/2005/Atom" + +type AtomPerson struct { + Name string `xml:"name,omitempty"` + Uri string `xml:"uri,omitempty"` + Email string `xml:"email,omitempty"` +} + +type AtomSummary struct { + XMLName xml.Name `xml:"summary"` + Content string `xml:",chardata"` + Type string `xml:"type,attr"` +} + +type AtomContent struct { + XMLName xml.Name `xml:"content"` + Content string `xml:",chardata"` + Type string `xml:"type,attr"` +} + +type AtomAuthor struct { + XMLName xml.Name `xml:"author"` + AtomPerson +} + +type AtomContributor struct { + XMLName xml.Name `xml:"contributor"` + AtomPerson +} + +type AtomEntry struct { + XMLName xml.Name `xml:"entry"` + Xmlns string `xml:"xmlns,attr,omitempty"` + Title string `xml:"title"` // required + Updated string `xml:"updated"` // required + Id string `xml:"id"` // required + Category string `xml:"category,omitempty"` + Content *AtomContent + Rights string `xml:"rights,omitempty"` + Source string `xml:"source,omitempty"` + Published string `xml:"published,omitempty"` + Contributor *AtomContributor + Link *AtomLink // required if no child 'content' elements + Summary *AtomSummary // required if content has src or content is base64 + Author *AtomAuthor // required if feed lacks an author +} + +type AtomLink struct { + //Atom 1.0 + XMLName xml.Name `xml:"link"` + Href string `xml:"href,attr"` + Rel string `xml:"rel,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + Length string `xml:"length,attr,omitempty"` +} + +type AtomFeed struct { + XMLName xml.Name `xml:"feed"` + Xmlns string `xml:"xmlns,attr"` + Title string `xml:"title"` // required + Id string `xml:"id"` // required + Updated string `xml:"updated"` // required + Category string `xml:"category,omitempty"` + Icon string `xml:"icon,omitempty"` + Logo string `xml:"logo,omitempty"` + Rights string `xml:"rights,omitempty"` // copyright used + Subtitle string `xml:"subtitle,omitempty"` + Link *AtomLink + Author *AtomAuthor `xml:"author,omitempty"` + Contributor *AtomContributor + Entries []*AtomEntry +} + +type Atom struct { + *Feed +} + +func newAtomEntry(i *Item) *AtomEntry { + id := i.Id + // assume the description is html + c := &AtomContent{Content: i.Description, Type: "html"} + + if len(id) == 0 { + // if there's no id set, try to create one, either from data or just a uuid + if len(i.Link.Href) > 0 && (!i.Created.IsZero() || !i.Updated.IsZero()) { + dateStr := anyTimeFormat("2006-01-02", i.Updated, i.Created) + host, path := i.Link.Href, "/invalid.html" + if url, err := url.Parse(i.Link.Href); err == nil { + host, path = url.Host, url.Path + } + id = fmt.Sprintf("tag:%s,%s:%s", host, dateStr, path) + } else { + id = "urn:uuid:" + NewUUID().String() + } + } + var name, email string + if i.Author != nil { + name, email = i.Author.Name, i.Author.Email + } + + x := &AtomEntry{ + Title: i.Title, + Link: &AtomLink{Href: i.Link.Href, Rel: i.Link.Rel, Type: i.Link.Type}, + Content: c, + Id: id, + Updated: anyTimeFormat(time.RFC3339, i.Updated, i.Created), + } + + intLength, err := strconv.ParseInt(i.Link.Length, 10, 64) + + if err == nil && (intLength > 0 || i.Link.Type != "") { + i.Link.Rel = "enclosure" + x.Link = &AtomLink{Href: i.Link.Href, Rel: i.Link.Rel, Type: i.Link.Type, Length: i.Link.Length} + } + + if len(name) > 0 || len(email) > 0 { + x.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: name, Email: email}} + } + return x +} + +// create a new AtomFeed with a generic Feed struct's data +func (a *Atom) AtomFeed() *AtomFeed { + updated := anyTimeFormat(time.RFC3339, a.Updated, a.Created) + feed := &AtomFeed{ + Xmlns: ns, + Title: a.Title, + Link: &AtomLink{Href: a.Link.Href, Rel: a.Link.Rel}, + Subtitle: a.Description, + Id: a.Link.Href, + Updated: updated, + Rights: a.Copyright, + } + if a.Author != nil { + feed.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: a.Author.Name, Email: a.Author.Email}} + } + for _, e := range a.Items { + feed.Entries = append(feed.Entries, newAtomEntry(e)) + } + return feed +} + +// return an XML-Ready object for an Atom object +func (a *Atom) FeedXml() interface{} { + return a.AtomFeed() +} + +// return an XML-ready object for an AtomFeed object +func (a *AtomFeed) FeedXml() interface{} { + return a +} diff --git a/vendor/github.com/gorilla/feeds/doc.go b/vendor/github.com/gorilla/feeds/doc.go new file mode 100644 index 0000000..5b005ea --- /dev/null +++ b/vendor/github.com/gorilla/feeds/doc.go @@ -0,0 +1,70 @@ +/* +Syndication (feed) generator library for golang. + +Installing + + go get github.com/gorilla/feeds + +Feeds provides a simple, generic Feed interface with a generic Item object as well as RSS and Atom specific RssFeed and AtomFeed objects which allow access to all of each spec's defined elements. + +Examples + +Create a Feed and some Items in that feed using the generic interfaces: + + import ( + "time" + . "github.com/gorilla/feeds + ) + + now = time.Now() + + feed := &Feed{ + Title: "jmoiron.net blog", + Link: &Link{Href: "http://jmoiron.net/blog"}, + Description: "discussion about tech, footie, photos", + Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, + Created: now, + Copyright: "This work is copyright © Benjamin Button", + } + + feed.Items = []*Item{ + &Item{ + Title: "Limiting Concurrency in Go", + Link: &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"}, + Description: "A discussion on controlled parallelism in golang", + Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"}, + Created: now, + }, + &Item{ + Title: "Logic-less Template Redux", + Link: &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"}, + Description: "More thoughts on logicless templates", + Created: now, + }, + &Item{ + Title: "Idiomatic Code Reuse in Go", + Link: &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"}, + Description: "How to use interfaces effectively", + Created: now, + }, + } + +From here, you can output Atom or RSS versions of this feed easily + + atom, err := feed.ToAtom() + rss, err := feed.ToRss() + +You can also get access to the underlying objects that feeds uses to export its XML + + atomFeed := &Atom{feed}.AtomFeed() + rssFeed := &Rss{feed}.RssFeed() + +From here, you can modify or add each syndication's specific fields before outputting + + atomFeed.Subtitle = "plays the blues" + atom, err := ToXML(atomFeed) + rssFeed.Generator = "gorilla/feeds v1.0 (github.com/gorilla/feeds)" + rss, err := ToXML(rssFeed) + +*/ +package feeds diff --git a/vendor/github.com/gorilla/feeds/feed.go b/vendor/github.com/gorilla/feeds/feed.go new file mode 100644 index 0000000..fe4833e --- /dev/null +++ b/vendor/github.com/gorilla/feeds/feed.go @@ -0,0 +1,106 @@ +package feeds + +import ( + "encoding/xml" + "io" + "time" +) + +type Link struct { + Href, Rel, Type, Length string +} + +type Author struct { + Name, Email string +} + +type Item struct { + Title string + Link *Link + Author *Author + Description string // used as description in rss, summary in atom + Id string // used as guid in rss, id in atom + Updated time.Time + Created time.Time +} + +type Feed struct { + Title string + Link *Link + Description string + Author *Author + Updated time.Time + Created time.Time + Id string + Subtitle string + Items []*Item + Copyright string +} + +// add a new Item to a Feed +func (f *Feed) Add(item *Item) { + f.Items = append(f.Items, item) +} + +// returns the first non-zero time formatted as a string or "" +func anyTimeFormat(format string, times ...time.Time) string { + for _, t := range times { + if !t.IsZero() { + return t.Format(format) + } + } + return "" +} + +// interface used by ToXML to get a object suitable for exporting XML. +type XmlFeed interface { + FeedXml() interface{} +} + +// turn a feed object (either a Feed, AtomFeed, or RssFeed) into xml +// returns an error if xml marshaling fails +func ToXML(feed XmlFeed) (string, error) { + x := feed.FeedXml() + data, err := xml.MarshalIndent(x, "", " ") + if err != nil { + return "", err + } + // strip empty line from default xml header + s := xml.Header[:len(xml.Header)-1] + string(data) + return s, nil +} + +// Write a feed object (either a Feed, AtomFeed, or RssFeed) as XML into +// the writer. Returns an error if XML marshaling fails. +func WriteXML(feed XmlFeed, w io.Writer) error { + x := feed.FeedXml() + // write default xml header, without the newline + if _, err := w.Write([]byte(xml.Header[:len(xml.Header)-1])); err != nil { + return err + } + e := xml.NewEncoder(w) + e.Indent("", " ") + return e.Encode(x) +} + +// creates an Atom representation of this feed +func (f *Feed) ToAtom() (string, error) { + a := &Atom{f} + return ToXML(a) +} + +// Writes an Atom representation of this feed to the writer. +func (f *Feed) WriteAtom(w io.Writer) error { + return WriteXML(&Atom{f}, w) +} + +// creates an Rss representation of this feed +func (f *Feed) ToRss() (string, error) { + r := &Rss{f} + return ToXML(r) +} + +// Writes an RSS representation of this feed to the writer. +func (f *Feed) WriteRss(w io.Writer) error { + return WriteXML(&Rss{f}, w) +} diff --git a/vendor/github.com/gorilla/feeds/rss.go b/vendor/github.com/gorilla/feeds/rss.go new file mode 100644 index 0000000..3381f74 --- /dev/null +++ b/vendor/github.com/gorilla/feeds/rss.go @@ -0,0 +1,146 @@ +package feeds + +// rss support +// validation done according to spec here: +// http://cyber.law.harvard.edu/rss/rss.html + +import ( + "encoding/xml" + "fmt" + "strconv" + "time" +) + +// private wrapper around the RssFeed which gives us the .. xml +type rssFeedXml struct { + XMLName xml.Name `xml:"rss"` + Version string `xml:"version,attr"` + Channel *RssFeed +} + +type RssImage struct { + XMLName xml.Name `xml:"image"` + Url string `xml:"url"` + Title string `xml:"title"` + Link string `xml:"link"` + Width int `xml:"width,omitempty"` + Height int `xml:"height,omitempty"` +} + +type RssTextInput struct { + XMLName xml.Name `xml:"textInput"` + Title string `xml:"title"` + Description string `xml:"description"` + Name string `xml:"name"` + Link string `xml:"link"` +} + +type RssFeed struct { + XMLName xml.Name `xml:"channel"` + Title string `xml:"title"` // required + Link string `xml:"link"` // required + Description string `xml:"description"` // required + Language string `xml:"language,omitempty"` + Copyright string `xml:"copyright,omitempty"` + ManagingEditor string `xml:"managingEditor,omitempty"` // Author used + WebMaster string `xml:"webMaster,omitempty"` + PubDate string `xml:"pubDate,omitempty"` // created or updated + LastBuildDate string `xml:"lastBuildDate,omitempty"` // updated used + Category string `xml:"category,omitempty"` + Generator string `xml:"generator,omitempty"` + Docs string `xml:"docs,omitempty"` + Cloud string `xml:"cloud,omitempty"` + Ttl int `xml:"ttl,omitempty"` + Rating string `xml:"rating,omitempty"` + SkipHours string `xml:"skipHours,omitempty"` + SkipDays string `xml:"skipDays,omitempty"` + Image *RssImage + TextInput *RssTextInput + Items []*RssItem +} + +type RssItem struct { + XMLName xml.Name `xml:"item"` + Title string `xml:"title"` // required + Link string `xml:"link"` // required + Description string `xml:"description"` // required + Author string `xml:"author,omitempty"` + Category string `xml:"category,omitempty"` + Comments string `xml:"comments,omitempty"` + Enclosure *RssEnclosure + Guid string `xml:"guid,omitempty"` // Id used + PubDate string `xml:"pubDate,omitempty"` // created or updated + Source string `xml:"source,omitempty"` +} + +type RssEnclosure struct { + //RSS 2.0 + XMLName xml.Name `xml:"enclosure"` + Url string `xml:"url,attr"` + Length string `xml:"length,attr"` + Type string `xml:"type,attr"` +} + +type Rss struct { + *Feed +} + +// create a new RssItem with a generic Item struct's data +func newRssItem(i *Item) *RssItem { + item := &RssItem{ + Title: i.Title, + Link: i.Link.Href, + Description: i.Description, + Guid: i.Id, + PubDate: anyTimeFormat(time.RFC1123Z, i.Created, i.Updated), + } + + intLength, err := strconv.ParseInt(i.Link.Length, 10, 64) + + if err == nil && (intLength > 0 || i.Link.Type != "") { + item.Enclosure = &RssEnclosure{Url: i.Link.Href, Type: i.Link.Type, Length: i.Link.Length} + } + if i.Author != nil { + item.Author = i.Author.Name + } + return item +} + +// create a new RssFeed with a generic Feed struct's data +func (r *Rss) RssFeed() *RssFeed { + pub := anyTimeFormat(time.RFC1123Z, r.Created, r.Updated) + build := anyTimeFormat(time.RFC1123Z, r.Updated) + author := "" + if r.Author != nil { + author = r.Author.Email + if len(r.Author.Name) > 0 { + author = fmt.Sprintf("%s (%s)", r.Author.Email, r.Author.Name) + } + } + + channel := &RssFeed{ + Title: r.Title, + Link: r.Link.Href, + Description: r.Description, + ManagingEditor: author, + PubDate: pub, + LastBuildDate: build, + Copyright: r.Copyright, + } + for _, i := range r.Items { + channel.Items = append(channel.Items, newRssItem(i)) + } + return channel +} + +// return an XML-Ready object for an Rss object +func (r *Rss) FeedXml() interface{} { + // only generate version 2.0 feeds for now + return r.RssFeed().FeedXml() + +} + +// return an XML-ready object for an RssFeed object +func (r *RssFeed) FeedXml() interface{} { + return &rssFeedXml{Version: "2.0", Channel: r} +} diff --git a/vendor/github.com/gorilla/feeds/uuid.go b/vendor/github.com/gorilla/feeds/uuid.go new file mode 100644 index 0000000..51bbafe --- /dev/null +++ b/vendor/github.com/gorilla/feeds/uuid.go @@ -0,0 +1,27 @@ +package feeds + +// relevant bits from https://github.com/abneptis/GoUUID/blob/master/uuid.go + +import ( + "crypto/rand" + "fmt" +) + +type UUID [16]byte + +// create a new uuid v4 +func NewUUID() *UUID { + u := &UUID{} + _, err := rand.Read(u[:16]) + if err != nil { + panic(err) + } + + u[8] = (u[8] | 0x80) & 0xBf + u[6] = (u[6] | 0x40) & 0x4f + return u +} + +func (u *UUID) String() string { + return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:]) +} diff --git a/vendor/github.com/kardianos/osext/osext.go b/vendor/github.com/kardianos/osext/osext.go new file mode 100644 index 0000000..17f380f --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext.go @@ -0,0 +1,33 @@ +// 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 "github.com/kardianos/osext" + +import "path/filepath" + +var cx, ce = executableClean() + +func executableClean() (string, error) { + p, err := executable() + return filepath.Clean(p), err +} + +// 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) { + return cx, ce +} + +// Returns same path as Executable, returns just the folder +// path. Excludes the executable name and any trailing slash. +func ExecutableFolder() (string, error) { + p, err := Executable() + if err != nil { + return "", err + } + + return filepath.Dir(p), nil +} diff --git a/vendor/github.com/kardianos/osext/osext_plan9.go b/vendor/github.com/kardianos/osext/osext_plan9.go new file mode 100644 index 0000000..655750c --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_plan9.go @@ -0,0 +1,20 @@ +// 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 ( + "os" + "strconv" + "syscall" +) + +func executable() (string, error) { + f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/vendor/github.com/kardianos/osext/osext_procfs.go b/vendor/github.com/kardianos/osext/osext_procfs.go new file mode 100644 index 0000000..d59847e --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_procfs.go @@ -0,0 +1,36 @@ +// 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 solaris dragonfly + +package osext + +import ( + "errors" + "fmt" + "os" + "runtime" + "strings" +) + +func executable() (string, error) { + switch runtime.GOOS { + case "linux": + const deletedTag = " (deleted)" + execpath, err := os.Readlink("/proc/self/exe") + if err != nil { + return execpath, err + } + execpath = strings.TrimSuffix(execpath, deletedTag) + execpath = strings.TrimPrefix(execpath, deletedTag) + return execpath, nil + case "netbsd": + return os.Readlink("/proc/curproc/exe") + case "dragonfly": + return os.Readlink("/proc/curproc/file") + case "solaris": + return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid())) + } + return "", errors.New("ExecPath not implemented for " + runtime.GOOS) +} diff --git a/vendor/github.com/kardianos/osext/osext_sysctl.go b/vendor/github.com/kardianos/osext/osext_sysctl.go new file mode 100644 index 0000000..66da0bc --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_sysctl.go @@ -0,0 +1,126 @@ +// 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 openbsd + +package osext + +import ( + "os" + "os/exec" + "path/filepath" + "runtime" + "syscall" + "unsafe" +) + +var initCwd, initCwdErr = 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} + case "openbsd": + mib = [4]int32{1 /* CTL_KERN */, 55 /* KERN_PROC_ARGS */, int32(os.Getpid()), 1 /* KERN_PROC_ARGV */} + } + + n := uintptr(0) + // Get length. + _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + buf := make([]byte, n) + _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + + var execPath string + switch runtime.GOOS { + case "openbsd": + // buf now contains **argv, with pointers to each of the C-style + // NULL terminated arguments. + var args []string + argv := uintptr(unsafe.Pointer(&buf[0])) + Loop: + for { + argp := *(**[1 << 20]byte)(unsafe.Pointer(argv)) + if argp == nil { + break + } + for i := 0; uintptr(i) < n; i++ { + // we don't want the full arguments list + if string(argp[i]) == " " { + break Loop + } + if argp[i] != 0 { + continue + } + args = append(args, string(argp[:i])) + n -= uintptr(i) + break + } + if n < unsafe.Sizeof(argv) { + break + } + argv += unsafe.Sizeof(argv) + n -= unsafe.Sizeof(argv) + } + execPath = args[0] + // There is no canonical way to get an executable path on + // OpenBSD, so check PATH in case we are called directly + if execPath[0] != '/' && execPath[0] != '.' { + execIsInPath, err := exec.LookPath(execPath) + if err == nil { + execPath = execIsInPath + } + } + default: + for i, v := range buf { + if v == 0 { + buf = buf[:i] + break + } + } + execPath = string(buf) + } + + var err error + // execPath will not be empty due to above checks. + // Try to get the absolute path if the execPath is not rooted. + if execPath[0] != '/' { + execPath, err = getAbs(execPath) + if err != nil { + return execPath, err + } + } + // For darwin KERN_PROCARGS may return the path to a symlink rather than the + // actual executable. + if runtime.GOOS == "darwin" { + if execPath, err = filepath.EvalSymlinks(execPath); err != nil { + return execPath, err + } + } + return execPath, nil +} + +func getAbs(execPath string) (string, error) { + if initCwdErr != nil { + return execPath, initCwdErr + } + // The execPath may begin with a "../" or a "./" so clean it first. + // Join the two paths, trailing and starting slashes undetermined, so use + // the generic Join function. + return filepath.Join(initCwd, filepath.Clean(execPath)), nil +} diff --git a/vendor/github.com/kardianos/osext/osext_windows.go b/vendor/github.com/kardianos/osext/osext_windows.go new file mode 100644 index 0000000..72d282c --- /dev/null +++ b/vendor/github.com/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/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 0000000..842ee80 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 0000000..6b1f289 --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,178 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} diff --git a/vendor/github.com/russross/blackfriday/block.go b/vendor/github.com/russross/blackfriday/block.go new file mode 100644 index 0000000..7fc731d --- /dev/null +++ b/vendor/github.com/russross/blackfriday/block.go @@ -0,0 +1,1450 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. +// + +// +// Functions to parse block-level elements. +// + +package blackfriday + +import ( + "bytes" + "unicode" +) + +// Parse block-level data. +// Note: this function and many that it calls assume that +// the input buffer ends with a newline. +func (p *parser) block(out *bytes.Buffer, data []byte) { + if len(data) == 0 || data[len(data)-1] != '\n' { + panic("block input is missing terminating newline") + } + + // this is called recursively: enforce a maximum depth + if p.nesting >= p.maxNesting { + return + } + p.nesting++ + + // parse out one block-level construct at a time + for len(data) > 0 { + // prefixed header: + // + // # Header 1 + // ## Header 2 + // ... + // ###### Header 6 + if p.isPrefixHeader(data) { + data = data[p.prefixHeader(out, data):] + continue + } + + // block of preformatted HTML: + // + //
+ // ... + //
+ if data[0] == '<' { + if i := p.html(out, data, true); i > 0 { + data = data[i:] + continue + } + } + + // title block + // + // % stuff + // % more stuff + // % even more stuff + if p.flags&EXTENSION_TITLEBLOCK != 0 { + if data[0] == '%' { + if i := p.titleBlock(out, data, true); i > 0 { + data = data[i:] + continue + } + } + } + + // blank lines. note: returns the # of bytes to skip + if i := p.isEmpty(data); i > 0 { + data = data[i:] + continue + } + + // indented code block: + // + // func max(a, b int) int { + // if a > b { + // return a + // } + // return b + // } + if p.codePrefix(data) > 0 { + data = data[p.code(out, data):] + continue + } + + // fenced code block: + // + // ``` go + // func fact(n int) int { + // if n <= 1 { + // return n + // } + // return n * fact(n-1) + // } + // ``` + if p.flags&EXTENSION_FENCED_CODE != 0 { + if i := p.fencedCodeBlock(out, data, true); i > 0 { + data = data[i:] + continue + } + } + + // horizontal rule: + // + // ------ + // or + // ****** + // or + // ______ + if p.isHRule(data) { + p.r.HRule(out) + var i int + for i = 0; data[i] != '\n'; i++ { + } + data = data[i:] + continue + } + + // block quote: + // + // > A big quote I found somewhere + // > on the web + if p.quotePrefix(data) > 0 { + data = data[p.quote(out, data):] + continue + } + + // table: + // + // Name | Age | Phone + // ------|-----|--------- + // Bob | 31 | 555-1234 + // Alice | 27 | 555-4321 + if p.flags&EXTENSION_TABLES != 0 { + if i := p.table(out, data); i > 0 { + data = data[i:] + continue + } + } + + // an itemized/unordered list: + // + // * Item 1 + // * Item 2 + // + // also works with + or - + if p.uliPrefix(data) > 0 { + data = data[p.list(out, data, 0):] + continue + } + + // a numbered/ordered list: + // + // 1. Item 1 + // 2. Item 2 + if p.oliPrefix(data) > 0 { + data = data[p.list(out, data, LIST_TYPE_ORDERED):] + continue + } + + // definition lists: + // + // Term 1 + // : Definition a + // : Definition b + // + // Term 2 + // : Definition c + if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.dliPrefix(data) > 0 { + data = data[p.list(out, data, LIST_TYPE_DEFINITION):] + continue + } + } + + // anything else must look like a normal paragraph + // note: this finds underlined headers, too + data = data[p.paragraph(out, data):] + } + + p.nesting-- +} + +func (p *parser) isPrefixHeader(data []byte) bool { + if data[0] != '#' { + return false + } + + if p.flags&EXTENSION_SPACE_HEADERS != 0 { + level := 0 + for level < 6 && data[level] == '#' { + level++ + } + if data[level] != ' ' { + return false + } + } + return true +} + +func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int { + level := 0 + for level < 6 && data[level] == '#' { + level++ + } + i := skipChar(data, level, ' ') + end := skipUntilChar(data, i, '\n') + skip := end + id := "" + if p.flags&EXTENSION_HEADER_IDS != 0 { + j, k := 0, 0 + // find start/end of header id + for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ { + } + for k = j + 1; k < end && data[k] != '}'; k++ { + } + // extract header id iff found + if j < end && k < end { + id = string(data[j+2 : k]) + end = j + skip = k + 1 + for end > 0 && data[end-1] == ' ' { + end-- + } + } + } + for end > 0 && data[end-1] == '#' { + if isBackslashEscaped(data, end-1) { + break + } + end-- + } + for end > 0 && data[end-1] == ' ' { + end-- + } + if end > i { + if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { + id = SanitizedAnchorName(string(data[i:end])) + } + work := func() bool { + p.inline(out, data[i:end]) + return true + } + p.r.Header(out, work, level, id) + } + return skip +} + +func (p *parser) isUnderlinedHeader(data []byte) int { + // test of level 1 header + if data[0] == '=' { + i := skipChar(data, 1, '=') + i = skipChar(data, i, ' ') + if data[i] == '\n' { + return 1 + } else { + return 0 + } + } + + // test of level 2 header + if data[0] == '-' { + i := skipChar(data, 1, '-') + i = skipChar(data, i, ' ') + if data[i] == '\n' { + return 2 + } else { + return 0 + } + } + + return 0 +} + +func (p *parser) titleBlock(out *bytes.Buffer, data []byte, doRender bool) int { + if data[0] != '%' { + return 0 + } + splitData := bytes.Split(data, []byte("\n")) + var i int + for idx, b := range splitData { + if !bytes.HasPrefix(b, []byte("%")) { + i = idx // - 1 + break + } + } + + data = bytes.Join(splitData[0:i], []byte("\n")) + p.r.TitleBlock(out, data) + + return len(data) +} + +func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int { + var i, j int + + // identify the opening tag + if data[0] != '<' { + return 0 + } + curtag, tagfound := p.htmlFindTag(data[1:]) + + // handle special cases + if !tagfound { + // check for an HTML comment + if size := p.htmlComment(out, data, doRender); size > 0 { + return size + } + + // check for an
tag + if size := p.htmlHr(out, data, doRender); size > 0 { + return size + } + + // check for HTML CDATA + if size := p.htmlCDATA(out, data, doRender); size > 0 { + return size + } + + // no special case recognized + return 0 + } + + // look for an unindented matching closing tag + // followed by a blank line + found := false + /* + closetag := []byte("\n") + j = len(curtag) + 1 + for !found { + // scan for a closing tag at the beginning of a line + if skip := bytes.Index(data[j:], closetag); skip >= 0 { + j += skip + len(closetag) + } else { + break + } + + // see if it is the only thing on the line + if skip := p.isEmpty(data[j:]); skip > 0 { + // see if it is followed by a blank line/eof + j += skip + if j >= len(data) { + found = true + i = j + } else { + if skip := p.isEmpty(data[j:]); skip > 0 { + j += skip + found = true + i = j + } + } + } + } + */ + + // if not found, try a second pass looking for indented match + // but not if tag is "ins" or "del" (following original Markdown.pl) + if !found && curtag != "ins" && curtag != "del" { + i = 1 + for i < len(data) { + i++ + for i < len(data) && !(data[i-1] == '<' && data[i] == '/') { + i++ + } + + if i+2+len(curtag) >= len(data) { + break + } + + j = p.htmlFindEnd(curtag, data[i-1:]) + + if j > 0 { + i += j - 1 + found = true + break + } + } + } + + if !found { + return 0 + } + + // the end of the block has been found + if doRender { + // trim newlines + end := i + for end > 0 && data[end-1] == '\n' { + end-- + } + p.r.BlockHtml(out, data[:end]) + } + + return i +} + +func (p *parser) renderHTMLBlock(out *bytes.Buffer, data []byte, start int, doRender bool) int { + // html block needs to end with a blank line + if i := p.isEmpty(data[start:]); i > 0 { + size := start + i + if doRender { + // trim trailing newlines + end := size + for end > 0 && data[end-1] == '\n' { + end-- + } + p.r.BlockHtml(out, data[:end]) + } + return size + } + return 0 +} + +// HTML comment, lax form +func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int { + i := p.inlineHTMLComment(out, data) + return p.renderHTMLBlock(out, data, i, doRender) +} + +// HTML CDATA section +func (p *parser) htmlCDATA(out *bytes.Buffer, data []byte, doRender bool) int { + const cdataTag = "') { + i++ + } + i++ + // no end-of-comment marker + if i >= len(data) { + return 0 + } + return p.renderHTMLBlock(out, data, i, doRender) +} + +// HR, which is the only self-closing block tag considered +func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int { + if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') { + return 0 + } + if data[3] != ' ' && data[3] != '/' && data[3] != '>' { + // not an
tag after all; at least not a valid one + return 0 + } + + i := 3 + for data[i] != '>' && data[i] != '\n' { + i++ + } + + if data[i] == '>' { + return p.renderHTMLBlock(out, data, i+1, doRender) + } + + return 0 +} + +func (p *parser) htmlFindTag(data []byte) (string, bool) { + i := 0 + for isalnum(data[i]) { + i++ + } + key := string(data[:i]) + if _, ok := blockTags[key]; ok { + return key, true + } + return "", false +} + +func (p *parser) htmlFindEnd(tag string, data []byte) int { + // assume data[0] == '<' && data[1] == '/' already tested + + // check if tag is a match + closetag := []byte("") + if !bytes.HasPrefix(data, closetag) { + return 0 + } + i := len(closetag) + + // check that the rest of the line is blank + skip := 0 + if skip = p.isEmpty(data[i:]); skip == 0 { + return 0 + } + i += skip + skip = 0 + + if i >= len(data) { + return i + } + + if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { + return i + } + if skip = p.isEmpty(data[i:]); skip == 0 { + // following line must be blank + return 0 + } + + return i + skip +} + +func (*parser) isEmpty(data []byte) int { + // it is okay to call isEmpty on an empty buffer + if len(data) == 0 { + return 0 + } + + var i int + for i = 0; i < len(data) && data[i] != '\n'; i++ { + if data[i] != ' ' && data[i] != '\t' { + return 0 + } + } + return i + 1 +} + +func (*parser) isHRule(data []byte) bool { + i := 0 + + // skip up to three spaces + for i < 3 && data[i] == ' ' { + i++ + } + + // look at the hrule char + if data[i] != '*' && data[i] != '-' && data[i] != '_' { + return false + } + c := data[i] + + // the whole line must be the char or whitespace + n := 0 + for data[i] != '\n' { + switch { + case data[i] == c: + n++ + case data[i] != ' ': + return false + } + i++ + } + + return n >= 3 +} + +// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data, +// and returns the end index if so, or 0 otherwise. It also returns the marker found. +// If syntax is not nil, it gets set to the syntax specified in the fence line. +// A final newline is mandatory to recognize the fence line, unless newlineOptional is true. +func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional bool) (end int, marker string) { + i, size := 0, 0 + + // skip up to three spaces + for i < len(data) && i < 3 && data[i] == ' ' { + i++ + } + + // check for the marker characters: ~ or ` + if i >= len(data) { + return 0, "" + } + if data[i] != '~' && data[i] != '`' { + return 0, "" + } + + c := data[i] + + // the whole line must be the same char or whitespace + for i < len(data) && data[i] == c { + size++ + i++ + } + + // the marker char must occur at least 3 times + if size < 3 { + return 0, "" + } + marker = string(data[i-size : i]) + + // if this is the end marker, it must match the beginning marker + if oldmarker != "" && marker != oldmarker { + return 0, "" + } + + // TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here + // into one, always get the syntax, and discard it if the caller doesn't care. + if syntax != nil { + syn := 0 + i = skipChar(data, i, ' ') + + if i >= len(data) { + if newlineOptional && i == len(data) { + return i, marker + } + return 0, "" + } + + syntaxStart := i + + if data[i] == '{' { + i++ + syntaxStart++ + + for i < len(data) && data[i] != '}' && data[i] != '\n' { + syn++ + i++ + } + + if i >= len(data) || data[i] != '}' { + return 0, "" + } + + // strip all whitespace at the beginning and the end + // of the {} block + for syn > 0 && isspace(data[syntaxStart]) { + syntaxStart++ + syn-- + } + + for syn > 0 && isspace(data[syntaxStart+syn-1]) { + syn-- + } + + i++ + } else { + for i < len(data) && !isspace(data[i]) { + syn++ + i++ + } + } + + *syntax = string(data[syntaxStart : syntaxStart+syn]) + } + + i = skipChar(data, i, ' ') + if i >= len(data) || data[i] != '\n' { + if newlineOptional && i == len(data) { + return i, marker + } + return 0, "" + } + + return i + 1, marker // Take newline into account. +} + +// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning, +// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects. +// If doRender is true, a final newline is mandatory to recognize the fenced code block. +func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) int { + var syntax string + beg, marker := isFenceLine(data, &syntax, "", false) + if beg == 0 || beg >= len(data) { + return 0 + } + + var work bytes.Buffer + + for { + // safe to assume beg < len(data) + + // check for the end of the code block + newlineOptional := !doRender + fenceEnd, _ := isFenceLine(data[beg:], nil, marker, newlineOptional) + if fenceEnd != 0 { + beg += fenceEnd + break + } + + // copy the current line + end := skipUntilChar(data, beg, '\n') + 1 + + // did we reach the end of the buffer without a closing marker? + if end >= len(data) { + return 0 + } + + // verbatim copy to the working buffer + if doRender { + work.Write(data[beg:end]) + } + beg = end + } + + if doRender { + p.r.BlockCode(out, work.Bytes(), syntax) + } + + return beg +} + +func (p *parser) table(out *bytes.Buffer, data []byte) int { + var header bytes.Buffer + i, columns := p.tableHeader(&header, data) + if i == 0 { + return 0 + } + + var body bytes.Buffer + + for i < len(data) { + pipes, rowStart := 0, i + for ; data[i] != '\n'; i++ { + if data[i] == '|' { + pipes++ + } + } + + if pipes == 0 { + i = rowStart + break + } + + // include the newline in data sent to tableRow + i++ + p.tableRow(&body, data[rowStart:i], columns, false) + } + + p.r.Table(out, header.Bytes(), body.Bytes(), columns) + + return i +} + +// check if the specified position is preceded by an odd number of backslashes +func isBackslashEscaped(data []byte, i int) bool { + backslashes := 0 + for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' { + backslashes++ + } + return backslashes&1 == 1 +} + +func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns []int) { + i := 0 + colCount := 1 + for i = 0; data[i] != '\n'; i++ { + if data[i] == '|' && !isBackslashEscaped(data, i) { + colCount++ + } + } + + // doesn't look like a table header + if colCount == 1 { + return + } + + // include the newline in the data sent to tableRow + header := data[:i+1] + + // column count ignores pipes at beginning or end of line + if data[0] == '|' { + colCount-- + } + if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) { + colCount-- + } + + columns = make([]int, colCount) + + // move on to the header underline + i++ + if i >= len(data) { + return + } + + if data[i] == '|' && !isBackslashEscaped(data, i) { + i++ + } + i = skipChar(data, i, ' ') + + // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3 + // and trailing | optional on last column + col := 0 + for data[i] != '\n' { + dashes := 0 + + if data[i] == ':' { + i++ + columns[col] |= TABLE_ALIGNMENT_LEFT + dashes++ + } + for data[i] == '-' { + i++ + dashes++ + } + if data[i] == ':' { + i++ + columns[col] |= TABLE_ALIGNMENT_RIGHT + dashes++ + } + for data[i] == ' ' { + i++ + } + + // end of column test is messy + switch { + case dashes < 3: + // not a valid column + return + + case data[i] == '|' && !isBackslashEscaped(data, i): + // marker found, now skip past trailing whitespace + col++ + i++ + for data[i] == ' ' { + i++ + } + + // trailing junk found after last column + if col >= colCount && data[i] != '\n' { + return + } + + case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount: + // something else found where marker was required + return + + case data[i] == '\n': + // marker is optional for the last column + col++ + + default: + // trailing junk found after last column + return + } + } + if col != colCount { + return + } + + p.tableRow(out, header, columns, true) + size = i + 1 + return +} + +func (p *parser) tableRow(out *bytes.Buffer, data []byte, columns []int, header bool) { + i, col := 0, 0 + var rowWork bytes.Buffer + + if data[i] == '|' && !isBackslashEscaped(data, i) { + i++ + } + + for col = 0; col < len(columns) && i < len(data); col++ { + for data[i] == ' ' { + i++ + } + + cellStart := i + + for (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' { + i++ + } + + cellEnd := i + + // skip the end-of-cell marker, possibly taking us past end of buffer + i++ + + for cellEnd > cellStart && data[cellEnd-1] == ' ' { + cellEnd-- + } + + var cellWork bytes.Buffer + p.inline(&cellWork, data[cellStart:cellEnd]) + + if header { + p.r.TableHeaderCell(&rowWork, cellWork.Bytes(), columns[col]) + } else { + p.r.TableCell(&rowWork, cellWork.Bytes(), columns[col]) + } + } + + // pad it out with empty columns to get the right number + for ; col < len(columns); col++ { + if header { + p.r.TableHeaderCell(&rowWork, nil, columns[col]) + } else { + p.r.TableCell(&rowWork, nil, columns[col]) + } + } + + // silently ignore rows with too many cells + + p.r.TableRow(out, rowWork.Bytes()) +} + +// returns blockquote prefix length +func (p *parser) quotePrefix(data []byte) int { + i := 0 + for i < 3 && data[i] == ' ' { + i++ + } + if data[i] == '>' { + if data[i+1] == ' ' { + return i + 2 + } + return i + 1 + } + return 0 +} + +// blockquote ends with at least one blank line +// followed by something without a blockquote prefix +func (p *parser) terminateBlockquote(data []byte, beg, end int) bool { + if p.isEmpty(data[beg:]) <= 0 { + return false + } + if end >= len(data) { + return true + } + return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0 +} + +// parse a blockquote fragment +func (p *parser) quote(out *bytes.Buffer, data []byte) int { + var raw bytes.Buffer + beg, end := 0, 0 + for beg < len(data) { + end = beg + // Step over whole lines, collecting them. While doing that, check for + // fenced code and if one's found, incorporate it altogether, + // irregardless of any contents inside it + for data[end] != '\n' { + if p.flags&EXTENSION_FENCED_CODE != 0 { + if i := p.fencedCodeBlock(out, data[end:], false); i > 0 { + // -1 to compensate for the extra end++ after the loop: + end += i - 1 + break + } + } + end++ + } + end++ + + if pre := p.quotePrefix(data[beg:]); pre > 0 { + // skip the prefix + beg += pre + } else if p.terminateBlockquote(data, beg, end) { + break + } + + // this line is part of the blockquote + raw.Write(data[beg:end]) + beg = end + } + + var cooked bytes.Buffer + p.block(&cooked, raw.Bytes()) + p.r.BlockQuote(out, cooked.Bytes()) + return end +} + +// returns prefix length for block code +func (p *parser) codePrefix(data []byte) int { + if data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { + return 4 + } + return 0 +} + +func (p *parser) code(out *bytes.Buffer, data []byte) int { + var work bytes.Buffer + + i := 0 + for i < len(data) { + beg := i + for data[i] != '\n' { + i++ + } + i++ + + blankline := p.isEmpty(data[beg:i]) > 0 + if pre := p.codePrefix(data[beg:i]); pre > 0 { + beg += pre + } else if !blankline { + // non-empty, non-prefixed line breaks the pre + i = beg + break + } + + // verbatim copy to the working buffeu + if blankline { + work.WriteByte('\n') + } else { + work.Write(data[beg:i]) + } + } + + // trim all the \n off the end of work + workbytes := work.Bytes() + eol := len(workbytes) + for eol > 0 && workbytes[eol-1] == '\n' { + eol-- + } + if eol != len(workbytes) { + work.Truncate(eol) + } + + work.WriteByte('\n') + + p.r.BlockCode(out, work.Bytes(), "") + + return i +} + +// returns unordered list item prefix +func (p *parser) uliPrefix(data []byte) int { + i := 0 + + // start with up to 3 spaces + for i < 3 && data[i] == ' ' { + i++ + } + + // need a *, +, or - followed by a space + if (data[i] != '*' && data[i] != '+' && data[i] != '-') || + data[i+1] != ' ' { + return 0 + } + return i + 2 +} + +// returns ordered list item prefix +func (p *parser) oliPrefix(data []byte) int { + i := 0 + + // start with up to 3 spaces + for i < 3 && data[i] == ' ' { + i++ + } + + // count the digits + start := i + for data[i] >= '0' && data[i] <= '9' { + i++ + } + + // we need >= 1 digits followed by a dot and a space + if start == i || data[i] != '.' || data[i+1] != ' ' { + return 0 + } + return i + 2 +} + +// returns definition list item prefix +func (p *parser) dliPrefix(data []byte) int { + i := 0 + + // need a : followed by a spaces + if data[i] != ':' || data[i+1] != ' ' { + return 0 + } + for data[i] == ' ' { + i++ + } + return i + 2 +} + +// parse ordered or unordered list block +func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int { + i := 0 + flags |= LIST_ITEM_BEGINNING_OF_LIST + work := func() bool { + for i < len(data) { + skip := p.listItem(out, data[i:], &flags) + i += skip + + if skip == 0 || flags&LIST_ITEM_END_OF_LIST != 0 { + break + } + flags &= ^LIST_ITEM_BEGINNING_OF_LIST + } + return true + } + + p.r.List(out, work, flags) + return i +} + +// Parse a single list item. +// Assumes initial prefix is already removed if this is a sublist. +func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int { + // keep track of the indentation of the first line + itemIndent := 0 + for itemIndent < 3 && data[itemIndent] == ' ' { + itemIndent++ + } + + i := p.uliPrefix(data) + if i == 0 { + i = p.oliPrefix(data) + } + if i == 0 { + i = p.dliPrefix(data) + // reset definition term flag + if i > 0 { + *flags &= ^LIST_TYPE_TERM + } + } + if i == 0 { + // if in defnition list, set term flag and continue + if *flags&LIST_TYPE_DEFINITION != 0 { + *flags |= LIST_TYPE_TERM + } else { + return 0 + } + } + + // skip leading whitespace on first line + for data[i] == ' ' { + i++ + } + + // find the end of the line + line := i + for i > 0 && data[i-1] != '\n' { + i++ + } + + // get working buffer + var raw bytes.Buffer + + // put the first line into the working buffer + raw.Write(data[line:i]) + line = i + + // process the following lines + containsBlankLine := false + sublist := 0 + +gatherlines: + for line < len(data) { + i++ + + // find the end of this line + for data[i-1] != '\n' { + i++ + } + + // if it is an empty line, guess that it is part of this item + // and move on to the next line + if p.isEmpty(data[line:i]) > 0 { + containsBlankLine = true + raw.Write(data[line:i]) + line = i + continue + } + + // calculate the indentation + indent := 0 + for indent < 4 && line+indent < i && data[line+indent] == ' ' { + indent++ + } + + chunk := data[line+indent : i] + + // evaluate how this line fits in + switch { + // is this a nested list item? + case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) || + p.oliPrefix(chunk) > 0 || + p.dliPrefix(chunk) > 0: + + if containsBlankLine { + // end the list if the type changed after a blank line + if indent <= itemIndent && + ((*flags&LIST_TYPE_ORDERED != 0 && p.uliPrefix(chunk) > 0) || + (*flags&LIST_TYPE_ORDERED == 0 && p.oliPrefix(chunk) > 0)) { + + *flags |= LIST_ITEM_END_OF_LIST + break gatherlines + } + *flags |= LIST_ITEM_CONTAINS_BLOCK + } + + // to be a nested list, it must be indented more + // if not, it is the next item in the same list + if indent <= itemIndent { + break gatherlines + } + + // is this the first item in the nested list? + if sublist == 0 { + sublist = raw.Len() + } + + // is this a nested prefix header? + case p.isPrefixHeader(chunk): + // if the header is not indented, it is not nested in the list + // and thus ends the list + if containsBlankLine && indent < 4 { + *flags |= LIST_ITEM_END_OF_LIST + break gatherlines + } + *flags |= LIST_ITEM_CONTAINS_BLOCK + + // anything following an empty line is only part + // of this item if it is indented 4 spaces + // (regardless of the indentation of the beginning of the item) + case containsBlankLine && indent < 4: + if *flags&LIST_TYPE_DEFINITION != 0 && i < len(data)-1 { + // is the next item still a part of this list? + next := i + for data[next] != '\n' { + next++ + } + for next < len(data)-1 && data[next] == '\n' { + next++ + } + if i < len(data)-1 && data[i] != ':' && data[next] != ':' { + *flags |= LIST_ITEM_END_OF_LIST + } + } else { + *flags |= LIST_ITEM_END_OF_LIST + } + break gatherlines + + // a blank line means this should be parsed as a block + case containsBlankLine: + *flags |= LIST_ITEM_CONTAINS_BLOCK + } + + containsBlankLine = false + + // add the line into the working buffer without prefix + raw.Write(data[line+indent : i]) + + line = i + } + + // If reached end of data, the Renderer.ListItem call we're going to make below + // is definitely the last in the list. + if line >= len(data) { + *flags |= LIST_ITEM_END_OF_LIST + } + + rawBytes := raw.Bytes() + + // render the contents of the list item + var cooked bytes.Buffer + if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 && *flags&LIST_TYPE_TERM == 0 { + // intermediate render of block item, except for definition term + if sublist > 0 { + p.block(&cooked, rawBytes[:sublist]) + p.block(&cooked, rawBytes[sublist:]) + } else { + p.block(&cooked, rawBytes) + } + } else { + // intermediate render of inline item + if sublist > 0 { + p.inline(&cooked, rawBytes[:sublist]) + p.block(&cooked, rawBytes[sublist:]) + } else { + p.inline(&cooked, rawBytes) + } + } + + // render the actual list item + cookedBytes := cooked.Bytes() + parsedEnd := len(cookedBytes) + + // strip trailing newlines + for parsedEnd > 0 && cookedBytes[parsedEnd-1] == '\n' { + parsedEnd-- + } + p.r.ListItem(out, cookedBytes[:parsedEnd], *flags) + + return line +} + +// render a single paragraph that has already been parsed out +func (p *parser) renderParagraph(out *bytes.Buffer, data []byte) { + if len(data) == 0 { + return + } + + // trim leading spaces + beg := 0 + for data[beg] == ' ' { + beg++ + } + + // trim trailing newline + end := len(data) - 1 + + // trim trailing spaces + for end > beg && data[end-1] == ' ' { + end-- + } + + work := func() bool { + p.inline(out, data[beg:end]) + return true + } + p.r.Paragraph(out, work) +} + +func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { + // prev: index of 1st char of previous line + // line: index of 1st char of current line + // i: index of cursor/end of current line + var prev, line, i int + + // keep going until we find something to mark the end of the paragraph + for i < len(data) { + // mark the beginning of the current line + prev = line + current := data[i:] + line = i + + // did we find a blank line marking the end of the paragraph? + if n := p.isEmpty(current); n > 0 { + // did this blank line followed by a definition list item? + if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if i < len(data)-1 && data[i+1] == ':' { + return p.list(out, data[prev:], LIST_TYPE_DEFINITION) + } + } + + p.renderParagraph(out, data[:i]) + return i + n + } + + // an underline under some text marks a header, so our paragraph ended on prev line + if i > 0 { + if level := p.isUnderlinedHeader(current); level > 0 { + // render the paragraph + p.renderParagraph(out, data[:prev]) + + // ignore leading and trailing whitespace + eol := i - 1 + for prev < eol && data[prev] == ' ' { + prev++ + } + for eol > prev && data[eol-1] == ' ' { + eol-- + } + + // render the header + // this ugly double closure avoids forcing variables onto the heap + work := func(o *bytes.Buffer, pp *parser, d []byte) func() bool { + return func() bool { + pp.inline(o, d) + return true + } + }(out, p, data[prev:eol]) + + id := "" + if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { + id = SanitizedAnchorName(string(data[prev:eol])) + } + + p.r.Header(out, work, level, id) + + // find the end of the underline + for data[i] != '\n' { + i++ + } + return i + } + } + + // if the next line starts a block of HTML, then the paragraph ends here + if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { + if data[i] == '<' && p.html(out, current, false) > 0 { + // rewind to before the HTML block + p.renderParagraph(out, data[:i]) + return i + } + } + + // if there's a prefixed header or a horizontal rule after this, paragraph is over + if p.isPrefixHeader(current) || p.isHRule(current) { + p.renderParagraph(out, data[:i]) + return i + } + + // if there's a fenced code block, paragraph is over + if p.flags&EXTENSION_FENCED_CODE != 0 { + if p.fencedCodeBlock(out, current, false) > 0 { + p.renderParagraph(out, data[:i]) + return i + } + } + + // if there's a definition list item, prev line is a definition term + if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.dliPrefix(current) != 0 { + return p.list(out, data[prev:], LIST_TYPE_DEFINITION) + } + } + + // if there's a list after this, paragraph is over + if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 { + if p.uliPrefix(current) != 0 || + p.oliPrefix(current) != 0 || + p.quotePrefix(current) != 0 || + p.codePrefix(current) != 0 { + p.renderParagraph(out, data[:i]) + return i + } + } + + // otherwise, scan to the beginning of the next line + for data[i] != '\n' { + i++ + } + i++ + } + + p.renderParagraph(out, data[:i]) + return i +} + +// SanitizedAnchorName returns a sanitized anchor name for the given text. +// +// It implements the algorithm specified in the package comment. +func SanitizedAnchorName(text string) string { + var anchorName []rune + futureDash := false + for _, r := range text { + switch { + case unicode.IsLetter(r) || unicode.IsNumber(r): + if futureDash && len(anchorName) > 0 { + anchorName = append(anchorName, '-') + } + futureDash = false + anchorName = append(anchorName, unicode.ToLower(r)) + default: + futureDash = true + } + } + return string(anchorName) +} diff --git a/vendor/github.com/russross/blackfriday/doc.go b/vendor/github.com/russross/blackfriday/doc.go new file mode 100644 index 0000000..9656c42 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/doc.go @@ -0,0 +1,32 @@ +// Package blackfriday is a Markdown processor. +// +// It translates plain text with simple formatting rules into HTML or LaTeX. +// +// Sanitized Anchor Names +// +// Blackfriday includes an algorithm for creating sanitized anchor names +// corresponding to a given input text. This algorithm is used to create +// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The +// algorithm is specified below, so that other packages can create +// compatible anchor names and links to those anchors. +// +// The algorithm iterates over the input text, interpreted as UTF-8, +// one Unicode code point (rune) at a time. All runes that are letters (category L) +// or numbers (category N) are considered valid characters. They are mapped to +// lower case, and included in the output. All other runes are considered +// invalid characters. Invalid characters that preceed the first valid character, +// as well as invalid character that follow the last valid character +// are dropped completely. All other sequences of invalid characters +// between two valid characters are replaced with a single dash character '-'. +// +// SanitizedAnchorName exposes this functionality, and can be used to +// create compatible links to the anchor names generated by blackfriday. +// This algorithm is also implemented in a small standalone package at +// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients +// that want a small package and don't need full functionality of blackfriday. +package blackfriday + +// NOTE: Keep Sanitized Anchor Name algorithm in sync with package +// github.com/shurcooL/sanitized_anchor_name. +// Otherwise, users of sanitized_anchor_name will get anchor names +// that are incompatible with those generated by blackfriday. diff --git a/vendor/github.com/russross/blackfriday/html.go b/vendor/github.com/russross/blackfriday/html.go new file mode 100644 index 0000000..74e67ee --- /dev/null +++ b/vendor/github.com/russross/blackfriday/html.go @@ -0,0 +1,949 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. +// + +// +// +// HTML rendering backend +// +// + +package blackfriday + +import ( + "bytes" + "fmt" + "regexp" + "strconv" + "strings" +) + +// Html renderer configuration options. +const ( + HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks + HTML_SKIP_STYLE // skip embedded